Logging Manager¶
Public API for the logging subsystem. kstlib.logging.LogManager wraps the standard library logger with Rich
rendering, preset selection, rotation, and async-friendly helpers so you can ship console/file output without
rewriting plumbing.
Tip
Pair this reference with Logging for the feature guide.
Quick overview¶
LogManageracceptsconfigoverrides or apreset(dev,prod,debug), falling back to the cascade described below.Console output relies on Rich handlers with custom icons, color themes, and traceback rendering.
File logging uses timed rotation (
when,interval,backup_count) and auto-creates directories when needed.Async helpers (
ainfo,asuccess, …) dispatch to a thread pool so event loops do not block while performing I/O heavy logging.Structured context (
logger.info("msg", key=value)) is flattened intokey=valuesegments automatically.
Configuration cascade¶
The constructor merges configuration from six sources. Later entries override earlier ones:
FALLBACK_DEFAULTSfromkstlib.logging.managerFALLBACK_PRESETS(dev,prod,debug)logger.defaultsinkstlib.conf.ymllogger.presets[<name>]inkstlib.conf.ymlRemaining keys under
logger(output,icons,rotation, etc.)The explicit
configargument
You can pass preset="prod" to force a preset without touching the config file, or supply a full dict for
one-off tweaks inside tests.
Default profile¶
logger:
defaults:
output: both # console | file | both
theme:
trace: "medium_purple4 on dark_olive_green1"
debug: "black on deep_sky_blue1"
info: "sky_blue1"
success: "black on sea_green3"
warning: "bold white on salmon1"
error: "bold white on deep_pink2"
critical: "blink bold white on red3"
icons:
show: true
debug: "🔎"
info: "📄"
success: "✅"
warning: "🚨"
error: "❌"
critical: "💀"
console:
level: DEBUG
datefmt: "%Y-%m-%d %H:%M:%S"
format: "::: PID %(process)d / TID %(thread)d ::: %(message)s"
show_path: true
tracebacks_show_locals: true
file:
level: DEBUG
datefmt: "%Y-%m-%d %H:%M:%S"
format: "[%(asctime)s | %(levelname)-8s] ::: PID %(process)d / TID %(thread)d ::: %(message)s"
log_path: "./"
log_dir: "logs"
log_name: "kstlib.log"
log_dir_auto_create: true
rotation:
when: midnight
interval: 1
backup_count: 7
Presets adjust only the relevant sections (output, per-handler levels, icon visibility). Anything left
unspecified inherits from the defaults above.
Usage patterns¶
Basic logging¶
def demo_basic_logging() -> None:
"""Demonstrate basic logging with all levels."""
print("\n=== Basic Logging Demo ===\n")
# Create logger with default config (console + file)
logger = LogManager(name="basic_demo")
# Log different levels (TRACE is the most verbose, below DEBUG)
logger.trace("This is a trace message") # Ultra-verbose, for HTTP debugging
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.success("This is a success message") # Custom level
logger.warning("This is a warning message")
logger.error("This is an error message")
logger.critical("This is a critical message")
print("\n✅ Check ./logs/kstlib.log for file output\n")
Structured context¶
def demo_structured_logging() -> None:
"""Demonstrate structured logging with context."""
print("\n=== Structured Logging Demo ===\n")
logger = LogManager(name="structured_demo")
# Log with context key=value pairs
logger.info("Server started", host="localhost", port=8080)
logger.success("Connection established", client_id=12345, ip="192.168.1.100")
logger.warning("High memory usage", usage_percent=85, threshold=80)
logger.error(
"Database connection failed",
db_host="localhost",
db_port=5432,
retry_count=3,
)
print("\n✅ Structured logging adds context to messages\n")
Presets¶
def demo_presets() -> None:
"""Demonstrate logging presets (dev, prod, debug)."""
print("\n=== Presets Demo ===\n")
# Dev preset: console only, DEBUG level, show path
print("1. Dev preset (console only, DEBUG):")
dev_logger = LogManager(name="dev", preset="dev")
dev_logger.debug("Dev mode debug message")
dev_logger.info("Dev mode info message")
# Prod preset: file only, INFO level, no path
print("\n2. Prod preset (file only, INFO):")
prod_logger = LogManager(name="prod", preset="prod")
prod_logger.debug("This won't show (DEBUG < INFO)")
prod_logger.info("Prod mode info message (file only)")
print(" → Check logs/kstlib.log (no console output)")
# Debug preset: both console+file, DEBUG level, show locals in tracebacks
print("\n3. Debug preset (both, DEBUG, show locals):")
debug_logger = LogManager(name="debug", preset="debug")
debug_logger.debug("Debug mode with full tracebacks")
debug_logger.info("Debug mode info message")
print("\n✅ Presets configure logger for different environments\n")
Async helpers¶
async def demo_async_logging() -> None:
"""Demonstrate the async logging helpers inside an asyncio workflow."""
print("\n=== Async Logging Demo ===\n")
logger = LogManager(name="async_demo", config={"output": "console"})
jitter = (0.05, 0.3)
async def simulate_task(task_name: str, duration: float) -> None:
await logger.ainfo("Task started", task=task_name, duration=duration)
await asyncio.sleep(duration + random.uniform(*jitter))
await logger.asuccess("Task finished", task=task_name)
await asyncio.gather(
simulate_task("cache-refresh", 0.2),
simulate_task("price-feed", 0.1),
simulate_task("heartbeat", 0.05),
)
try:
raise RuntimeError("Websocket disconnected")
except RuntimeError as exc:
await logger.aerror("Background worker failed", reason=str(exc))
print("\n✅ Async helpers let you log without blocking the event loop\n")
# Example 7: HTTP trace logging
def demo_trace_logging() -> None:
"""Demonstrate TRACE level for HTTP debugging."""
print("\n=== TRACE Level Demo ===\n")
# TRACE level is below DEBUG - for ultra-verbose protocol logging
logger = LogManager(
name="trace_demo",
config={
"output": "console",
"console": {"level": "TRACE"}, # Enable TRACE
},
)
# Simulate HTTP request/response logging
logger.trace("HTTP Request", method="POST", url="https://auth.example.com/token")
logger.trace("Request headers", content_type="application/json", accept="*/*")
logger.trace("Request body", grant_type="authorization_code", code="abc123")
logger.debug("Sending token request...")
logger.trace("HTTP Response", status=200, content_type="application/json")
logger.trace("Response body", access_token="***", expires_in=3600)
logger.info("Token obtained successfully")
print("\n✅ TRACE level helps debug HTTP/OAuth flows\n")
Module reference¶
Logging module with Rich console output and async-friendly wrappers.
This module provides a LogManager class for structured logging with:
Rich console output (colored, emoji icons, traceback with locals)
File logging with rotation
Preset configurations (dev, prod, debug)
Async wrappers executed via thread pool
Structured logging with context key=value pairs
Opt-in lazy auto-init driven by the config file
Two ways to use LogManager:
LogManager(preset="dev")returns an isolated instance.logging.getLogger("kstlib")is untouched, so this form is safe for local use and tests.LogManager(preset="dev", register=True)(or theinit_logging()alias) registers the instance as the global root of the"kstlib"logger hierarchy so thatlogging.getLogger("kstlib.foo")child loggers propagate records back to it.
Consumers who never call init_logging() can still activate internal
kstlib logs by flipping kstlib.logging.enabled in kstlib.conf.yml.
The first call to get_logger() from any kstlib module then triggers
LogManager(preset=..., register=True) transparently. Any error on that
cascade is swallowed silently so the library never breaks the host
application.
Example
>>> from kstlib.logging import get_logger
>>> logger = get_logger(__name__)
>>> logger.info("Server started")
- class kstlib.logging.LogManager(name='kstlib', config=None, preset=None, register=False)[source]¶
Bases:
LoggerRich-based logger with async-friendly wrappers and flexible configuration.
Supports multiple configuration sources with priority order (lowest to highest): 1. Built-in defaults (module fallback) 2. Built-in presets 3.
logger.defaultsfrom configuration file 4.logger.presets[<name>]from configuration file 5. Remainingloggerkeys from configuration file (global overrides) 6. Explicitconfigparameter (constructor argument)- Parameters:
name (str) – Logger name (default: “kstlib”)
config (Box | dict[str, Any] | None) – Explicit configuration dict/Box
preset (str | None) – Preset name (“dev”, “prod”, “debug”, or custom from config)
register (bool) – When
True, register this instance as the global root of the"kstlib"logger hierarchy. The instance is injected intologging.Logger.manager.loggerDictsologging.getLogger("kstlib")returns the same object,.trace()and.success()are installed on the baselogging.Loggerclass (so child loggers returned bylogging.getLogger("kstlib.foo")also support them), and theTRACE_LEVELis propagated to all pre-existing"kstlib.*"child loggers. Defaults toFalse, which produces an isolated instance suitable for local use and tests.
Example
>>> logger = LogManager(preset="dev") >>> logger.info("Server started", host="localhost", port=8080) >>> logger.success("Connection established")
Global bootstrap used by
init_logging():LogManager(preset="dev", register=True) # doctest: +SKIP
- __init__(self, name: str = 'kstlib', config: box.box.Box | dict[str, Any] | None = None, preset: str | None = None, register: bool = False) None -> None[source]¶
Initialize LogManager with configuration priority chain.
- Parameters:
name (str) – Logger name (default: “kstlib”).
config (Box | dict[str, Any] | None) – Explicit configuration dict/Box.
preset (str | None) – Preset name (“dev”, “prod”, “debug”, or custom from config).
register (bool) – When
True, register this instance as the global kstlib root logger (see class docstring for details). WhenFalse(default), the instance stays isolated and does not affectlogging.getLogger("kstlib").
- trace(self, msg: object, *args: object, **kwargs: Any) None -> None[source]¶
Log trace message (custom level 5, below DEBUG).
Use for detailed HTTP traces, protocol dumps, and low-level diagnostics.
- success(self, msg: object, *args: object, **kwargs: Any) None -> None[source]¶
Log success message (custom level 25).
- critical(self, msg: object, *args: object, **kwargs: Any) None -> None[source]¶
Log critical message.
- traceback(self, exc: BaseException) None -> None[source]¶
Print Rich traceback, respecting the configured show_locals setting.
- Parameters:
exc (BaseException) – Exception to display
- async atrace(self, msg: str, **context: Any) None -> None[source]¶
Async trace wrapper executed via thread pool.
- async adebug(self, msg: str, **context: Any) None -> None[source]¶
Async debug wrapper executed via thread pool.
- async ainfo(self, msg: str, **context: Any) None -> None[source]¶
Async info wrapper executed via thread pool.
- async asuccess(self, msg: str, **context: Any) None -> None[source]¶
Async success wrapper executed via thread pool.
- async awarning(self, msg: str, **context: Any) None -> None[source]¶
Async warning wrapper executed via thread pool.
- kstlib.logging.get_logger(name: 'str | None' = None) 'logging.Logger' -> logging.Logger[source]¶
Get a logger for the given module name.
Returns a child logger under the ‘kstlib’ namespace. When the root logger is initialized via
init_logging()or CLI--log-level, child loggers inherit its handlers and configuration.On first call, if no explicit
init_logging()has been made, this function readskstlib.logging.enabledfromkstlib.conf.ymland triggers a silent auto-init when the flag is true. Config errors are swallowed: the cascade simply falls back to Python’s default logging.- Parameters:
name (str | None) – Module name (typically
__name__). If None, returns the root kstlib logger.- Returns:
A logger instance.
- Return type:
Example
>>> from kstlib.logging import get_logger >>> logger = get_logger(__name__) >>> logger.debug("Processing item", extra={"item_id": 123})
- kstlib.logging.init_logging(*, preset: 'str | None' = None, config: 'dict[str, Any] | None' = None) 'LogManager' -> LogManager[source]¶
Initialize the kstlib root logger (alias of
LogManager(register=True)).This is a thin backward-compatible wrapper around
LogManager(name="kstlib", preset=..., config=..., register=True). The heavy lifting (logger-dict injection,.trace/.successmonkey-patch, level propagation to pre-existing children) happens insideLogManager.__init__whenregister=True.Call this function early in the application startup to configure logging. If it is never called explicitly,
get_logger()may still trigger a silent auto-init based onkstlib.logging.enabledinkstlib.conf.yml.- Parameters:
- Returns:
The root LogManager instance (same as
logging.getLogger("kstlib")).- Return type:
Example
>>> from kstlib.logging import init_logging >>> logger = init_logging(preset="dev")
- kstlib.logging.list_available_presets() list[str] -> list[str][source]¶
Return the list of logging preset names available to
LogManager.Merges the built-in fallback presets (
dev,prod,debug) with the presets declared underlogger.presetsinkstlib.conf.yml. User-defined presets override built-ins with the same name.- Returns:
Sorted list of unique preset names. Falls back to the built-in names when the configuration cannot be read (silent on all errors).
- Return type:
Examples
>>> names = list_available_presets() >>> "prod" in names True