@@ -170,14 +170,22 @@ pairwise; use `:any` to check that the pattern matches at least once somewhere
170
170
in the sequence.)
171
171
172
172
The most useful log pattern is a simple tuple of the form `(level,message)`.
173
- A different number of tuple elements may be used to match other log metadata,
173
+ A different number of tuple elements may also be used to match other log metadata,
174
174
corresponding to the arguments to passed to `AbstractLogger` via the
175
- `handle_message` function: `(level,message,module,group,id,file,line)`.
175
+ `handle_message` function: `(level,message,module,group,id,file,line,kwargs )`.
176
176
Elements which are present will be matched pairwise with the log record fields
177
177
using `==` by default, with the special cases that `Symbol`s may be used for
178
178
the standard log levels, and `Regex`s in the pattern will match string or
179
179
Symbol fields using `occursin`.
180
180
181
+ Individual pieces of a log record can be matched by passing a NamedTuple pattern,
182
+ matching fields from log metadata, such as: `(;message="...")` or
183
+ `(level=:info, kwargs=(;a=1, b=2))`.
184
+
185
+ To match a log record's keyword arguments, you can use the kwargs field in the pattern,
186
+ and provide a NamedTuple or Dict of key-value pairs. The supplied kwargs can be a partial
187
+ set, and the log record's kwargs are matched against the provided pairs.
188
+
181
189
# Examples
182
190
183
191
Consider a function which logs a warning, and several debug messages:
@@ -219,6 +227,19 @@ patterns and set the `min_level` accordingly:
219
227
220
228
If you want to test the absence of warnings (or error messages) in
221
229
[`stderr`](@ref) which are not generated by `@warn`, see [`@test_nowarn`](@ref).
230
+
231
+ If you only want to test for a given field, and don't care about the others, you can do
232
+ this by passing a NamedTuple for the pattern. (Don't forget the `;` if you only have a
233
+ single field). For example, if you only want to match the message:
234
+
235
+ @test_logs (; message="hi") @info "hi"
236
+ @test_logs (; message="hi") @warn "hi"
237
+
238
+ To test a log message's key=value pairs, you can use the `kwargs` field. Only supplied
239
+ key,value pairs are tested:
240
+
241
+ @test_logs (level=:info, message="hi", kwargs=(; x=2)) @info("hi", x=2)
242
+ @test_logs (level=:info, message="hi", kwargs=(; x=2)) @info("hi", x=2, y=3)
222
243
"""
223
244
macro test_logs (exs... )
224
245
length (exs) >= 1 || throw (ArgumentError (""" `@test_logs` needs at least one arguments.
@@ -285,12 +306,33 @@ logfield_contains(a, r::Regex) = occursin(r, a)
285
306
logfield_contains (a:: Symbol , r:: Regex ) = occursin (r, String (a))
286
307
logfield_contains (a:: LogLevel , b:: Symbol ) = a == parse_level (b)
287
308
logfield_contains (a, b:: Ignored ) = true
309
+ logfield_contains (a:: NamedTuple , b) = logfield_contains (pairs (a), b)
310
+ logfield_contains (a:: NamedTuple , b:: Ignored ) = true # method amibguity resolution
311
+ function logfield_contains (a:: Base.Pairs , pattern)
312
+ pattern = pairs (pattern)
313
+ for (k, bv) in pattern
314
+ av = get (a, k, :__test_logfield_key_not_found )
315
+ if av === :__test_logfield_key_not_found
316
+ return false
317
+ end
318
+ logfield_contains (av, bv) || return false
319
+ end
320
+ return true
321
+ end
288
322
289
323
function occursin (pattern:: Tuple , r:: LogRecord )
290
- stdfields = (r. level, r. message, r. _module, r. group, r. id, r. file, r. line)
324
+ stdfields = (r. level, r. message, r. _module, r. group, r. id, r. file, r. line, r . kwargs )
291
325
all (logfield_contains (f, p) for (f, p) in zip (stdfields[1 : length (pattern)], pattern))
292
326
end
293
327
328
+ function occursin (pattern:: NamedTuple , r:: LogRecord )
329
+ fieldnames = keys (pattern)
330
+ stdfields = getfield .(Ref (r), fieldnames)
331
+ vals = values (pattern)
332
+ all (logfield_contains (f, p) for (f, p) in zip (stdfields, vals))
333
+ end
334
+
335
+
294
336
"""
295
337
@test_deprecated [pattern] expression
296
338
0 commit comments