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())

🎯 Decorators

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.


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'}

🧩 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-0.4.0.tar.gz (34.6 kB view details)

Uploaded Source

Built Distribution

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

logctx-0.4.0-py3-none-any.whl (8.9 kB view details)

Uploaded Python 3

File details

Details for the file logctx-0.4.0.tar.gz.

File metadata

  • Download URL: logctx-0.4.0.tar.gz
  • Upload date:
  • Size: 34.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for logctx-0.4.0.tar.gz
Algorithm Hash digest
SHA256 7fbbfc46c2d903d1cd444b353f474f7cac17afd11ab9a16a6eae23a3e0e6f877
MD5 3f06faf667512a08d17c68c4d534396c
BLAKE2b-256 51daf63a35411fb3b02c34cd1037a24e04a54b0d7502014620829461dc81c42d

See more details on using hashes here.

Provenance

The following attestation bundles were made for logctx-0.4.0.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-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: logctx-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 8.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for logctx-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 898fa91104fef10a72f4e93c8281863f3e8a59f983ab3f72a31b729336b0c7ed
MD5 973ee1c7a63e30acf3d22be3d454af87
BLAKE2b-256 8f53d661e4ec056d3a3168f5f6306ebcc943265bf249cd50e2e1c15d7476297a

See more details on using hashes here.

Provenance

The following attestation bundles were made for logctx-0.4.0-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