Every backend service MUST use structured logging — pino/structlog/slog, JSON format, request IDs, proper log levels. console.log is never acceptable.
88
85%
Does it follow best practices?
Impact
97%
3.03xAverage score across 4 eval scenarios
Passed
No known issues
Any time you create or modify a backend service, API, or background worker, you MUST set up structured logging. There is no exception. console.log, print, and fmt.Println are never acceptable in production code.
This is not optional. This is not something to add later. Structured logging is part of building the application, just like error handling and input validation.
When you build any backend service (API, worker, CLI tool, background processor), always:
LOG_LEVEL env var with info as default.| Level | Use for | Examples |
|---|---|---|
| error | Something failed that shouldn't have | Database connection lost, unhandled exception |
| warn | Something unexpected but handled | Rate limit hit, deprecated endpoint called, resource not found (404) |
| info | Business events worth recording | Order created, user signed up, payment processed, job completed |
| debug | Developer details, off in production | Query results, calculated values, cache hits |
Critical rules:
info should tell the business story of what happeneddebug is for development only — too noisy for productionEvery HTTP service must assign and propagate request IDs for traceability.
import pino from 'pino';
import { randomUUID } from 'crypto';
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
// Middleware: assign request ID and child logger
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] as string || randomUUID();
res.setHeader('x-request-id', req.id);
req.log = logger.child({ request_id: req.id });
next();
});
// Request completion logging middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
req.log.info({
method: req.method,
path: req.path,
status: res.statusCode,
duration_ms: Date.now() - start,
}, "request_completed");
});
next();
});
// In route handlers — use req.log, structured fields, correct levels
router.post("/api/orders", (req, res) => {
req.log.info({ customer_id: body.customer_id }, "creating_order");
const order = orderService.create(body);
req.log.info({ order_id: order.id, total_cents: order.total }, "order_created");
res.status(201).json({ data: order });
});import structlog
import uuid
logger = structlog.get_logger()
@app.middleware("http")
async def add_request_id(request, call_next):
request_id = request.headers.get("x-request-id", str(uuid.uuid4()))
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(request_id=request_id)
response = await call_next(request)
response.headers["x-request-id"] = request_id
structlog.contextvars.unbind_contextvars("request_id")
return response
@app.post("/api/orders")
async def create_order(body: CreateOrderRequest):
logger.info("creating_order", customer_id=body.customer_id)
order = order_service.create(body)
logger.info("order_created", order_id=order.id, total_cents=order.total_cents)
return {"data": order}import "log/slog"
func requestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = uuid.NewString()
}
w.Header().Set("X-Request-ID", requestID)
logger := slog.With("request_id", requestID)
ctx := context.WithValue(r.Context(), "logger", logger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}For non-HTTP services (queue consumers, cron jobs, background workers):
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
async function processJob(job: Job) {
const jobLog = logger.child({ job_id: job.id, queue: job.queue });
jobLog.info({ type: job.type }, "job_started");
try {
const result = await executeJob(job);
jobLog.info({ duration_ms: result.duration }, "job_completed");
} catch (err) {
jobLog.error({ err, type: job.type }, "job_failed");
throw err;
}
}Log errors with full context in structured fields — never embed stack traces in the message string:
// GOOD — structured error with context
req.log.error({ err, order_id: orderId, customer_id: customerId }, "order_creation_failed");
// BAD — unstructured string
console.error(`Error creating order: ${err.message} ${err.stack}`);