Configuration#
tuning supports two primary configuration paths: programmatic root logger
configuration with basicConfig() and YAML configuration with
basicConfigFromYaml().
How Configuration Works#
tuning.basicConfig()configures the actual process root logger, likelogging.basicConfig().Calling logger methods before explicit configuration installs console-only zero-config defaults.
Repeated
basicConfig()andbasicConfigFromYaml()calls do nothing unlessforce=True.basicConfigFromYaml()loads packaged defaults fromtuning/conf.ymlfirst.The YAML file you pass in is deep-merged on top of packaged defaults.
basicConfigFromYaml()preservesroot:as the real process root logger config.examples/custom_logger.ymlis intentionally a partial override, not a standalone full config.tuning.export(...)writes the packaged default config as a full standalone YAML file.
Programmatic Configuration#
Console output:
import tuning
tuning.basicConfig(
level="INFO",
show_time=True,
datefmt=tuning.ISO_FORMAT,
show_icon=True,
show_level=True,
)
File output:
import tuning
tuning.basicConfig(filename="app.log", level="INFO")
Console and file output together:
tuning.basicConfig(
filename="app.log",
console=True,
level="INFO",
show_icon=True,
)
Rotating file output:
tuning.basicConfig(
filename="app.log",
level="INFO",
max_bytes="10 MB",
backup_count=5,
)
basicConfig(filename=...) is file-only. Use
basicConfig(filename=..., console=True) for console plus file.
Set show_level=False to hide the console level prefix entirely and render only
the message, apart from any enabled time or path columns.
max_bytes and backup_count are ignored when filename is omitted. If only
one rotation option is provided, the other uses DEFAULT_MAX_BYTES or
DEFAULT_BACKUP_COUNT and emits a warning.
Useful constants:
tuning.ISO_FORMAT:[%Y-%m-%d %H:%M:%S]tuning.DEFAULT_MAX_BYTES: default size used when onlybackup_countis providedtuning.DEFAULT_BACKUP_COUNT: default backup count used when onlymax_bytesis provided
YAML Configuration#
Generate a full starter config:
import tuning
tuning.export("tuning.yml")
Load the YAML config:
import tuning
tuning.basicConfigFromYaml("tuning.yml", force=True)
basicConfigFromYaml() with no path loads only the packaged default config.
The exported YAML is copied from packaged tuning/conf.yml; it does not
reconstruct live runtime logger state.
export()#
export() writes the packaged default config to a YAML file:
import tuning
path = tuning.export()
Path behavior:
tuning.export()writestuning.ymlnext to the calling Python file.tuning.export("app.yml")writes exactlyapp.yml.tuning.export("config")writes exactlyconfigif that path does not already exist as a directory.tuning.export("configs/")writesconfigs/tuning.ymlifconfigsalready exists as a directory.Missing parent directories are created automatically for explicit file paths.
Existing files raise
FileExistsErrorunlessforce=Trueis passed.The returned value is the resolved
Paththat was written.
levels:#
levels: defines metadata for built-in and custom log levels:
levels:
SUCCESS:
code: 25
symbol: '[+]'
icon: '✅'
style: 'green'
Fields:
code: integer logging level codesymbol: compact prefix used when icons are disabledicon: prefix used when icons are enabledstyle: Rich style applied to console level prefixes and message text
Built-in levels must keep stdlib codes: DEBUG=10, INFO=20, WARNING=30,
ERROR=40, and CRITICAL=50. Use WARNING, not WARN. Use CRITICAL, not
FATAL.
Custom levels become methods on TunedLogger. For example, TRACE creates
logger.trace(...), and MY-CUSTOM-LEVEL creates
logger.my_custom_level(...).
Do not define levels.INPUT; prompt styling belongs in top-level prompt:.
Runtime Custom Levels#
Use addLevel() to register a custom level for the current Python process
without editing YAML:
import tuning
tuning.addLevel(
7,
"MY_CUSTOM_LEVEL",
symbol="MC",
icon="MY",
style="bright_blue",
)
logger = tuning.getLogger(__name__)
logger.my_custom_level("runtime custom output")
Runtime levels are process-global and only exist until the process exits. They
do not update tuning.yml or exported defaults. Names must be custom level
names that create valid Python method names; built-in names such as INFO and
aliases such as WARN are rejected.
prompt:#
prompt: controls logger.prompt(...) styling:
prompt:
symbol: '<<<'
icon: '✏️'
style: 'italic bold black on magenta'
Prompt icon selection follows the first configured TunedHandler. The spacer
after the prompt text and the user’s typed answer are not styled.
Handler Options#
Handlers follow stdlib logging.config.dictConfig syntax. TunedHandler adds
console-specific options:
handlers:
console:
'()': 'tuning.TunedHandler'
level: TRACE
log_time_format: "[%Y-%m-%d %H:%M:%S]"
show_time: false
show_level: true
show_path: false
boxes: false
rich_tracebacks: true
markup: true
show_icon: false
show_icon: true switches the console prefix from symbol to icon when an
icon is configured for the level. show_icon is only valid on TunedHandler.
show_level: false hides the symbol, icon, and level name/title entirely.
Use log_time_format to customize YAML-configured console timestamps. Formatter
datefmt is for normal formatter-driven timestamps; Rich console time rendering
uses log_time_format on the handler.
The console level prefix column has a minimum width of 3 terminal cells. Longer fallback labels are not truncated and expand naturally.
Level styles apply to the console prefix, the separator before the message, and message text. Time and path columns stay structural, and file handlers use plain detailed text formatting.
With boxes: true, each console log record is rendered in its own Rich panel.
The panel border, title, padding, and fill use the same level style as the
message. If show_level is enabled, the panel title contains the configured
symbol or icon plus the level name. If show_time or show_path are enabled,
those columns stay outside the box.
File Rotation In YAML#
File handlers can use normal stdlib handler classes. Parent directories are
created automatically, and human-readable maxBytes values are normalized:
handlers:
file:
class: logging.handlers.RotatingFileHandler
level: TRACE
formatter: detailed
filename: '.logs/app.log'
maxBytes: '5 MB'
backupCount: 3
encoding: utf8
Logger Selection#
Use tuning.getLogger(name) instead of directly instantiating loggers:
import tuning
logger = tuning.getLogger(__name__)
basicConfig() and basicConfigFromYaml() configure the actual process root
logger. Module loggers created with tuning.getLogger(__name__) inherit root
handlers, matching stdlib logging practice.
TunedLogger.from_yaml() configures only the requested named logger. If the
YAML only defines root:, that section is treated as the template for the named
logger and does not configure the actual process root logger.
You can also configure the requested logger explicitly:
loggers:
my_app:
level: INFO
handlers: [console, file]
propagate: false
Unexpected logger entries are rejected because this path configures one requested logger at a time.
Reconfiguration Rules#
Repeated
basicConfig()andbasicConfigFromYaml()calls do nothing unlessforce=True.force=Truereplaces root handlers for the basic config APIs.Repeated
TunedLogger.from_yaml()calls with the same config are idempotent.Repeated
TunedLogger.from_yaml()calls with a different config raise unless you passforce=True.force=Truereplaces handlers on the same named logger forfrom_yaml().Custom level registration is process-global, so conflicting redefinitions are rejected.