loggingjulia

Machine-readable logging


Currently, I am trying to benchmark some algorithms that I implemented. Besides other things, I want to collect statistics about objects used in a function f that's not called directly, but somewhere deeper inside the algorithm. How can I get the statistics to the outside world? Say if I call a -> b -> f, I can extend a, b, f such that they pass around the data. But that's a lot of changes. Is there a smarter way, that somewhat "collects data in the background", that I can later retrieve? I am aware of Logging.jl, but all loggers there seem to in some sense print (or discard) the data immediately, but not print it. A mockup of what I want would be

logs = Dict()
function f()
  global logs
  push!(logs, :f => "Log message from f")
end

f()

println(logs[:f])

This is very simplistic, but you get what I mean. Do you know of any package that offers this functionality?


Solution

  • The TestLogger(...) from the built-in Logging package supports this exact thing! This logger is built for testing, and stores all logs in a Vector of type LogRecord. This vector can then easily be read to find whatever logs you are looking for. A LogRecord has a bunch of fields, including a kwargs field that will be particularly helpful in reproducing your example mock-up. We can create a keyword to represent your idea of "log message from ...", and then it will be simple to parse the logs and pull out any where that keyword is equal to a particular value.

    
    # Get the nec. packages
    using Test, Logging
    
    # Create a TestLogger
    test_logger = TestLogger();
    
    # Create example functions, and have them log some random piece of data.
    f() = @info "This is the info log! My random number: $(rand())" myidentifier = :f;
    g() = @warn "This is the warn log! My random number: $(rand())" myidentifier = :g;
    
    # use the logger and call both functions
    with_logger(test_logger) do
        f()
        g()
    end
    
    # Nothing will have printed out or shown as logs in the REPL. Now we read the logs.
    
    println("Entire Logger: ", test_logger)
    #=
    Output:
    
    Entire Logger: TestLogger(LogRecord[LogRecord(Info, "This is the info log! My random number: 0.6271649847098303", Main, Symbol("REPL[89]"), :Main_0994cbfd, "REPL[89]", 1, Base.Pairs(:myidentifier => :f)), LogRecord(Warn, "This is the warn log! My random number: 0.7007816573748056", Main, Symbol("REPL[90]"), :Main_2be29651, "REPL[90]", 1, Base.Pairs(:myidentifier => :g))], Info, false, (Warn, Main, Symbol("REPL[90]"), :Main_2be29651), Dict{Any, Int64}(), true)
    
    =#
    
    
    println("Just Log messages: ", [i.message for i in test_logger.logs if :f in values(i.kwargs)])
    #=
    Output:
    
    Just Log messages: LogRecord[LogRecord(Info, "This is the info log! My random number: 0.6271649847098303", Main, Symbol("REPL[89]"), :Main_0994cbfd, "REPL[89]", 1, Base.Pairs(:myidentifier => :f)), LogRecord(Warn, "This is the warn log! My random number: 0.7007816573748056", Main, Symbol("REPL[90]"), :Main_2be29651, "REPL[90]", 1, Base.Pairs(:myidentifier => :g))]
    =#
    
    
    println("Just :f Log messages: ", [i.message for i in test_logger.logs if :f in values(i.kwargs)])
    #=
    Output:
    
    Just :f Log messages: ["This is the info log! My random number: 0.6211639846090303"]
    =#
    
    println("Just :g Log messages: ", [i.message for i in test_logger.logs if :f in values(i.kwargs)])
    #=
    Output:
    
    Just :g Log messages: ["This is the warn log! My random number: 0.7267826573742089"]
    =#
    

    Using this method, you can log "behind the scenes", and still retrieve any of the logging messages, while also storing pieces of data in the kwarg field. Hope this helps!