skills-ws
Version:
87 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.
104 lines (85 loc) • 4.11 kB
Markdown
## Contents
- Structured Logging That Actually Helps
- The Pattern
- Express Middleware
- Log Levels That Actually Mean Something
## Structured Logging That Actually Helps
### The Pattern
**What ships to production must be structured (JSON), so a log pipeline can index and query it.** No `console.log("user signed up")` in app code. Locally, pretty-print for human eyes — but only at the *sink*, never by changing what the app emits: pipe through `pino-pretty` in dev (`node app.js | pino-pretty`) or set `transport: { target: 'pino-pretty' }` behind a `NODE_ENV !== 'production'` guard. The emitted log object stays identical; only rendering differs.
```typescript
// lib/logger.ts
import pino from 'pino';
import { trace, context } from '@opentelemetry/api';
export const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level(label) {
return { level: label }; // "info" not 30
},
},
serializers: {
err: pino.stdSerializers.err,
req: pino.stdSerializers.req,
res: pino.stdSerializers.res,
},
// Stamp every line with the active trace/span so logs link to traces.
// This is what the Loki `derivedFields` regex (`"trace_id":"(\w+)"`) and the
// Tempo `tracesToLogsV2` link rely on — without it, trace↔log jumps are dead.
mixin() {
const span = trace.getSpan(context.active());
if (!span) return {};
const { traceId, spanId } = span.spanContext();
return { trace_id: traceId, span_id: spanId };
},
// Add service metadata to every log
base: {
service: process.env.SERVICE_NAME || 'api',
version: process.env.APP_VERSION || 'unknown',
environment: process.env.NODE_ENV || 'development',
},
});
// Request-scoped logger with correlation ID
export function createRequestLogger(requestId: string, userId?: string) {
return logger.child({
requestId,
userId,
});
}
```
### Express Middleware
```typescript
import { randomUUID } from 'crypto';
import { createRequestLogger } from './logger';
app.use((req, res, next) => {
const requestId = req.headers['x-request-id'] as string || randomUUID();
req.log = createRequestLogger(requestId, req.user?.id);
res.setHeader('x-request-id', requestId);
const start = performance.now();
res.on('finish', () => {
const duration = performance.now() - start;
req.log.info({
method: req.method,
url: req.originalUrl,
statusCode: res.statusCode,
duration: Math.round(duration),
contentLength: res.getHeader('content-length'),
}, 'request completed');
});
next();
});
```
### Log Levels That Actually Mean Something
| Level | When to Use | Example |
|-------|-------------|---------|
| `fatal` | Process is about to crash | Uncaught exception, out of memory |
| `error` | Operation failed, needs attention | Payment processing failed, DB connection lost |
| `warn` | Something unexpected, but handled | Rate limit approaching, deprecated API called |
| `info` | Business events worth recording | User signed up, order placed, deploy completed |
| `debug` | Technical details for debugging | SQL queries, cache hit/miss, request/response bodies |
| `trace` | Extremely verbose, rarely enabled | Function entry/exit, variable values |
**Rule of thumb:** If you'd want to see it in production logs during an incident, it's `info`. If you'd only want it when actively debugging, it's `debug`.
**But logs are not your business-analytics pipeline.** High-volume, high-cardinality business events (every page view, every cache lookup, per-item loop iterations) should NOT be `info` logs — they blow up ingestion cost and bury signal. Instead:
- **Count them as metrics** (`Counter`/`Histogram`) — `signups_total`, `orders_total{status}` — and log only the exceptional cases.
- **Sample** routine successes if you must log them: log 1-in-N, or log the slow/failed tail only.
- Reserve `info` for events you'd actually read one-by-one during an incident (deploys, config changes, a payment that failed). A useful budget: an idle service should emit roughly *zero* `info` lines per second.
---