{"id":31703836,"url":"https://github.com/julialogging/componentlogging.jl","last_synced_at":"2026-01-19T10:00:49.212Z","repository":{"id":316553102,"uuid":"1063842752","full_name":"JuliaLogging/ComponentLogging.jl","owner":"JuliaLogging","description":"Fine-grained logging router for Julia modules and functions—configure levels by group and keep hot paths cheap.","archived":false,"fork":false,"pushed_at":"2026-01-14T03:09:38.000Z","size":348,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-01-14T05:52:01.379Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JuliaLogging.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-25T07:34:14.000Z","updated_at":"2026-01-14T04:33:13.000Z","dependencies_parsed_at":"2025-10-07T05:53:36.244Z","dependency_job_id":null,"html_url":"https://github.com/JuliaLogging/ComponentLogging.jl","commit_stats":null,"previous_names":["abcdvvvv/componentlogging.jl","julialogging/componentlogging.jl"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/JuliaLogging/ComponentLogging.jl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuliaLogging%2FComponentLogging.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuliaLogging%2FComponentLogging.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuliaLogging%2FComponentLogging.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuliaLogging%2FComponentLogging.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JuliaLogging","download_url":"https://codeload.github.com/JuliaLogging/ComponentLogging.jl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuliaLogging%2FComponentLogging.jl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28565048,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-19T08:53:44.001Z","status":"ssl_error","status_checked_at":"2026-01-19T08:52:40.245Z","response_time":67,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-08T22:44:50.667Z","updated_at":"2026-01-19T10:00:49.191Z","avatar_url":"https://github.com/JuliaLogging.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ComponentLogging\n\n[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://julialogging.github.io/ComponentLogging.jl/dev/)\n[![Build Status](https://github.com/JuliaLogging/ComponentLogging.jl/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/JuliaLogging/ComponentLogging.jl/actions/workflows/CI.yml?query=branch%3Amaster)\n[![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet)](https://github.com/SciML/ColPrac)\n[![PkgEval](https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/C/ComponentLogging.svg)](https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/C/ComponentLogging.html)\n\nComponentLogging provides hierarchical control over log levels and messages. It is designed to replace ad‑hoc `print/println` calls and verbose flags inside functions, and to strengthen control flow in Julia programs.\n\n## Introduction\n\nComponentLogging builds on Julia’s stdlib `Logging` to provide a compositional router/logger, `ComponentLogger`, that applies hierarchical minimum levels keyed by `group` (`Symbol` or `NTuple{N,Symbol}`), then delegates to an `AbstractLogger` sink (e.g. `ConsoleLogger` or the included `PlainLogger`).\n\nFor performance-sensitive code paths, the core APIs are logger-first (`clog`, `clogenabled`, `clogf`): when you already have a logger, they bypass task-local logger lookup and make it easy to avoid expensive work when logs are off. Use `@forward_logger` to generate module-local forwarding wrappers.\n\n### Features\n- High performance; negligible overhead when logging is disabled. See [Benchmarking](@ref).\n- Suited for controlling module‑wide output granularity using one (or a few) loggers.\n- Enables control‑flow changes based on hierarchical log levels to eliminate unnecessary computations from hot paths.\n- `@forward_logger` macro for ergonomic, module-local forwarding wrappers.\n\n## Installation\n\n```julia\njulia\u003e] add ComponentLogging\n```\n\n## Quick Start\n\n```julia\nusing ComponentLogging\n\n# Keys = groups; Values = minimum enabled level (integer or LogLevel)\nrules = Dict(\n    :core         =\u003e 0,      # Info+\n    :io           =\u003e 1000,   # Warn+\n    (:net, :http) =\u003e 2000,   # Error+\n    :__default__  =\u003e 0       # fallback for unmatched groups (default to Info)\n)\n\nsink = PlainLogger()                   # any AbstractLogger sink works\nclogger = ComponentLogger(rules; sink) # router/filter; does not own IO\n\n@forward_logger clogger\n\nclog(:core, 0, \"starting job\"; jobid=42)  # 0 = Info\nclog(:io, 1000, \"retrying I/O\"; attempt=3) # 1000 = Warn\n```\n\nTo inspect the hierarchical rules inside `clogger`, use `display(clogger)`.\n\nOutput:\n\n```text\nComponentLogger\n  sink:  PlainLogger\n  min:   -1000\n  rules: 4\n   ├─ :__default__     0\n   ├─ :core            0\n   ├─ :io              1000\n   └─ (:net,:http)     2000\n```\n\n**What the rules mean**\n\n* **Key**: a group (`Symbol` or `NTuple{N,Symbol}`) such as `:core` or `(:net,:http)`.\n* **Value**: the minimum level enabled for that group. Messages below this level are filtered out.\n* Define a catch-all like `:__default__ =\u003e 0` to control unmatched groups.\n\n### Core APIs\n\nThis package exposes three small, *function-first* APIs for logging. You use them everywhere; the router (`ComponentLogger`) and its rules just decide *what* gets through.\n\n```julia\nclog(logger,        group, level, msg...; kwargs...)\nclogenabled(logger, group, level)::Bool\nclogf(f::Function,  logger, group, level)\n```\n\n**Arguments:**\n* `logger::AbstractLogger` — any logger instance.\nTypically you pass a `ComponentLogger` configured with per-group rules and a sink (e.g. `PlainLogger`). Many codebases also define forwarding helpers to avoid threading the logger explicitly (see below).\n* `group::Union{Symbol,NTuple{N,Symbol}}` — a `Symbol` or a tuple of symbols, e.g. `:core` or `(:net, :http)`.\n* `level::Union{Integer,LogLevel}` — prefer integers (no need to import `Logging`). We immediately convert with `LogLevel(level)`.\n  * Mapping (integers first): `-1000 (Debug)`, `0 (Info)`, `1000 (Warn)`, `2000 (Error)`.\n  * General rule:  `n → LogLevel(n)`.\n  * Passing `LogLevel` values (e.g. `Info`) is also supported and equivalent.\n\n\u003e **Why logger-first? Performance \u0026 type-stability.** The stdlib logging macros (`@info`, `@logmsg`, …) typically start by looking up the current logger (task-local, with a global fallback). When you already have a logger (e.g. stored in a `const` or a `Ref`), calling `clog(logger, ...)` bypasses that lookup and can reduce overhead in hot paths, while keeping behavior explicit and predictable under concurrency.\n\n**`clog` — emit a log record for a group at a given level**\n\n```julia\nclog(clogger, :core,  0, \"starting job\"; jobid=42)   # 0 = Info\nclog(clogger, :io, 1000, \"retrying I/O\"; attempt=3) # 1000 = Warn\n```\n\n**`clogenabled` — check if logs at `level` would pass for `group`**\n\n```julia\nif clogenabled(clogger, :core, 1000)  # guard expensive work\n    stats = compute_expensive_stats()\n    clog(clogger, :core, 1000, \"stats ready\"; stats)\nend\n```\n\n**`clogf` — evaluate the block only when enabled and log its return value**\n\n```julia\nclogf(clogger, :core, 1000) do\n    val = compute_expensive_stats()\n    \"result = $val\"\nend\n```\n\n**`@forward_logger` — ergonomic short paths used throughout your codebase**\n\n```julia\n@forward_logger clogger\n```\n\nThe macro above is equivalent to defining the following forwarding methods at module top-level (shown here for clarity):\n\n```julia\nclog(args...; kwargs...) = ComponentLogging.clog(clogger, args...; kwargs...)\nclogenabled(args...)     = ComponentLogging.clogenabled(clogger, args...)\nclogf(f, args...)        = ComponentLogging.clogf(f, clogger, args...)\nset_log_level(g, lvl)    = ComponentLogging.set_log_level!(clogger, g, lvl)\nwith_min_level(f, lvl)   = ComponentLogging.with_min_level(f, clogger, lvl)\n```\n\n### More examples\n\nAssuming you set up the forwarding helpers, you can use `clog` like this:\n\n```julia\nfunction compute_vector_sum(n)\n    clog(:core, 2, \"Processing a $n-element vector\")\n    v = randn(n)\n    s = sum(v)\n    clog(:core, 0, \"Done.\"; s, v)\n    return s\nend\ncompute_vector_sum(3);\n```\nOutput:\n```julia\nProcessing a 3-element vector\nDone.\n s = 2.435219412665466\n v = [-0.20970686116839346, 1.2387800065077361, 1.4061462673261231]\n```\n\n### Avoid work when logs are off — `clogenabled`\n\n`clogenabled` checks whether a given component is enabled at a given level. It is intended to drive control‑flow decisions so that certain code runs only when logging is enabled. Returns `Bool`.\n\n```julia\nfunction compute_sumsq()\n    arr = randn(1000)\n    sumsq = 0.0\n    for i in eachindex(arr)\n        x = arr[i]\n        sumsq += x^2\n        if clogenabled(:core, 1)\n            # Compute mean and standard deviation (intermediates) only when logging is enabled\n            meanval = mean(arr[1:i])\n            stdval = std(arr[1:i])\n            clog(:core, 1, \"i=$i, x=$x, mean=$(meanval), std=$(stdval), sumsq=$(sumsq)\")\n        end\n    end\nend\n```\n\nBy guarding with `clogenabled`, intermediate computations are performed only when logs will be emitted, maximizing performance.\n\n### Lazy messages — `clogf`\n\n`clogf` is similar to `clogenabled`, except it logs the return value of the `do`-block. When disabled, the block is skipped entirely.\n\n```julia\nfunction compute_sumsq()\n    arr = randn(1000)\n    sumsq = 0.0\n    for i in eachindex(arr)\n        x = arr[i]\n        sumsq += x^2\n        clogf(:core, 1) do\n            meanval = mean(arr[1:i])\n            stdval = std(arr[1:i])\n            # The return value will be used as the log message\n            \"i=$i, x=$x, mean=$(meanval), std=$(stdval), sumsq=$(sumsq)\"\n        end\n    end\nend\n```\n\n### Temporarily raise/lower the minimum level\n\n`with_min_level` temporarily sets the logger’s global minimum level and restores it on exit.\n\nFor example, to benchmark `compute_sumsq()` without any logging‑related work:\n\n```julia\nwith_min_level(2000) do\n    @benchmark compute_sumsq()\nend\n```\n\n### Notes\n\n* **Routing vs. formatting**: `ComponentLogger` only routes/filters; the **sink** (`PlainLogger` or any `AbstractLogger`) controls formatting/IO.\n* **Grouping**: groups are `Symbol` or tuples of `Symbol` (supports hierarchical/prefix matching if enabled). Be explicit about your matching policy in docs if you customize it.\n* The function API is the primary entry point. Macro helpers are also provided for convenience. See the [Documentation](https://abcdvvvv.github.io/ComponentLogging.jl/dev/).\n\n## PlainLogger\n\n`PlainLogger` is roughly a `Base.CoreLogging.SimpleLogger` without the `[Info:`‑style prefixes. Its output looks like `print`/`println`. It writes messages directly to the console, without additional formatting or filtering beyond color.\n\n`PlainLogger` and `ComponentLogger` are independent. You can also `include(\"src/PlainLogger.jl\")` to use `PlainLogger` on its own.\n\nExample:\n\n```julia\nusing ComponentLogging, Logging\n\nlogger = PlainLogger()\nwith_logger(logger) do\n    @info \"Hello, Julia!\"\nend\n```\nOutput:\n```julia\nHello, Julia!\n@ README.md:183\n```\n\n`PlainLogger` uses `show` with `MIME\"text/plain\"` to display 2D and 3D matrices, as it improves matrix readability. For other types, it prints them directly using `print` or `printstyled`.\n\n```julia\nwith_logger(logger) do\n    @warn rand(1:9, 3, 3)\nend\n```\nOutput:\n```julia\n3×3 Matrix{Int64}:\n 8  5  6\n 3  4  9\n 7  8  5\n@ README.md:196\n```\n\n## Similar Packages\n\n[**Memento.jl**][1] is a *flexible, hierarchical* logging framework that brings its own ecosystem of loggers, handlers, formatters, records, and IO backends. Loggers are named (e.g., `\"Foo.bar\"`), form a hierarchy with propagation to a root logger, and are configured via `config!`, `setlevel!`, and by attaching handlers (file, custom formatters, etc.).\n\n[**HierarchicalLogging.jl**][2] defines a `Base.Logging`-compatible `HierarchicalLogger` that associates loggers to *hierarchically-related objects* (e.g., `module → submodule`). Each node has a `LogLevel` that can be set with `min_enabled_level!`, which also recursively updates children; you can attach different underlying loggers (e.g., `ConsoleLogger`) to different parts of the tree. \n\n[**ComponentLogging.jl**][3] is a thin, high‑performance layer over the stdlib `Base.CoreLogging` interface. It focuses on:\n\n- **Performance first:** fully type‑stable, no global logger, explicit logger argument for optimal inlining; when disabled, checks are branch‑predictable and near zero‑overhead; when enabled, messages can be built lazily via `clogf`/`clogenabled` so expensive work is skipped unless needed.\n- **Simple composition:** routes to any `AbstractLogger` sink (`ConsoleLogger`, custom sinks, or `LoggingExtras` combinators) and defers formatting/IO to the sink.\n- **Explicit component routing:** hierarchical group keys (`NTuple{N,Symbol}`) give precise control over noisy areas without imposing a separate handler/formatter stack.\n\n\u003e - Choose **Memento.jl** if you want a *self-contained* logging framework with built-in handlers/formatters and hierarchical named loggers.\n\u003e - Choose **HierarchicalLogging.jl** if you want *stdlib-compatible* hierarchical control keyed to modules/keys with recursive level management.\n\u003e - Choose **ComponentLogging.jl** if you want a *high‑performance*, type‑stable, component (group) router atop stdlib `Base.CoreLogging`, with lazy message evaluation and minimal overhead when disabled; formatting/IO remains in the sink (`ConsoleLogger`, custom sinks, `LoggingExtras`, etc.).\n\n### Benchmarking\n\nWe benchmark two paths under identical thresholds:\n1) filtered (`min=Error`, log at `Info`) — hot path in production;\n2) enabled (`min=Info`, log at `Info`).\n\nAll three systems log the same short string to a null sink (no I/O). Keys test four depths: `default`, `:opti`, `(:a,:b)`, `(:a,…,:h)`.\n\nIn these benchmarks, ComponentLogging (CL) checks group-level thresholds and, if allowed, calls the sink’s `handle_message` directly, bypassing the stdlib `@info` macro path. HierarchicalLogging (HL) is exercised via the stdlib macros (macro expansion + metadata before reaching the logger). Memento is exercised via its own API and internal handler pipeline. With all three routed to a null/devnull sink, the results mainly measure macro/dispatch/routing overhead, not I/O. The test script is in \"benchmark/bench_CL_HL_Me.jl\".\n\n| system | path | key | time (ns) | allocs | memory (B) |\n|:--|:--|:--|--:|--:|--:|\n| CL | enabled | default/str | 9 | 0 | 0 |\n| CL | enabled | opti/str | 9 | 0 | 0 |\n| CL | enabled | tuple2/str | 15 | 0 | 0 |\n| CL | enabled | tuple8/str | 144 | 0 | 0 |\n| CL | filtered | default | 2 | 0 | 0 |\n| CL | filtered | opti | 2 | 0 | 0 |\n| CL | filtered | tuple2 | 2 | 0 | 0 |\n| CL | filtered | tuple8 | 2 | 0 | 0 |\n| HL | enabled | default/str | 2189 | 47 | 1984 |\n| HL | enabled | opti/str | 2178 | 47 | 1984 |\n| HL | enabled | tuple2/str | 2478 | 51 | 2176 |\n| HL | enabled | tuple8/str | 4857 | 77 | 5728 |\n| HL | filtered | default | 2178 | 47 | 1984 |\n| HL | filtered | opti | 2178 | 47 | 1984 |\n| HL | filtered | tuple2 | 2467 | 51 | 2176 |\n| HL | filtered | tuple8 | 4814 | 77 | 5728 |\n| Memento | enabled | a.b..8/str | 2656 | 76 | 4096 |\n| Memento | enabled | a.b/str | 1050 | 31 | 1408 |\n| Memento | enabled | opti/str | 770 | 24 | 1072 |\n| Memento | enabled | root/str | 622 | 22 | 800 |\n| Memento | filtered | a.b | 1040 | 31 | 1408 |\n| Memento | filtered | a.b..8 | 2700 | 76 | 4096 |\n| Memento | filtered | opti | 770 | 24 | 1072 |\n| Memento | filtered | root | 621 | 22 | 800 |\n\n\u003cp\u003e\u003csmall\u003e\nNote: Julia v1.10.10, BenchmarkTools v1.6.0, ComponentLogging v0.1.0,\nHierarchicalLogging v1.0.2, Memento v1.4.1; Windows x86_64, JULIA_NUM_THREADS=1, -O2.\n\u003c/small\u003e\u003c/p\u003e\n\n## Logger scoping semantics compared with Julia’s stdlib Logging\n\n**Stdlib Logging (task-local)** treats loggers as task-local: messages emitted via `@info`/`@warn`/... go to the current task’s logger (set with `with_logger`/`global_logger`). New tasks inherit the parent’s logger upon creation, so concurrent tasks can run with different loggers, levels, and sinks simultaneously.\n\n**ComponentLogging (module/group-routed)**, by design, exposes a module/group-routed policy: the same module or group (e.g., `:core` or `(:db, :read)`) is routed through the same rules and sink by default—ideal for component-wide noise control and predictable behavior across tasks.\n\n**Scope of intent:** `ComponentLogging` is not aimed at per-task logger isolation by default. Its primary goal is stable, component-level policies with very low overhead on filtered paths.\n\n[1]: https://invenia.github.io/Memento.jl/latest/ \"Home · Memento.jl\"\n[2]: https://github.com/curtd/HierarchicalLogging.jl \"GitHub - curtd/HierarchicalLogging.jl: Loggers, loggers everywhere\"\n[3]: https://github.com/abcdvvvv/ComponentLogging.jl \"GitHub - abcdvvvv/ComponentLogging.jl: ComponentLogging.jl\"\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjulialogging%2Fcomponentlogging.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjulialogging%2Fcomponentlogging.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjulialogging%2Fcomponentlogging.jl/lists"}