Configuration

Configure fapilog using presets for quick setup, environment variables, or the Settings class for full control.

Choosing an Approach

Situation

Recommended Approach

Why

Just getting started

get_logger(preset="...")

Zero config, sensible defaults

FastAPI app

FastAPIBuilder().with_preset("production").build()

Automatic middleware and request context

Writing new code

LoggerBuilder()

IDE autocomplete, type safety, discoverable API

Config from env/files

Settings + environment variables

12-factor apps, Kubernetes, external config

Need compliance presets

LoggerBuilder().with_redaction(preset="GDPR_PII")

One-liner GDPR, HIPAA, PCI-DSS protection

Quick decision:

Start here
    │
    ├── Want sensible defaults with minimal code?
    │   └── Use presets: get_logger(preset="production")
    │
    ├── Want IDE autocomplete and type checking?
    │   └── Use Builder: LoggerBuilder().with_preset("production").build()
    │
    └── Config comes from environment or external files?
        └── Use Settings + env vars: FAPILOG_CORE__LOG_LEVEL=INFO

All approaches can be combined. For example, start with a preset and customize with the builder:

from fapilog import LoggerBuilder

logger = (
    LoggerBuilder()
    .with_preset("production")           # Start with production defaults
    .with_redaction(preset="HIPAA_PHI")  # Add HIPAA compliance
    .with_sampling(rate=0.1)             # Sample 10% of debug logs
    .build()
)

How Configuration Layers Work

Fapilog’s three configuration methods form a layered hierarchy:

┌─────────────────────────────────────────────────────────┐
│  Builder Methods           (highest priority)           │
│  .with_level("DEBUG")                                   │
├─────────────────────────────────────────────────────────┤
│  Settings Object                                        │
│  Settings(core__log_level="INFO")                       │
├─────────────────────────────────────────────────────────┤
│  Environment Variables     (lowest priority)            │
│  FAPILOG_CORE__LOG_LEVEL=WARNING                        │
└─────────────────────────────────────────────────────────┘

How they interact:

  1. Builder creates Settings - When you call .build(), the builder constructs a Settings object internally with all your configured values.

  2. Settings reads environment variables - The Settings class uses Pydantic’s env var support. Any setting not explicitly provided is automatically populated from environment variables.

  3. Explicit values override env vars - Builder methods and explicit Settings parameters take precedence over environment variables.

Example: If you set FAPILOG_CORE__LOG_LEVEL=WARNING in your environment:

# Environment variable applies (WARNING)
logger = LoggerBuilder().add_stdout().build()

# Builder method overrides env var (DEBUG)
logger = LoggerBuilder().with_level("DEBUG").add_stdout().build()

# Explicit Settings parameter overrides env var (ERROR)
settings = Settings(core__log_level="ERROR")
logger = get_logger(settings=settings)

This layering enables a common 12-factor pattern: define defaults in code with the builder, allow operational overrides via environment variables at deployment time.

Output format

Use format to control stdout output without building a full Settings object:

from fapilog import get_logger

logger = get_logger(format="auto")   # Default: pretty in TTY, JSON when piped
logger = get_logger(format="pretty") # Force human-readable output
logger = get_logger(format="json")   # Force structured JSON

Notes:

  • format is mutually exclusive with settings.

  • If both preset and format are provided, format overrides the preset’s stdout sink.

  • When settings is omitted, format defaults to auto.

Default behaviors

When you call get_logger() without a preset, settings, or FAPILOG_CORE__LOG_LEVEL, fapilog selects a sensible default log level:

  • TTY (interactive terminal): DEBUG

  • Non-TTY (pipes, scripts): INFO

  • CI: forces INFO even if TTY

Explicit core.log_level or a preset always overrides these defaults.

Environment Auto-Detection

When you call get_logger() without a preset or settings, fapilog automatically detects your runtime environment and applies lightweight configuration tweaks. This is controlled by auto_detect=True (the default).

Detected Environment

Detection Method

Applied Configuration

Lambda

AWS_LAMBDA_FUNCTION_NAME env var

Smaller batches (10), faster flush (0.1s), smaller queue (1000)

Kubernetes

/var/run/secrets/kubernetes.io/serviceaccount or POD_NAME env var

INFO level, kubernetes enricher

Docker

/.dockerenv file or /proc/1/cgroup contains “docker”

INFO level

CI

Common CI env vars (CI, GITHUB_ACTIONS, etc.)

INFO level

Local

Default fallback

Uses TTY-based log level defaults

Important: Auto-detection applies incremental tweaks to the base configuration—it does not apply full preset configurations. For example:

  • Auto-detecting Lambda adds smaller batches and the runtime_info enricher

  • preset="serverless" provides the complete serverless config including redactors

If you need full preset behavior (especially redaction) in cloud environments, use explicit presets:

# Full preset with redaction enabled
logger = get_logger(preset="serverless")

# Auto-detect only (applies tweaks but no redaction beyond url_credentials default)
logger = get_logger()

# Explicit environment without full preset
logger = get_logger(environment="lambda")  # Same as auto-detect for Lambda

To disable auto-detection entirely:

logger = get_logger(auto_detect=False)

On sink write failures (exceptions raised by a sink), fapilog falls back to stderr. If stderr fails too, the entry is dropped. Diagnostics warnings are emitted when internal diagnostics are enabled:

export FAPILOG_CORE__INTERNAL_LOGGING_ENABLED=true

Quick setup (env)

# Log level
export FAPILOG_CORE__LOG_LEVEL=INFO

# Rotating file sink (optional)
export FAPILOG_SINK_CONFIG__ROTATING_FILE__DIRECTORY=/var/log/myapp
export FAPILOG_SINK_CONFIG__ROTATING_FILE__MAX_BYTES="10 MB"
export FAPILOG_SINK_CONFIG__ROTATING_FILE__INTERVAL_SECONDS="daily"

# Performance tuning
export FAPILOG_CORE__BATCH_MAX_SIZE=128
export FAPILOG_CORE__MAX_QUEUE_SIZE=10000

Programmatic settings

from fapilog import Settings, get_logger

settings = Settings(
    core__log_level="INFO",
    core__enable_metrics=True,
    http__endpoint=None,  # default stdout/file selection applies
)

logger = get_logger(settings=settings)
logger.info("configured", queue=settings.core.max_queue_size)

Size and duration fields accept human-readable strings (e.g., "10 MB", "5s") as well as numeric values. Rotation keywords ("hourly", "daily", "weekly") apply to rotation interval settings and represent fixed intervals (not wall-clock boundaries).

Common patterns

  • Stdout auto (default): pretty in TTY, JSON when piped.

  • Rotating file sink: set FAPILOG_SINK_CONFIG__ROTATING_FILE__DIRECTORY; tune rotation via FAPILOG_SINK_CONFIG__ROTATING_FILE__MAX_BYTES, FAPILOG_SINK_CONFIG__ROTATING_FILE__MAX_FILES.

  • HTTP sink: set FAPILOG_HTTP__ENDPOINT and optional timeout/retry envs.

  • Metrics: set FAPILOG_CORE__ENABLE_METRICS=true to record internal metrics.

Drop and Dedupe Visibility

When drop_on_full=True (the default), events may be dropped during queue backpressure. Similarly, error deduplication (error_dedupe_window_seconds) suppresses duplicate ERROR/CRITICAL messages. By default, these events are silently handled.

Enable emit_drop_summary to receive visibility into dropped and deduplicated events:

from fapilog import LoggerBuilder

logger = (
    LoggerBuilder()
    .with_drop_summary(enabled=True, window_seconds=60.0)
    .build()
)

Or via environment variables:

export FAPILOG_CORE__EMIT_DROP_SUMMARY=true
export FAPILOG_CORE__DROP_SUMMARY_WINDOW_SECONDS=60

When enabled:

  • Drop summaries: Emitted when events are dropped due to backpressure. Contains dropped_count and window_seconds.

  • Dedupe summaries: Emitted when error deduplication window expires with suppressed messages. Contains error_message, suppressed_count, and window_seconds.

Summary events are:

  • Level WARNING (drops) or INFO (dedupe)

  • Marked with data._fapilog_internal: True for filtering

  • Rate-limited by drop_summary_window_seconds (default: 60s, minimum: 1s)

  • Written directly to sinks, bypassing the queue

This feature is disabled by default to maintain backwards compatibility.

Deprecated setting: legacy sampling

observability.logging.sampling_rate is deprecated and now raises a DeprecationWarning. Move to filter-based sampling to avoid double-sampling and to unlock sampling metrics:

core:
  filters: ["sampling"]
filter_config:
  sampling:
    config:
      sample_rate: 0.25

Plugin Security

By default, fapilog only loads built-in plugins. External plugins (registered via Python entry points) are blocked to prevent arbitrary code execution from untrusted packages.

Enabling External Plugins

To use external plugins, explicitly opt-in using one of these approaches:

Recommended: Allowlist specific plugins

from fapilog import Settings, get_logger

settings = Settings(plugins={"allowlist": ["my-trusted-sink", "approved-enricher"]})
logger = get_logger(settings=settings)
# Via environment variable
export FAPILOG_PLUGINS__ALLOWLIST='["my-trusted-sink", "approved-enricher"]'

Less secure: Allow all external plugins

settings = Settings(plugins={"allow_external": True})
export FAPILOG_PLUGINS__ALLOW_EXTERNAL=true

Security Implications

External plugins can execute arbitrary code during loading. Only enable plugins you trust:

  • Allowlist approach: Limits exposure to specific, known plugins

  • allow_external=True: Permits any entry point plugin (use with caution)

When external plugins are loaded, a diagnostic warning is emitted to help track plugin sources.

Migration from Previous Versions

If you were using external plugins that now fail to load, add them to the allowlist:

# Before (external plugins loaded automatically)
settings = Settings(core={"sinks": ["external-sink"]})

# After (explicit opt-in required)
settings = Settings(
    core={"sinks": ["external-sink"]},
    plugins={"allowlist": ["external-sink"]},
)

Shutdown Handler Installation

Fapilog automatically installs signal handlers (SIGTERM/SIGINT) and atexit handlers for graceful shutdown. These handlers ensure pending logs are drained before process exit.

Lazy Installation (Default)

Handlers are installed lazily on first logger start, not at module import time. This design:

  • Avoids conflicts with frameworks (FastAPI, Uvicorn, Gunicorn) that manage their own signal handlers

  • Follows library best practices by not modifying global state at import time

  • Allows opt-out before any handlers are installed

import fapilog

# No handlers installed yet - safe to configure framework handlers first
logger = fapilog.get_logger()  # Handlers installed here

Manual Installation

For cases where you need handlers installed before creating a logger, use install_shutdown_handlers():

import fapilog

# Explicitly install handlers early
fapilog.install_shutdown_handlers()

# Later, create loggers
logger = fapilog.get_logger()

This function is idempotent - calling it multiple times has no effect after the first call.

Disabling Handlers

To prevent handler installation entirely:

# Disable signal handlers (atexit still active)
export FAPILOG_CORE__SIGNAL_HANDLER_ENABLED=false

# Disable atexit drain (signal handlers still active)
export FAPILOG_CORE__ATEXIT_DRAIN_ENABLED=false

# Disable both
export FAPILOG_CORE__SIGNAL_HANDLER_ENABLED=false
export FAPILOG_CORE__ATEXIT_DRAIN_ENABLED=false

Or via code:

from fapilog import Settings, get_logger

settings = Settings(
    core={
        "signal_handler_enabled": False,
        "atexit_drain_enabled": False,
    }
)
logger = get_logger(settings=settings)

Framework Integration

When using fapilog with frameworks that manage their own shutdown:

FastAPI/Uvicorn: The lazy installation works well because FastAPI’s lifespan handlers run after fapilog handlers are installed. Use the setup_logging() lifespan for proper integration.

Gunicorn: If you need to ensure fapilog handlers don’t conflict, disable them and rely on Gunicorn’s worker lifecycle:

settings = Settings(core={"signal_handler_enabled": False})
logger = get_logger(settings=settings)

Testing: The handlers are designed for test isolation. Each test can reset handler state if needed using internal APIs.

Full reference

  • Execution Modes - Understanding async and sync facades with dedicated thread architecture

  • Configuration Map - Complete reference mapping every setting to its env var and builder method

  • Environment Variables - Full matrix of env names and aliases (including short forms like FAPILOG_CLOUDWATCH__REGION)