Overview
Logging and observability at the code level means designing classes so their behavior can be understood in production: structured logs with context (orderId, userId, traceId), appropriate levels (DEBUG, INFO, WARN, ERROR), metrics for counts and latency, and tracing spans around important operations. Good logs explain what happened without a debugger.
Design-wise, logging is a cross-cutting concern: inject a logger interface, use decorators or middleware for common instrumentation, never log secrets or personal data, and keep log statements meaningful. In LLD discussions, mentioning audit logs (who did what), metrics for key actions, and correlation IDs shows production awareness.
The crew records position, weather, and important events with timestamps. When something goes wrong, the log reconstructs what happened. Writing 'something happened' every minute would be useless noise.
When to use it
- Any code running in production.
- Audit requirements (payments, admin actions).
- Debugging intermittent issues and performance.
Where it shows up in interviews
Recognize it when: 'how would you debug this in production?'
- Design a payment processor
- Design a task scheduler
- Design a rate limiter
Where it is used in real software
Structured logging libraries with levels and JSON output.
Standard APIs for traces, metrics, and log correlation.
Attaches request IDs to every log line in a thread or async context.
Key terms
- Structured logging
- Key-value or JSON logs, machine-queryable.
- Log levels
- DEBUG, INFO, WARN, ERROR by severity.
- Correlation ID
- ID linking all logs of one request.
- Audit log
- Immutable record of who did what and when.
- PII redaction
- Removing personal data from logs.
How it works, step by step
- 1Inject a logger
Via constructor or a framework factory.
- 2Log events, not narration
'order placed' with orderId and total.
- 3Choose levels carefully
ERROR for failures needing attention; INFO for business events.
- 4Attach context
traceId, userId (not secrets).
- 5Add metrics and spans
Counters for outcomes, histograms for latency.
Useful vs noisy logs
Payment processing
| Log line | Quality | Why |
|---|---|---|
| 'here 1', 'here 2' | Bad | No meaning or context |
| 'payment failed' | Weak | Which payment? Why? |
| {event: 'payment_declined', orderId, reason: 'insufficient_funds', amountCents} | Good | Queryable and actionable |
| {card: '4111 1111 1111 1111'} | Dangerous | Never log card numbers or secrets |
NOWLog line: 'here 1', 'here 2' | Quality: Bad | Why: No meaning or context
Structured, contextual, and safe logs turn production debugging from guesswork into queries.
Implementation
interface Logger { info(event: string, fields?: Record<string, unknown>): void; error(event: string, fields?: Record<string, unknown>): void; child(fields: Record<string, unknown>): Logger;} const REDACT = new Set(["password", "cardNumber", "token"]);const jsonLogger = (base: Record<string, unknown> = {}): Logger => { const write = (level: string, event: string, fields: Record<string, unknown> = {}) => { const safe = Object.fromEntries(Object.entries({ ...base, ...fields }).map(([k, v]) => [k, REDACT.has(k) ? "[redacted]" : v])); console.log(JSON.stringify({ ts: new Date().toISOString(), level, event, ...safe })); }; return { info: (e, f) => write("info", e, f), error: (e, f) => write("error", e, f), child: (f) => jsonLogger({ ...base, ...f }), };}; class PaymentProcessor { constructor(private gateway: PaymentGateway, private log: Logger) {} async pay(orderId: string, amountCents: number) { const log = this.log.child({ orderId }); const start = performance.now(); try { const res = await this.gateway.charge(orderId, amountCents); log.info("payment_succeeded", { amountCents, ms: Math.round(performance.now() - start) }); return res; } catch (e) { log.error("payment_failed", { amountCents, reason: (e as Error).message }); throw e; } }}Complexity and performance
Async appenders keep it off the hot path.
Sample DEBUG logs.
Trade-offs
More logs aid debugging but cost storage and bury signals; log business events and failures, sample the rest.
Async appenders reduce latency but can drop logs on crash.
Variants and related techniques
Separate durable, append-only log for compliance.
Wrap services with logging and metrics decorators.
Common mistakes
- Logging secrets, tokens, or personal data.
Fix: Redact centrally and review log fields.
- String concatenation at DEBUG level.
Fix: Use parameterized logging to avoid building unused strings.
- Logging and rethrowing at every layer.
Fix: Log once where the error is handled.
Interview questions
How do you design logging for an LLD component?
Inject a logger, emit structured events for key business actions and failures with IDs and context, use proper levels, redact sensitive fields, add metrics for counts and latency, and propagate a correlation ID.
Why structured logs?
Key-value logs can be filtered, aggregated, and alerted on reliably (for example count of payment_failed by reason), while free-text logs require fragile parsing.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Add structured logging to a booking service | Easy | Events and context. |
| Build a logging decorator with timing metrics | Medium | Cross-cutting concern. |