|
6 | 6 | [](https://github.com/SciML/ColPrac) |
7 | 7 | [](https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/C/ComponentLogging.html) |
8 | 8 |
|
9 | | -See the [Changelog](./CHANGELOG.md). |
| 9 | +ComponentLogging 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. |
| 10 | + |
| 11 | +[Changelog](./CHANGELOG.md). |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## Introduction |
| 16 | + |
| 17 | +Hierarchical logging is critical for building reliable software, especially in compute‑intensive systems. Many computational functions need to emit messages at different detail levels, and there can be lots of such functions. This calls for fine‑grained, per‑function control over logging. |
| 18 | + |
| 19 | +Another challenge is performance: printing intermediate values can be expensive and slow down hot paths. Ideally, those intermediates should be computed and printed only when logging is actually enabled. This requires the ability to alter control flow based on logging decisions. |
| 20 | + |
| 21 | +Julia’s `CoreLogging` module provides a solid foundation, and this package builds on it. At its core is the `ComponentLogger`, which uses a `Dict` keyed by `NTuple{N,Symbol}` to control output hierarchically across a module. You can choose the `LogLevel` per feature, type, module, or function name to achieve global control with precision. |
| 22 | + |
| 23 | +`ComponentLogger` only routes and filters messages. It does not own IO streams. You provide an `AbstractLogger` sink such as `ConsoleLogger` or the included `PlainLogger`. The sink determines where and how messages are written. |
| 24 | + |
| 25 | +### Features |
| 26 | +- High performance; negligible overhead when logging is disabled. |
| 27 | +- Suited for controlling module‑wide output granularity using one (or a few) loggers. |
| 28 | +- Enables control‑flow changes based on hierarchical log levels to eliminate unnecessary computations from hot paths. |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +## Installation |
| 33 | + |
| 34 | +```julia |
| 35 | +julia>] add ComponentLogging |
| 36 | +``` |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +## Quick Start |
| 41 | + |
| 42 | +The following is a general pattern you can copy and adapt. |
| 43 | + |
| 44 | +```julia |
| 45 | +using ComponentLogging |
| 46 | + |
| 47 | +sink = PlainLogger() |
| 48 | +rules = Dict( |
| 49 | + :core => Info, |
| 50 | + :io => Warn, |
| 51 | + :net => Debug |
| 52 | +) |
| 53 | +clogger = ComponentLogger(rules; sink) |
| 54 | +``` |
| 55 | +Output: |
| 56 | +```julia |
| 57 | +ComponentLogger |
| 58 | + sink: PlainLogger |
| 59 | + min: Debug |
| 60 | + rules: 4 |
| 61 | + ├─ :__default__ Info |
| 62 | + ├─ :core Info |
| 63 | + ├─ :io Warn |
| 64 | + └─ :net Debug |
| 65 | +``` |
| 66 | + |
| 67 | +Convenience forwarding helpers (short paths) |
| 68 | + |
| 69 | +```julia |
| 70 | +set_log_level(group, level) = ComponentLogging.set_log_level!(clogger, group, level) |
| 71 | +with_min_level(f, level) = ComponentLogging.with_min_level(f, clogger, level) |
| 72 | +clog(group, level, message...; file=nothing, line=nothing, kwargs...) = |
| 73 | + ComponentLogging.clog(clogger, group, level, message...; file, line, kwargs...) |
| 74 | +clogenabled(group, level) = ComponentLogging.clogenabled(clogger, group, level) |
| 75 | +clogf(f::F, group, level) where {F<:Function} = ComponentLogging.clogf(f, clogger, group, level) |
| 76 | +``` |
| 77 | + |
| 78 | +### `clog` usage |
| 79 | + |
| 80 | +`clog(group::Union{Symbol,NTuple{N,Symbol}}, level::Union{Integer,LogLevel}, message...)` |
| 81 | + |
| 82 | +```julia |
| 83 | +function foo(a) |
| 84 | + a > 0 || clog(:core, 1000, "a should be positive") |
| 85 | + a += 1 |
| 86 | + clog(:core, 0, "a is now $a") |
| 87 | + return a |
| 88 | +end |
| 89 | +``` |
| 90 | + |
| 91 | +Here `level` can be an `Integer` or a `LogLevel`. When it is an integer, it is interpreted as `LogLevel(Integer)`. The common mapping is 0 => Info, −1000 => Debug, 1000 => Warn, 2000 => Error. |
| 92 | + |
| 93 | +### `clogenabled` usage |
| 94 | + |
| 95 | +`clogenabled(group::Union{Symbol,NTuple{N,Symbol}}, level::Union{Integer,LogLevel})` |
| 96 | + |
| 97 | +`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`. |
| 98 | + |
| 99 | +```julia |
| 100 | +function compute_sumsq() |
| 101 | + arr = randn(1000) |
| 102 | + sumsq = 0.0 |
| 103 | + for i in eachindex(arr) |
| 104 | + x = arr[i] |
| 105 | + sumsq += x^2 |
| 106 | + if clogenabled(:core, 1) |
| 107 | + # Compute mean and standard deviation (intermediates) only when logging is enabled |
| 108 | + meanval = mean(arr[1:i]) |
| 109 | + stdval = std(arr[1:i]) |
| 110 | + clog(:core, 1, "i=$i, x=$x, mean=$(meanval), std=$(stdval), sumsq=$(sumsq)") |
| 111 | + end |
| 112 | + end |
| 113 | +end |
| 114 | +``` |
| 115 | + |
| 116 | +By guarding with `clogenabled`, intermediate computations are performed only when logs will be emitted, maximizing performance. |
| 117 | + |
| 118 | +### `clogf` usage |
| 119 | + |
| 120 | +`clogf(f::Function, group::Union{Symbol,NTuple{N,Symbol}}, level::Union{Integer,LogLevel})` |
| 121 | + |
| 122 | +`clogf` is similar to `clogenabled`: when logging is enabled, it executes the `do`‑block as a zero‑argument function and logs its return value. When disabled, the block is skipped entirely. |
| 123 | + |
| 124 | +```julia |
| 125 | +function compute_sumsq() |
| 126 | + arr = randn(1000) |
| 127 | + sumsq = 0.0 |
| 128 | + for i in eachindex(arr) |
| 129 | + x = arr[i] |
| 130 | + sumsq += x^2 |
| 131 | + clogf(:core, 1) do |
| 132 | + meanval = mean(arr[1:i]) |
| 133 | + stdval = std(arr[1:i]) |
| 134 | + # The return value will be used as the log message |
| 135 | + "i=$i, x=$x, mean=$(meanval), std=$(stdval), sumsq=$(sumsq)" |
| 136 | + end |
| 137 | + end |
| 138 | +end |
| 139 | +``` |
| 140 | + |
| 141 | +### `with_min_level` usage |
| 142 | + |
| 143 | +`with_min_level(f::Function, logger::ComponentLogger, level::Union{Integer,LogLevel})` |
| 144 | + |
| 145 | +`with_min_level` temporarily sets the logger’s global minimum level and restores it on exit. |
| 146 | + |
| 147 | +For example, to benchmark `compute_sumsq()` without any logging‑related work: |
| 148 | + |
| 149 | +```julia |
| 150 | +with_min_level(2000) do |
| 151 | + @benchmark compute_sumsq() |
| 152 | +end |
| 153 | +``` |
| 154 | + |
| 155 | +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/). |
| 156 | + |
| 157 | +## `PlainLogger` |
| 158 | + |
| 159 | +`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. |
| 160 | + |
| 161 | +`PlainLogger` and `ComponentLogger` are independent. You can also `include("src/PlainLogger.jl")` to use `PlainLogger` on its own. |
| 162 | + |
| 163 | +Example: |
| 164 | + |
| 165 | +```julia |
| 166 | +using ComponentLogging, Logging |
| 167 | + |
| 168 | +logger = PlainLogger() |
| 169 | +with_logger(logger) do |
| 170 | + @info "Hello, Julia!" |
| 171 | +end |
| 172 | +``` |
| 173 | +Output: |
| 174 | +```julia |
| 175 | +Hello, Julia! |
| 176 | +@ README.md:165 |
| 177 | +``` |
| 178 | + |
| 179 | +## Comparison with Similar Packages |
| 180 | + |
0 commit comments