A Python service can emit diagnostic events from many modules, libraries and worker functions. Adding an identifier to each individual logging call becomes difficult to maintain. The logging.setLogRecordFactory() hook supplies a controlled way to add a consistent field to each newly created LogRecord, while contextvars.ContextVar provides a value associated with the current execution context. Together, they can help operators correlate application events from servers, monitoring collectors and network automation systems.
OSPython.045 extends OSPython.043 on correlation IDs and OSPython.044 on buffered logging. Previous lessons explained how records flow, how an error can release diagnostic context, and how loggers connect through handlers. The new lesson focuses on changing the record-creation step itself. No third-party packages are required.
Learning Objectives and Prerequisites
- Describe the roles of a Python
Logger,LogRecord, handler, formatter, filter, and record factory. - Understand why a custom record factory affects logging across one Python process and why it is not a substitute for explicit interprocess context propagation.
- Add a safe, non-secret
request_idfield to each record while preserving the original factory. - Demonstrate
ContextVar.set()andreset()so one operation does not leak an identifier into later work. - Run a Python standard-library example and an executable unit test with expected output.
- Identify global-state, performance, naming-collision, concurrency and secret-handling risks.
Prerequisites: Python logging basics, formatters and handlers, and LoggerAdapter and extra context. Examples use the Python standard library and were executed on Python 3.13.5. The basic APIs are available on earlier Python 3 releases; the explanation of replacement records returned by filters is explicitly version-qualified.
How Python Creates a Log Record
When an enabled logger handles an event, Python constructs a LogRecord carrying properties such as logger name, level, message template, positional arguments, path, source line and timestamp. Handler filters may then examine a record; handlers send accepted events to destinations, and formatters convert the data to text. The record factory sits earlier: it participates whenever a logger creates a normal record. The official logging library reference documents replacing and retrieving the current factory.
A factory is a callable accepting the usual LogRecord constructor parameters and returning a record. Wrapping the previous factory is safer than discarding it because another framework or instrumentation library may have installed customization already. The wrapper must return a valid record with all standard attributes intact.
mCoding, “Modern Python logging” (January 22, 2024). The detailed tutorial discusses the logging pipeline, formatters, added context, filters and queued handlers; the segment around 15:46 covers the extra mechanism and context. Video identity is verified from its public source; WordPress playback is checked separately.
A Context Variable Is Not a Global Request Identifier
A normal mutable module-level string is shared by concurrent callers in one process. If two tasks overwrite a single global request ID, their diagnostic lines can be mislabeled. Python’s ContextVar instead associates a value with an execution context. Separate asynchronous tasks normally maintain their own context; new threads or separate processes require deliberate handling when continuity is needed. The Python contextvars reference explains context-local values, tokens, and context propagation.
The demonstration uses a single synchronous function, but the same mechanism can be integrated with an asynchronous request handler that sets the ID at request entry and resets the token in a finally clause. Do not assume ContextVar automatically carries a value to a remote API, container, or process; explicit transmission and privacy rules remain necessary.

A Complete Runnable Lab: One Factory, Three Events
Save the following complete source as record_factory_lab.py in a new, unprivileged working directory. It imports only Python standard-library modules. The StringIO destination makes the example deterministic, so it creates no log files and sends no data over the network. The logger’s handler and global factory are restored when the function finishes:
"""OSPython.045: application-wide log context, Python 3.12+ stdlib."""
from contextvars import ContextVar
import io
import logging
request_id = ContextVar("request_id", default="unassigned")
old_factory = logging.getLogRecordFactory()
def context_record_factory(*args, **kwargs):
record = old_factory(*args, **kwargs)
record.request_id = request_id.get()
return record
def demo() -> str:
output = io.StringIO()
handler = logging.StreamHandler(output)
handler.setFormatter(logging.Formatter(
"%(levelname)s %(name)s id=%(request_id)s %(message)s"
))
logger = logging.getLogger("open_cert.telemetry")
old_level = logger.level
old_propagate = logger.propagate
logging.setLogRecordFactory(context_record_factory)
logger.setLevel(logging.INFO)
logger.propagate = False
logger.addHandler(handler)
try:
logger.info("collector started")
token = request_id.set("rack-A7")
try:
logger.warning("temperature sensor unavailable")
finally:
request_id.reset(token)
logger.info("collector finished")
finally:
logger.removeHandler(handler)
handler.close()
logger.setLevel(old_level)
logger.propagate = old_propagate
logging.setLogRecordFactory(old_factory)
return output.getvalue()
if __name__ == "__main__":
print(demo(), end="")
The original factory is captured before the wrapper is installed. The wrapper delegates ordinary record creation to that factory and adds request_id from the active context. The formatter expects the new field on every record it processes. A try/finally sequence restores the context token after the simulated warning, and another finally removes the handler and restores prior logger state. Restoring global changes is particularly important in unit tests, interactive notebooks, and applications that share a logging process with other libraries.
Run the file from the directory containing the source using the Python interpreter matching your environment:
python --version
python record_factory_lab.py
The example was executed on Python 3.13.5. Its verified output was:
INFO open_cert.telemetry id=unassigned collector started
WARNING open_cert.telemetry id=rack-A7 temperature sensor unavailable
INFO open_cert.telemetry id=unassigned collector finished
The second line includes the temporary rack identifier. The third line returns to unassigned, demonstrating that the diagnostic context was removed rather than leaking into the subsequent event. These lines are actual observed output from the stated test environment, not proposed production metrics.
Executable Unit Test for Context and Cleanup
Create a second file, test_record_factory.py, in the same directory. The test asserts the complete output, checks that the original record factory is restored by identity, and verifies that the context variable has its original default value:
import logging
import unittest
from record_factory_lab import demo, request_id
class RecordFactoryTests(unittest.TestCase):
def test_context_and_cleanup(self):
before = logging.getLogRecordFactory()
result = demo().splitlines()
self.assertEqual(result, [
"INFO open_cert.telemetry id=unassigned collector started",
"WARNING open_cert.telemetry id=rack-A7 temperature sensor unavailable",
"INFO open_cert.telemetry id=unassigned collector finished",
])
self.assertIs(logging.getLogRecordFactory(), before)
self.assertEqual(request_id.get(), "unassigned")
if __name__ == "__main__":
unittest.main()
Execute the test module as follows:
python -m unittest -v test_record_factory.py
The test passed on Python 3.13.5, reporting one successful test. The observed terminal summary was:
Ran 1 test in 0.001s
OK
Runtime values and elapsed execution time may vary by computer; the important invariants are one passing test, exact event fields, no leftover context, and restoration of the prior factory. For integration into a larger application, also test nested context changes, concurrent tasks, and cooperation with framework-installed record factories.
Factory, Filter, and LoggerAdapter: Different Responsibilities
A LoggerAdapter supplies contextual fields to calls made through that adapter. A filter can accept, reject, or modify records at a selected logger or handler. A LogRecordFactory changes record creation across the process for qualifying events. Each solution has a different scope. The earlier logging filters lesson covered selection rules; a factory is useful when a field should exist before downstream formatters examine a newly created record.
Python 3.12 introduced the ability for a handler filter to return a replacement LogRecord instead of simply mutating an existing one. That mechanism can isolate changes to one output handler. A factory remains process-wide, so it should be adopted only when the wider scope is intentional. Refer to the official logging.Filter reference for the version-specific distinction.
Tech With Tim, “Python Logging – Tutorial” (April 27, 2022). Introduces event levels and shows how loggers, handlers, and formatters interact; the discussion of handlers and formatters begins around 12:21. The official channel and video metadata were checked. The presentation supplies foundational context for the record-factory demonstration.
Naming Collisions and Secret-Handling Rules
A factory-inserted field must avoid the names Python already reserves for records, including message, levelname, and asctime. The demonstration uses the new attribute request_id. Do not also pass a conflicting extra={"request_id": ...} field on the same event: Logger.makeRecord() rejects an attempt to overwrite an attribute already set on the record, raising a KeyError. Choose one source of truth, or document a different field name for adapter-specific context.
Never add tokens, passwords, private keys, customer secrets, session cookies or entire authorization headers to the factory. A globally injected field reaches more handlers than a developer may expect. Review OSPython.041 on redaction and credential protection before logging identifiers derived from sensitive user data. The public sample uses an invented equipment label and no secrets.
Factories also have performance overhead: every enabled event creates a record and invokes the wrapper. Complex database calls, file reads, blocking network operations or recursive logging inside a record factory can slow down applications or create errors. Keep the wrapper small and predictable. If an application sends logs through a process boundary, serialize appropriate context explicitly rather than assuming that a process-local factory setting has propagated.

Practical Exercise: Two Maintenance Tickets
Modify the demonstration to log two sequential simulated maintenance tickets. Set request_id to rack-A7 for the first operation and rack-B2 for the second. Reset each token after its own operation. Add a third event after both assignments and verify that the final record again shows unassigned. Do not print raw credentials, device secrets or personal identifiers.
- Record the Python version and the exact source file used for the lab.
- Run the unchanged sample and save its three verified output lines.
- Create a second request context with a distinct synthetic device ID.
- Check that the first ticket’s ID never appears in the second ticket’s output.
- Ensure the post-ticket log record has the default value; restore the factory even if a simulated exception occurs.
- Use
unittestto assert both the expected event sequence and the cleanup invariants.
A second extension is to route the same logger to two StringIO stream handlers, each with a different formatter. When the global factory supplies the context field, both handlers can access it without each event specifying the ID manually. Compare with the MemoryHandler lesson, where buffering affects delivery timing rather than record construction.
Troubleshooting Common Factory Errors
KeyError: Attempt to overwrite 'request_id': avoid passing the same field in both a global factory and a per-callextradictionary.ValueError: Formatting field not found in record: make sure a formatter requiring a custom field is paired with a record producer that actually supplies it.- Unexpected duplicate log lines: inspect handler registration and
propagate; adding a handler twice or forwarding to the root logger can duplicate output. - Incorrect context after work finishes: reset the exact
ContextVartoken inside afinallyclause, including on exception paths. - Context absent in a new worker: evaluate how the execution context is propagated into threads, tasks or independent processes.
- Slow log production: keep the factory lightweight and avoid logging recursively inside the record-creation path.
Knowledge Check and Explained Answers
- What does a record factory customize? The creation of eligible
LogRecordobjects, before downstream handlers format them. - Why call the previous factory? To preserve existing record behavior and avoid replacing framework customizations unnecessarily.
- What is the purpose of a ContextVar token? It permits restoring the exact prior context state after a temporary value is assigned.
- Does a ContextVar automatically synchronize across separate processes? No. Separate processes require explicit state and correlation handling.
- Why does a formatter fail when it references a field missing from the record? Its format string assumes an attribute that the record producer did not set.
- Why can a custom request ID conflict with
extra? Python prevents per-callextraentries from overwriting an existing record attribute. - What did the executable test validate? The exact event sequence and request IDs, absence of leaked context, and restoration of the original global factory.
Primary Documentation and Further Reading
Primary reference: the Python standard-library logging API, including getLogRecordFactory() and setLogRecordFactory(); the Logging Cookbook’s customizing LogRecord section; the contextvars module reference; and unittest documentation. The sample was tested locally with the Python 3.13.5 interpreter; no external packages, elevated permissions, external HTTP calls or production infrastructure were involved.
Elementary Summary
A record factory gives an application a central place to attach uniform metadata to new Python log events. Used with a context variable, it can expose a temporary synthetic request ID without adding that identifier to every logging statement. Correct implementation delegates to the previous factory, keeps global changes reversible, resets context tokens, avoids record-key collisions and redacts secrets. Testing both the event output and cleanup steps turns a convenient diagnostic technique into a maintainable engineering pattern.
Editor’s Note: OSPython.045 advances the verified Python lesson sequence after OSPython.044. The complete Python 3.13.5 example and one unittest were executed successfully; the three sample output lines are observed results. The technical explanations were checked against the Python standard library documentation. The original featured artwork and in-body illustrations are AI-generated conceptual editorial imagery, not verified live software screenshots. The YouTube blocks are checked separately for WordPress iframe rendering; interactive playback cannot be confirmed without player interaction.

For mobile scanning, open the lesson on another display and use a compatible Bitcoin wallet to scan the original QR code. Verify the destination before donating 5,000 sats: 3C9o19EH5HSiwEPyCTmEKzxhNCbo2X6TTb.
We volunteer daily to ensure the credibility of the information on this platform is Verifiably True. If you would like to support our work and help further secure the integrity of our research initiatives, please scan the QR code below to donate 5,000 sats.
BitcoinVersus.tech is not a financial advisor. This media platform reports on financial subjects purely for informational purposes.

Leave a Reply