Changelog¶
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
Changed¶
- Expanded API documentation and docstrings for
ConsoleRenderer, processor helpers, testing helpers, the stdlib bridge, andCore. - Internal cleanup: removed unused internals (
Core._print_error,_frames._format_exception,_utils.eval_dict) and consolidated duplicated profile builders and name-splitting logic. No public API change. - Renamed the internal
Core.min_level_noattribute toCore.min_level.
0.9.0 - 2026-10-02¶
Added¶
plainlog.testing— a framework-agnosticcapture_logs()context manager andPlainlogCaptureobject for asserting on logs in pytest or unittest.Logger.flush()— block until queued records have been processed.
Changed¶
Core.configure()no longer closes the processors it replaces — reconfiguration is an attach/detach operation.Logger.configure()was replaced byconfigure_log(), which can apply a named profile, explicit processors, or both. Profiles now return a processor list, orNoneto leave the pipeline unchanged.configure_log(..., close_before_configure=True)closes the previously registered processors before installing the new pipeline.Core.close()now releases the resources of every registered processor but keeps the worker thread running. The previous behaviour (release and stop) lives in the newCore.shutdown(), which is also what runs at interpreter exit.
0.8.0 - 2026-09-27¶
Changed¶
- Record schema flattened: the
extrakey is gone. Keyword arguments passed tologger.info(...),bind()andcontextualize()now become top-level fields on the record instead of being nested underrecord["extra"]. SimpleFormatterandJsonFormatterwrite the rendered string torecord["formatted_message"]instead of overwritingrecord["message"].JsonFormatterserializes a fixed set of keys plusadditional_keys; arbitrary user fields are no longer included automatically.SubProcessorruns its nested processors on a shallowcopyof the record (previouslydeepcopy).Logger.error(),critical()andexception()request caller info (function,line, ...) by default.- Documentation and benchmark examples now create a child logger with
logger.new()and log through it rather than logging to the rootlogger. The benchmark also logs through a bound name so CPython's imported-name call-site overhead does not skew the comparison. Logger.new()auto-detected names keep the class/nesting prefix fromco_qualnameon Python 3.11+ (e.g.module.Class.method), falling back tomodule.methodon 3.10;<locals>frame markers are stripped.
Removed¶
eval_extra,eval_lambda_extraandremove_extra_itemsprocessors — fields are plain top-level record keys now.- The
extraparameter ofLogger(...)and theverboseparameter ofLogger(...),Logger.new()andLogger.configure(); pass keyword arguments directly and usecaller_info=Truefor caller info.
0.7.0 - 2026-09-20¶
Added¶
develop_no_colorprofile — likedevelopbut without ANSI colors.
Changed¶
Logger.new()inherits the parent logger'sverbosesetting whenverboseis not given, instead of always defaulting toFalse.SimpleFormatternow renders a full ISO timestamp and appendsextrainstead of dropping it.- The
defaultprofile now usesSimpleFormatter(writing to stdout). develophonors astreamkwarg;default,fileandfingerscrossed_fileaccept aformatkwarg.- Profiles now set
verboseexplicitly, so applying a profile no longer inherits a previousverbosesetting.Logger.configure(verbose=None)andLogger.new(verbose=None)keep inheriting, as before.
Removed¶
DefaultFormatter— useSimpleFormatterinstead.- Profiles
simple,fastandconsole_no_color(console_no_coloris replaced bydevelop_no_color).
Fixed¶
std_handler_defaultandstd_handler_developnow forward their kwargs (e.g.stream,format) to the wrapped profile.- Documentation corrections: profile count, changelog comparison links, and
the missing callable
logger(...)form plus manualcontext()/reset_context()usage. - Corrected
Loggerdocstrings: removed the non-existentcoreattribute and refreshed theconfigure(),new()and__call__()descriptions.
0.6.0 - 2026-09-18¶
Added¶
redact_fieldsandredact_by_patternprocessors — mask sensitiveextravalues by exact key name or substring pattern. contributed by @kashyapm94
Changed¶
- License changed from Apache-2.0 OR MIT to BSD 3-Clause.
0.5.0 - 2026-09-13¶
Changed¶
- Everything is a processor.
configure(handler=...)is replaced byconfigure(processors=[...]). The Core runs an ordered processor list. The usual pipeline is filter, format, handle. - Formatters are processors.
SimpleFormatter,DefaultFormatter,JsonFormatterandConsoleRenderernow setrecord["message"]and return the record instead of returning a string. - Merged
formatters.pyandhandlers.pyintoplainlog.processors. Theplainlog.formattersandplainlog.handlersmodules are gone. - Handler renames.
StreamHandler→Stream,FileHandler→FileWriter,AsyncHandler→AsyncBridge,FingersCrossedHandler→FingersCrossed. JsonHandler,ConsoleHandlerandDefaultHandlerremoved. Build the equivalent pipeline from a formatter plusStream.configure(print_errors=...)removed. Processor errors are now emitted by the newprint_processor_errorprocessor instead of a Core flag.- Tests consolidated into
tests/test_processors.py. - Documentation reorganized;
docs/handlers.mdis nowdocs/processors.md.
Added¶
SubProcessor— runs a nested processor pipeline on a copy of the record.print_processor_error— prints a record's processor error to stderr.ProcessorProtocol,ProcessorCloseProtocolandUniversalProcessorProtocolinplainlog._base.- Fork support: the Core worker is restarted in forked child processes.
verbose=Trueto Logger, adding caller info (function,line, ...) to the record.
Removed¶
BaseHandler,ProcessingHandlerand the preprocessor concept.preformat_message.
Fixed¶
- Thread-safety of concurrent logging and reconfiguration.
- Async handler (
AsyncBridge) reliability.
0.4.0 - 2026-08-28¶
Changed¶
- Log level is now a plain
int. TheLevelNamedTuplewas removed.record["level"]is anint(e.g.10,20,30), and the human-readable name is stored alongside it asrecord["level_name"](astr). - Record schema simplified. The separate
contextandkwargskeys are now merged into the record'sextradict. Caller/context variables and per-call keyword arguments are all available underrecord["extra"]. - Exception handling.
exc_infois replaced byexception, aRecordException(pickle-safe) stored underrecord["exception"].
Added¶
level_name(the human-readable level name) is written directly onto every record alongsidelevel.- Performance improvements on the logging hot path:
time()is used instead ofdatetimefor the record timestamp, and benchmarks against stdlib logging were improved.
Removed¶
Core.level()method. Level validation is handled internally by_validate_level(which now useslogging._checkLevel).
Fixed¶
- Bug with caller-level resolution in
DevelopHandler. - Documentation references and examples updated to the new record schema.
0.3.0¶
Changed¶
- Renamed internal entry points; the main interface now lives on
logger(logger.debug,logger.log,logger.configure, ...). TheCoreis no longer part of the public surface.
Added¶
- Initial documentation site and runnable doc examples (
pytest-examples). - More tests and benchmark coverage.