Shared Python library with logging and configuration utilities.
pip install git+https://github.com/RomLecat/pyutils.gitSetup for the standard logging module. setup_logging attaches one
handler to the root logger and returns a named logger. All named loggers
propagate their records to that handler. You can call setup_logging
more than one time. Only the first call configures the handler.
from pyutils import Log
logger = Log.setup_logging('myapp')
logger.info('Info message')
logger.warning('Warning')
logger.error('Error')The library adds a TRACE level below DEBUG:
logger.trace('Very detailed message')Set the level at runtime:
Log.set_level('TRACE') # a level name or the matching integer
Log.enable_debug() # shortcut for the DEBUG levelset_level and enable_debug change the root logger only. A level that
silence puts on a named logger stays in place.
Log.silence('urllib3') # WARNING and above only
Log.capture('worker.engine') # remove a foreign handler, propagate againUse capture on a framework that attaches its own handler. The records
then go to the root handler with the same format as the rest. capture
also removes the level of the logger: the logger then follows the root
level.
| Variable | Values | Default |
|---|---|---|
LOG_LEVEL |
TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL |
INFO |
DEBUG |
true, 1, t |
unset |
LOG_FORMAT |
json, console |
console on a terminal, else json |
LOG_LEVEL has priority over DEBUG. The values are case insensitive.
An invalid LOG_LEVEL is ignored.
The json format writes one JSON object per line on stdout. A log
collector reads the severity from the level field. A traceback stays on
the same line.
{"timestamp":"2026-01-01T12:00:00.000Z","level":"ERROR","logger":"worker","message":"Task failed","exception":"Traceback..."}Fields sent through extra are added to the object:
logger.info('Task done', extra={'task_id': 42})The formatter keeps these field names for itself: timestamp, level,
logger, message, exception and stack. An extra field with one
of these names does not go into the output. Choose another name.
The output is ASCII: the formatter writes other characters as \uXXXX
escapes. A field that JSON cannot write, such as a circular container,
goes into the output through repr(). The record is never lost.
The console format writes a human readable line. It adds colors only on
a terminal:
2026-01-01T12:00:00.000 WARNING [worker] Disk is almost full
Both formats write the time in UTC.
| Level | Color |
|---|---|
| TRACE | Grey |
| DEBUG | Grey |
| INFO | Plain |
| WARNING | Yellow |
| ERROR | Red |
| CRITICAL | Bold red |
Configuration manager built on Dynaconf, with scope support and environment variable overrides.
from pyutils import Config
config = Config("custom_scope")
db_url = config.get("url")
paths = config.get_paths("directory")By default, Config loads config.yml from the working directory. This path can be overridden via the CONFIG_FILE environment variable.
Example config.yml:
global:
debug: false
dry_run: false
my_class:
foo: barValues are resolved in the following priority order:
- Environment variable
SCOPE_PARAM(e.g.DATABASE_URL) - Environment variable
PARAM(e.g.URL) - Scoped key in config file (e.g.
my_class.foo) - Key under the
globalscope in config file - Default value (if provided), otherwise
KeyError
config.dry_run # reads DRY_RUN, returns a boolean
config.debug # reads DEBUG, returns a boolean