Management and injection of contextual variables into log messages.
Project description
Logctx
Logctx is a lightweight Python library that enhances logging with contextual information — useful for debugging, tracing, and observability. It integrates seamlessly with Python's built-in logging module and supports context management via decorators or context managers.
🚀 Features
- Automatic Context Injection: Seamlessly inject context into log messages using Python's logging framework.
- Scoped Contexts: Define context with decorators or context managers, supporting nested and inherited contexts.
- Argument Logging: Automatically log function arguments as part of the context.
- Runtime Context Manipulation: Update or clear contexts dynamically during runtime.
- Thread and Async Safety: Supports threading and async.
📦 Installation
pip install logctx
Or with uv:
uv add logctx
⚡ Quick Start
Minimal usage example to get started. More information down below.
import logging
import logctx
# Setup log injection
root_logger = logging.getLogger()
console_handler = logging.StreamHandler()
formatter = logging.Formatter('%(message)s %(logctx)s')
context_filter = ContextInjectingLoggingFilter(output_field='logctx')
console_handler.setFormatter(formatter)
console_handler.addFilter(context_filter)
root_logger.addHandler(console_handler)
root_logger.setLevel(logging.DEBUG)
# Usage example
with new_context(user='alice'):
root_logger.info('User logged in') # > User logged in {'user': 'alice'}
🧠 How It Works
Contexts are key-value pairs bound to a specific scope using context managers or decorators. These contexts are automatically included in log messages, providing additional information alongside the normal log content.
Contexts can also be nested, meaning that inner contexts inherit and may override key-value pairs from outer contexts.
🛠️ Context Managers
Use logctx.new_context() to create a new context:
import logctx
with logctx.new_context(user="alice", request_id="abc123"):
# Each log message inside this context manager
# now additionally carries the user and request_id.
pass
You can inspect the current context using:
print(logctx.get_current().to_dict())
The root context, i.e. the context outside any active contextmanager or decorator, is protected to avoid accidental changes (since it would apply everywhere inside the current thread). You must access it via logctx.root. It provides the same methods as in logctx, without the ability to create a nested context via logctx.new_context().
🎯 Decorators
The decorators described here will always create a new basic context with the same scope as the decorated function/method. There is currently no support for using these decorators to manipulate the root context.
Static Context
Use @logctx.decorators.inject_context() to add static context to your functions.
Supports (sync & async): functions, generators, classmethods, instancemethods
import logctx
@logctx.decorators.inject_context(fn="my_function")
def my_function():
print(logctx.get_current().to_dict()) # {'fn': 'my_function'}
Special Case: Generators
import logctx
@logctx.decorators.inject_context(inside=True)
def my_generator():
# The context inside the generator execution will inherit
# the context that was active during initialization of
# the generator ({'inside': False, 'foo': 'bar'}),
# BUT uses its own contexts (from the decorator or created inside)
# as child contexts. Hence the overridden inside attribute.
print(logctx.get_current().to_dict()) # {'inside': True, 'foo': 'bar'}
yield
with logctx.new_context(inside=False, foo="bar"):
gen = my_generator()
for _ in gen():
print(logctx.get_current().to_dict()) # {'inside': False, 'foo': 'bar'}
Argument Logging
Automatically log function arguments as part of the context:
Supports: functions, classmethods, instancemethods
Note: Does not work properly with generator or async functions.
import logctx
# -> log arguments a and b as is, rename c to d
@logctx.decorators.log_arguments(args=["a", "b"], c="d")
def my_function(a, b, c):
print(logctx.get_current().to_dict()) # {'a': 1, 'b': 2, 'd': 3}
my_function(1, 2, 3)
If one of the specified arguments is not found in the function signature during initialization, a ValueError will be raised. This also implicates that an extraction from *args or **kwargs is not possible.
🔄 Manipulating Contexts
Please note that the context attributes should never be updated directly through instances of LogContext (the return values of e.g. the contextmanagers and logctx.get_current()).
Below context manipulation functions work for contexts created by contextmanagers as well as context created by decorators. Use these to alter your current active context. In addition, they also work on logctx.root.
Updating Contexts
You can add or alter attributes of the current context by calling logctx.update():
import logctx
with logctx.new_context(section="main"):
logctx.update(request_id="network_request_id")
print(logctx.get_current().to_dict()) # {'section': 'main', 'request_id': 'network_request_id'}
Warning: Calling this method without an active context will change your root context, which may cause unexpected side effects. To reset your root context, use logctx.clear without an active context present.
Clearing Contexts
Clear the current context with logctx.clear():
import logctx
with logctx.new_context(section="main"):
logctx.clear()
print(logctx.get_current().to_dict()) # {}
Clearing a context will affect all key-value pairs of all contexts in scope, but the clearance will only last for the current active scope. After leaving the current scope, the context gets reset to its state before the leaving scope, essentially restoring some of its variables:
import logctx
with logctx.new_context(section="main"):
with logctx.new_context(user="alice"):
logctx.get_current().to_dict() # {'section': 'main', 'user': 'alice'}
logctx.clear()
logctx.get_current().to_dict() # {}
logctx.get_current().to_dict() # {'section': 'main'}
🔗 Context Propagation
Generally, the contexts are isolated across threads / async on purpose. If you still want to propagate the context you can use the ContextPropagator object:
import logctx
propagator = logctx.ContextPropagator()
with logctx.new_context(event_id="1234"):
propagator.capture()
with logctx.new_context():
logctx.get_current() # > {}
propagator.restore()
logctx.get_current() # > {'event_id': #1234'}
🧩 Log Injection
Context gets injected into log messages with filter objects from Python's built-in logging module.
Configure your logger to include the context:
import logging
from pythonjsonlogger import jsonlogger
import logctx
# Configure logging
logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter("%(message)s %(logctx)s")
handler.setFormatter(formatter)
handler.addFilter(logctx.ContextInjectingLoggingFilter(output_field="logctx"))
# output_field may be ommitted, which injects all context attributes at the root level of the log records.
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# Inject context
with logctx.new_context(user="alice", request_id="abc123"):
logger.info("User logged in")
# Output:
# {"message": "User logged in", "logctx": {"user": "alice", "request_id": "abc123"}}
Note: This example uses python-json-logger for better log formats.
🤝 Contributing
Contributions and feedback are welcome! Please reach out to me through issues or else before opening a pull request.
Thanks.
📚 See Also
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 logctx-1.0.1.tar.gz.
File metadata
- Download URL: logctx-1.0.1.tar.gz
- Upload date:
- Size: 37.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49d79da145dc518a2f4c203ca6a775f94de9911eb9443ec6b7550dac8ef05d69
|
|
| MD5 |
4d474bad7362be0ac3cd51b06e5ca02e
|
|
| BLAKE2b-256 |
50f78db7ae5c67134c716835c5ec70479bdc7fdf5635dad6a4531bbc58b2ec11
|
Provenance
The following attestation bundles were made for logctx-1.0.1.tar.gz:
Publisher:
release.yml on aschulte201/logctx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
logctx-1.0.1.tar.gz -
Subject digest:
49d79da145dc518a2f4c203ca6a775f94de9911eb9443ec6b7550dac8ef05d69 - Sigstore transparency entry: 1260927376
- Sigstore integration time:
-
Permalink:
aschulte201/logctx@b2d33408d29b8680c73c00f0bf0494a4bf98da6b -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/aschulte201
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b2d33408d29b8680c73c00f0bf0494a4bf98da6b -
Trigger Event:
push
-
Statement type:
File details
Details for the file logctx-1.0.1-py3-none-any.whl.
File metadata
- Download URL: logctx-1.0.1-py3-none-any.whl
- Upload date:
- Size: 10.6 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 |
1c90ad9268453c8199f7298efebf4f5cdfc172582d822ddef863139587026a82
|
|
| MD5 |
704ab3f5023332bf1d7db135bffb4dff
|
|
| BLAKE2b-256 |
5dacba0cad9ca918fb2953d1213eeea9e2762b07176c82b473764811b0bcc3fd
|
Provenance
The following attestation bundles were made for logctx-1.0.1-py3-none-any.whl:
Publisher:
release.yml on aschulte201/logctx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
logctx-1.0.1-py3-none-any.whl -
Subject digest:
1c90ad9268453c8199f7298efebf4f5cdfc172582d822ddef863139587026a82 - Sigstore transparency entry: 1260927464
- Sigstore integration time:
-
Permalink:
aschulte201/logctx@b2d33408d29b8680c73c00f0bf0494a4bf98da6b -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/aschulte201
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b2d33408d29b8680c73c00f0bf0494a4bf98da6b -
Trigger Event:
push
-
Statement type: