Skip to content

Commit 680527e

Browse files
author
karei
committed
chore(release): v0.1.4
- add `@forward_logger` for module-local forwarding wrappers - add `set_log_level!(::Bool)` convenience switch - make `@clog` accept no-group form - forward `id` in `clog` and update docs/tests modified: .gitignore modified: CHANGELOG.md modified: Project.toml modified: README.md modified: docs/src/functions.md modified: docs/src/macros.md modified: src/ComponentLogging.jl modified: src/docstrings.jl new file: src/helpers.jl modified: test/runtests.jl
1 parent 889af05 commit 680527e

File tree

10 files changed

+351
-196
lines changed

10 files changed

+351
-196
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/playground
12
/Manifest*.toml
23
/docs/Manifest*.toml
34
/docs/build/

CHANGELOG.md

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,29 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7-
## [Unreleased]
8-
7+
## [0.1.4] - 2026-01-15
98
### Added
10-
-
9+
- `@forward_logger` to generate module-local forwarding methods for a logger (or `Ref{<:AbstractLogger}`).
10+
- `set_log_level!(logger, group, on::Bool)` convenience switch.
1111

1212
### Changed
13-
-
13+
- `@clog` now supports the no-group form `@clog level msg...`.
1414

1515
### Fixed
16-
-
17-
18-
### Deprecated
19-
-
16+
- `clog` now forwards `id` to `Logging.handle_message`.
2017

2118
### Removed
22-
-
23-
24-
### Security
25-
-
19+
- Removed overloads without an explicit `group` (`clog(logger, level, ...)`, `clogenabled(logger, level)`).
2620

27-
## [0.1.0] - 2025-09-25
28-
### Added
29-
- Initial release of `ComponentLogging.jl`: component-level routing, `clog`/`clogf`, `bind_logger`, minimal PlainLogger style, warn+ file:line display, colorized levels.
21+
## [0.1.3] - 2026-01-14
22+
### Fixed
23+
- `@bind_logger`: fix sink keyword handling, remove `min=` handling, and use `mod=` keyword.
3024

31-
## [0.1.1] - 2025-10-07
3225
### Changed
33-
- Documentation: general improvements and clarifications.
26+
- Documentation: update `@bind_logger` docs and add a usage example.
27+
- Tests: add `@bind_logger` regression coverage.
28+
- CI: test against Julia `lts` and `1`.
29+
- Compat: relax Julia requirement to `julia = "1"`.
3430

3531
## [0.1.2] - 2025-10-13
3632
### Changed
@@ -44,6 +40,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4440
### Fixed
4541
- Ensure `handle_message` always returns `nothing` for type stability.
4642

47-
[Unreleased]: https://github.com/<owner>/<repo>/compare/v0.1.1...HEAD
48-
[0.1.2]: https://github.com/<owner>/<repo>/compare/v0.1.1...v0.1.2
49-
[0.1.1]: https://github.com/<owner>/<repo>/compare/v0.1.0...v0.1.1
43+
## [0.1.1] - 2025-10-07
44+
### Changed
45+
- Documentation: general improvements and clarifications.
46+
47+
## [0.1.0] - 2025-09-25
48+
### Added
49+
- Initial release of `ComponentLogging.jl`: component-level routing, `clog`/`clogf`, `bind_logger`, minimal PlainLogger style, warn+ file:line display, colorized levels.
50+
51+
[Unreleased]: https://github.com/JuliaLogging/ComponentLogging.jl/compare/v0.1.4...HEAD
52+
[0.1.4]: https://github.com/JuliaLogging/ComponentLogging.jl/compare/v0.1.3...v0.1.4
53+
[0.1.3]: https://github.com/JuliaLogging/ComponentLogging.jl/compare/v0.1.2...v0.1.3
54+
[0.1.2]: https://github.com/JuliaLogging/ComponentLogging.jl/compare/v0.1.1...v0.1.2
55+
[0.1.1]: https://github.com/JuliaLogging/ComponentLogging.jl/compare/v0.1.0...v0.1.1

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ComponentLogging"
22
uuid = "3f6ec708-df3b-46b4-8b70-ec9fa53b9f7d"
33
authors = ["karei <abcdvvvv@gmail.com>"]
4-
version = "0.1.3"
4+
version = "0.1.4"
55

66
[deps]
77
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Julia’s `CoreLogging` module provides a solid foundation, and this package bui
2121
- High performance; negligible overhead when logging is disabled. See [Benchmarking](@ref).
2222
- Suited for controlling module‑wide output granularity using one (or a few) loggers.
2323
- Enables control‑flow changes based on hierarchical log levels to eliminate unnecessary computations from hot paths.
24+
- `@forward_logger` macro for ergonomic, module-local forwarding wrappers.
2425

2526
## Installation
2627

@@ -49,7 +50,7 @@ Typically you pass a `ComponentLogger` configured with per-group rules and a sin
4950
* General rule: `n → LogLevel(n)`.
5051
* Passing `LogLevel` values (e.g. `Info`) is also supported and equivalent.
5152

52-
> **Why logger-first? Performance & type-stability.** No `global_logger()`: `clog`/`clogenabled`/`clogf` take an `AbstractLogger` as the first parameter, which also keeps behavior predictable under concurrency.
53+
> **Why logger-first? Performance & 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.
5354
5455
**`clog` — emit a log record for a group at a given level**
5556

@@ -114,11 +115,17 @@ ComponentLogger
114115

115116
**Forwarding helpers (recommended)** — ergonomic short paths used throughout your codebase
116117

118+
```julia
119+
@forward_logger clogger
120+
```
121+
122+
The macro above is equivalent to defining the following forwarding methods at module top-level (shown here for clarity):
123+
117124
```julia
118125
clog(args...; kwargs...) = ComponentLogging.clog(clogger, args...; kwargs...)
119126
clogenabled(args...) = ComponentLogging.clogenabled(clogger, args...)
120127
clogf(f, args...) = ComponentLogging.clogf(f, clogger, args...)
121-
set_log_level(g, lvl) = ComponentLogging.set_log_level!(clogger, g, lvl)
128+
set_log_level!(g, lvl) = ComponentLogging.set_log_level!(clogger, g, lvl)
122129
with_min_level(f, lvl) = ComponentLogging.with_min_level(f, clogger, lvl)
123130
```
124131

docs/src/functions.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ This page documents the **function-first** logging APIs exported by `ComponentLo
88
All functions require the logger to be passed explicitly as the first argument.
99
In application code you will typically define *forwarding helpers* to avoid threading the logger manually.
1010

11+
When you already have a logger available (e.g. stored in a `const` or a `Ref`), calling `clog(logger, ...)` bypasses the task-local logger lookup performed by stdlib logging macros (`@info`, `@logmsg`, …) and can reduce overhead in hot paths.
12+
13+
## Forwarding macro
14+
15+
`@forward_logger` generates module-local forwarding methods (`clog`, `clogenabled`, `clogf`, `set_log_level!`, `with_min_level`) so you can call them without explicitly passing a logger at every call site.
16+
1117
!!! info
1218
The function APIs do not automatically pass the current module, file, or line information; you need to provide them manually if needed. In the example below, the current module, file, and line are explicitly passed at the call site.
1319

@@ -19,4 +25,5 @@ In application code you will typically define *forwarding helpers* to avoid thre
1925
clog
2026
clogenabled
2127
clogf
22-
```
28+
@forward_logger
29+
```

docs/src/macros.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function __init__()
1717
end
1818
# or
1919
function __init__()
20-
@bind_logger logger
20+
@bind_logger sink=logger
2121
end
2222
```
2323

@@ -35,4 +35,4 @@ get_logger
3535
@cerror
3636
@clogenabled
3737
@clogf
38-
```
38+
```

src/ComponentLogging.jl

Lines changed: 6 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export PlainLogger
66

77
export ComponentLogger, get_logger, set_module_logger, set_log_level!, with_min_level
88
export clog, clogenabled, clogf
9-
export @bind_logger, @clog, @cdebug, @cinfo, @cwarn, @cerror, @clogenabled, @clogf
9+
export @bind_logger, @clog, @cdebug, @cinfo, @cwarn, @cerror, @clogenabled, @clogf, @forward_logger
1010

1111
const RuleKey = NTuple{N,Symbol} where {N}
1212
const DEFAULT_SYM = :__default__
@@ -53,6 +53,9 @@ function set_log_level!(logger::ComponentLogger, group, lvl::Union{Integer,LogLe
5353
return logger
5454
end
5555

56+
set_log_level!(logger::ComponentLogger, group, on::Bool) =
57+
set_log_level!(logger, group, on ? 0 : 1)
58+
5659
"Temporarily set the minimum level within a do-block; restore afterward even if an exception is thrown; no lock"
5760
function with_min_level(f::Function, logger::ComponentLogger, lvl::Union{Integer,LogLevel})
5861
old_lvl = logger.min
@@ -125,52 +128,6 @@ function get_logger(mod::Module)
125128
end
126129
end
127130

128-
## Generic macro: @clog level message [group]
129-
@inline function _enabled(logger::AbstractLogger, lvl::LogLevel, grp)
130-
lvl >= Logging.min_enabled_level(logger) &&
131-
Logging.shouldlog(logger, lvl, @__MODULE__, grp, nothing)
132-
end
133-
134-
## clog
135-
function clog(logger::AbstractLogger, group::Union{Symbol,RuleKey}, level::Union{Integer,LogLevel}, message...; _module=nothing, file=nothing, line=nothing, kwargs...)::Nothing
136-
grp = _tokey(group)
137-
lvl = LogLevel(level)
138-
if _enabled(logger, lvl, grp)
139-
Logging.handle_message(logger, lvl, message, _module, grp, nothing, file, line; kwargs...)
140-
end
141-
nothing
142-
end
143-
144-
clog(logger::AbstractLogger, level::Union{Integer,LogLevel}, message...;
145-
_module=nothing, file=nothing, line=nothing, kwargs...) =
146-
clog(logger, (DEFAULT_SYM,), level, message...; _module, file, line, kwargs...)
147-
148-
## clogenabled
149-
function clogenabled(logger::AbstractLogger, group::Union{Symbol,RuleKey}, level::Union{Integer,LogLevel})::Bool
150-
grp = _tokey(group)
151-
lvl = LogLevel(level)
152-
return _enabled(logger, lvl, grp)
153-
end
154-
155-
clogenabled(logger::AbstractLogger, level::Union{Integer,LogLevel}) =
156-
clogenabled(logger, (DEFAULT_SYM,), level)
157-
158-
## clogf
159-
@inline function clogf(f::F, logger::AbstractLogger, group::Union{Symbol,RuleKey}, level::Union{Integer,LogLevel}; _module=nothing, file=nothing, line=nothing)::Nothing where {F<:Function}
160-
grp = _tokey(group)
161-
lvl = LogLevel(level)
162-
if _enabled(logger, lvl, grp)
163-
msg = f()
164-
if msg !== nothing
165-
Logging.handle_message(logger, lvl, msg_to_tuple(msg), _module, grp, nothing, file, line)
166-
end
167-
end
168-
nothing
169-
end
170-
171-
clogf(f::F, logger::AbstractLogger, level::Union{Integer,LogLevel}; _module=nothing, file=nothing, line=nothing) where {F<:Function} =
172-
clogf(f, logger, (DEFAULT_SYM,), level; _module, file, line)
173-
174131
## Module binding macro
175132
macro bind_logger(args...)
176133
sink_ex = nothing
@@ -201,124 +158,8 @@ macro bind_logger(args...)
201158
end
202159
end
203160

204-
## macro
205-
macro clog(args...)
206-
n = length(args)
207-
n >= 2 || error("@clog: need (level, msgs...) or (group, level, msgs...)")
208-
209-
# accept only literal group: :sym or (:a,:b,...)
210-
is_sym_lit(ex) = (ex isa QuoteNode && ex.value isa Symbol)
211-
is_symtuple_lit(ex) = ex isa Expr && ex.head === :tuple &&
212-
all(a -> (a isa QuoteNode && a.value isa Symbol), ex.args)
213-
214-
grp_ast = :((ComponentLogging.DEFAULT_SYM,)) # default group
215-
216-
looks_like_group = (args[1] isa Symbol) || (args[1] isa Expr && args[1].head === :tuple) ||
217-
(args[1] isa QuoteNode)
218-
if looks_like_group
219-
if !(is_sym_lit(args[1]) || is_symtuple_lit(args[1]))
220-
error("@clog: group must be a literal Symbol like :core or a tuple of literal Symbols like (:a,:b)")
221-
end
222-
grp_ast = is_sym_lit(args[1]) ? Expr(:tuple, args[1]) : args[1]
223-
n >= 3 || error("@clog: with group, need (group, level, msgs...)")
224-
lvl_ex = args[2]
225-
msg_ps = args[3:end]
226-
else
227-
lvl_ex = args[1]
228-
msg_ps = args[2:end]
229-
end
230-
231-
msg_tuple = Expr(:tuple, map(esc, msg_ps)...)
232-
mod, file, line = Base.CoreLogging.@_sourceinfo
233-
return :(
234-
let _lg = ComponentLogging.get_logger($mod),
235-
_lvl = LogLevel($(esc(lvl_ex))),
236-
_grp = $grp_ast
237-
238-
if _lvl >= Logging.min_enabled_level(_lg) && Logging.shouldlog(_lg, _lvl, $mod, _grp, nothing)
239-
Logging.handle_message(_lg, _lvl, $msg_tuple, $mod, _grp, nothing, $file, $line)
240-
end
241-
nothing
242-
end
243-
)
244-
end
245-
246-
macro clogf(args...)
247-
n = length(args)
248-
n >= 2 || error("@clogf: need (level, expr) or (group, level, expr)")
249-
250-
is_sym_lit(ex) = (ex isa QuoteNode && ex.value isa Symbol)
251-
is_symtuple_lit(ex) = ex isa Expr && ex.head === :tuple &&
252-
all(a -> (a isa QuoteNode && a.value isa Symbol), ex.args)
253-
254-
grp_ast = :((ComponentLogging.DEFAULT_SYM,)) # default group
255-
256-
looks_like_group = (args[1] isa Symbol) || (args[1] isa Expr && args[1].head === :tuple) ||
257-
(args[1] isa QuoteNode)
258-
if looks_like_group
259-
if !(is_sym_lit(args[1]) || is_symtuple_lit(args[1]))
260-
error("@clogf: group must be a literal Symbol like :core or a tuple of literal Symbols like (:a,:b)")
261-
end
262-
grp_ast = is_sym_lit(args[1]) ? Expr(:tuple, args[1]) : args[1]
263-
n >= 3 || error("@clogf: with group, need (group, level, expr)")
264-
lvl_ex = args[2]
265-
body_ex = args[3]
266-
else
267-
lvl_ex = args[1]
268-
body_ex = args[2]
269-
end
270-
271-
mod, file, line = Base.CoreLogging.@_sourceinfo
272-
return :(
273-
let _lg = ComponentLogging.get_logger($mod),
274-
_lvl = LogLevel($(esc(lvl_ex))),
275-
_grp = $grp_ast
276-
277-
if _lvl >= Logging.min_enabled_level(_lg) && Logging.shouldlog(_lg, _lvl, $mod, _grp, nothing)
278-
_msg = $(esc(body_ex))
279-
if _msg isa Function
280-
_msg = _msg()
281-
end
282-
if _msg !== nothing
283-
Logging.handle_message(_lg, _lvl, ComponentLogging.msg_to_tuple(_msg),
284-
$mod, _grp, nothing, $file, $line)
285-
end
286-
end
287-
nothing
288-
end
289-
)
290-
end
291-
292-
macro clogenabled(group, lvl)
293-
is_sym_lit(x) = (x isa QuoteNode && x.value isa Symbol)
294-
is_tuple_sym_lit(x) = x isa Expr && x.head === :tuple &&
295-
all(y -> y isa QuoteNode && y.value isa Symbol, x.args)
296-
297-
grp_ast = is_sym_lit(group) ? Expr(:tuple, group) :
298-
is_tuple_sym_lit(group) ? group :
299-
error("@clogenabled: `group` must be a Symbol literal like :core ",
300-
"or a tuple literal of Symbols like (:net,:http)")
301-
302-
return :(
303-
let
304-
_lg = ComponentLogging.get_logger(@__MODULE__)
305-
_lvl = LogLevel($(esc(lvl)))
306-
_grp = $grp_ast
307-
_lvl >= Logging.min_enabled_level(_lg) &&
308-
Logging.shouldlog(_lg, _lvl, @__MODULE__, _grp, nothing)
309-
end
310-
)
311-
end
312-
313-
#! format: off
314-
macro cdebug(args...); esc(:(ComponentLogging.@clog Debug $(args...))) end
315-
macro cinfo(args...); esc(:(ComponentLogging.@clog Info $(args...))) end
316-
macro cwarn(args...); esc(:(ComponentLogging.@clog Warn $(args...))) end
317-
macro cerror(args...); esc(:(ComponentLogging.@clog Error $(args...))) end
318-
#! format: on
319-
320161
## Pretty show for ComponentLogger
321-
@inline _lvname(lv::LogLevel) = string(lv)
162+
_lvname(lv::LogLevel) = string(lv)
322163

323164
@inline function _print_level(io::IO, lv::LogLevel)
324165
if get(io, :color, false)
@@ -437,6 +278,7 @@ function Base.show(io::IO, ::MIME"text/plain", logger::ComponentLogger)
437278
_print_tree(io, logger.rules)
438279
end
439280

281+
include("helpers.jl")
440282
include("docstrings.jl")
441283

442284
end # module

0 commit comments

Comments
 (0)