# 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:

```python
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:

```python
# 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.

## Configuration Presets (Recommended)

Presets provide pre-configured settings for common use cases. Use a preset when you want quick, sensible defaults:

```python
from fapilog import get_logger, get_async_logger

# Choose the preset that matches your use case
logger = get_logger(preset="dev")                # Local development
logger = get_logger(preset="production")         # Durable production (never drops logs)
logger = get_logger(preset="adaptive")           # Auto-scaling under load
logger = get_logger(preset="serverless")         # Lambda, Cloud Run, Azure Functions
logger = get_logger(preset="hardened")           # Regulated environments (HIPAA, PCI)
logger = get_logger(preset="minimal")            # Backwards compatible default
```

**See [Presets Guide](presets.md)** for complete documentation including:
- Decision matrix for choosing the right preset
- Detailed comparison of presets
- Full settings tables and customization examples

### Quick Comparison

| Preset | Drops Logs? | File Output | Redaction | Best For |
|--------|-------------|-------------|-----------|----------|
| `dev` | No | No | No | Local development |
| `minimal` | Default | No | No | Migration, explicit defaults |
| `production` | Never | Yes | Yes | Audit trails, compliance |
| `adaptive` | If needed | Fallback only | Yes | Auto-scaling under load |
| `serverless` | If needed | No | Yes | Lambda/Cloud Functions |
| `hardened` | Never | Yes | Yes (HIPAA+PCI) | Regulated environments |

### FastAPI Integration

Use `FastAPIBuilder` for FastAPI apps:

```python
from fastapi import Depends, FastAPI
from fapilog.fastapi import FastAPIBuilder, get_request_logger

app = FastAPI(
    lifespan=FastAPIBuilder()
        .with_preset("production")
        .build()
)

@app.get("/users/{user_id}")
async def get_user(user_id: int, logger=Depends(get_request_logger)):
    await logger.info("Fetching user", user_id=user_id)
    return {"user_id": user_id}
```

See the [FastAPI Integration Guide](fastapi.md) for full configuration options including `skip_paths()`, `include_headers()`, and `sample_rate()`.

### Preset vs Settings

Presets and `Settings` are mutually exclusive. Choose one approach:

```python
# Option 1: Use a preset (simple)
logger = get_logger(preset="production")

# Option 2: Use Settings (full control)
logger = get_logger(settings=Settings(...))

# NOT allowed - raises ValueError
logger = get_logger(preset="production", settings=Settings(...))
```

If you need customization beyond what presets offer, use the `Settings` class directly or customize via the Builder API.

## Output format

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

```python
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:

```python
# 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:

```python
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:

```bash
export FAPILOG_CORE__INTERNAL_LOGGING_ENABLED=true
```

## Quick setup (env)

```bash
# 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

```python
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:

```python
from fapilog import LoggerBuilder

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

Or via environment variables:

```bash
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:

```yaml
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**

```python
from fapilog import Settings, get_logger

settings = Settings(plugins={"allowlist": ["my-trusted-sink", "approved-enricher"]})
logger = get_logger(settings=settings)
```

```bash
# Via environment variable
export FAPILOG_PLUGINS__ALLOWLIST='["my-trusted-sink", "approved-enricher"]'
```

**Less secure: Allow all external plugins**

```python
settings = Settings(plugins={"allow_external": True})
```

```bash
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:

```python
# 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

```python
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()`:

```python
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:

```bash
# 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:

```python
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:

```python
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](execution-modes.md)** - Understanding async and sync facades with dedicated thread architecture
- **[Configuration Map](../api-reference/configuration-map.md)** - Complete reference mapping every setting to its env var and builder method
- **[Environment Variables](environment-variables.md)** - Full matrix of env names and aliases (including short forms like `FAPILOG_CLOUDWATCH__REGION`)
