wshtlib
Lightweight observability library for AWS Lambda and FastAPI. Zero external dependencies.
A focused alternative to aws-powertools — covers structured logging, CloudWatch metrics (EMF), request context propagation, and Lambda handler boilerplate. Nothing more.
Install
pip install wshtlib
FastAPI/Starlette middleware is optional:
pip install wshtlib[fastapi]
Usage
Lambda handler
Two decorators, one per invocation mode.
@bootstrap — synchronous invocations (API Gateway), where the return value is the response:
from wshtlib import bootstrap, get_logger
logger = get_logger("my-service")
@bootstrap
def handler(event, context):
logger.info("invoked", path=event.get("path"))
return {"statusCode": 200}
It handles:
- Warming events (
"source": "lambda-warming") — returns 200 early - Context init and structured log enrichment
- Unhandled exceptions — logs error, returns 500
@worker — asynchronous invocations (S3, EventBridge, SQS), where the return value is discarded:
from wshtlib import worker, get_logger
logger = get_logger("my-worker")
@worker
def handler(event, context):
logger.info("processing", records=len(event["Records"]))
Same context init and structured error logging, but the exception is re-raised rather than swallowed — retries, on_failure destinations, the DLQ, and the Errors metric all depend on Lambda seeing the invocation fail. No warming-event handling.
Structured logging
from wshtlib import get_logger
logger = get_logger("my-service")
logger.info("user signed in", user_id="u_123", plan="pro")
Output is JSON to stdout, enriched with level, timestamp, service, location, runtime fields, and Lambda context on invocation. location names the calling function and line.
Keyword arguments are the preferred spelling, but stdlib's extra={...} works too and lands in the same JSON entry; kwargs win if both supply the same key. Fields are also set as attributes on the LogRecord, so custom filters and %(field)s formatters can read them.
Field names are unrestricted — including msg, args, and level. Only exc_info, extra, stack_info, and stacklevel keep their stdlib meanings and cannot be used as fields. A field whose name collides with one the formatter owns (level, message, timestamp, service, location, trace_id, exception, and the runtime/Lambda fields) is emitted with an extra_ prefix rather than replacing it:
logger.info("subscription renewed", level="premium")
# {"level": "INFO", ..., "message": "subscription renewed", "extra_level": "premium"}
This keeps an enrichment field from falsifying the record it was meant to enrich.
CloudWatch metrics (EMF)
from wshtlib.metrics import metrics
metrics.count("OrderPlaced")
metrics.put("Duration", 142.5, unit="Milliseconds")
metrics.flush()
metrics is a module-level MetricsContext instance. For isolated contexts (e.g. per-request), instantiate MetricsContext() directly.
Namespace defaults to the METRICS_NAMESPACE env var, falling back to "Wholeshoot".
Request context
from wshtlib import get_context, set_user_id
set_user_id(claims["sub"])
ctx = get_context() # {"trace_id": ..., "correlation_id": ..., "user_id": ...}
Context is stored in a ContextVar — safe for concurrent async handlers.
FastAPI middleware
from fastapi import FastAPI
from wshtlib.middleware import WshtlibMiddleware
app = FastAPI()
app.add_middleware(WshtlibMiddleware)
Initialises request context, logs method, path, status, duration_ms per request, and injects X-Trace-Id into the response.
Utilities
from wshtlib import require_env, require_https_url, require_secret
db_url = require_env("DATABASE_URL") # raises RuntimeError if missing/empty
endpoint = require_https_url(require_env("API_URL")) # raises ValueError if not https
api_key = require_secret("api/key") # raises RuntimeError if missing/empty, cached
Environment variables
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
INFO |
Logger level |
METRICS_NAMESPACE |
Wholeshoot |
CloudWatch namespace |
ENVIRONMENT |
— | Added as a metrics dimension if set |
Development
uv sync --group dev
uv run pytest
uv run mypy wshtlib
uv run ruff check wshtlib
License
MIT
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 wshtlib-0.3.0.tar.gz.
File metadata
- Download URL: wshtlib-0.3.0.tar.gz
- Upload date:
- Size: 51.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1112613c2014cc14abb0fd3469d8872f26fd8e2a51667c8a746d18c6e56eb8d1
|
|
| MD5 |
3eb620e100e351c2102015fbe56fa7fa
|
|
| BLAKE2b-256 |
74605f39e92583c2a95bb02edb0149b89bea8cd92dc75056ef4e835733697d40
|
Provenance
The following attestation bundles were made for wshtlib-0.3.0.tar.gz:
Publisher:
ci.yml on pjosols/wshtlib
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wshtlib-0.3.0.tar.gz -
Subject digest:
1112613c2014cc14abb0fd3469d8872f26fd8e2a51667c8a746d18c6e56eb8d1 - Sigstore transparency entry: 2329703338
- Sigstore integration time:
-
Permalink:
pjosols/wshtlib@5a3d52b20f18c4d4bef673c8973ced9202aff072 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/pjosols
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@5a3d52b20f18c4d4bef673c8973ced9202aff072 -
Trigger Event:
push
-
Statement type:
File details
Details for the file wshtlib-0.3.0-py3-none-any.whl.
File metadata
- Download URL: wshtlib-0.3.0-py3-none-any.whl
- Upload date:
- Size: 12.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cff793c5cce85ad6fcc57c69fecde5a234d3315a2f93b770de4cb4f3165e5869
|
|
| MD5 |
a4b3a7f211e97f0ce0be610ad38638ef
|
|
| BLAKE2b-256 |
61ca56e04fcc8ca6c85761b958f25d79176a5d0eb67f6f736fdecebb99e38d64
|
Provenance
The following attestation bundles were made for wshtlib-0.3.0-py3-none-any.whl:
Publisher:
ci.yml on pjosols/wshtlib
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wshtlib-0.3.0-py3-none-any.whl -
Subject digest:
cff793c5cce85ad6fcc57c69fecde5a234d3315a2f93b770de4cb4f3165e5869 - Sigstore transparency entry: 2329703387
- Sigstore integration time:
-
Permalink:
pjosols/wshtlib@5a3d52b20f18c4d4bef673c8973ced9202aff072 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/pjosols
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@5a3d52b20f18c4d4bef673c8973ced9202aff072 -
Trigger Event:
push
-
Statement type: