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 timestamplog_level- INFO, WARNING, ERROR, etc.logger- Your service identifier (the name passed toget_logger())message- Log messagetrace_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
- Create a project at sentry.io
- Copy your DSN from Settings → Client Keys
- 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:
Truewhen stdout is an interactive terminal → renders as console.Falsewhen 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
82093b7e40685688f8ce8de6b23992d683c6757b26d12e97cc6b5be0a8e76e33
|
|
| MD5 |
f75e6db3ac6b3be281883c201f371d0d
|
|
| BLAKE2b-256 |
7ecf0dd10cc65bc636c299c74860923372430d3ccc761e90d76149ec3fba8260
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sentry_struct_logger-1.0.0.tar.gz -
Subject digest:
82093b7e40685688f8ce8de6b23992d683c6757b26d12e97cc6b5be0a8e76e33 - Sigstore transparency entry: 1527737896
- Sigstore integration time:
-
Permalink:
HEAL-Engineering/python-sentry-logger-wrapper@9f881eab31b7f6a9fb3ee1a8d48b6ada666dfacf -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/HEAL-Engineering
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9f881eab31b7f6a9fb3ee1a8d48b6ada666dfacf -
Trigger Event:
push
-
Statement type:
File details
Details for the file sentry_struct_logger-1.0.0-py3-none-any.whl.
File metadata
- Download URL: sentry_struct_logger-1.0.0-py3-none-any.whl
- Upload date:
- Size: 13.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d8141f4f03103dd7325f852f57b58ee757919cf7beed72bfd3918d83229b1ec
|
|
| MD5 |
0fd0396248a89adb27c9cf485665bf08
|
|
| BLAKE2b-256 |
c7cdba8c2b3ad5068c19fd71913ade217bb25aaaf865ec4ef4ff8c7a1abc0534
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sentry_struct_logger-1.0.0-py3-none-any.whl -
Subject digest:
5d8141f4f03103dd7325f852f57b58ee757919cf7beed72bfd3918d83229b1ec - Sigstore transparency entry: 1527737980
- Sigstore integration time:
-
Permalink:
HEAL-Engineering/python-sentry-logger-wrapper@9f881eab31b7f6a9fb3ee1a8d48b6ada666dfacf -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/HEAL-Engineering
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9f881eab31b7f6a9fb3ee1a8d48b6ada666dfacf -
Trigger Event:
push
-
Statement type: