Configuring Fapilog with the Builder API

The Builder API provides a fluent, type-safe way to configure fapilog loggers. This guide covers common configuration tasks using the builder pattern.

Why Use the Builder?

The Builder API offers several advantages over Settings-based configuration:

  • Discoverability - IDE autocomplete shows all available options

  • Validation - Configuration errors caught at build time with clear messages

  • Readability - Configuration intent is clear from chained method calls

  • Type Safety - Full type hints for all parameters

Quick Start

from fapilog import LoggerBuilder

# Minimal setup - stdout JSON logging
logger = LoggerBuilder().add_stdout().build()
logger.info("Hello from builder!")

# With preset for sensible defaults
logger = (
    LoggerBuilder()
    .with_preset("production")
    .build()
)

For async applications:

from fapilog import AsyncLoggerBuilder

async def main():
    logger = await (
        AsyncLoggerBuilder()
        .with_preset("production")
        .add_stdout()
        .build_async()
    )
    await logger.info("Async logging ready")

Common Tasks

Setting Log Level

Control which messages are logged:

logger = (
    LoggerBuilder()
    .with_level("INFO")  # DEBUG, INFO, WARNING, ERROR
    .add_stdout()
    .build()
)

Adding Multiple Sinks

Send logs to multiple destinations:

logger = (
    LoggerBuilder()
    .add_stdout()                                    # Console output
    .add_file("logs/app", max_bytes="50 MB")        # File rotation
    .add_cloudwatch("/myapp/prod", region="us-east-1")  # CloudWatch
    .build()
)

Configuring File Rotation

Set up rotating file logs with compression:

logger = (
    LoggerBuilder()
    .add_file(
        "logs/app",
        max_bytes="100 MB",    # Rotate at 100 MB
        max_files=10,          # Keep 10 rotated files
        compress=True,         # Gzip old files
    )
    .build()
)

Setting Up Redaction

Protect sensitive data in logs:

# Simple field-based redaction
logger = (
    LoggerBuilder()
    .with_redaction(fields=["password", "api_key", "ssn"])
    .add_stdout()
    .build()
)

# Advanced redaction with patterns
logger = (
    LoggerBuilder()
    .with_field_mask(
        ["password", "credit_card"],
        mask="[REDACTED]",
    )
    .with_regex_mask(["(?i).*secret.*", "(?i).*token.*"])
    .with_url_credential_redaction()  # Scrub user:pass from URLs
    .add_stdout()
    .build()
)

Configuring Sampling

Reduce log volume while maintaining visibility:

# Fixed-rate sampling (keep 10%)
logger = (
    LoggerBuilder()
    .with_sampling(rate=0.1)
    .add_stdout()
    .build()
)

# Adaptive sampling based on volume
logger = (
    LoggerBuilder()
    .with_adaptive_sampling(
        target_events_per_sec=1000,
        min_rate=0.01,
        max_rate=1.0,
    )
    .add_stdout()
    .build()
)

# Trace-aware sampling (honor distributed trace decisions)
logger = (
    LoggerBuilder()
    .with_trace_sampling(default_rate=0.1, honor_upstream=True)
    .add_stdout()
    .build()
)

Rate Limiting

Prevent log flooding:

logger = (
    LoggerBuilder()
    .with_rate_limit(
        capacity=100,        # Bucket size
        refill_rate=10.0,    # Tokens per second
    )
    .add_stdout()
    .build()
)

# Per-key rate limiting
logger = (
    LoggerBuilder()
    .with_rate_limit(
        capacity=10,
        refill_rate=1.0,
        key_field="user_id",  # Separate bucket per user
    )
    .add_stdout()
    .build()
)

Configuring Circuit Breaker

Isolate failing sinks to prevent cascading failures:

logger = (
    LoggerBuilder()
    .with_circuit_breaker(
        enabled=True,
        failure_threshold=5,      # Open after 5 failures
        recovery_timeout="30s",   # Probe after 30 seconds
    )
    .add_cloudwatch("/myapp/prod")
    .build()
)

Level-Based Routing

Send different log levels to different destinations:

logger = (
    LoggerBuilder()
    .add_stdout()
    .add_cloudwatch("/myapp/errors")
    .add_file("logs/debug")
    .with_routing(
        rules=[
            {"levels": ["ERROR", "CRITICAL"], "sinks": ["cloudwatch"]},
            {"levels": ["DEBUG"], "sinks": ["rotating_file"]},
            {"levels": ["INFO", "WARNING"], "sinks": ["stdout_json"]},
        ],
        fallback=["stdout_json"],
    )
    .build()
)

Adding Context

Bind default context to all log entries:

logger = (
    LoggerBuilder()
    .with_context(
        service="api-gateway",
        environment="production",
        version="1.2.3",
    )
    .add_stdout()
    .build()
)

# All logs now include service, environment, version
logger.info("Request received")

Configuring Enrichers

Add automatic metadata to logs:

logger = (
    LoggerBuilder()
    .with_enrichers("runtime_info", "context_vars")
    .configure_enricher("runtime_info", service="my-api")
    .add_stdout()
    .build()
)

Performance Tuning

Queue and Batch Configuration

Optimize for throughput or latency:

# High-throughput configuration
logger = (
    LoggerBuilder()
    .with_queue_size(10000)       # Large buffer
    .with_batch_size(500)         # Large batches
    .with_batch_timeout("1s")     # Flush every second
    .with_workers(4)              # Parallel processing
    .add_stdout()
    .build()
)

# Low-latency configuration
logger = (
    LoggerBuilder()
    .with_queue_size(1000)
    .with_batch_size(10)          # Small batches
    .with_batch_timeout("100ms")  # Flush quickly
    .add_stdout()
    .build()
)

Adaptive Pipeline

Enable automatic scaling based on queue pressure:

# Adaptive pipeline with all actuators (works in all contexts)
logger = (
    LoggerBuilder()
    .with_adaptive(
        enabled=True,
        max_workers=8,
    )
    .with_queue_budget(main_mb=50, protected_mb=10)
    .add_stdout()
    .build()
)

See Adaptive Pipeline for pressure levels, actuator behavior, and tuning guidelines.

Backpressure Configuration

Control behavior when queue is full:

# Drop logs when queue full (default, protects app performance)
logger = (
    LoggerBuilder()
    .with_backpressure(wait_ms=50, drop_on_full=True)
    .add_stdout()
    .build()
)

# Wait for queue space (preserves logs, may slow app)
logger = (
    LoggerBuilder()
    .with_backpressure(wait_ms=100, drop_on_full=False)
    .add_stdout()
    .build()
)

Size Guards

Prevent oversized payloads:

logger = (
    LoggerBuilder()
    .with_size_guard(
        max_bytes="256 KB",
        action="truncate",
        preserve_fields=["level", "timestamp", "message"],
    )
    .add_stdout()
    .build()
)

Production Checklist

A complete production configuration:

from fapilog import LoggerBuilder

logger = (
    LoggerBuilder()
    # Start with production preset
    .with_preset("production")

    # Override specific settings
    .with_level("INFO")
    .with_app_name("my-service")

    # Add sinks
    .add_file("logs/app", max_bytes="100 MB", max_files=20, compress=True)
    .add_cloudwatch("/myapp/prod", region="us-east-1")

    # Security
    .with_field_mask(["password", "api_key", "ssn", "credit_card"])
    .with_url_credential_redaction()

    # Reliability
    .with_circuit_breaker(enabled=True, failure_threshold=5)
    .with_backpressure(drop_on_full=False)  # Don't lose logs
    .with_shutdown_timeout("10s")

    # Performance
    .with_queue_size(10000)
    .with_batch_size(100)
    .with_workers(2)

    # Routing (errors to CloudWatch, all to file)
    .with_routing(
        rules=[
            {"levels": ["ERROR", "CRITICAL"], "sinks": ["cloudwatch"]},
        ],
        fallback=["rotating_file"],
    )

    # Observability
    .with_metrics(enabled=True)
    .with_diagnostics(enabled=True)

    .build()
)

Presets Reference

Presets provide pre-configured defaults for common scenarios:

dev - Local Development

logger = LoggerBuilder().with_preset("dev").build()
  • DEBUG level for maximum visibility

  • Pretty console output in terminals

  • Immediate flushing (batch_size=1) for real-time debugging

  • No redaction (safe for local testing)

  • Internal diagnostics enabled

production - Production Deployments

logger = LoggerBuilder().with_preset("production").build()
  • INFO level

  • File rotation (50MB, 10 files, compressed)

  • Automatic redaction of 9 sensitive fields

  • drop_on_full=False ensures no log loss

  • Larger batch size for efficiency

minimal - Backwards Compatible

logger = LoggerBuilder().with_preset("minimal").build()
  • Matches get_logger() with no arguments

  • INFO level, stdout JSON, default batching


Builder vs Settings

Feature

Builder

Settings

IDE Autocomplete

Yes

Limited

Type Safety

Full

Partial

Chaining

Yes

No

Runtime Flexibility

At build time

Full runtime

Env Var Support

Via preset

Native

Serializable Config

No

Yes (JSON/YAML)

Use Builder when:

  • Writing new code with IDE support

  • Configuration is known at development time

  • Readability and discoverability matter

Use Settings when:

  • Configuration comes from environment/files

  • Need runtime configuration changes

  • Integrating with config management systems


Next Steps