- Logger-level formatting is fixed (
"{logger} [LEVEL] message"). To customize output wrap handlers with formatter callables using the builder API:
def json_formatter(record: dict[str, object]) -> str:
import json
return json.dumps(
{
"logger": record["logger"],
"level": record["level"],
"message": record["message"],
},
separators=(",", ":"),
)
stream = StreamHandlerBuilder.stdout().with_formatter(json_formatter).build()
- Formatter identifiers are limited to
"default"for now. Future releases will connectFormatterBuilderand handler formatter IDs, but this is not yet implemented. - Filters are available through both
ConfigBuilderanddictConfig.fileConfigdoes not yet support filter declarations. Handler-levelfiltersandlevelattributes remain unsupported, as these require Rust infrastructure changes outside the current scope. - Python callback filters receive a mutable
logging.LogRecordview on the producer thread. When they accept the record, any new supported attributes they add are copied intometadata.key_valuesbefore the record enters the async queue, making them visible to formatter callables andStdlibHandlerAdapter.
import contextvars
import logging
from femtologging import PythonCallbackFilterBuilder
request_id = contextvars.ContextVar("request_id", default="")
def enrich_request(record: logging.LogRecord) -> bool:
record.request_id = request_id.get()
return record.levelname == "INFO"
callback_filter = PythonCallbackFilterBuilder(enrich_request)