The Problem with Microservice Logging
Traditional logging approaches fail in microservices. Learn how to implement effective distributed logging.
Distributed logging seems simple until your system grows. Here's why traditional logging approaches fail in microservices, and how to do it right.
Common Anti-Patterns
1. Inconsistent Log Formats
// ❌ BAD: Different formats across services
// Service A
logger.info('User created', { userId: 123 });
// Service B
console.log(`Order processed: ${orderId}`);
// Service C
winston.log('info', 'Payment received', {
amount: 100,
currency: 'USD'
});
2. Missing Context
// ❌ BAD: Insufficient context
class PaymentService {
async processPayment(payment: Payment) {
try {
await this.stripe.charge(payment.amount);
logger.info('Payment processed'); // Which payment? What amount?
} catch (error) {
logger.error('Payment failed'); // Why? For whom?
}
}
}
Better Approaches
1. Structured Logging
// ✅ BETTER: Consistent structured logs
interface LogEntry {
timestamp: string;
level: LogLevel;
service: string;
traceId: string;
spanId: string;
message: string;
context: Record<string, any>;
metadata: {
host: string;
environment: string;
version: string;
};
}
class StructuredLogger {
constructor(
private service: string,
private options: LoggerOptions
) {}
info(
message: string,
context?: Record<string, any>
): void {
this.log('info', message, context);
}
private log(
level: LogLevel,
message: string,
context?: Record<string, any>
): void {
const entry: LogEntry = {
timestamp: new Date().toISOString(),
level,
service: this.service,
traceId: this.getTraceId(),
spanId: this.getSpanId(),
message,
context: context || {},
metadata: {
host: os.hostname(),
environment: process.env.NODE_ENV!,
version: process.env.APP_VERSION!
}
};
this.output(entry);
}
}
2. Correlation Context
// ✅ BETTER: Request tracing
class RequestContext {
private static store = new AsyncLocalStorage<Context>();
static middleware() {
return (req: Request, res: Response, next: NextFunction) => {
const context = {
traceId: req.headers['x-trace-id'] || uuid(),
spanId: uuid(),
userId: req.user?.id,
sessionId: req.session?.id
};
RequestContext.store.run(context, () => next());
};
}
static getContext(): Context {
return this.store.getStore() || {};
}
}
class CorrelatedLogger {
info(message: string, context?: Record<string, any>) {
const requestContext = RequestContext.getContext();
this.log('info', message, {
...requestContext,
...context
});
}
}
3. Log Aggregation
// ✅ BETTER: Centralized logging
class LogAggregator {
private readonly buffer: LogEntry[] = [];
private readonly flushInterval: number = 5000; // 5s
constructor(
private readonly logstash: LogstashClient,
private readonly options: AggregatorOptions
) {
this.startFlushInterval();
}
append(entry: LogEntry): void {
this.buffer.push(entry);
if (this.buffer.length >= this.options.batchSize) {
this.flush();
}
}
private async flush(): Promise<void> {
if (this.buffer.length === 0) return;
const batch = this.buffer.splice(0, this.buffer.length);
try {
await this.logstash.bulk(batch);
} catch (error) {
// Handle failed delivery
this.handleFailedDelivery(batch, error);
}
}
}
Advanced Patterns
1. Semantic Logging
// ✅ BETTER: Event-based logging
class OrderLogger {
logOrderCreated(order: Order): void {
this.logger.event('order_created', {
orderId: order.id,
userId: order.userId,
items: order.items.length,
total: order.total,
currency: order.currency
});
}
logOrderFulfillment(order: Order): void {
this.logger.event('order_fulfilled', {
orderId: order.id,
warehouse: order.fulfillment.warehouse,
shippingMethod: order.fulfillment.method,
estimatedDelivery: order.fulfillment.estimatedDate
});
}
}
class EventLogger {
event(
name: string,
data: Record<string, any>
): void {
this.logger.info(name, {
type: 'event',
event: name,
data,
timestamp: new Date().toISOString()
});
}
}
2. Log Sampling
// ✅ BETTER: Smart log sampling
class SamplingLogger {
private samplingRates = {
error: 1.0, // Log all errors
warn: 1.0, // Log all warnings
info: 0.1, // Log 10% of info
debug: 0.01 // Log 1% of debug
};
log(
level: LogLevel,
message: string,
context?: Record<string, any>
): void {
if (!this.shouldSample(level)) {
return;
}
this.logger.log(level, message, context);
}
private shouldSample(level: LogLevel): boolean {
const rate = this.samplingRates[level] || 1.0;
return Math.random() < rate;
}
}
3. Log Enrichment
// ✅ BETTER: Context enrichment
class LogEnricher {
enrich(entry: LogEntry): EnrichedLogEntry {
return {
...entry,
environment: this.getEnvironmentInfo(),
runtime: this.getRuntimeInfo(),
metrics: this.getMetrics(),
kubernetes: this.getK8sMetadata()
};
}
private getMetrics() {
return {
memory: process.memoryUsage(),
cpu: process.cpuUsage(),
uptime: process.uptime()
};
}
private getK8sMetadata() {
return {
namespace: process.env.K8S_NAMESPACE,
pod: process.env.K8S_POD_NAME,
node: process.env.K8S_NODE_NAME
};
}
}
Best Practices
1. Log Levels
// ✅ BETTER: Clear log level guidelines
enum LogLevel {
ERROR = 'error', // Service is unusable
WARN = 'warn', // Service degraded
INFO = 'info', // Important business events
DEBUG = 'debug', // Development information
TRACE = 'trace' // Detailed debugging
}
class ServiceLogger {
error(message: string, error: Error): void {
this.log(LogLevel.ERROR, message, {
error: {
message: error.message,
stack: error.stack,
name: error.name
}
});
}
}
2. Performance Monitoring
// ✅ BETTER: Performance tracking
class PerformanceLogger {
startOperation(name: string): Operation {
const start = process.hrtime.bigint();
return {
end: () => {
const end = process.hrtime.bigint();
const duration = Number(end - start) / 1e6; // ms
this.logger.info(`Operation ${name} completed`, {
operation: name,
durationMs: duration,
timestamp: new Date().toISOString()
});
}
};
}
}
// Usage
async function processOrder(order: Order) {
const operation = performanceLogger
.startOperation('process_order');
try {
await processOrderSteps(order);
} finally {
operation.end();
}
}
Conclusion
For better microservice logging:
- Use structured, consistent log formats
- Maintain request context across services
- Implement proper log aggregation
- Consider log sampling for high-volume services
- Enrich logs with relevant metadata
Remember: Good logging is about finding the right balance between visibility and noise.