• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #38002

06 Feb 2025 06:14AM UTC coverage: 20.322% (-2.4%) from 22.722%
#38002

push

local

web-flow
bpart: Fully switch to partitioned semantics (#57253)

This is the final PR in the binding partitions series (modulo bugs and
tweaks), i.e. it closes #54654 and thus closes #40399, which was the
original design sketch.

This thus activates the full designed semantics for binding partitions,
in particular allowing safe replacement of const bindings. It in
particular allows struct redefinitions. This thus closes
timholy/Revise.jl#18 and also closes #38584.

The biggest semantic change here is probably that this gets rid of the
notion of "resolvedness" of a binding. Previously, a lot of the behavior
of our implementation depended on when bindings were "resolved", which
could happen at basically an arbitrary point (in the compiler, in REPL
completion, in a different thread), making a lot of the semantics around
bindings ill- or at least implementation-defined. There are several
related issues in the bugtracker, so this closes #14055 closes #44604
closes #46354 closes #30277

It is also the last step to close #24569.
It also supports bindings for undef->defined transitions and thus closes
#53958 closes #54733 - however, this is not activated yet for
performance reasons and may need some further optimization.

Since resolvedness no longer exists, we need to replace it with some
hopefully more well-defined semantics. I will describe the semantics
below, but before I do I will make two notes:

1. There are a number of cases where these semantics will behave
slightly differently than the old semantics absent some other task going
around resolving random bindings.
2. The new behavior (except for the replacement stuff) was generally
permissible under the old semantics if the bindings happened to be
resolved at the right time.

With all that said, there are essentially three "strengths" of bindings:

1. Implicit Bindings: Anything implicitly obtained from `using Mod`, "no
binding", plus slightly more exotic corner cases around conflicts

2. Weakly declared bindin... (continued)

11 of 111 new or added lines in 7 files covered. (9.91%)

1273 existing lines in 68 files now uncovered.

9908 of 48755 relevant lines covered (20.32%)

105126.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

40.42
/base/logging/logging.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
module CoreLogging
4

5
import Base: isless, +, -, convert, show
6
import Base.ScopedValues: ScopedValue, with, @with
7

8
export
9
    AbstractLogger,
10
    LogLevel,
11
    NullLogger,
12
    @debug,
13
    @info,
14
    @warn,
15
    @error,
16
    @logmsg,
17
    with_logger,
18
    current_logger,
19
    global_logger,
20
    disable_logging,
21
    SimpleLogger
22

23
#-------------------------------------------------------------------------------
24
# The AbstractLogger interface
25
"""
26
A logger controls how log records are filtered and dispatched.  When a log
27
record is generated, the logger is the first piece of user configurable code
28
which gets to inspect the record and decide what to do with it.
29
"""
30
abstract type AbstractLogger ; end
31

32
"""
33
    handle_message(logger, level, message, _module, group, id, file, line; key1=val1, ...)
34

35
Log a message to `logger` at `level`.  The logical location at which the
36
message was generated is given by module `_module` and `group`; the source
37
location by `file` and `line`. `id` is an arbitrary unique value (typically a
38
[`Symbol`](@ref)) to be used as a key to identify the log statement when
39
filtering.
40
"""
41
function handle_message end
42

43
"""
44
    shouldlog(logger, level, _module, group, id)
45

46
Return `true` when `logger` accepts a message at `level`, generated for
47
`_module`, `group` and with unique log identifier `id`.
48
"""
49
function shouldlog end
50

51
"""
52
    min_enabled_level(logger)
53

54
Return the minimum enabled level for `logger` for early filtering.  That is,
55
the log level below or equal to which all messages are filtered.
56
"""
57
function min_enabled_level end
58

59
"""
60
    catch_exceptions(logger)
61

62
Return `true` if the logger should catch exceptions which happen during log
63
record construction.  By default, messages are caught.
64

65
By default all exceptions are caught to prevent log message generation from
66
crashing the program.  This lets users confidently toggle little-used
67
functionality - such as debug logging - in a production system.
68

69
If you want to use logging as an audit trail you should disable this for your
70
logger type.
71
"""
72
catch_exceptions(logger) = true
×
73

74

75
# Prevent invalidation when packages define custom loggers
76
# Using invoke in combination with @nospecialize eliminates backedges to these methods
77
Base.@constprop :none function _invoked_shouldlog(logger, level, _module, group, id)
×
78
    @nospecialize
×
79
    return invoke(
×
80
        shouldlog,
81
        Tuple{typeof(logger), typeof(level), typeof(_module), typeof(group), typeof(id)},
82
        logger, level, _module, group, id
83
    )::Bool
84
end
85

86
function _invoked_min_enabled_level(@nospecialize(logger))
×
87
    return invoke(min_enabled_level, Tuple{typeof(logger)}, logger)::LogLevel
×
88
end
89

90
function _invoked_catch_exceptions(@nospecialize(logger))
×
91
    return invoke(catch_exceptions, Tuple{typeof(logger)}, logger)::Bool
×
92
end
93

94
"""
95
    NullLogger()
96

97
Logger which disables all messages and produces no output - the logger
98
equivalent of /dev/null.
99
"""
100
struct NullLogger <: AbstractLogger; end
101

102
min_enabled_level(::NullLogger) = AboveMaxLevel
×
103
shouldlog(::NullLogger, args...) = false
×
104
handle_message(::NullLogger, args...; kwargs...) =
×
105
    (@nospecialize; error("Null logger handle_message() should not be called"))
×
106

107

108
#-------------------------------------------------------------------------------
109
# Standard log levels
110
"""
111
    LogLevel(level)
112

113
Severity/verbosity of a log record.
114

115
The log level provides a key against which potential log records may be
116
filtered, before any other work is done to construct the log record data
117
structure itself.
118

119
# Examples
120
```julia-repl
121
julia> Logging.LogLevel(0) == Logging.Info
122
true
123
```
124
"""
125
struct LogLevel
126
    level::Int32
127
end
128

129
LogLevel(level::LogLevel) = level
×
130

131
isless(a::LogLevel, b::LogLevel) = isless(a.level, b.level)
2,511✔
132
+(level::LogLevel, inc::Integer) = LogLevel(level.level+inc)
×
133
-(level::LogLevel, inc::Integer) = LogLevel(level.level-inc)
×
134
convert(::Type{LogLevel}, level::Integer) = LogLevel(level)
×
135

136
"""
137
    BelowMinLevel
138

139
Alias for [`LogLevel(-1_000_001)`](@ref LogLevel).
140
"""
141
const BelowMinLevel = LogLevel(-1000001)
142
"""
143
    Debug
144

145
Alias for [`LogLevel(-1000)`](@ref LogLevel).
146
"""
147
const Debug         = LogLevel(   -1000)
148
"""
149
    Info
150

151
Alias for [`LogLevel(0)`](@ref LogLevel).
152
"""
153
const Info          = LogLevel(       0)
154
"""
155
    Warn
156

157
Alias for [`LogLevel(1000)`](@ref LogLevel).
158
"""
159
const Warn          = LogLevel(    1000)
160
"""
161
    Error
162

163
Alias for [`LogLevel(2000)`](@ref LogLevel).
164
"""
165
const Error         = LogLevel(    2000)
166
"""
167
    AboveMaxLevel
168

169
Alias for [`LogLevel(1_000_001)`](@ref LogLevel).
170
"""
171
const AboveMaxLevel = LogLevel( 1000001)
172

173
# Global log limiting mechanism for super fast but inflexible global log limiting.
174
const _min_enabled_level = Ref{LogLevel}(Debug)
175

176
function show(io::IO, level::LogLevel)
×
177
    if     level == BelowMinLevel  print(io, "BelowMinLevel")
×
178
    elseif level == Debug          print(io, "Debug")
×
179
    elseif level == Info           print(io, "Info")
×
180
    elseif level == Warn           print(io, "Warn")
×
181
    elseif level == Error          print(io, "Error")
×
182
    elseif level == AboveMaxLevel  print(io, "AboveMaxLevel")
×
183
    else                           print(io, "LogLevel($(level.level))")
×
184
    end
185
end
186

187

188
#-------------------------------------------------------------------------------
189
# Logging macros
190

191
_logmsg_docs = """
192
    @debug message  [key=value | value ...]
193
    @info  message  [key=value | value ...]
194
    @warn  message  [key=value | value ...]
195
    @error message  [key=value | value ...]
196

197
    @logmsg level message [key=value | value ...]
198

199
Create a log record with an informational `message`.  For convenience, four
200
logging macros `@debug`, `@info`, `@warn` and `@error` are defined which log at
201
the standard severity levels `Debug`, `Info`, `Warn` and `Error`.  `@logmsg`
202
allows `level` to be set programmatically to any `LogLevel` or custom log level
203
types.
204

205
`message` should be an expression which evaluates to a string which is a human
206
readable description of the log event.  By convention, this string will be
207
formatted as markdown when presented.
208

209
The optional list of `key=value` pairs supports arbitrary user defined
210
metadata which will be passed through to the logging backend as part of the
211
log record.  If only a `value` expression is supplied, a key representing the
212
expression will be generated using [`Symbol`](@ref). For example, `x` becomes `x=x`,
213
and `foo(10)` becomes `Symbol("foo(10)")=foo(10)`.  For splatting a list of
214
key value pairs, use the normal splatting syntax, `@info "blah" kws...`.
215

216
There are some keys which allow automatically generated log data to be
217
overridden:
218

219
  * `_module=mod` can be used to specify a different originating module from
220
    the source location of the message.
221
  * `_group=symbol` can be used to override the message group (this is
222
    normally derived from the base name of the source file).
223
  * `_id=symbol` can be used to override the automatically generated unique
224
    message identifier.  This is useful if you need to very closely associate
225
    messages generated on different source lines.
226
  * `_file=string` and `_line=integer` can be used to override the apparent
227
    source location of a log message.
228

229
There's also some key value pairs which have conventional meaning:
230

231
  * `maxlog=integer` should be used as a hint to the backend that the message
232
    should be displayed no more than `maxlog` times.
233
  * `exception=ex` should be used to transport an exception with a log message,
234
    often used with `@error`. An associated backtrace `bt` may be attached
235
    using the tuple `exception=(ex,bt)`.
236

237
# Examples
238

239
```julia
240
@debug "Verbose debugging information.  Invisible by default"
241
@info  "An informational message"
242
@warn  "Something was odd.  You should pay attention"
243
@error "A non fatal error occurred"
244

245
x = 10
246
@info "Some variables attached to the message" x a=42.0
247

248
@debug begin
249
    sA = sum(A)
250
    "sum(A) = \$sA is an expensive operation, evaluated only when `shouldlog` returns true"
251
end
252

253
for i=1:10000
254
    @info "With the default backend, you will only see (i = \$i) ten times"  maxlog=10
255
    @debug "Algorithm1" i progress=i/10000
256
end
257
```
258
"""
259

260
# Get (module,filepath,line) for the location of the caller of a macro.
261
# Designed to be used from within the body of a macro.
262
macro _sourceinfo()
263
    esc(quote
264
        (__module__,
13✔
265
         __source__.file === nothing ? "?" : String(__source__.file::Symbol),
266
         __source__.line)
267
    end)
268
end
269

270
macro logmsg(level, exs...) logmsg_code((@_sourceinfo)..., esc(level), exs...) end
271
macro debug(exs...) logmsg_code((@_sourceinfo)..., :Debug, exs...) end
272
macro  info(exs...) logmsg_code((@_sourceinfo)..., :Info,  exs...) end
1✔
273
macro  warn(exs...) logmsg_code((@_sourceinfo)..., :Warn,  exs...) end
5✔
274
macro error(exs...) logmsg_code((@_sourceinfo)..., :Error, exs...) end
7✔
275

276
# Logging macros share documentation
277
@eval @doc $_logmsg_docs :(@logmsg)
278
@eval @doc $_logmsg_docs :(@debug)
279
@eval @doc $_logmsg_docs :(@info)
280
@eval @doc $_logmsg_docs :(@warn)
281
@eval @doc $_logmsg_docs :(@error)
282

283
_log_record_ids = Set{Symbol}()
284
# Generate a unique, stable, short, somewhat human readable identifier for a
285
# logging *statement*. The idea here is to have a key against which log events
286
# can be filtered and otherwise manipulated. The key should uniquely identify
287
# the source location in the originating module, but ideally should be stable
288
# across versions of the originating module, provided the log generating
289
# statement itself doesn't change.
290
function log_record_id(_module, level, message, log_kws)
13✔
291
    @nospecialize
13✔
292
    modname = _module === nothing ?  "" : join(fullname(_module), "_")
26✔
293
    # Use an arbitrarily chosen eight hex digits here. TODO: Figure out how to
294
    # make the id exactly the same on 32 and 64 bit systems.
295
    h = UInt32(hash(string(modname, level, message, log_kws)::String) & 0xFFFFFFFF)
13✔
296
    while true
13✔
297
        id = Symbol(modname, '_', string(h, base = 16, pad = 8))
26✔
298
        # _log_record_ids is a registry of log record ids for use during
299
        # compilation, to ensure uniqueness of ids.  Note that this state will
300
        # only persist during module compilation so it will be empty when a
301
        # precompiled module is loaded.
302
        if !(id in _log_record_ids)
13✔
303
            push!(_log_record_ids, id)
13✔
304
            return id
13✔
305
        end
306
        h += 1
×
307
    end
×
308
end
309

310
default_group(file) = Symbol(splitext(basename(file))[1])
×
311

312
function issimple(@nospecialize val)
313
    val isa String && return true
29✔
314
    val isa Symbol && return true
21✔
315
    val isa QuoteNode && return true
11✔
316
    val isa Number && return true
11✔
317
    val isa Char && return true
11✔
318
    if val isa Expr
11✔
319
        val.head === :quote && issimple(val.args[1]) && return true
11✔
320
        val.head === :inert && return true
11✔
321
    end
322
    return false
11✔
323
end
324
function issimplekw(@nospecialize val)
325
    if val isa Expr
11✔
326
        if val.head === :kw
11✔
327
            val = val.args[2]
11✔
328
            if val isa Expr && val.head === :escape
11✔
329
                issimple(val.args[1]) && return true
22✔
330
            end
331
        end
332
    end
333
    return false
5✔
334
end
335

336
# helper function to get the current logger, if enabled for the specified message type
UNCOV
337
@noinline Base.@constprop :none function current_logger_for_env(std_level::LogLevel, group, _module)
×
UNCOV
338
    logstate = @inline current_logstate()
×
UNCOV
339
    if std_level >= logstate.min_enabled_level || env_override_minlevel(group, _module)
×
340
        return logstate.logger
×
341
    end
UNCOV
342
    return nothing
×
343
end
344

345
# Generate code for logging macros
346
function logmsg_code(_module, file, line, level, message, exs...)
13✔
347
    @nospecialize
13✔
348
    log_data = process_logmsg_exs(_module, file, line, level, message, exs...)
13✔
349
    if !isa(message, Symbol) && issimple(message) && isempty(log_data.kwargs)
16✔
350
        logrecord = quote
4✔
351
            msg = $(message)
2✔
352
            kwargs = (;)
2✔
353
            true
2✔
354
        end
355
    elseif issimple(message) && all(issimplekw, log_data.kwargs)
9✔
356
        # if message and kwargs are just values and variables, we can avoid try/catch
357
        # complexity by adding the code for testing the UndefVarError by hand
358
        checkerrors = nothing
1✔
359
        for kwarg in reverse(log_data.kwargs)
1✔
360
            if isa(kwarg.args[2].args[1], Symbol)
2✔
361
                checkerrors = Expr(:if, Expr(:isdefined, kwarg.args[2]), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(kwarg.args[2].args[1]), QuoteNode(:local)))
3✔
362
            end
363
        end
2✔
364
        if isa(message, Symbol)
1✔
365
            message = esc(message)
1✔
366
            checkerrors = Expr(:if, Expr(:isdefined, message), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(message.args[1]), QuoteNode(:local)))
2✔
367
        end
368
        logrecord = quote
1✔
369
            let err = $checkerrors
×
370
                if err === nothing
×
371
                    msg = $(message)
×
372
                    kwargs = (;$(log_data.kwargs...))
×
373
                    true
×
374
                else
375
                    @invokelatest $(logging_error)(logger, level, _module, group, id, file, line, err, false)
376
                    false
×
377
                end
378
            end
379
        end
380
    else
381
        logrecord = quote
8✔
382
            try
1✔
383
                msg = $(esc(message))
1✔
384
                kwargs = (;$(log_data.kwargs...))
1✔
385
                true
1✔
386
            catch err
387
                @invokelatest $(logging_error)(logger, level, _module, group, id, file, line, err, true)
×
388
                false
1✔
389
            end
390
        end
391
    end
392
    return quote
13✔
393
        let
2,511✔
394
            level = $level
2,511✔
395
            # simplify std_level code emitted, if we know it is one of our global constants
396
            std_level = $(level isa Symbol ? :level : :(level isa $LogLevel ? level : convert($LogLevel, level)::$LogLevel))
2,511✔
397
            if std_level >= $(_min_enabled_level)[]
2,511✔
398
                group = $(log_data._group)
2,511✔
399
                _module = $(log_data._module)
2,511✔
400
                logger = $(current_logger_for_env)(std_level, group, _module)
2,511✔
401
                if !(logger === nothing)
2,511✔
402
                    id = $(log_data._id)
3✔
403
                    # Second chance at an early bail-out (before computing the message),
404
                    # based on arbitrary logger-specific logic.
405
                    if invokelatest($shouldlog, logger, level, _module, group, id)
3✔
406
                        file = $(log_data._file)
3✔
407
                        if file isa String
3✔
408
                            file = Base.fixup_stdlib_path(file)
3✔
409
                        end
410
                        line = $(log_data._line)
3✔
411
                        local msg, kwargs
412
                        $(logrecord) && $handle_message_nothrow(
413
                            logger, level, msg, _module, group, id, file, line;
414
                            kwargs...)
415
                    end
416
                end
417
            end
418
            nothing
2,511✔
419
        end
420
    end
421
end
422

423
@noinline function handle_message_nothrow(logger, level, msg, _module, group, id, file, line; kwargs...)
×
424
    @nospecialize
×
425
    try
×
426
        @invokelatest handle_message(
×
427
            logger, level, msg, _module, group, id, file, line;
428
            kwargs...)
429

430
    catch err
431
        @invokelatest logging_error(logger, level, _module, group, id, file, line, err, true)
×
432
    end
433
end
434

435
function process_logmsg_exs(_orig_module, _file, _line, level, message, exs...)
13✔
436
    @nospecialize
13✔
437
    local _group, _id
13✔
438
    _module = _orig_module
13✔
439
    kwargs = Any[]
13✔
440
    for ex in exs
13✔
441
        if ex isa Expr && ex.head === :(=)
17✔
442
            k, v = ex.args
9✔
443
            if !(k isa Symbol)
9✔
444
                k = Symbol(k)
×
445
            end
446

447
            # Recognize several special keyword arguments
448
            if k === :_group
9✔
449
                _group = esc(v)
×
450
            elseif k === :_id
9✔
451
                _id = esc(v)
×
452
            elseif k === :_module
9✔
453
                _module = esc(v)
×
454
            elseif k === :_file
9✔
455
                _file = esc(v)
×
456
            elseif k === :_line
9✔
457
                _line = esc(v)
×
458
            else
459
                # Copy across key value pairs for structured log records
460
                push!(kwargs, Expr(:kw, k, esc(v)))
9✔
461
            end
462
        elseif ex isa Expr && ex.head === :... # Keyword splatting
8✔
463
            push!(kwargs, esc(ex))
×
464
        else # Positional arguments - will be converted to key value pairs automatically.
465
            push!(kwargs, Expr(:kw, Symbol(ex), esc(ex)))
8✔
466
        end
467
    end
23✔
468

469
    if !@isdefined(_group)
13✔
470
        _group = default_group_code(_file)
13✔
471
    end
472
    if !@isdefined(_id)
13✔
473
        _id = Expr(:quote, log_record_id(_orig_module, level, message, exs))
13✔
474
    end
475
    return (;_module, _group, _id, _file, _line, kwargs)
13✔
476
end
477

478
function default_group_code(file)
×
479
    @nospecialize
×
480
    if file isa String && isdefined(Base, :basename)
×
481
        QuoteNode(default_group(file))  # precompute if we can
×
482
    else
483
        ref = Ref{Symbol}()  # memoized run-time execution
×
484
        :(isassigned($ref) ? $ref[] : $ref[] = default_group(something($file, ""))::Symbol)
×
485
    end
486
end
487

488

489
# Report an error in log message creation
490
@noinline function logging_error(logger, level, _module, group, id,
×
491
                                 filepath, line, @nospecialize(err), real::Bool)
492
    @nospecialize
×
493
    if !_invoked_catch_exceptions(logger)
×
494
        real ? rethrow(err) : throw(err)
×
495
    end
496
    msg = try
×
497
              "Exception while generating log record in module $_module at $filepath:$line"
×
498
          catch ex
499
              LazyString("Exception handling log message: ", ex)
×
500
          end
501
    bt = real ? catch_backtrace() : backtrace()
×
502
    handle_message(
×
503
        logger, Error, msg, _module, :logevent_error, id, filepath, line;
504
        exception=(err,bt))
505
    nothing
×
506
end
507

508
# Log a message. Called from the julia C code; kwargs is in the format
509
# Any[key1,val1, ...] for simplicity in construction on the C side.
510
function logmsg_shim(level, message, _module, group, id, file, line, kwargs)
×
511
    @nospecialize
×
512
    real_kws = Any[(kwargs[i], kwargs[i+1]) for i in 1:2:length(kwargs)]
×
513
    @logmsg(convert(LogLevel, level), message,
×
514
            _module=_module, _id=id, _group=group,
515
            _file=String(file), _line=line, real_kws...)
516
    nothing
×
517
end
518

519
# LogState - a cache of data extracted from the logger, plus the logger itself.
520
struct LogState
521
    min_enabled_level::LogLevel
1✔
522
    logger::AbstractLogger
523
end
524

525
LogState(logger) = LogState(LogLevel(_invoked_min_enabled_level(logger)), logger)
1✔
526

527
const CURRENT_LOGSTATE = ScopedValue{LogState}()
528

529
function current_logstate()
UNCOV
530
    maybe = @inline Base.ScopedValues.get(CURRENT_LOGSTATE)
×
UNCOV
531
    return something(maybe, _global_logstate)::LogState
×
532
end
533

534
with_logstate(f::Function, logstate) = @with(CURRENT_LOGSTATE => logstate, f())
×
535

536
#-------------------------------------------------------------------------------
537
# Control of the current logger and early log filtering
538

539
"""
540
    disable_logging(level)
541

542
Disable all log messages at log levels equal to or less than `level`.  This is
543
a *global* setting, intended to make debug logging extremely cheap when
544
disabled.
545

546
# Examples
547
```julia
548
Logging.disable_logging(Logging.Info) # Disable debug and info
549
```
550
"""
551
function disable_logging(level::LogLevel)
×
552
    _min_enabled_level[] = level + 1
×
553
end
554

555
let _debug_groups_include::Vector{Symbol} = Symbol[],
556
    _debug_groups_exclude::Vector{Symbol} = Symbol[],
557
    _debug_str::String = ""
UNCOV
558
global Base.@constprop :none function env_override_minlevel(group, _module)
×
UNCOV
559
    debug = get(ENV, "JULIA_DEBUG", "")
×
UNCOV
560
    if !(debug === _debug_str)
×
561
        _debug_str = debug
×
562
        empty!(_debug_groups_include)
×
563
        empty!(_debug_groups_exclude)
×
564
        for g in split(debug, ',')
×
565
            if !isempty(g)
×
566
                if startswith(g, "!")
×
567
                    if !isempty(g[2:end])
×
568
                        push!(_debug_groups_exclude, Symbol(g[2:end]))
×
569
                    end
570
                else
571
                    push!(_debug_groups_include, Symbol(g))
×
572
                end
573
            end
574
        end
×
575
        unique!(_debug_groups_include)
×
576
        unique!(_debug_groups_exclude)
×
577
    end
578

UNCOV
579
    if !(:all in _debug_groups_exclude) && (:all in _debug_groups_include || !isempty(_debug_groups_exclude))
×
580
        if isempty(_debug_groups_exclude)
×
581
            return true
×
582
        elseif isa(group, Symbol) && group in _debug_groups_exclude
×
583
            return false
×
584
        elseif isa(_module, Module) && (nameof(_module) in _debug_groups_exclude || nameof(Base.moduleroot(_module)) in _debug_groups_exclude)
×
585
            return false
×
586
        else
587
            return true
×
588
        end
589
    else
UNCOV
590
        if isempty(_debug_groups_include)
×
UNCOV
591
            return false
×
592
        elseif isa(group, Symbol) && group in _debug_groups_include
×
593
            return true
×
594
        elseif isa(_module, Module) && (nameof(_module) in _debug_groups_include || nameof(Base.moduleroot(_module)) in _debug_groups_include)
×
595
            return true
×
596
        else
597
            return false
×
598
        end
599
    end
600
    return false
×
601
end
602
end
603

604

605
global _global_logstate::LogState
606

607
"""
608
    global_logger()
609

610
Return the global logger, used to receive messages when no specific logger
611
exists for the current task.
612

613
    global_logger(logger)
614

615
Set the global logger to `logger`, and return the previous global logger.
616
"""
617
global_logger() = _global_logstate.logger
×
618

619
function global_logger(logger::AbstractLogger)
620
    prev = _global_logstate.logger
1✔
621
    global _global_logstate = LogState(logger)
1✔
622
    prev
1✔
623
end
624

625
"""
626
    with_logger(function, logger)
627

628
Execute `function`, directing all log messages to `logger`.
629

630
# Examples
631

632
```julia
633
function test(x)
634
    @info "x = \$x"
635
end
636

637
with_logger(logger) do
638
    test(1)
639
    test([1,2])
640
end
641
```
642
"""
643
function with_logger(@nospecialize(f::Function), logger::AbstractLogger)
×
644
    with_logstate(f, LogState(logger))
×
645
end
646

647
"""
648
    current_logger()
649

650
Return the logger for the current task, or the global logger if none is
651
attached to the task.
652
"""
653
current_logger() = current_logstate().logger
×
654

655
const closed_stream = IOBuffer(UInt8[])
656
close(closed_stream)
657

658
#-------------------------------------------------------------------------------
659
# SimpleLogger
660
"""
661
    SimpleLogger([stream,] min_level=Info)
662

663
Simplistic logger for logging all messages with level greater than or equal to
664
`min_level` to `stream`. If stream is closed then messages with log level
665
greater or equal to `Warn` will be logged to `stderr` and below to `stdout`.
666
"""
667
struct SimpleLogger <: AbstractLogger
668
    stream::IO
669
    min_level::LogLevel
670
    message_limits::Dict{Any,Int}
671
end
672
SimpleLogger(stream::IO, level=Info) = SimpleLogger(stream, level, Dict{Any,Int}())
×
673
SimpleLogger(level=Info) = SimpleLogger(closed_stream, level)
×
674

675
shouldlog(logger::SimpleLogger, level, _module, group, id) =
×
676
    get(logger.message_limits, id, 1) > 0
677

678
min_enabled_level(logger::SimpleLogger) = logger.min_level
×
679

680
catch_exceptions(logger::SimpleLogger) = false
×
681

682
function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, group, id,
×
683
                        filepath, line; kwargs...)
684
    @nospecialize
×
685
    maxlog = get(kwargs, :maxlog, nothing)
×
686
    if maxlog isa Core.BuiltinInts
×
687
        remaining = get!(logger.message_limits, id, Int(maxlog)::Int)
×
688
        logger.message_limits[id] = remaining - 1
×
689
        remaining > 0 || return
×
690
    end
691
    buf = IOBuffer()
×
692
    stream::IO = logger.stream
×
693
    if !(isopen(stream)::Bool)
×
694
        stream = stderr
×
695
    end
696
    iob = IOContext(buf, stream)
×
697
    levelstr = level == Warn ? "Warning" : string(level)
×
698
    msglines = eachsplit(chomp(convert(String, string(message))::String), '\n')
×
699
    msg1, rest = Iterators.peel(msglines)
×
700
    println(iob, "┌ ", levelstr, ": ", msg1)
×
701
    for msg in rest
×
702
        println(iob, "│ ", msg)
×
703
    end
×
704
    for (key, val) in kwargs
×
705
        key === :maxlog && continue
×
706
        println(iob, "│   ", key, " = ", val)
×
707
    end
×
708
    println(iob, "└ @ ", _module, " ", filepath, ":", line)
×
709
    write(stream, take!(buf))
×
710
    nothing
×
711
end
712

713
_global_logstate = LogState(SimpleLogger())
714

715
include("logging/ConsoleLogger.jl")
716

717
end # CoreLogging
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc