Skip to content

Backend structured logging with traceId #249

@test3207

Description

@test3207

Parent Epic: #190

Overview

Enhance M3W backend logging with structured JSON output and request tracing for better observability in Loki.

Log Format Specification

Common Fields (Auto-injected)

Field Type Source Description
timestamp string Auto ISO8601 format
level string Manual error / warn / info / debug
service string Config Fixed m3w-backend
region string Env var jp / sea (from REGION)
traceId string Middleware Request trace ID (from X-Trace-Id header)
gateway string Header Which gateway handled the request (from X-Gateway header)
userId string Middleware Current user ID

Business Fields (Manual)

Field Type Description
source string filename.functionName (手写)
col1 string Business category (e.g., auth, upload, playlist)
col2 string Sub-category (e.g., login, delete, create)
col3 string Business ID (e.g., githubId, songId)
raw object Arbitrary raw data
error string Error message (for error level only)
errorStack string Error stack trace (for error level only)
message string Human-readable description

Log Levels

Level Usage
error Errors requiring attention (API failures, exceptions)
warn Abnormal but recoverable situations (token expired, invalid input)
info Key business events (login, upload, delete)
debug Debug info (disabled in production)

Example: Info Log

{
  "timestamp": "2025-12-22T10:30:00.000Z",
  "level": "info",
  "service": "m3w-backend",
  "region": "jp",
  "traceId": "550e8400-e29b-41d4-a716-446655440000",
  "gateway": "gateway1",
  "userId": "user_123",

  "source": "auth.handleOAuthCallback",
  "col1": "auth",
  "col2": "login",
  "col3": "20714425",
  "raw": { "isNewUser": false, "email": "test@example.com" },

  "message": "User logged in successfully"
}

Example: Error Log

{
  "timestamp": "2025-12-22T10:30:00.000Z",
  "level": "error",
  "service": "m3w-backend",
  "region": "jp",
  "traceId": "550e8400-e29b-41d4-a716-446655440000",
  "gateway": "gateway1",
  "userId": "user_123",

  "source": "auth.handleOAuthCallback",
  "col1": "auth",
  "col2": "login",
  "col3": "20714425",
  "raw": { "response": { "status": 401 } },

  "error": "GitHub API returned 401",
  "errorStack": "Error: GitHub API returned 401\n    at fetchUser (auth.ts:82)\n    at handleOAuthCallback (auth.ts:142)",

  "message": "GitHub OAuth failed"
}

Implementation

1. TraceId Middleware

// src/lib/trace-middleware.ts
export const traceMiddleware = () => {
  return async (c: Context, next: Next) => {
    const traceId = c.req.header('x-trace-id') || crypto.randomUUID();
    c.set('traceId', traceId);
    c.header('x-trace-id', traceId);
    await next();
  };
};

2. Logger Factory

// src/lib/logger.ts
import pino from 'pino';

const baseLogger = pino({
  level: process.env.LOG_LEVEL || 'info',
  // ... existing config
});

export function createLogger(c?: Context) {
  const base = {
    service: 'm3w-backend',
    region: process.env.REGION || 'unknown',
    traceId: c?.get('traceId'),
    gateway: c?.req.header('x-gateway'),
    userId: c?.get('userId'),
  };

  return {
    info: (source: string, col1: string, col2: string, col3: string, raw: object, message: string) => {
      baseLogger.info({ ...base, source, col1, col2, col3, raw, message });
    },
    warn: (source: string, col1: string, col2: string, col3: string, raw: object, message: string) => {
      baseLogger.warn({ ...base, source, col1, col2, col3, raw, message });
    },
    error: (source: string, col1: string, col2: string, col3: string, raw: object, err: Error, message: string) => {
      baseLogger.error({
        ...base, source, col1, col2, col3, raw,
        error: err.message,
        errorStack: err.stack,
        message,
      });
    },
  };
}

3. Usage Example

// routes/auth.ts
async function handleOAuthCallback(c: Context) {
  const log = createLogger(c);

  // Success log
  log.info(
    'auth.handleOAuthCallback',  // source
    'auth',                       // col1
    'login',                      // col2
    user.githubId,                // col3
    { isNewUser, email },         // raw
    'User logged in'              // message
  );

  // Error log
  log.error(
    'auth.handleOAuthCallback',
    'auth',
    'login',
    githubId || '',
    { response: data },
    error,
    'GitHub OAuth failed'
  );
}

4. Frontend Log Endpoint

// routes/logs.ts
app.post('/api/logs', async (c) => {
  const logs = await c.req.json();
  // Validate, sanitize, rate limit
  // Output to stdout (picked up by Alloy)
  for (const log of logs) {
    baseLogger[log.level || 'info']({
      ...log,
      service: 'm3w-frontend',  // Override to ensure correct service
    });
  }
  return c.json({ ok: true });
});

Tasks

Backend (m3w)

Gateway (m3w-k8s)

修改 gateway-routing.lua.j2

-- 生成或透传 traceId
local trace_id = ngx.req.get_headers()["X-Trace-Id"]
if not trace_id or trace_id == "" then
    trace_id = ngx.var.request_id  -- nginx 内置请求 ID
end
ngx.req.set_header("X-Trace-Id", trace_id)
ngx.req.set_header("X-Gateway", "{{ inventory_hostname }}")
  • Modify gateway-routing.lua.j2 to set X-Trace-Id header (generate if not present)
  • Modify gateway-routing.lua.j2 to set X-Gateway header (e.g., gateway1)
  • Redeploy gateways

Loki Query Examples

# Query all backend logs
{service="m3w-backend"}

# Query by source
{service="m3w-backend"} | json | source =~ "auth.*"

# Query by business category
{service="m3w-backend"} | json | col1 = "upload"

# Query errors
{service="m3w-backend"} | json | level = "error"

# Query by user
{service="m3w-backend"} | json | userId = "user_123"

# Trace full request chain
{service=~"m3w-.*"} | json | traceId = "550e8400..."

Dependencies

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions