From c5f7cb1f90b65adc4e0e1a2857956c8f3865c5d7 Mon Sep 17 00:00:00 2001 From: Curt D Date: Mon, 22 Apr 2024 18:01:17 -0700 Subject: [PATCH] feat: Made `LogRecordData` keys parametric, rather than being restricted to `Symbol`s. (#5) `LogRecordData` is now backed by a `Dictionaries.Dictionary` feat: Added string interning feat: Added `add_record_data!` method --- Project.toml | 7 +++ src/LoggingCommon.jl | 4 +- src/levels.jl | 2 +- src/records.jl | 89 ++++++++++++++++++++++++++------------- test/Project.toml | 6 ++- test/TestLoggingCommon.jl | 23 ++++++---- test/runtests.jl | 7 +++ 7 files changed, 96 insertions(+), 42 deletions(-) diff --git a/Project.toml b/Project.toml index 71f3d31..ac42947 100644 --- a/Project.toml +++ b/Project.toml @@ -7,10 +7,17 @@ version = "1.0.2" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" +ForwardMethods = "5fe2550f-d27e-4649-9aea-fdf9a83a1aa9" +InternedStrings = "7d512f48-7fb1-5a58-b986-67e6dc259f01" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" [compat] +Dates = "1" Dictionaries = "0.3, 0.4" +Distributed = "1" +ForwardMethods = "1.6" +InternedStrings = "0.7" +Logging = "1" PrecompileTools = "1" julia = "1.6" diff --git a/src/LoggingCommon.jl b/src/LoggingCommon.jl index fbd2f94..1350065 100644 --- a/src/LoggingCommon.jl +++ b/src/LoggingCommon.jl @@ -1,6 +1,6 @@ module LoggingCommon - using Dates, Dictionaries, Distributed, Logging, PrecompileTools + using Dates, Dictionaries, Distributed, ForwardMethods, Logging, InternedStrings, PrecompileTools # Log levels export NotSet, All, Trace,Notice, Critical, Alert, Emergency, Fatal, AboveMax, Off @@ -19,7 +19,7 @@ module LoggingCommon export AbstractLogRecord, LogRecord, MessageLogRecord, StacktraceLogRecord - export message_log_record, stacktrace_log_record + export message_log_record, stacktrace_log_record, add_record_data! export log_record_data, static_metadata, runtime_metadata, is_error_record diff --git a/src/levels.jl b/src/levels.jl index c3e3e0a..f56d48b 100644 --- a/src/levels.jl +++ b/src/levels.jl @@ -133,7 +133,7 @@ end NamedLogLevel(l::NamedLogLevel) = l -Base.string(l::NamedLogLevel) = string(l.name) +Base.string(l::NamedLogLevel) = intern(string(l.name)) log_level(l::NamedLogLevel) = symbol_to_log_levels[l.name] diff --git a/src/records.jl b/src/records.jl index 1b3df9c..151647e 100644 --- a/src/records.jl +++ b/src/records.jl @@ -1,7 +1,7 @@ function module_str_trim_main(_module::Module) name = fullname(_module) if first(name) == :Main && length(name) > 1 - return join(name[2:end], '.') + return join(view(name, 2:length(name)), '.') else return join(name, '.') end @@ -31,13 +31,14 @@ function StaticLogRecordMetadata(source::AbstractString, level::LogLevel, level_ return StaticLogRecordMetadata(string(source), level, level_name, something(filename, "?"), line_num, group, id) end -StaticLogRecordMetadata(source::AbstractString, level, lnn::LineNumberNode, args...) = StaticLogRecordMetadata(source, level, "", _filename(lnn), lnn.line, args...) +StaticLogRecordMetadata(source::AbstractString, level::LogLevel, lnn::LineNumberNode, args...) = StaticLogRecordMetadata(source, level, intern(""), _filename(lnn), lnn.line, args...) -StaticLogRecordMetadata(source::AbstractString, level::LogLevel, filename::Union{LineNumberNode, String, Nothing}, line_num::Union{LineNumberNode, Int}, args...) = StaticLogRecordMetadata(source, level, string(nearest_log_level(level)), filename, line_num, args...) +StaticLogRecordMetadata(source::AbstractString, level::LogLevel, filename::Union{String, Nothing}, line_num::Union{LineNumberNode, Int}, args...) = StaticLogRecordMetadata(source, level, string(nearest_log_level(level)), filename, line_num, args...) StaticLogRecordMetadata(source::AbstractString, level::NamedLogLevel, args...) = StaticLogRecordMetadata(source, log_level(level), string(level), args...) -StaticLogRecordMetadata(source::Module, args...) = StaticLogRecordMetadata(module_str_trim_main(source), args...) + +StaticLogRecordMetadata(source::Module, args...) = StaticLogRecordMetadata(intern(module_str_trim_main(source)), args...) """ RuntimeLogRecordMetadata(datetime::DateTime, thread_id::Int, worker_id::Int) @@ -59,34 +60,61 @@ end """ LogRecordData(data) - LogRecordData(args::Pair{Symbol, <:Any}...) + LogRecordData(args::Pair{<:Any, <:Any}...) + +A type representing an optional collection of `key => value` pairs attached to a log record. -A type representing an optional collection of `key => value` pairs attached to a log record. +`data` must be an iterable collection where each element is a `Pair` """ -struct LogRecordData - data::Union{Nothing,Vector{Pair{Symbol, Any}}} - function LogRecordData(d; exclude::Union{Symbol,Vector{Symbol}}=Symbol[]) - if !isnothing(d) && !isempty(d) - _exclude = exclude isa Symbol ? [exclude] : exclude - return new([convert(Pair{Symbol,Any}, di) for di in d if first(di) ∉ _exclude]) - else - return new(nothing) - end - end +struct LogRecordData{K} + data::Dictionary{K, Any} + LogRecordData{K}() where {K} = new(Dictionary{K, Any}()) end -Base.isempty(l::LogRecordData) = isnothing(l.data) || isempty(l.data) -Base.length(l::LogRecordData) = isnothing(l.data) ? 0 : length(l.data) -Base.pairs(l::LogRecordData) = isnothing(l.data) ? pairs((;)) : l.data -Base.iterate(l::LogRecordData) = isnothing(l.data) ? nothing : iterate(l.data) -Base.iterate(l::LogRecordData, st) = isnothing(st) ? nothing : iterate(l.data, st) - -function LogRecordData(args::Pair{Symbol, <:Any}...) - if !isempty(args) - return LogRecordData(collect(args)) - else - return LogRecordData(nothing) +@forward_methods LogRecordData field=data Base.isempty(_) Base.length(_) Base.pairs(_) +Base.eltype(::Type{LogRecordData{K}}) where {K} = Pair{K, Any} +Base.iterate(d::LogRecordData) = iterate(pairs(d.data)) +Base.iterate(d::LogRecordData, st) = iterate(pairs(d.data), st) +Base.collect(d::LogRecordData) = collect(pairs(d.data)) + +""" + add_record_data!(r, data::Pair) + +Adds the `data := key => value` pair to `r` +""" +add_record_data!(d::LogRecordData{K}, data::Pair{K, <:Any}) where {K} = (set!(d.data, first(data), last(data)); nothing) + +add_record_data!(d::LogRecordData{K}, data::Pair) where {K} = add_record_data!(d, convert(K, first(data))::K => last(data)) + +function _log_record_data(kv_pairs, T; exclude=()) + d = LogRecordData{T}() + _exclude = (exclude isa Tuple || exclude isa Vector) ? exclude : (exclude,) + for (k, v) in kv_pairs + if k ∉ _exclude + set!(d.data, k, v) + end end + return d end +key_type(::Type{Pair{K, V}}) where {K, V} = K + +""" + log_record_data(kv_pairs; [exclude=()]) -> LogRecordData + +Returns a `LogRecordData` from the input `key => value` pairs +""" +log_record_data(kv_pairs; exclude=()) = _log_record_data(kv_pairs, mapfoldl(key_type ∘ typeof, promote_type, kv_pairs; init=Union{}); exclude) + +""" + log_record_data() -> LogRecordData{Symbol} + +""" +log_record_data() = _log_record_data((), Symbol) + +LogRecordData(::Nothing; kwargs...) = _log_record_data((), Symbol; kwargs...) +LogRecordData(data; kwargs...) = log_record_data(data; kwargs...) +LogRecordData(args::Pair{Symbol, <:Any}...; kwargs...) = _log_record_data(args, Symbol; kwargs...) + + """ AbstractLogRecord @@ -98,7 +126,7 @@ abstract type AbstractLogRecord end """ log_record_data(record) -Returns the `key` => `value` pairs associated with `record` +Returns an iterator over the `key` => `value` pairs associated with `record` """ log_record_data(::AbstractLogRecord) = nothing @@ -155,6 +183,9 @@ struct LogRecord{R} <: AbstractLogRecord data::LogRecordData record::R end +@forward_methods LogRecord field=data add_record_data!(_, data) + +Base.propertynames(::LogRecord{R}) where {R} = (fieldnames(LogRecord)..., fieldnames(R)...) function Base.getproperty(l::LogRecord, name::Symbol) if name === :static_meta || name === :runtime_meta || name === :data || name === :record @@ -166,7 +197,7 @@ end LogRecord(static_meta::StaticLogRecordMetadata, runtime_meta::RuntimeLogRecordMetadata, record::AbstractLogRecord, data::LogRecordData) = LogRecord{typeof(record)}(static_meta, runtime_meta, data, record) -LogRecord(static_meta::StaticLogRecordMetadata, runtime_meta::RuntimeLogRecordMetadata, record::AbstractLogRecord, args::Pair{Symbol, <:Any}...) = LogRecord(static_meta, runtime_meta, record, LogRecordData(args...)) +LogRecord(static_meta::StaticLogRecordMetadata, runtime_meta::RuntimeLogRecordMetadata, record::AbstractLogRecord, args::Pair{<:Any, <:Any}...) = LogRecord(static_meta, runtime_meta, record, LogRecordData(args...)) LogRecord(static_meta::StaticLogRecordMetadata, record::AbstractLogRecord, args...; runtime_meta::RuntimeLogRecordMetadata=RuntimeLogRecordMetadata()) = LogRecord(static_meta, runtime_meta, record, args...) diff --git a/test/Project.toml b/test/Project.toml index c30f7d8..3accec6 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,3 +1,7 @@ [deps] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TestingUtilities = "40452611-1178-4e48-bdfc-3af4bebad9c9" \ No newline at end of file +TestingUtilities = "40452611-1178-4e48-bdfc-3af4bebad9c9" + +[compat] +Aqua = "0.8" \ No newline at end of file diff --git a/test/TestLoggingCommon.jl b/test/TestLoggingCommon.jl index 2888650..03f2928 100644 --- a/test/TestLoggingCommon.jl +++ b/test/TestLoggingCommon.jl @@ -73,16 +73,14 @@ module TestLoggingCommon end @testset "LogRecordData" begin d = LogRecordData() - @Test isnothing(d.data) @Test isnothing(Base.iterate(d)) @Test isempty(Base.pairs(d)) @Test isempty(d) @Test length(d) == 0 d = LogRecordData(:a => 1) - @Test d.data == [:a => 1] - @Test Base.pairs(d) == d.data - @Test Base.iterate(d) == (:a => 1, 2) - @Test Base.iterate(d, 2) |> isnothing + @Test Base.pairs(d) |> collect == [:a => 1] + @Test Base.iterate(d) == (:a => 1, 1) + @Test Base.iterate(d, 1) |> isnothing @Test !isempty(d) @Test length(d) == 1 @@ -90,13 +88,15 @@ module TestLoggingCommon @Test length(d) == 2 for (i, (k,v)) in enumerate(d) if i == 1 - @test k == :a - @test v == 1 + @Test k == :a + @Test v == 1 else - @test k == :b - @test v == String + @Test k == :b + @Test v == String end end + d = log_record_data(("a" => 1, "b" => 2); exclude="a") + @Test collect(d) == ["b" => 2] end @testset "message_log_record" begin static = StaticLogRecordMetadata(Main, NamedLogLevel(:info), "a.jl", 1, "group", "id") @@ -109,6 +109,11 @@ module TestLoggingCommon @test !is_error_record(record) @test isempty(log_record_data(record)) + add_record_data!(record, :a => "1") + @Test log_record_data(record) |> collect == [:a => "1"] + add_record_data!(record, :b => true) + @Test log_record_data(record) |> collect == [:a => "1", :b => true] + static = StaticLogRecordMetadata(Main, NamedLogLevel(:error), "a.jl", 1, "group", "id") record = message_log_record(static, "Error message", :a => 1, :b => String) @test static_metadata(record) == static diff --git a/test/runtests.jl b/test/runtests.jl index 017628c..aa7633e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,10 @@ using Test +using LoggingCommon + +if VERSION ≥ v"1.9" + using Aqua + Aqua.test_all(LoggingCommon) +end + include("TestLoggingCommon.jl") \ No newline at end of file