Logging

Functions

Info

These functions take a logger explicitly, so they do not need to discover logging state from the current task or consult the module registry. This makes them the preferred interface for hot paths, library internals, and execution contexts that need their own logger.

Explicit logger passing

clog(logger, group, level, msg...; kwargs...)
clogenabled(logger, group, level)::Bool
@clog logger group level msg...
function solve(problem, logger)
    clog(logger, :solver, 0, "starting")

    clogenabled(logger, (:solver, :diagnostics)) && collect_diagnostics!(problem)

    @clog logger (:solver, :summary) 1 "objective = $(objective_value(problem))"
end

Why pass a logger explicitly?

The standard-library logging macros (@info, @logmsg, ...) first look up the current task's logger, with the global logger as fallback. When a logger is already available—for example, in a const binding or a Ref—calling clog(logger, ...) bypasses that lookup, keeps the logging policy explicit, and helps keep hot paths very fast.

Task-specific loggers

The module-scoped model does not prevent task- or instance-specific logging. Use separate logger instances and pass them explicitly:

logger_a = ComponentLogger(...)
logger_b = ComponentLogger(...)

Threads.@spawn solve(problem_a, logger_a)
Threads.@spawn solve(problem_b, logger_b)

The two tasks now have independent component rules and sinks without requiring task-local logging state.

Avoiding unnecessary work

clogenabled is intended to guard arbitrary work that should only run when a group/level is enabled:

if clogenabled(logger, (:solver, :trace), -1000)
    trace = compute_expensive_trace()
    clog(logger, (:solver, :trace), -1000, "trace"; trace)
end

The no-level form checks at Info:

clogenabled(logger, group)

This also makes clogenabled useful as a lightweight runtime switch; see Hierarchical Runtime Control.

@clog provides lazy message construction and automatically captures caller metadata:

@clog logger :summary 0 begin
    stats = compute_expensive_stats()
    "stats = $stats"
end

Forwarding macro

At module top level, @forward_logger logger_expr creates local forwarding methods for clog, clogenabled, set_log_level!, get_log_level, and with_min_level, plus a shorter local @clog form bound to logger_expr:

const logger = ComponentLogger(...)
@forward_logger logger

clog(:core, 0, "hello")
@clog :core 0 "hello"

clogenabled(:core)
set_log_level!(:core, true)
get_log_level(:core)

The expression can be a Ref or field expression and is resolved at each call. Its global names are bound in the module that invokes @forward_logger, even if its generated @clog is imported elsewhere.

@clog and level shorthands

@clog provides lazy logging with automatic caller metadata. Its level shorthands take an explicit logger argument at each call site.

Logging macros

The main macro interface mirrors familiar logging operations while adding explicit component groups:

@clog logger :solver 0 "starting"
@cdebug logger :solver "debug message"
@cinfo logger :solver "information"
@cwarn logger :solver "warning"
@cerror logger :solver "error"

Hierarchical tuple groups are also supported where accepted by the macro:

@clog logger (:solver, :iteration) 0 "iteration started"

Why use @clog?

@clog evaluates message expressions only after logging is enabled and keeps them inline, avoiding closure capture of surrounding local values.

It also automatically captures the emitting call's module, file, and line. The function APIs require that metadata to be supplied manually when it is needed.

When logging is enabled, @clog evaluates all message expressions from left to right. If the final expression evaluates to nothing, the log record is discarded and Logging.handle_message is not called. The @cdebug, @cinfo, @cwarn, and @cerror shorthands have the same behavior.

Caller metadata

Macros automatically capture the caller's module, file, and line number. This is the main ergonomic advantage over the explicit function API when source-location metadata matters.

Performance trade-off

With an explicit logger, @clog uses it directly; a forwarded local @clog evaluates its bound logger expression directly. Neither uses a module registry lookup.

This keeps the macro path suitable for hot code while retaining lazy message evaluation and caller metadata.

Reference

ComponentLogging.clogFunction
clog(logger, group, level, msg...; _module, id, file, line, kwargs...)

Emit a log message through the given logger. group is a Symbol or NTuple{N,Symbol}. level may be LogLevel or Integer. msg can be one or more values; tuples are passed through as-is.

Keyword arguments file, line, and arbitrary kwargs... are forwarded to the underlying logger sink.

If @forward_logger is already used, the following forwarding signatures are available:

clog(group, level, msg...; kwargs...)
clog(group, msg...; kwargs...)
source
ComponentLogging.clogenabledFunction
clogenabled(logger, group, level) -> Bool
clogenabled(logger, group) -> Bool

Return whether logging is enabled for the given logger, group, and level. If level is omitted, 0 is used.

If @forward_logger is already used, the following forwarding signatures are available:

clogenabled(group, level) -> Bool
clogenabled(group) -> Bool
source
ComponentLogging.@forward_loggerMacro
@forward_logger logger_expr

Define forwarding methods in the current module so you can call clog, clogenabled, set_log_level!, get_log_level, and with_min_level without explicitly passing a logger each time. Also define a local @clog group level msg... bound to logger_expr.

logger_expr may be an AbstractLogger, a Ref holding one, or an expression such as STATE[].logger. Its names are bound in the module that invokes @forward_logger; importing the generated @clog elsewhere does not change which logger expression it uses. The expression is evaluated at each call, so replacing a Ref value affects subsequent calls.

Example:

using ComponentLogging

const pkg_logger = Ref(ComponentLogger(...))
@forward_logger pkg_logger

clog(:core, 0, "hello")
@clog :core 0 "hello"
set_log_level!(:core, 1000)
get_log_level(:core)
with_min_level(2000) do
    # Temporarily raise this logger's minimum level (fast early rejection).
    clog(:core, 0, "suppressed by the temporary minimum")
end

Note: Use this macro at module top-level.

source
ComponentLogging.@clogMacro
@clog logger group level msg...

Macro version of clog that captures the caller's Module, file, and line for accurate provenance. All positional arguments are required, including at least one message expression. group and level may be runtime expressions.

Message expressions are evaluated only after logging is enabled and remain inline, avoiding closure capture of surrounding local variables. Unlike clog, caller metadata is captured automatically rather than supplied as keywords.

When logging is enabled, all message expressions are evaluated from left to right. If the final expression evaluates to nothing, the log record is discarded and Logging.handle_message is not called.

Example:

@clog logger :core 0 "hello"
@clog logger (:a, :b) 2000 "hello"
source