How to log to a file

Tip

It is a good idea to follow along by copy-pasting the code snippets into a Julia REPL!

Instead of logging to the terminal it is quite common to send log messages to a log file. Both the ConsoleLogger and the SimpleLogger from the Logging standard library accept an IO stream as input, so it is pretty easy to plug an open filestream in those loggers and log to a file. Here is an example:

using Logging

io = open("logfile.txt", "w")
logger = ConsoleLogger(io)

with_logger(logger) do
    @info "Message to the logfile"
end

close(io)

When reading the file and printing the result we can verify that the file contains the expected log output:

print(read("logfile.txt", String))
[ Info: Message to the logfile

With this approach you might notice that, due to IO buffering the messages will be written to the file with a delay, or possibly not until the program ends and the file is closed. In addition it is somewhat annoying to manage the file IO stream yourself like in the example above. Because of these reasons it is often better to use a logger that are implemented specifically for file-writing.

The LoggingExtras.jl package provide the FileLogger, and, as the name suggest, this is implemented specifically for writing messages to a file. The FileLogger opens the file and automatically flushes the stream after handling each messages. Here is an example of using the FileLogger:

using Logging, LoggingExtras

logger = FileLogger("logfile.txt")

with_logger(logger) do
    @info "First message to the FileLogger!"
end

Reading and printing the content:

print(read("logfile.txt", String))
┌ Info: First message to the FileLogger!
└ @ Main log-to-file.md:51

By default, when the FileLogger opens the file stream it uses "write" mode (see documentation for open), which means that if the file exist and have some content this will be overwritten. There is an option to use "append" mode by passing append = true to the constructor. This will preserve content in the file and only append at the end. Here is an example of that where we open the same file from above, but use append = true:

logger = FileLogger("logfile.txt"; append = true)

with_logger(logger) do
    @info "Second message to the FileLogger?"
end

print(read("logfile.txt", String))
┌ Info: First message to the FileLogger!
└ @ Main log-to-file.md:51
┌ Info: Second message to the FileLogger?
└ @ Main log-to-file.md:73

As you can see, the first message is still in the file, and we only appended a second one.

The FileLogger uses a SimpleLogger internally, so the output formatting is the same as the SimpleLogger. If you want to control the output formatting you can use a FormatLogger (also from LoggingExtras.jl) instead. The FormatLogger accepts a formatting function as the first argument. The formatting function takes two arguments: (i) an IO stream to write the formatted message to and (ii) the logging arguments (see LoggingExtras.handle_message_args). Here is an example:

using Logging, LoggingExtras

logger = FormatLogger(open("logfile.txt", "w")) do io, args
    # Write the module, level and message only
    println(io, args._module, " | ", "[", args.level, "] ", args.message)
end

with_logger(logger) do
    @info "Info message to the FormatLogger!"
    @warn "Warning message to the FormatLogger!"
end

print(read("logfile.txt", String))
Main | [Info] Info message to the FormatLogger!
Main | [Warn] Warning message to the FormatLogger!

With the FormatLogger we need to open the file stream manually with the preferred option ("w", "a", etc) but, just like the FileLogger, it flushes the stream after every message.

See also: