# How to properly implement logging in Julia package?

**URL:** https://discourse.julialang.org/t/how-to-properly-implement-logging-in-julia-package/109863
**Category:** General Usage
**Tags:** question, logging
**Created:** [February 7, 2024, 10:40am UTC](https://discourse.julialang.org/t/how-to-properly-implement-logging-in-julia-package/109863 "2024-02-07T10:40:37Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![p-gw](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/p-gw/32/210518_2.png) [@p-gw](https://discourse.julialang.org/u/p-gw)
#### Post date: [February 7, 2024, 10:40am UTC](https://discourse.julialang.org/t/how-to-properly-implement-logging-in-julia-package/109863/1 "2024-02-07T10:40:37Z")

</div>

Hi everyone,

I was wondering how to properly implement logging for a function in a julia package.  
My problem looks something like this: I have an algorithm with `iter` iterations and I want to provide the user with information about the current state, so naturally I’d use the `@info` macro like this:

```julia
function algorithm(iter = 1000)
    @info "starting algorithm..."
    for i in 1:iter
        # do something
        @info "current iteration: $i"
    end
    @info "finished algorithm..."
end

```

This works fine, but the algorithm is quite fast so the calls to `@info` take up a lot of time compared to the “raw” runtime.  
In the real example the algorithm runs in about 60 microseconds without logging and takes 4 milliseconds with logging.  
For this reason I’d like to make logging optional.  
For now I settled on an approach using a `verbose` keyword argument like this:

```julia
function algorithm(iter = 1000; verbose = false)
    verbose && @info "starting algorithm..."
    for i in 1:iter
        # do something
        verbose && @info "current iteration: $i"
    end
    verbose && @info "finished algorithm..."
end

```

However I don’t particularly like the fact that this kind of disables logging independently of the minimum global log level. As a user I would expect to get `@info` statements if my log level is set accordingly.  
Additionally this leads to code where users always have to specify keyword arguments if they like verbose output:

```julia
algorithm(..., verbose = true)
algorithm(..., verbose = true)

```

How do you handle these types of logging in your packages?  
Is there a “best-practice” way to implement something like this?

The real problem concerns the [`rotate`](https://github.com/p-gw/FactorRotations.jl/blob/0d6e2148ef689782b9485209f426b0c8e5973a4f/src/rotate.jl#L82) function in [FactorRotations.jl](https://github.com/p-gw/FactorRotations.jl).

---

<div class="post-metadata">

### Author: ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)
#### Post date: [February 7, 2024, 1:54pm UTC](https://discourse.julialang.org/t/how-to-properly-implement-logging-in-julia-package/109863/2 "2024-02-07T13:54:22Z")

</div>

Have you considered using `@debug` instead?

One can then set the logging level:  
[https://docs.julialang.org/en/v1/stdlib/Logging/](https://docs.julialang.org/en/v1/stdlib/Logging/)

Also see the logging organization:  
[https://julialogging.github.io/](https://julialogging.github.io/)

---

<div class="post-metadata">

### Author: ![goerz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerz/32/3269_2.png) [@goerz](https://discourse.julialang.org/u/goerz)
#### Post date: [February 7, 2024, 2:29pm UTC](https://discourse.julialang.org/t/how-to-properly-implement-logging-in-julia-package/109863/3 "2024-02-07T14:29:42Z")

</div>

You can use [`@logmsg`](https://docs.julialang.org/en/v1/stdlib/Logging/#Logging.@logmsg):

```julia
level = verbose ? Logging.Info : Logging.Debug
@logmsg level "Hello World"

```

---

<div class="post-metadata">

### Author: ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)
#### Post date: [February 7, 2024, 3:14pm UTC](https://discourse.julialang.org/t/how-to-properly-implement-logging-in-julia-package/109863/4 "2024-02-07T15:14:18Z")

</div>

Have a look at `LoggingExtras.jl`: neat and capable. I use it to print to screen and concurrently into a log file.

```julia
using LoggingExtras, Dates

const date_format = "yyyy-mm-dd HH:MM:SS"

timestamp_logger(logger) = TransformerLogger(logger) do log
  merge(log, (; message = "$(Dates.format(now(), date_format)) $(log.message)"))
end

"""
    update_simulation(cdir, simp)

Update the state of the simulation.

Carry out whatever needs to be done to produce the results.

`cdir` = campaign directory, 
`simp` = dictionary of simulation parameters
"""
function update_simulation(cdir, simp)
    @info "Simulation $(simp["name"])"

    dir = joinpath(cdir, "simulations", simp["name"])
    mkpath(dir)
    file = with_extension(simp["name"], ".json")
    jsn = joinpath(dir, file)

    logfile = joinpath(dir, with_extension(simp["name"], ".log"))
    logfilefd = open(logfile, "a")
    logging_level = "logging_level" in keys(simp) ? simp["logging_level"] : "info"
    ll = logging_level == "debug" ? Logging.Debug : Logging.Info
    demux_logger = TeeLogger(
        MinLevelLogger(timestamp_logger(FileLogger(logfilefd)), ll),
        ConsoleLogger(stdout, ll),
    );

    global_logger(demux_logger)
    ...

```

---

<div class="post-metadata">

### Author: ![p-gw](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/p-gw/32/210518_2.png) [@p-gw](https://discourse.julialang.org/u/p-gw)
#### Post date: [February 8, 2024, 8:52am UTC](https://discourse.julialang.org/t/how-to-properly-implement-logging-in-julia-package/109863/5 "2024-02-08T08:52:33Z")

</div>

Thanks for your replies,

> [@mkitti](#):
>
> Have you considered using `@debug` instead?

Yes I did originally, but the official documentation states: _[`Logging.Debug`](https://docs.julialang.org/en/v1/stdlib/Logging/#Logging.Debug) (log level -1000) is information intended for the developer of the program. These events are disabled by default._

Since the information is not intended for the developer but the end user I figured `@debug` is not a very good fit in this case.

The `@logmsg level "..."` syntax provided by @goerz looks quite nice. Will this have any performance impact over `verbose && @info "..."`?

To avoid having to specify `verbose` for every function call I also looked at what the Turing.jl folk did with their progress logging. They have an option to set the progress logging at a global and function level using this pattern:

```julia
const verbosity = Ref(false) # default value

setverbosity!(x::Bool) = verbosity[] = x

function algorithm(args...; verbose = verbosity[])
    # do something
    level = verbose ? Logging.Info : Logging.Debug
    @logmsg level "..."
end

```

This seems like quite a nice pattern to me since it allows the user to conveniently enable verbosity globally while still logging at the “correct” level.

---

<div class="post-metadata">

### Author: ![pfitzseb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pfitzseb/32/45566_2.png) [@pfitzseb](https://discourse.julialang.org/u/pfitzseb)
#### Post date: [February 8, 2024, 9:34am UTC](https://discourse.julialang.org/t/how-to-properly-implement-logging-in-julia-package/109863/6 "2024-02-08T09:34:49Z")

</div>

Also check out

> **[GitHub - JuliaLogging/ProgressLogging.jl](https://github.com/JuliaLogging/ProgressLogging.jl)**
>
> Contribute to JuliaLogging/ProgressLogging.jl development by creating an account on GitHub.
