Skip to main content

Standardized structured (JSON) logging package with Sentry integration.

Project description

sentry-struct-logger

Standardized structured (JSON) logging package with built-in Sentry integration for Python applications.

Features

  • Structured JSON logging - Outputs to stdout for log aggregation systems (ELK, Datadog, etc.)
  • Automatic Sentry integration - Error tracking and performance monitoring with zero configuration
  • Automatic trace correlation - Logs across your entire call stack share the same trace_id
  • FastAPI automatic request tracing - Each HTTP request gets a unique trace ID
  • Clean schema - Standard fields at top level, custom fields nested in details

Requirements

  • Python >= 3.8
  • FastAPI (required for automatic request tracing)

Installation

uv add sentry-struct-logger

# With Lambda support (for AWS Lambda functions)
uv add "python-sentry-logger-wrapper[lambda] @ git+https://github.com/HEAL-Engineering/python-sentry-logger-wrapper.git"

# Or using pip
pip install sentry-struct-logger

Quick Start

Basic Usage with FastAPI

from fastapi import FastAPI
from python_sentry_logger_wrapper import get_logger
import os

# Initialize logger with Sentry BEFORE creating FastAPI app
logger = get_logger(
    service_name="api-service",
    sentry_dsn=os.getenv("SENTRY_DSN"),
    sentry_environment="production"
)

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    logger.info("Fetching user from API")
    user = await fetch_user(user_id)
    return user

async def fetch_user(user_id: int):
    logger.info("Querying database", user_id=user_id)
    return await db.query(...)

What happens automatically:

  • Each HTTP request gets a unique trace_id
  • ALL logs during that request share the same trace_id
  • Logs appear in stdout as JSON AND in Sentry with full request context
  • Errors are automatically linked to the request that caused them

Multi-Layer Application Example

# API Layer
from python_sentry_logger_wrapper import get_logger

logger = get_logger("example-api", sentry_dsn=os.getenv("SENTRY_DSN"))

@app.post("/items")
async def create_item(item: Item):
    logger.info("Creating item", item_count=len(item.data))
    result = await item_service.create(item)
    logger.info("Item created", item_id=result.id)
    return result

# Service Layer
logger = get_logger("example-api")

async def create(item: Item):
    logger.info("Validating item")
    await validate_data(item.data)

    logger.info("Processing external request")
    response = await external_service.process(item)

    if not response.success:
        logger.error("External service failed", reason=response.error_code)
        raise ServiceError(response.error_code)

    logger.info("Saving to database")
    return await db.items.create(item)

Console output (all logs share the same trace_id):

{"timestamp": "...", "log_level": "INFO", "logger": "example-api", "message": "Creating item", "trace_id": "abc123...", "span_id": "def456...", "details": {"item_count": 3}}
{"timestamp": "...", "log_level": "INFO", "logger": "example-api", "message": "Validating item", "trace_id": "abc123...", "span_id": "def456..."}
{"timestamp": "...", "log_level": "INFO", "logger": "example-api", "message": "Processing external request", "trace_id": "abc123...", "span_id": "def456..."}
{"timestamp": "...", "log_level": "INFO", "logger": "example-api", "message": "Saving to database", "trace_id": "abc123...", "span_id": "def456..."}

In Sentry:

  • Click the trace to see all logs in timeline
  • View request duration, endpoint, and status code
  • If an error occurs, see which request caused it with full context

Log Schema

Standard Fields (top-level)

  • timestamp - ISO 8601 UTC timestamp
  • log_level - INFO, WARNING, ERROR, etc.
  • logger - Your service identifier (the name passed to get_logger())
  • message - Log message
  • trace_id - Distributed tracing ID (only present when Sentry is enabled)
  • span_id - Span ID for the current operation (only present when Sentry is enabled)

Custom Fields (nested under details)

Any additional fields you pass are automatically nested:

logger.info("User login", user_id=123, ip="10.0.1.100", method="oauth")
# Output: {..., "details": {"user_id": 123, "ip": "10.0.1.100", "method": "oauth"}}

Sentry Configuration

Get Your DSN

  1. Create a project at sentry.io
  2. Copy your DSN from Settings → Client Keys
  3. Set it as an environment variable: export SENTRY_DSN="https://..."

Free Tier

Sentry offers 5,000 errors/events per month free - perfect for small projects.

Configuration Options

logger = get_logger(
    service_name="my-service",
    log_level=logging.INFO,                       # Minimum log level for stdout
    sentry_dsn="https://...",                     # Optional: enables Sentry
    sentry_environment="production",              # Optional: environment tag
    sentry_breadcrumbs_level=logging.INFO,        # Min level for Sentry breadcrumbs
    sentry_event_level=logging.ERROR,             # Min level captured as Sentry events (Issues)
    sentry_logs_level=logging.INFO,               # Min level captured by Sentry Logs product
    traces_sample_rate=0.0,                       # Performance tracing sample rate (0.0 = off)
    renderer="auto",                              # "json" (default), "console", or "auto"
)

Sentry quota control

Two parameters let you dial back Sentry usage without forking the package — useful for projects on the free plan or chatty services that would otherwise burn through quota.

Parameter Default What it controls When to change
sentry_logs_level logging.INFO Minimum level captured by Sentry's Logs product (separate from Errors/Issues). Raise to WARNING or ERROR to stop info-level chatter from filling Sentry Logs without affecting error capture.
traces_sample_rate 0.0 Performance tracing sample rate (0.0–1.0). Passed to sentry_sdk.init. Leave at 0.0 unless you need tracing. Set to 0.1 (10%) for sampled tracing. Above that on a busy service eats the free plan's 10k performance units/month quickly.

The three log-level knobs are intentionally distinct and map 1:1 to LoggingIntegration:

Parameter Maps to Default Purpose
sentry_breadcrumbs_level LoggingIntegration(level=...) INFO Context attached to errors
sentry_event_level LoggingIntegration(event_level=...) ERROR Becomes an Issue in the Errors product
sentry_logs_level LoggingIntegration(sentry_logs_level=...) INFO Structured logs in the Logs product

Renderer modes

get_logger() accepts renderer="json" | "console" | "auto" to control how logs are formatted on stdout. The default is "json"existing consumers see zero behavior change.

Mode Output When to use
"json" (default) Structured JSON, one event per line Production. Anywhere logs feed an aggregator (ELK, Datadog, CloudWatch, Loki, etc.).
"console" Colored, human-readable via structlog.dev.ConsoleRenderer Local development. Reading logs in your terminal.
"auto" "console" when sys.stdout.isatty() is True, else "json" Set-and-forget. Mirrors git / ls --color=auto.

How "auto" decides

sys.stdout.isatty() returns:

  • True when stdout is an interactive terminal → renders as console.
  • False when stdout is redirected to a file, piped to another process, or running under Docker / CI / Kubernetes / systemd → renders as JSON.

That means a consumer can set renderer="auto" once and get colored logs while developing, and JSON the moment their service ships — no environment branching required.

Renderer choice does NOT affect Sentry

Sentry's LoggingIntegration intercepts events at the stdlib logging.Handler level — before the renderer runs. Switching between "json" and "console" only changes what hits stdout; Sentry ingestion (errors, breadcrumbs, trace context) is identical either way.

Example

# Production service feeding a log aggregator — default works fine
logger = get_logger(service_name="my-service")

# Local dev script — pretty colored output
logger = get_logger(service_name="my-service", renderer="console")

# Best of both: pretty in your shell, JSON when piped to a file or shipped to a container
logger = get_logger(service_name="my-service", renderer="auto")

Migration note

If you currently call get_logger(...) without the renderer argument, the default stays "json" — your output won't change. Opt into "console" or "auto" only where you want it.

What Gets Sent to Sentry

  • ERROR/CRITICAL logs - Sent as searchable events
  • INFO/WARNING logs - Sent as breadcrumbs (attached to errors for context)
  • All custom fields - Searchable in Sentry UI (e.g., details.user_id:123)
  • Request context - Automatic with FastAPI (URL, method, headers, duration)

AWS Lambda Usage

For AWS Lambda functions, enable the Lambda integration for automatic timeout warnings and Lambda-specific context:

from typing import Optional
from pydantic_settings import BaseSettings
from python_sentry_logger_wrapper import get_logger

class Settings(BaseSettings):
    """Lambda configuration using Pydantic BaseSettings."""
    service_name: str = "my-lambda"
    sentry_dsn: Optional[str] = None
    environment: str = "unknown"

    class Config:
        env_prefix = ""  # or use a prefix like "LAMBDA_"

settings = Settings()

logger = get_logger(
    service_name=settings.service_name,
    sentry_dsn=settings.sentry_dsn,
    sentry_environment=settings.environment,
    lambda_integration=True,       # Enables AwsLambdaIntegration
    lambda_timeout_warning=True,   # Warn before Lambda timeout (default: True)
)

def lambda_handler(event, context):
    logger.info("Processing event", event_type=event.get("triggerSource"))
    # Your Lambda logic here
    return {"statusCode": 200}

What happens automatically:

  • Lambda context (function name, memory, request ID) added to logs
  • Timeout warnings before Lambda times out
  • Errors captured with full Lambda context

Advanced Usage

Exception Handling

try:
    result = await process_data(item)
except ProcessingError as e:
    logger.error(
        "Data processing failed",
        error_type=type(e).__name__,
        item_id=item.id,
        exc_info=True  # Includes full stack trace
    )
    raise

Different Log Levels

logger.debug("Detailed debugging info", query="SELECT * FROM users")
logger.info("Normal operation", status="healthy")
logger.warning("Degraded performance", latency_ms=2500)
logger.error("Operation failed", retry_count=3)
logger.critical("System down", reason="database_unavailable")

FAQ

Q: Do I need to pass trace_id manually through my functions? A: No! It's automatically propagated through your entire call stack via Sentry's tracing.

Q: Can I use this without Sentry? A: Yes! Just omit sentry_dsn and you'll get JSON logs to stdout only.

Q: Does Sentry integration affect my JSON stdout logs? A: No, logs are sent to both Sentry AND stdout independently. Your log aggregation system still works.

Q: How do I search logs in Sentry? A: Custom fields are under details, so search like: details.user_id:123 or details.transaction_id:txn_*

Q: Can I use this without FastAPI? A: Yes, but automatic request tracing requires FastAPI. Without it, you'll need to manage trace context manually.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sentry_struct_logger-1.0.0.tar.gz (27.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sentry_struct_logger-1.0.0-py3-none-any.whl (13.9 kB view details)

Uploaded Python 3

File details

Details for the file sentry_struct_logger-1.0.0.tar.gz.

File metadata

  • Download URL: sentry_struct_logger-1.0.0.tar.gz
  • Upload date:
  • Size: 27.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sentry_struct_logger-1.0.0.tar.gz
Algorithm Hash digest
SHA256 82093b7e40685688f8ce8de6b23992d683c6757b26d12e97cc6b5be0a8e76e33
MD5 f75e6db3ac6b3be281883c201f371d0d
BLAKE2b-256 7ecf0dd10cc65bc636c299c74860923372430d3ccc761e90d76149ec3fba8260

See more details on using hashes here.

Provenance

The following attestation bundles were made for sentry_struct_logger-1.0.0.tar.gz:

Publisher: publish.yml on HEAL-Engineering/python-sentry-logger-wrapper

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sentry_struct_logger-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sentry_struct_logger-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5d8141f4f03103dd7325f852f57b58ee757919cf7beed72bfd3918d83229b1ec
MD5 0fd0396248a89adb27c9cf485665bf08
BLAKE2b-256 c7cdba8c2b3ad5068c19fd71913ade217bb25aaaf865ec4ef4ff8c7a1abc0534

See more details on using hashes here.

Provenance

The following attestation bundles were made for sentry_struct_logger-1.0.0-py3-none-any.whl:

Publisher: publish.yml on HEAL-Engineering/python-sentry-logger-wrapper

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page