Skip to content

Commit e11636f

Browse files
committed
some actual progress
1 parent 81c23a1 commit e11636f

3 files changed

Lines changed: 107 additions & 92 deletions

File tree

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ version = "0.1.0"
55

66
[deps]
77
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
8+
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
9+
ScopedValues = "7e506255-f358-4e82-b7e4-beb19740aa63"
810

911
[compat]
1012
Aqua = "0.8"
1113
Dates = "1.10"
14+
Printf = "1.11.0"
1215
SafeTestsets = "0.1"
16+
ScopedValues = "1.5.0"
1317
Test = "1.10"
1418
julia = "1.10"
1519

src/AlgorithmsInterface.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
module AlgorithmsInterface
1010

1111
using Dates: Millisecond, Nanosecond, Period, canonicalize, value
12+
using Printf
13+
using ScopedValues
1214

1315
include("interface/algorithm.jl")
1416
include("interface/problem.jl")

src/logging.jl

Lines changed: 101 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,136 @@
1-
"""
2-
log!(problem::Problem, algorithm::Algorithm, state::State; context::Symbol) -> nothing
3-
4-
Generate a log record for the given `(problem, algorithm, state)`, using the logging action associated to `context`.
5-
By default, the following events trigger a logging action with the given `context`:
6-
7-
| context | event |
8-
| --------- | ------------------------------------- |
9-
| :Start | The solver has been initialized. |
10-
| :PreStep | The solver is about to take a step. |
11-
| :PostStep | The solver has taken a step. |
12-
| :Stop | The solver has finished. |
13-
14-
Specific algorithms can associate other events with other contexts.
15-
16-
See also [`register_action!`](@ref) to associate custom actions with these contexts.
17-
"""
18-
function log!(problem::Problem, algorithm::Algorithm, state::State; context)::Nothing
19-
action = @something(logging_action(problem, algorithm, state, context), return nothing)
20-
# TODO: filter out `nothing` logdata
21-
@logmsg(
22-
loglevel(action), logdata!(action, problem, algorithm, state),
23-
_id = logid(action), _group = loggroup(algorithm)
24-
)
25-
return nothing
26-
end
27-
28-
"""
29-
logging_action(problem::Problem, algorithm::Algorithm, state::State, context::Symbol)
30-
-> Union{Nothing,LoggingAction}
31-
32-
Obtain the registered logging action associated to an event identified by `context`.
33-
The default implementation assumes a dictionary-like object `state.logging_action`, which holds
34-
the different registered actions.
35-
"""
36-
function logging_action(::Problem, ::Algorithm, state::State, context::Symbol)
37-
return get(state.logging_actions, context, nothing)
38-
end
39-
40-
@doc """
41-
loggroup(algorithm::Algorithm) -> Symbol
42-
loggroup(::Type{<:Algorithm}) -> Symbol
43-
44-
Generate a group id to attach to all log records for a given algorithm.
45-
""" loggroup
46-
47-
loggroup(algorithm::Algorithm) = loggroup(typeof(algorithm))
48-
loggroup(Alg::Type{<:Algorithm}) = Base.nameof(Alg)
49-
50-
# Sources
51-
# -------
1+
# LoggingAction interface
2+
# -----------------------
523
"""
534
LoggingAction
545
556
Abstract supertype for defining an action that generates a log record.
567
578
## Methods
58-
Any concrete subtype should at least implement the following method:
59-
- [`logdata!(action, problem, algorithm, state)`](@ref logdata!) : generate the data for the log record.
9+
Any concrete subtype should at least implement the following method to handle the logging event:
6010
61-
Addionally, the following methods can be specialized to alter the default behavior:
62-
- [`loglevel(action) = Logging.Info`](@ref loglevel) : specify the logging level of the generated record.
63-
- [`logid(action) = objectid(action)`](@ref logid) : specify a unique identifier to associate with the record.
11+
- [`handle_message!(action, problem, algorithm, state, args...; kwargs...)`](@ref handle_message!)
6412
"""
6513
abstract type LoggingAction end
6614

67-
logdata!(::LoggingAction, ::Problem, ::Algorithm, ::State) = missing
68-
loglevel(::LoggingAction) = Logging.Info
69-
logid(action::LoggingAction) = objectid(action)
15+
@doc """
16+
handle_message!(action::LoggingAction, problem::Problem, algorithm::Algorithm, state::State; kwargs...)
7017
71-
struct LogCallback{F} <: LoggingAction
72-
f::F
73-
end
18+
Entry-point for defining an implementation of how to handle a logging event for a given [`LoggingAction`](@ref).
19+
""" handle_message!(::LoggingAction, ::Algorithm, ::Problem, ::State; kwargs...)
7420

75-
logdata!(action::LogCallback, problem, algorithm, state) =
76-
action.f(problem, algorithm, state)
21+
# Concrete LoggingActions
22+
# -----------------------
23+
"""
24+
LogGroup(actions::Vector{<:LoggingAction})
7725
26+
Concrete [`LoggingAction`](@ref) that can be used to sequentially perform each of the `actions`.
27+
"""
7828
struct LogGroup{A <: LoggingAction} <: LoggingAction
7929
actions::Vector{A}
8030
end
8131

82-
loglevel(alg::LogGroup) = maximum(loglevel, alg.actions)
83-
84-
logdata!(action::LogGroup, problem, algorithm, state) = map(action.actions) do action
85-
return logdata!(action, problem, algorithm, state)
32+
function handle_message!(
33+
action::LogGroup, problem::Problem, algorithm::Algorithm, state::State; kwargs...
34+
)
35+
for child in action.actions
36+
handle_message!(child, algorithm, problem, state; kwargs...)
37+
end
38+
return nothing
8639
end
8740

88-
struct LogLvl{F, A <: LoggingAction} <: LoggingAction
89-
action::A
90-
lvl::LogLevel
41+
"""
42+
CallbackAction(callback)
43+
44+
Concrete [`LoggingAction`](@ref) that handles a logging event through an arbitrary callback function.
45+
The callback function must have the following signature:
46+
```julia
47+
callback(algorithm, problem, state; kwargs...) = ...
48+
```
49+
Here `args...` and `kwargs...` are optional and can be filled out with context-specific information.
50+
"""
51+
struct CallbackAction{F} <: LoggingAction
52+
callback::F
9153
end
9254

93-
loglevel(alg::LogLvl) = alg.lvl
55+
function handle_message!(
56+
action::CallbackAction, problem::Problem, algorithm::Algorithm, state::State; kwargs...
57+
)
58+
action.callback(algorithm, problem, state; kwargs...)
59+
return nothing
60+
end
9461

95-
struct LogIf{F, A <: LoggingAction} <: LoggingAction
62+
struct IfAction{F, A <: LoggingAction} <: LoggingAction
9663
predicate::F
9764
action::A
9865
end
9966

100-
# first cheap check through the level
101-
loglevel(alg::LogIf) = loglevel(alg.action)
67+
function handle_message!(
68+
action::IfAction, problem::Problem, algorithm::Algorithm, state::State; kwargs...
69+
)
70+
return action.predicate(problem, algorithm, state; kwargs...) ?
71+
handle_message(action.action, problem, algorithm, state; kwargs...) :
72+
nothing
73+
end
10274

103-
# second check through the predicate
104-
logdata!(action::LogIf, problem::Problem, algorithm::Algorithm, state::State) =
105-
action.predicate(problem, algorithm, state) ? logdata!(action.action, problem, algorithm, state) : nothing
75+
# Algorithm Logger
76+
# ----------------
77+
"""
78+
AlgorithmLogger(context => action, ...)
10679
107-
# Sinks
108-
# -----
109-
struct StringFormat{F <: PrintF.Format}
110-
format::F
111-
end
112-
function (formatter::StringFormat)(io::IO, lvl, msg, _module, group, id, file = nothing, line = nothing; indent::Integer = 0, kwargs...)
113-
iob = IOBuffer()
114-
return ioc = IOContext(iob, io)
80+
Logging transformer that handles the logic of dispatching logging events to logging actions.
81+
By default, the following events trigger a logging action with the given `context`:
11582
83+
| context | event |
84+
| --------- | ----------------------------------- |
85+
| :Start | The solver will start. |
86+
| :Init | The solver has been initialized. |
87+
| :PreStep | The solver is about to take a step. |
88+
| :PostStep | The solver has taken a step. |
89+
| :Stop | The solver has finished. |
90+
91+
Specific algorithms can associate other events with other contexts.
92+
93+
See also the scoped value [`AlgorithmsInterface.algorithm_logger`](@ref).
94+
"""
95+
struct AlgorithmLogger
96+
actions::Dict{Symbol, LogAction}
11697
end
98+
AlgorithmLogger(args...) = AlgorithmLogger(Dict{Symbol, LogAction}(args...))
11799

118-
struct FormatterGroup{K, V}
119-
rules::Dict{K, V}
100+
"""
101+
const LOGGING_ENABLED = Ref(true)
102+
103+
Global toggle for enabling and disabling all logging features.
104+
"""
105+
const LOGGING_ENABLED = Ref(true)
106+
107+
"""
108+
const algorithm_logger = ScopedValue(AlgorithmLogger())
109+
110+
Scoped value for handling the logging events of arbitrary algorithms.
111+
"""
112+
const ALGORITHM_LOGGER = ScopedValue(AlgorithmLogger())
113+
114+
# @inline here to enable the cheap global check
115+
@inline function log!(problem::Problem, algorithm::Algorithm, state::State, context::Symbol; kwargs...)
116+
if LOGGING_ENABLED[]
117+
logger::AlgorithmLogger = ALGORITHM_LOGGER[]
118+
handle_message(logger, problem, algorithm, state, context; kwargs...)
119+
end
120+
return nothing
120121
end
121122

122-
function (formatter::AlgorithmFormatter)(io::IO, log)
123-
rule = get(formatter.rules, log.id, nothing)
124-
isnothing(rule) ? println(io, log) :
125-
rule(io, log.level, log.message, log._module, log.group, log.id, log.file, log.line; log.kwargs...)
123+
# @noinline to keep the algorithm function bodies small
124+
@noinline function handle_message(
125+
alglogger::AlgorithmLogger, problem::Problem, algorithm::Algorithm, state::State, context::Symbol;
126+
kwargs...
127+
)
128+
action::LoggingAction = @something(get(alglogger.actions, context, nothing), return nothing)
129+
try
130+
handle_message!(action, problem, algorithm, state, args...; kwargs...)
131+
catch err
132+
bt = catch_backtrace()
133+
@error "Error during the handling of a logging action" action exception = (err, bt)
134+
end
126135
return nothing
127136
end

0 commit comments

Comments
 (0)