Logger local variable context

I want to have a custom logger that will log certain local variables if available. I can’t seem to make the logger see the local variables.
Normally I could do:

@info gs_tester

to get an output like

Info: gs_tester="MYGSTESTERvalue"

I however, want to just log like this:

function some_worker(gs_tester)
   @info "blahblah"
end
with_logger(logger_that_logs_gs_tester) do
    some_worker(my_gs_tester)
end

And I would expect the logging output to be:

Info: [MYGSTESTERvalue] blahblah

Or, if gs_tester is undeclared:

Info: [?] blahblah

To this end I have written a logger:

logger_that_logs_gs_tester(logger) = TransformerLogger(logger) do log
  try  
      merge(log, (; message = "[$gs_tester] $(log.message)"))
  catch e
      merge(log, (; message = "[?] $(log.message)"))
  end
end

However, the gs_tester local variable context is not available. I only get gs_tester if I declare it before the β€œwith_logger” statement which makes this a lot less useful.

How could I go about having a custom logger that will automatically prefix my log message with elements of context if available?

I am making extensive use of LoggingExtras.jl but I’ll settle for a working example in any manner.

I don’t think this is possible at all in Julia.
Like you can use @locals to get local variables.
But I don’t think you can get the local variables of any other scope.
And the dictionary you get from using @locals is not updated when new ones are defined.

This is kinda by design.
It is one of the things that makes julia faster than python.

You could define your own logging macro which does this though.

Something like (untested)

macro smart_info(outer_msg)
    quote
        msg = $(esc(outer_msg))
        if isdefined(gs_tester)
            @info "[gs_tester=$gs_tester] $msg"
        else
            @info "$msg"
        end
    end
end

Which you would use as

@smart_info "hi"
1 Like

Seems like the gs_tester var is still not available using smart_info macro. Am I missing something obvious? I simplified the example and ran it:

julia> macro smart_info(outer_msg)
           quote
               @info "$gs_tester"
           end
       end
@smart_info (macro with 1 method)

julia> function testsmartinfo()
          gs_tester=1
          @smart_info "did this work?"
       end
testsmartinfo (generic function with 1 method)

julia> testsmartinfo()
β”Œ Error: Exception while generating log record in module Main at REPL[22]:3
β”‚   exception =
β”‚    UndefVarError: gs_tester not defined
β”‚    Stacktrace:
β”‚      [1] macro expansion
β”‚        @ ./logging.jl:340 [inlined]
β”‚      [2] macro expansion
β”‚        @ ./REPL[22]:3 [inlined]
β”‚      [3] testsmartinfo()
β”‚        @ Main ./REPL[23]:3
β”‚      [4] top-level scope
β”‚        @ REPL[24]:1
β”‚      [5] eval
β”‚        @ ./boot.jl:360 [inlined]
β”‚      [6] eval_user_input(ast::Any, backend::REPL.REPLBackend)
β”‚        @ REPL /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:139
β”‚      [7] repl_backend_loop(backend::REPL.REPLBackend)
β”‚        @ REPL /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:200
β”‚      [8] start_repl_backend(backend::REPL.REPLBackend, consumer::Any)
β”‚        @ REPL /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:185
β”‚      [9] run_repl(repl::REPL.AbstractREPL, consumer::Any; backend_on_current_task::Bool)
β”‚        @ REPL /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:317
β”‚     [10] run_repl(repl::REPL.AbstractREPL, consumer::Any)
β”‚        @ REPL /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:305
β”‚     [11] (::Base.var"#874#876"{Bool, Bool, Bool})(REPL::Module)
β”‚        @ Base ./client.jl:387
β”‚     [12] #invokelatest#2
β”‚        @ ./essentials.jl:708 [inlined]
β”‚     [13] invokelatest
β”‚        @ ./essentials.jl:706 [inlined]
β”‚     [14] run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_file::Bool, color_set::Bool)
β”‚        @ Base ./client.jl:372
β”‚     [15] exec_options(opts::Base.JLOptions)
β”‚        @ Base ./client.jl:302
β”‚     [16] _start()
β”‚        @ Base ./client.jl:485
β”” @ Main REPL[22]:3

Got it to work, but unfortunately I cannot really explain why it works …

julia> macro smart_info(outer_msg)
           quote
               @info "$($(esc(:gs_tester)))"
           end
       end
@smart_info (macro with 1 method)

julia> function testsmartinfo()
           gs_tester=1
           @smart_info "did this work?"
       end
testsmartinfo (generic function with 1 method)

julia> testsmartinfo()
[ Info: 1

In any case, the esc(:gs_tester) is needed to paste the symbol gs_tester into the code without the usual hygiene rules. What I don’t quite follow yet is the interaction with the interpolation in the call of the @info macro, i.e., simply printing the variable within a function is easier and would work like:

macro smart_print(outer_msg)
    quote
        print($(esc(:gs_tester))
    end
end

Hope you can make some sense out of this …

Ah yes, it needs to be interpolated twice.
Once into the quote, and once into the string.
I always find that fiddly