Claude
Skills
Sign in
Back

platxa-logging

Included with Lifetime
$97 forever

Structured logging and correlation ID patterns for Platxa services. Covers Python logging, Go slog, TypeScript console patterns, and request tracing across microservices.

General

What this skill does


# Platxa Logging

Guide for structured logging and correlation ID patterns across Platxa services.

## Overview

| Language | Logger | Correlation | Output Format |
|----------|--------|-------------|---------------|
| **Python** | logging module | ContextVar | JSON (audit) |
| **TypeScript** | Console | Session ID | Text/JSON |
| **Go** | log/slog | context.Context | JSON |

## Core Principles

1. **Structured Over Unstructured**: JSON format for machine parsing and log aggregation
2. **Correlation Required**: Every request gets a unique ID, propagated across all services
3. **Context Propagation**: Thread/goroutine-safe context passing
4. **Consistent Field Keys**: Standardized keys across all languages
5. **Log Levels by Impact**: ERROR=failed, WARN=degraded, INFO=normal, DEBUG=diagnostic

## Standard Field Keys

| Key | Type | Description |
|-----|------|-------------|
| `timestamp` | ISO8601 | When event occurred |
| `level` | string | error/warn/info/debug |
| `message` | string | Human-readable description |
| `request_id` | string | Correlation ID (UUID) |
| `namespace` | string | K8s namespace (instance-xxx) |
| `service` | string | Service name |
| `duration_ms` | int | Operation duration in milliseconds |
| `error` | string | Error message (if any) |
| `user_id` | int | User identifier (if authenticated) |

## Correlation ID Patterns

### HTTP Header Propagation

```
Client → Service A → Service B → Service C
         │            │            │
         └─ X-Request-ID: abc123 ─┘
```

**Header Name**: `X-Request-ID` (primary) or `X-Correlation-ID`

### Generation Rules

1. **At Service Boundary**: Generate if header missing
2. **Format**: UUID v4 (`550e8400-e29b-41d4-a716-446655440000`)
3. **Propagate**: Include in all downstream requests
4. **Log**: Include in every log entry

### Python Implementation

```python
import uuid
from contextvars import ContextVar

# Thread-safe storage
request_id: ContextVar[str] = ContextVar('request_id', default='')

def get_request_id() -> str:
    """Get current request ID or generate new one."""
    rid = request_id.get()
    return rid if rid else str(uuid.uuid4())

def set_request_id(rid: str) -> None:
    """Set request ID for current context."""
    request_id.set(rid)

# Flask/Werkzeug middleware
@app.before_request
def extract_request_id():
    rid = request.headers.get('X-Request-ID', str(uuid.uuid4()))
    set_request_id(rid)
    g.request_id = rid

@app.after_request
def add_request_id_header(response):
    response.headers['X-Request-ID'] = get_request_id()
    return response
```

### Go Implementation

```go
type contextKey string

const RequestIDKey contextKey = "request_id"

func RequestIDMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        reqID := r.Header.Get("X-Request-ID")
        if reqID == "" {
            reqID = uuid.New().String()
        }
        ctx := context.WithValue(r.Context(), RequestIDKey, reqID)
        w.Header().Set("X-Request-ID", reqID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func GetRequestID(ctx context.Context) string {
    if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
        return reqID
    }
    return ""
}
```

### TypeScript Implementation

```typescript
import { v4 as uuidv4 } from 'uuid';

interface RequestContext {
  requestId: string;
  sessionId: string;
  userId?: string;
}

// Express middleware
function requestIdMiddleware(req: Request, res: Response, next: NextFunction) {
  const requestId = req.headers['x-request-id'] as string || uuidv4();
  req.context = { requestId, sessionId: req.sessionID };
  res.setHeader('X-Request-ID', requestId);
  next();
}

// Fetch wrapper for downstream calls
async function fetchWithCorrelation(url: string, ctx: RequestContext, options = {}) {
  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'X-Request-ID': ctx.requestId,
    },
  });
}
```

## Structured Logging

### JSON Output Format

```json
{
  "timestamp": "2024-01-15T10:30:45.123Z",
  "level": "info",
  "message": "request completed",
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "namespace": "instance-abc123xy",
  "service": "waking-service",
  "duration_ms": 45,
  "method": "GET",
  "path": "/api/status",
  "status": 200
}
```

### Python Structured Logger

```python
import logging
import json
from datetime import datetime

class StructuredFormatter(logging.Formatter):
    """JSON formatter with correlation ID."""

    def format(self, record: logging.LogRecord) -> str:
        log_entry = {
            'timestamp': datetime.utcnow().isoformat() + 'Z',
            'level': record.levelname.lower(),
            'message': record.getMessage(),
            'logger': record.name,
            'request_id': get_request_id(),
        }

        # Add extra fields
        if hasattr(record, 'namespace'):
            log_entry['namespace'] = record.namespace
        if hasattr(record, 'duration_ms'):
            log_entry['duration_ms'] = record.duration_ms
        if record.exc_info:
            log_entry['exception'] = self.formatException(record.exc_info)

        return json.dumps(log_entry)

# Setup
handler = logging.StreamHandler()
handler.setFormatter(StructuredFormatter())
logging.root.addHandler(handler)
```

### Go slog Setup

```go
import (
    "log/slog"
    "os"
)

// Field key constants
const (
    KeyRequestID  = "request_id"
    KeyNamespace  = "namespace"
    KeyDurationMS = "duration_ms"
    KeyError      = "error"
    KeyService    = "service"
)

func SetupLogger(level string, jsonFormat bool) {
    opts := &slog.HandlerOptions{Level: parseLevel(level)}

    var handler slog.Handler
    if jsonFormat {
        handler = slog.NewJSONHandler(os.Stdout, opts)
    } else {
        handler = slog.NewTextHandler(os.Stdout, opts)
    }

    slog.SetDefault(slog.New(handler))
}

// Usage with context
func LogWithContext(ctx context.Context, msg string, args ...any) {
    allArgs := append([]any{KeyRequestID, GetRequestID(ctx)}, args...)
    slog.Info(msg, allArgs...)
}
```

### TypeScript Logger

```typescript
interface LogEntry {
  timestamp: string;
  level: 'error' | 'warn' | 'info' | 'debug';
  message: string;
  requestId?: string;
  [key: string]: unknown;
}

class StructuredLogger {
  constructor(private service: string, private context?: RequestContext) {}

  private log(level: LogEntry['level'], message: string, data?: Record<string, unknown>) {
    const entry: LogEntry = {
      timestamp: new Date().toISOString(),
      level,
      message,
      service: this.service,
      requestId: this.context?.requestId,
      ...data,
    };

    if (level === 'error') {
      console.error(JSON.stringify(entry));
    } else {
      console.log(JSON.stringify(entry));
    }
  }

  info(message: string, data?: Record<string, unknown>) { this.log('info', message, data); }
  warn(message: string, data?: Record<string, unknown>) { this.log('warn', message, data); }
  error(message: string, data?: Record<string, unknown>) { this.log('error', message, data); }
  debug(message: string, data?: Record<string, unknown>) { this.log('debug', message, data); }
}
```

## Log Level Guidelines

| Level | When to Use | Example |
|-------|-------------|---------|
| ERROR | Operation failed, requires attention | `"database connection failed"` |
| WARN | Degraded but working, potential issue | `"rate limit at 80%"` |
| INFO | Normal operation, significant events | `"instance started"` |
| DEBUG | Diagnostic detail, development | `"parsing config file"` |

### Dynamic Log Levels (Go)

```go
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        wrapped := &responseWriter{ResponseWriter: w}

        next.ServeHTTP(wrapped, r)

        // Dynamic level based on status
        level := slog.LevelInfo
        if wrap

Related in General