Interaction between `Logging.jl`, `JuliaFormatter.jl` and `Documenter.jl`?

Hi there,

I am very confused by the interaction(s?) between Logging.jl, JuliaFormatter.jl and Documenter.jl. My initial problem was that I had blocks like this in my package documentation:

```@example
my_function() # returns 5
```

Rendering in the HTML docs as:

my_function() # returns 5
┌ Warning: My warning message.
└ @ MyModule ~/path/to/my/source/file.jl:42
5

I have been willing to remove the warning from the generated docs, because I know it will be fixed in the future and it’s just confusing at this point in the docs. The following does work:

```@example
using Logging # hide
with_logger(NullLogger()) do # hide
my_function()
end # hide    
```

Which correctly renders as:

my_function()
5

But then JuliaFormatter.jl reformats the example to add the required indentation, which leaves me with the following rather unsatisfying situation:

```@example
using Logging # hide
with_logger(NullLogger()) do # hide
    my_function()
end # hide
```

Rendering as:

    my_function()
5

I have tried adding the following lines to leave instructions to JuliaFormatter.jl in a way that would not be rendered in the final docs:

#! format: off
#! format: off # hide
# hide #! format: off

None of which worked. So here is my first question then: How can I use #! format: off in my docstrings @example blocks?

I went for another approach then:

```
using Logging # hide
logger_safe = global_logger(NullLogger()) # hide
my_function()
global_logger(logger_safe) # hide
```

But this did not work and rendered as:

my_function()
┌ Warning: My warning message.
└ @ MyModule ~/path/to/my/source/file.jl:42
5

How come with_logger() appears to work in docstrings @example blocks and yet global_logger() does not?

Since my docs are rather long to generate, I have then decided to toy around with a smaller project just to better understand what is happening here, using the following structure:

JuliaTests/
├── Manifest.toml
├── Project.toml
└── src/
    ├── JuliaTests.jl
    └── main.md

src/JuliaTests.jl

module JuliaTests

function f()
  @info "INFO message"
  @warn "WARN message"
  @error "ERROR message"
  5
end
export f

end # module JuliaTests 

src/main.md

```@example JuliaTests
using JuliaTests
f()
```

```@example JuliaTests
function g()
  @info "INFO message"
  @warn "WARN message"
  @error "ERROR message"
  8
end

g()
```

Then I ran:

$ julia --project=.
julia> using JuliaTests
julia> using Documenter
julia> makedocs(;modules=[JuliaTests], sitename="toy")

Only to figure out that the resulting file in build/main/index.html was now rendered as:

using JuliaTests
f()
5
function g()
  @info "INFO message"
  @warn "WARN message"
  @error "ERROR message"
  8
end

g()
8

Which confuses me even more, because the logged messages are now not rendered within the resulting docs. My initial problem was to remove the unwanted ones in my actual package.

Why would there be a difference between my package rendering and my MWE? What is Documenter.jl policy regarding Logged messages? How can we control whether they should be rendered or not as part of the resulting, generated html doc?

with_logger with # hide is what I would recommend, and have used in the past. I don’t know how the formatting markers should (if they can) be used in markdown files.

In order to capture output Documenter replaces the logger before every expression to be evalutated. Your example can be expanded to the following, from which it is clear why the former works, and the latter doesn’t:

# Setup for Expr #1
old_logger = global_logger(ConsoleLogger(...) # Setup for Expr #1
with_logger(NullLogger()) do                  # Expr #1
    my_function()                             # Expr #1
end                                           # Expr #1
global_logger(old_logger)                     # Tear down for Expr #1
# Setup for Expr #1
old_logger = global_logger(ConsoleLogger(...) # Setup for Expr #1
logger_safe = global_logger(NullLogger())     # Expr #1
global_logger(old_logger)                     # Tear down for Expr #1

old_logger = global_logger(ConsoleLogger(...) # Setup for Expr #2
my_function()                                 # Expr #2
global_logger(old_logger)                     # Tear down for Expr #2

old_logger = global_logger(ConsoleLogger(...) # Setup for Expr #3
global_logger(logger_safe)                    # Expr #3
global_logger(old_logger)                     # Tear down for Expr #3

The rendered output from an @example block is selected in this order:

  1. Return value of the block (8 in your example)
  2. stdout/stderr

If you are truly returning 5 in the first example (return 5) then something is a bit off here, because then 2. would never be visible. Are you sure you are not printing 5? For example

```@example
function warn_and_return_five()
    @warn "Returning 5"
    return 5
end

warn_and_return_five()
```

will show

5

whereas

```@example
function warn_and_print_five()
    @warn "Printing 5"
    println(5)
end

warn_and_print_five()
```

will show

┌ Warning: Printing 5
└ @ Main index.md:13
5
2 Likes

I don’t think there is a great solution for the formatting problem right now, but I opened an issue about it.

What might work is to put the #! format: comments in an otherwise empty at-meta block just before and after:

```@meta
#! format: off
```
```@example
using Logging # hide
with_logger(NullLogger()) do # hide
my_function()
end # hide    
```
```@meta
#! format: on
```

Empty at-meta blocks should be fine from Documenter’s standpoint, but I don’t know if JuliaFormatter treats each of the code blocks independently or not.

1 Like

Well, you nailed it :smiley: This is exactly the source of my confusion here. I was actually “printing 5”, not because I wrote println(5), but because my actual expected output was the one from @time my_expression(), which is indeed just printed on stdout IIUC. This does explain the difference between my MWE and my actual documentation. Thank you <3

Unfortunately it does not seem to work. Maybe as you write, JuliaFormatter treats every block independently?

Thank you for that <3 I think this my only way out of here now :slight_smile: