Envelope

The envelope is the structured log payload emitted by fapilog before serialization.

Shape (v1.1 Schema)

Every log entry follows the v1.1 schema with semantic field groupings:

{
  "timestamp": "2024-01-15T10:30:00.123Z",
  "level": "INFO",
  "message": "User action",
  "logger": "app",
  "context": {
    "message_id": "550e8400-e29b-41d4-a716-446655440000",
    "correlation_id": "req-123",
    "request_id": "abc-456",
    "user_id": "user-789"
  },
  "diagnostics": {
    "service": "api",
    "env": "production",
    "host": "web-01",
    "pid": 12345
  },
  "data": {
    "action": "login",
    "duration_ms": 42
  }
}

Core Fields

  • timestamp: RFC3339 UTC string with millisecond precision (e.g., "2024-01-15T10:30:00.123Z").

  • level: one of DEBUG/INFO/WARNING/ERROR/CRITICAL.

  • message: the message string passed to the logger method.

  • logger: logger name (get_logger(name=...)).

Semantic Groupings

  • context: Request/trace identifiers. These identify WHO and WHAT request is being logged.

    • message_id: Unique UUID for each log entry. Always present, auto-generated.

    • correlation_id: Shared identifier across related log entries. Always present; null when no correlation context is active, populated when set via context variable (e.g., request_id_var). Use this for request-level tracing.

    • request_id, user_id, tenant_id, trace_id, span_id: Optional trace context fields.

  • diagnostics: Runtime/operational data (service, env, host, pid, exception). These identify WHERE the log originated and system state.

  • data: User-provided structured data from extra kwargs and bound context (excluding context fields).

message_id vs correlation_id

  • message_id: Uniquely identifies each individual log entry. A new UUID is generated for every log call. Use this when you need to reference a specific log line.

  • correlation_id: Groups related log entries together. Always present in the envelope for stable schema shape. Set to null when no correlation context is active; populated when set via request_id_var.set() or when using FastAPI integration (which sets it automatically per request). Use this for tracing all logs from a single HTTP request or operation.

from fapilog import get_logger
from fapilog.core.context import request_id_var

logger = get_logger()

# Without context - correlation_id is null
logger.info("standalone log")  # {"context": {"message_id": "aaa-111", "correlation_id": null}}

# With context - both message_id and correlation_id present
token = request_id_var.set("req-123")
logger.info("first")   # {"context": {"message_id": "bbb-222", "correlation_id": "req-123"}}
logger.info("second")  # {"context": {"message_id": "ccc-333", "correlation_id": "req-123"}}
request_id_var.reset(token)

Exceptions

When exc_info=True or exc is provided, the envelope includes structured exception data in the diagnostics.exception field:

{
  "diagnostics": {
    "exception": {
      "exception_type": "ValueError",
      "exception_message": "bad input",
      "stack": "... trimmed stack trace ...",
      "frames": [
        {"filename": "app.py", "lineno": 10, "function": "handle", "context_line": "..."}
      ]
    }
  }
}

Serialization respects exceptions_max_frames and exceptions_max_stack_chars from settings.

Redaction and serialization

  • Redactors (if enabled) run on the envelope after enrichment, before the sink.

  • When serialize_in_flush=True and the sink supports write_serialized, the envelope is serialized once per entry in the flush path.

Where to see it

  • Default stdout sink emits JSON lines preserving the envelope structure. The context, diagnostics, and data fields remain nested.

  • File/HTTP sinks receive the same envelope structure before serialization.