Skip to content

Logging chuyên nghiệp 📝

console.log('here') là thừa nhận thất bại.

Tip 1: Structured Logging

Problem

Text logs vô dụng ở quy mô lớn. Không thể query "Hiển thị tất cả lỗi của User 123".

Solution

Structured logging: Log as JSON objects, not strings.

Example

javascript
// Bad: String concatenation
console.log("User login failed: " + user.id + " at " + new Date());

// Bad: Template literals (still string)
console.log(`User ${user.id} login failed at ${new Date()}`);

// Good: Structured logging
logger.info({
  event: 'user_login_failed',
  userId: user.id,
  timestamp: new Date().toISOString(),
  context: {
    ip: req.ip,
    userAgent: req.headers['user-agent'],
    reason: 'invalid_password'
  }
});

Benefits:

  • Query by field: "Show all user_login_failed events"
  • Filter by user: "Show all events for userId: 123"
  • Aggregate: "Count login failures per hour"
  • Index in ELK, Datadog, CloudWatch

Pro Tips

  • Use logging library: Winston, Pino, Bunyan
  • Consistent field names: userId not user_id or uid
  • Include correlation ID for request tracing
  • Add metadata: Environment, service name, version

Tip 2: Log Levels

Problem

Mọi thứ đều log là INFO, không phân biệt được mức độ nghiêm trọng.

Solution

Use appropriate log levels: DEBUG, INFO, WARN, ERROR, FATAL.

Example

javascript
// DEBUG: Detailed info for development
logger.debug({
  event: 'cache_lookup',
  key: 'user:123',
  hit: true,
  ttl: 3600
});

// INFO: High-level events
logger.info({
  event: 'server_started',
  port: 3000,
  environment: 'production'
});

// WARN: Something unusual, but not breaking
logger.warn({
  event: 'api_retry',
  attempt: 2,
  maxAttempts: 3,
  reason: 'timeout'
});

// ERROR: Operation failed
logger.error({
  event: 'payment_failed',
  userId: 123,
  amount: 99.99,
  error: error.message,
  stack: error.stack
});

// FATAL: Application crash
logger.fatal({
  event: 'database_connection_lost',
  error: error.message
});

When to Use:

  • DEBUG: Development only, verbose details
  • INFO: Normal operations, milestones
  • WARN: Degraded state, retries, fallbacks
  • ERROR: Failed operations, caught exceptions
  • FATAL: Application crash, unrecoverable errors

Pro Tips

  • Production: Set level to INFO or WARN
  • Development: Set level to DEBUG
  • Environment variable: LOG_LEVEL=debug
  • Never log sensitive data at any level

Tip 3: Context is King

Problem

Log message không đủ thông tin để debug: "Payment failed" - Which user? Which payment?

Solution

Include context: User ID, request ID, transaction ID, etc.

Example

javascript
// Bad: No context
logger.error('Payment failed');

// Bad: Some context
logger.error('Payment failed for user 123');

// Good: Rich context
logger.error({
  event: 'payment_failed',
  userId: 123,
  orderId: 'ORD-456',
  amount: 99.99,
  currency: 'USD',
  paymentMethod: 'credit_card',
  gateway: 'stripe',
  gatewayTransactionId: 'ch_abc123',
  error: {
    code: 'card_declined',
    message: 'Insufficient funds',
    declineCode: 'insufficient_funds'
  },
  requestId: 'req-789',
  timestamp: '2024-01-15T10:30:00Z',
  userAgent: 'Mozilla/5.0...',
  ip: '192.168.1.1'
});

Essential Context:

  • Who: User ID, session ID
  • What: Event name, action
  • When: Timestamp (ISO 8601)
  • Where: Service name, server ID, environment
  • Why: Error message, reason code
  • How: Request ID for tracing

Pro Tips

  • Correlation ID: Track request across services
  • Include stack trace for errors
  • Sanitize sensitive data: Credit cards, passwords
  • Add business context: Order ID, product ID

Tip 4: Don't Log Sensitive Data

Problem

Logs chứa passwords, credit cards, API keys → Security breach.

Solution

Sanitize logs: Redact sensitive fields.

Example

javascript
// ⚠️ SECURITY WARNING: This example shows what NOT to do!
// NEVER log passwords, API keys, or credentials in production

// ❌ BAD: Logging sensitive data (DO NOT DO THIS!)
logger.info({
  event: 'user_created',
  user: {
    email: 'user@example.com',
    password: 'user-password-123',  // ❌ NEVER log passwords!
    creditCard: '4111111111111111'  // ❌ NEVER log credit cards!
  }
});

// ✅ GOOD: Sanitized logging
logger.info({
  event: 'user_created',
  user: {
    email: 'user@example.com',
    // Password should NEVER be logged, not even redacted
    creditCard: '****1111'  // Last 4 digits only
  }
});

// Better: Sanitization function
function sanitize(obj) {
  const sensitive = ['password', 'creditCard', 'ssn', 'apiKey'];
  const sanitized = { ...obj };
  
  sensitive.forEach(field => {
    if (sanitized[field]) {
      sanitized[field] = '[REDACTED]';
    }
  });
  
  return sanitized;
}

logger.info({
  event: 'user_created',
  user: sanitize(userData)
});

Never Log:

  • Passwords, API keys, tokens
  • Credit card numbers, CVV
  • Social security numbers
  • Personal health information
  • Private keys, certificates

Pro Tips

  • Automated sanitization: Middleware/interceptor
  • Log last 4 digits of cards: ****1111
  • Hash sensitive IDs if needed for correlation
  • Audit logs regularly for leaks

Tip 5: Performance Considerations

Problem

Excessive logging làm chậm application, tốn disk space.

Solution

Log strategically: Không log mọi thứ, optimize hot paths.

Example

javascript
// Bad: Logging in tight loop
for (let i = 0; i < 1000000; i++) {
  logger.debug(`Processing item ${i}`);  // ❌ 1M log entries!
}

// Good: Log summary
logger.info(`Processing ${items.length} items`);
for (let i = 0; i < items.length; i++) {
  processItem(items[i]);
}
logger.info(`Processed ${items.length} items in ${duration}ms`);

// Bad: Expensive computation in log
logger.debug(`User data: ${JSON.stringify(hugeObject)}`);  // ❌ Always computed

// Good: Lazy evaluation
logger.debug(() => `User data: ${JSON.stringify(hugeObject)}`);  // ✅ Only if DEBUG enabled

Optimization Strategies:

  • Sample logs: Log 1% of requests in high-traffic endpoints
  • Async logging: Don't block main thread
  • Log rotation: Prevent disk full
  • Conditional logging: Check level before expensive operations

Pro Tips

  • Use fast logging library: Pino (fastest for Node.js)
  • Avoid synchronous I/O in logs
  • Buffer logs, flush periodically
  • Monitor log volume, set alerts

Tip 6: Correlation IDs for Distributed Tracing

Problem

Request đi qua nhiều services, không trace được flow.

Solution

Correlation ID: Unique ID cho mỗi request, pass qua tất cả services.

Example

javascript
// API Gateway: Generate correlation ID
app.use((req, res, next) => {
  req.correlationId = req.headers['x-correlation-id'] || uuidv4();
  res.setHeader('x-correlation-id', req.correlationId);
  next();
});

// Service A: Log with correlation ID
logger.info({
  event: 'order_created',
  correlationId: req.correlationId,
  orderId: order.id
});

// Service A → Service B: Pass correlation ID
const response = await fetch('http://service-b/api/payment', {
  headers: {
    'x-correlation-id': req.correlationId
  }
});

// Service B: Log with same correlation ID
logger.info({
  event: 'payment_processed',
  correlationId: req.headers['x-correlation-id'],
  paymentId: payment.id
});

// Now query logs: "Show all events for correlationId: abc-123"
// → See entire request flow across services

Pro Tips

  • Use UUID v4 for correlation IDs
  • Include in all logs for that request
  • Pass in HTTP headers: x-correlation-id, x-request-id
  • Distributed tracing tools: Jaeger, Zipkin, OpenTelemetry

Tip 7: Error Logging Best Practices

Problem

Error logs thiếu thông tin để debug, hoặc quá nhiều noise.

Solution

Log errors properly: Stack trace, context, error code.

Example

javascript
// Bad: Swallow error
try {
  await processPayment();
} catch (error) {
  // ❌ Silent failure
}

// Bad: Log only message
try {
  await processPayment();
} catch (error) {
  logger.error(error.message);  // ❌ No stack trace
}

// Good: Full error logging
try {
  await processPayment();
} catch (error) {
  logger.error({
    event: 'payment_processing_failed',
    error: {
      name: error.name,
      message: error.message,
      stack: error.stack,
      code: error.code
    },
    context: {
      userId: user.id,
      orderId: order.id,
      amount: order.total
    },
    correlationId: req.correlationId
  });
  
  // Re-throw or handle
  throw error;
}

// Better: Error serialization
function serializeError(error) {
  return {
    name: error.name,
    message: error.message,
    stack: error.stack,
    code: error.code,
    ...error  // Include custom properties
  };
}

logger.error({
  event: 'payment_processing_failed',
  error: serializeError(error),
  context: { userId, orderId }
});

Pro Tips

  • Always include stack trace
  • Log at error boundary, not every catch
  • Include error code for categorization
  • Don't log same error multiple times (log once at boundary)

Tip 8: Log Aggregation and Monitoring

Problem

Logs scattered across nhiều servers, không thể search hoặc analyze.

Solution

Centralized logging: Ship logs to aggregation service.

Example

javascript
// Winston with Elasticsearch transport
const winston = require('winston');
const { ElasticsearchTransport } = require('winston-elasticsearch');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    // Console for development
    new winston.transports.Console(),
    
    // Elasticsearch for production
    new ElasticsearchTransport({
      level: 'info',
      clientOpts: {
        node: 'http://elasticsearch:9200'
      },
      index: 'logs'
    })
  ]
});

// Now logs are searchable in Kibana
logger.info({
  event: 'user_login',
  userId: 123
});

Popular Solutions:

  • ELK Stack: Elasticsearch, Logstash, Kibana
  • Datadog: Logs, metrics, traces in one platform
  • CloudWatch: AWS native logging
  • Splunk: Enterprise log management
  • Grafana Loki: Lightweight, Prometheus-like

Pro Tips

  • Set up alerts: Error rate > threshold
  • Create dashboards: Visualize trends
  • Retention policy: Keep logs 30-90 days
  • Cost optimization: Sample high-volume logs

Tip 9: Development vs Production Logging

Problem

Development logs quá verbose cho production, production logs không đủ cho debugging.

Solution

Environment-specific configuration: Different log levels per environment.

Example

javascript
// config/logger.js
const winston = require('winston');

const level = process.env.LOG_LEVEL || 
  (process.env.NODE_ENV === 'production' ? 'info' : 'debug');

const logger = winston.createLogger({
  level,
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    // Development: Pretty console output
    ...(process.env.NODE_ENV !== 'production' ? [
      new winston.transports.Console({
        format: winston.format.combine(
          winston.format.colorize(),
          winston.format.simple()
        )
      })
    ] : []),
    
    // Production: JSON to file/service
    ...(process.env.NODE_ENV === 'production' ? [
      new winston.transports.File({ filename: 'error.log', level: 'error' }),
      new winston.transports.File({ filename: 'combined.log' })
    ] : [])
  ]
});

module.exports = logger;

Environment Differences:

  • Development: DEBUG level, colorized console, pretty format
  • Staging: INFO level, JSON format, file + console
  • Production: WARN level, JSON format, centralized logging only

Pro Tips

  • Use environment variables: LOG_LEVEL=debug
  • Never log DEBUG in production (performance + security)
  • Enable DEBUG temporarily for troubleshooting
  • Separate error logs: error.log vs combined.log

Tip 10: Logging Metrics and Analytics

Problem

Logs chỉ dùng để debug, không dùng cho business insights.

Solution

Log business events: Track user behavior, feature usage, performance.

Example

javascript
// Technical logging
logger.info({
  event: 'http_request',
  method: 'POST',
  path: '/api/orders',
  statusCode: 201,
  duration: 145
});

// Business logging
logger.info({
  event: 'order_completed',
  userId: 123,
  orderId: 'ORD-456',
  revenue: 99.99,
  currency: 'USD',
  items: 3,
  paymentMethod: 'credit_card',
  shippingMethod: 'express',
  couponUsed: 'SAVE10',
  isFirstOrder: false,
  customerLifetimeValue: 450.00
});

// Feature usage logging
logger.info({
  event: 'feature_used',
  feature: 'dark_mode',
  userId: 123,
  enabled: true
});

// Performance logging
logger.info({
  event: 'query_performance',
  query: 'getUserOrders',
  duration: 234,
  rowsReturned: 50,
  cacheHit: false
});

Analytics Use Cases:

  • Revenue tracking: Sum order_completed events
  • Feature adoption: Count feature_used events
  • Performance monitoring: Average query_performance duration
  • User behavior: Funnel analysis from logs

Pro Tips

  • Separate analytics logs from debug logs
  • Use dedicated analytics tools: Mixpanel, Amplitude
  • Real-time dashboards: Grafana, Kibana
  • A/B testing: Log experiment variants

📋 Quick Reference

PracticeWhyHow
Structured LoggingQueryable, filterableLog JSON objects
Log LevelsPrioritize issuesDEBUG, INFO, WARN, ERROR, FATAL
ContextDebug fasterInclude user ID, request ID, etc.
SanitizeSecurityRedact passwords, cards
PerformanceDon't slow appSample, async, lazy eval
Correlation IDsTrace requestsPass ID across services
Error LoggingFull debugging infoStack trace + context
AggregationCentralized searchELK, Datadog, CloudWatch
Environment ConfigRight verbosityDEBUG in dev, INFO in prod
Business MetricsAnalyticsLog user behavior, revenue

🎯 Luyện tập ngay