structured-logging
npx machina-cli add skill JanSzewczyk/claude-plugins/structured-logging --openclawStructured Logging Skill
Structured logging patterns with Pino for Next.js applications.
Reference Files:
- pino-setup.md - Logger configuration
- log-levels.md - When to use each level
- patterns.md - Common logging patterns
- examples.md - Practical examples
Project Configuration
The logger is configured in lib/logger.ts:
import pino from "pino";
const logger = pino({
level: process.env.LOG_LEVEL || "info",
transport:
process.env.NODE_ENV === "development"
? {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "SYS:standard",
ignore: "pid,hostname",
},
}
: undefined,
formatters: {
level: (label) => ({ level: label.toUpperCase() }),
},
timestamp: pino.stdTimeFunctions.isoTime,
});
export function createLogger(context: Record<string, unknown>) {
return logger.child(context);
}
export default logger;
Quick Start
Basic Logging
import logger from "~/lib/logger";
// Simple message
logger.info("Server started");
// With context object
logger.info({ port: 3000 }, "Server started");
// Error logging
logger.error({ error, userId }, "Failed to process request");
Module-Specific Logger
import { createLogger } from "~/lib/logger";
const logger = createLogger({ module: "user-service" });
// All logs include { module: "user-service" }
logger.info({ userId: "123" }, "User created");
// Output: { module: "user-service", userId: "123", msg: "User created" }
Log Levels
| Level | When to Use | Example |
|---|---|---|
fatal | App crash, unrecoverable | Database connection lost |
error | Operation failed | User creation failed |
warn | Unexpected but recoverable | Rate limit approaching |
info | Normal operations | User logged in |
debug | Development details | Request payload |
trace | Fine-grained debugging | Function entry/exit |
Usage
logger.fatal({ error }, "Database connection lost, shutting down");
logger.error({ userId, errorCode: error.code }, "Failed to update user");
logger.warn({ requestCount, limit }, "Rate limit 80% reached");
logger.info({ userId }, "User logged in successfully");
logger.debug({ payload }, "Processing request");
logger.trace({ functionName: "processData" }, "Entering function");
Key Patterns
Always Log Context Objects First
// ✅ Good - context object first, then message
logger.info({ userId, action: "login" }, "User authenticated");
// ❌ Bad - no context
logger.info("User authenticated");
// ❌ Bad - string interpolation
logger.info(`User ${userId} authenticated`);
Error Logging
import { categorizeDbError, DbError } from "~/lib/firebase/errors";
try {
await updateUser(userId, data);
} catch (error) {
const dbError = categorizeDbError(error, "User");
logger.error(
{
userId,
errorCode: dbError.code,
isRetryable: dbError.isRetryable,
operation: "updateUser",
},
"Failed to update user",
);
return [dbError, null];
}
Database Operations
const logger = createLogger({ module: "user-db" });
export async function getUserById(id: string) {
logger.debug({ userId: id }, "Fetching user");
try {
const user = await db.collection("users").doc(id).get();
if (!user.exists) {
logger.warn({ userId: id }, "User not found");
return [DbError.notFound("User"), null];
}
logger.info({ userId: id }, "User fetched successfully");
return [null, transformUser(user)];
} catch (error) {
const dbError = categorizeDbError(error, "User");
logger.error(
{
userId: id,
errorCode: dbError.code,
isRetryable: dbError.isRetryable,
},
"Database error fetching user",
);
return [dbError, null];
}
}
Server Actions
const logger = createLogger({ module: "user-actions" });
export async function updateProfile(data: ProfileData): ActionResponse {
const { userId } = await auth();
if (!userId) {
logger.warn({ action: "updateProfile" }, "Unauthorized access attempt");
return { success: false, error: "Unauthorized" };
}
logger.info({ userId, action: "updateProfile" }, "Starting profile update");
const [error] = await updateUserProfile(userId, data);
if (error) {
logger.error(
{
userId,
errorCode: error.code,
action: "updateProfile",
},
"Profile update failed",
);
return { success: false, error: error.message };
}
logger.info(
{ userId, action: "updateProfile" },
"Profile updated successfully",
);
return { success: true, data: null };
}
Environment Configuration
# .env.local
LOG_LEVEL=debug # Development: see all logs
# .env.production
LOG_LEVEL=info # Production: info and above
File Locations
| Purpose | Location |
|---|---|
| Logger setup | lib/logger.ts |
| Feature loggers | Create in feature modules |
| Log level config | data/env/server.ts |
Sensitive Data Protection
Never log secrets, credentials, or personally identifiable information (PII).
What NOT to Log
// ❌ NEVER log these
logger.info({ password, token, apiKey }, "User login");
logger.info({ creditCard: card.number }, "Payment processed");
logger.info({ ssn, dateOfBirth, fullAddress }, "User profile loaded");
logger.debug({ cookie: req.headers.cookie }, "Request received");
logger.info({ authorization: req.headers.authorization }, "API call");
Safe Logging Patterns
// ✅ Log identifiers, not values
logger.info({ userId, email: maskEmail(email) }, "User login");
logger.info({ cardLast4: card.number.slice(-4) }, "Payment processed");
logger.debug({ hasAuthHeader: !!req.headers.authorization }, "API call");
// ✅ Log metadata, not content
logger.info({ bodySize: JSON.stringify(body).length }, "Request received");
logger.info({ fieldCount: Object.keys(formData).length }, "Form submitted");
Sensitive Fields Checklist
| Category | Fields to NEVER log |
|---|---|
| Auth | password, token, apiKey, secret, refreshToken, sessionId, cookie, authorization header |
| PII | SSN, date of birth, full address, phone number (log last 4 digits max) |
| Financial | credit card number, bank account, CVV, routing number |
| Health | medical records, diagnoses, insurance IDs |
Masking Helper
export function maskEmail(email: string): string {
const [local, domain] = email.split("@");
return `${local[0]}***@${domain}`;
}
export function maskId(id: string): string {
return id.length > 8 ? `${id.slice(0, 4)}...${id.slice(-4)}` : "***";
}
Framework-Level Protection
Consider adding a Pino redaction config to automatically strip sensitive fields:
const logger = pino({
level: process.env.LOG_LEVEL || "info",
redact: {
paths: [
"password",
"token",
"apiKey",
"*.password",
"*.token",
"*.apiKey",
"authorization",
"cookie",
"creditCard",
"ssn",
],
censor: "[REDACTED]",
},
});
Related Skills
firebase-firestore- Database logging patternsserver-actions- Action logging patternst3-env-validation- LOG_LEVEL configuration
Source
git clone https://github.com/JanSzewczyk/claude-plugins/blob/main/plugins/nextjs-react/skills/structured-logging/SKILL.mdView on GitHub Overview
This skill teaches structured logging using Pino in Next.js applications. It covers log levels, context enrichment, and creating child loggers to keep logs consistent and searchable, along with production best practices for observability.
How This Skill Works
Configure Pino in lib/logger.ts with a dynamic level and a development transport for readability. Use createLogger(context) to produce child loggers that automatically include context in every log entry, and always pass a context object before the message to keep logs structured and searchable in production.
When to Use It
- Add logging to a server action to trace flow and outcomes
- Decide which log level to use for events and errors
- Create a child logger with a shared context (e.g., module or request) for consistent logs
- Log errors with stack traces and relevant context to aid debugging
- Operate in production with structured JSON logs and appropriate transports
Quick Start
- Step 1: Configure the logger in lib/logger.ts with pino and development transport (pino-pretty) for readability
- Step 2: Use logger.info, logger.error, etc., and always pass a context object (e.g., { module: 'service' }) before the message
- Step 3: Create module-specific loggers via createLogger({ module: 'your-service' }) or logger.child(context) and reference them in code
Best Practices
- Always log a context object first, before the message
- Prefer structured object payloads over string interpolation in logs
- Use appropriate levels: fatal, error, warn, info, debug, trace
- Leverage child loggers to attach module, request, or operation context
- In production, rely on JSON logs and transport configurations; enable pretty printing only in development
Example Use Cases
- Server started with an info log: logger.info('Server started')
- Module-specific logging: const logger = createLogger({ module: 'user-service' }); logger.info({ userId: '123' }, 'User created')
- Error logging with context: logger.error({ userId, errorCode: error.code }, 'Failed to process request')
- Create a child logger with context: const logger = createLogger({ module: 'order-service', requestId: 'abc-123' });
- Comprehensive level usage: logger.fatal(...); logger.error(...); logger.warn(...); logger.info(...); logger.debug(...); logger.trace(...)
Frequently Asked Questions
Related Skills
log-analysis
chaterm/terminal-skills
日志分析与处理
monitoring
chaterm/terminal-skills
监控与告警
audit
chaterm/terminal-skills
--- name: audit description: 安全审计 version: 1.0.0 author: terminal-skills tags: [security, audit, auditd, logging, compliance, vulnerability] --- # 安全审计 ## 概述 安全审计、漏洞扫描、合规检查技能。 ## auditd 审计系统 ### 安装与管理 ```bash # 安装 apt install auditd audispd-plugins # Debian/Ubuntu yum install audit
system-admin
chaterm/terminal-skills
Linux system administration and monitoring
Alerting & Monitoring Testing
PramodDutta/qaskills
Testing monitoring and alerting configurations including threshold validation, alert routing, escalation policies, and false-positive rate monitoring.
prom-query
cacheforge-ai/cacheforge-skills
Prometheus Metrics Query & Alert Interpreter — query metrics, interpret timeseries, triage alerts