Skip to main content

Management and injection of contextual variables into log messages.

Project description

Logctx

CICD PyPI - Status PyPI - Version PyPI - Python Version

PyPI - License PyPI - Types PyPI - Downloads

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


Download files

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

Source Distribution

logctx-1.0.1.tar.gz (37.9 kB view details)

Uploaded Source

Built Distribution

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

logctx-1.0.1-py3-none-any.whl (10.6 kB view details)

Uploaded Python 3

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

Hashes for logctx-1.0.1.tar.gz
Algorithm Hash digest
SHA256 49d79da145dc518a2f4c203ca6a775f94de9911eb9443ec6b7550dac8ef05d69
MD5 4d474bad7362be0ac3cd51b06e5ca02e
BLAKE2b-256 50f78db7ae5c67134c716835c5ec70479bdc7fdf5635dad6a4531bbc58b2ec11

See more details on using hashes here.

Provenance

The following attestation bundles were made for logctx-1.0.1.tar.gz:

Publisher: release.yml on aschulte201/logctx

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

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

Hashes for logctx-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1c90ad9268453c8199f7298efebf4f5cdfc172582d822ddef863139587026a82
MD5 704ab3f5023332bf1d7db135bffb4dff
BLAKE2b-256 5dacba0cad9ca918fb2953d1213eeea9e2762b07176c82b473764811b0bcc3fd

See more details on using hashes here.

Provenance

The following attestation bundles were made for logctx-1.0.1-py3-none-any.whl:

Publisher: release.yml on aschulte201/logctx

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