Use log_context(...) to add structured key-values to every record emitted on
the current thread while the context is active:
import femtologging
logger = femtologging.get_logger("service")
with femtologging.log_context(request_id=42, user="alice"):
logger.info("request accepted")
Behavioural guarantees:
- Context values are merged on the producer thread before queueing.
- Inline structured fields emitted by Rust macros override outer context keys.
- Context values must be
str,int,float,bool, orNone. - Callback-filter enrichment uses the same scalar contract: keys must be
non-empty strings, and values must be
str,int,float,bool, orNone. - Keys must not collide with stdlib
logging.LogRecordattributes or femtologging-reserved metadata names. - Enrichment is bounded to 64 keys per record, 64 UTF-8 bytes per key, 1,024 UTF-8 bytes per value, and 16 kibibytes (KiB) total serialized enrichment per record.
Callback enrichment validation
Python callback filters may mutate the temporary logging.LogRecord object and
return a truthy result to accept the record. femtologging copies only the
validated scalar additions from that record into Rust-owned metadata before the
record is queued.
Accepted enrichment must satisfy all of the following:
- Keys are non-empty
strinstances. - Values are limited to
str,int,float,bool, orNone. - Keys do not overwrite built-in
logging.LogRecordfields such asname,msg,levelname,created, or femtologging-reserved metadata fields. - The serialized payload stays within 64 keys, 64 UTF-8 bytes per key, 1,024 UTF-8 bytes per value, and 16 KiB total.
Example of accepted enrichment:
import logging
import femtologging
def request_filter(record: logging.LogRecord) -> bool:
record.request_id = "req-123"
record.user_id = 42
record.cache_hit = False
return True
logger = femtologging.get_logger(
"service",
filters=[femtologging.PythonCallbackFilterBuilder(request_filter)],
)
Collisions and invalid values are rejected before the record enters the worker thread. For example, this filter tries to overwrite a stdlib field and attach an unsupported value:
import logging
def invalid_filter(record: logging.LogRecord) -> bool:
record.name = "rewritten"
record.payload = {"not": "allowed"}
return True
When validation fails, femtologging raises a Python exception inside the callback path, catches it at the logger boundary, emits a warning, and drops that record. The process keeps running, but the invalid record is not queued or delivered to handlers.