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

  • LogManager accepts config overrides or a preset (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 into key=value segments automatically.

Configuration cascade

The constructor merges configuration from six sources. Later entries override earlier ones:

  1. FALLBACK_DEFAULTS from kstlib.logging.manager

  2. FALLBACK_PRESETS (dev, prod, debug)

  3. logger.defaults in kstlib.conf.yml

  4. logger.presets[<name>] in kstlib.conf.yml

  5. Remaining keys under logger (output, icons, rotation, etc.)

  6. The explicit config argument

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 the init_logging() alias) registers the instance as the global root of the "kstlib" logger hierarchy so that logging.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: Logger

Rich-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.defaults from configuration file 4. logger.presets[<name>] from configuration file 5. Remaining logger keys from configuration file (global overrides) 6. Explicit config parameter (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 into logging.Logger.manager.loggerDict so logging.getLogger("kstlib") returns the same object, .trace() and .success() are installed on the base logging.Logger class (so child loggers returned by logging.getLogger("kstlib.foo") also support them), and the TRACE_LEVEL is propagated to all pre-existing "kstlib.*" child loggers. Defaults to False, 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). When False (default), the instance stays isolated and does not affect logging.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.

Parameters:
  • msg (object) – Log message

  • *args (object) – Format args

  • **kwargs (Any) – Context key=value pairs

debug(self, msg: object, *args: object, **kwargs: Any) None -> None[source]

Log debug message.

Parameters:
  • msg (object) – Log message

  • *args (object) – Format args

  • **kwargs (Any) – Context key=value pairs

info(self, msg: object, *args: object, **kwargs: Any) None -> None[source]

Log info message.

Parameters:
  • msg (object) – Log message

  • *args (object) – Format args

  • **kwargs (Any) – Context key=value pairs

success(self, msg: object, *args: object, **kwargs: Any) None -> None[source]

Log success message (custom level 25).

Parameters:
  • msg (object) – Log message

  • *args (object) – Format args

  • **kwargs (Any) – Context key=value pairs

warning(self, msg: object, *args: object, **kwargs: Any) None -> None[source]

Log warning message.

Parameters:
  • msg (object) – Log message

  • *args (object) – Format args

  • **kwargs (Any) – Context key=value pairs

error(self, msg: object, *args: object, **kwargs: Any) None -> None[source]

Log error message.

Parameters:
  • msg (object) – Log message

  • *args (object) – Format args

  • **kwargs (Any) – Context key=value pairs

critical(self, msg: object, *args: object, **kwargs: Any) None -> None[source]

Log critical message.

Parameters:
  • msg (object) – Log message

  • *args (object) – Format args

  • **kwargs (Any) – Context key=value pairs

traceback(self, exc: BaseException) None -> None[source]

Print Rich traceback, respecting the configured show_locals setting.

Parameters:

exc (BaseException) – Exception to display

property has_native_async_support: bool

Return whether native async logs are available.

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.

async aerror(self, msg: str, **context: Any) None -> None[source]

Async error wrapper executed via thread pool.

async acritical(self, msg: str, **context: Any) None -> None[source]

Async critical 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 reads kstlib.logging.enabled from kstlib.conf.yml and 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:

Logger

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/.success monkey-patch, level propagation to pre-existing children) happens inside LogManager.__init__ when register=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 on kstlib.logging.enabled in kstlib.conf.yml.

Parameters:
  • preset (str | None) – Logging preset (“dev”, “prod”, “debug”, or custom from config).

  • config (dict[str, Any] | None) – Explicit configuration dict.

Returns:

The root LogManager instance (same as logging.getLogger("kstlib")).

Return type:

LogManager

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 under logger.presets in kstlib.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:

list[str]

Examples

>>> names = list_available_presets()
>>> "prod" in names
True