Tutorial
This tutorial demonstrates how to use SciMLLogging.jl to add verbosity control to your Julia packages.
Basic Concepts
SciMLLogging.jl provides three main components:
Verbosity
- A sum type representing different verbosity levelsAbstractVerbositySpecifier{T}
- Base type for custom verbosity types@SciMLMessage
- Macro for emitting messages based on verbosity settings
Creating a Verbosity System
Step 1: Create Your Verbosity Type
Define a type that inherits from AbstractVerbositySpecifier{T}
with verbosity level fields:
using SciMLLogging
using Logging
struct MyVerbosity{T} <: AbstractVerbositySpecifier{T}
startup::MessageLevel
progress::MessageLevel
warnings::MessageLevel
function MyVerbosity{T}(;
startup = InfoLevel(),
progress = Silent(),
warnings = WarnLevel()
) where T
new{T}(startup, progress, warnings)
end
end
The type parameter T
determines whether verbosity is enabled:
T = true
: Messages will be processedT = false
: No runtime overhead (compiled away)
Step 2: Use the Verbosity System
# Create an enabled verbosity instance
verbose = MyVerbosity{true}()
# Emit messages at different levels
@SciMLMessage("Application starting...", verbose, :startup)
@SciMLMessage("Processing item 1/100", verbose, :progress)
@SciMLMessage("Memory usage high", verbose, :warnings)
[ Info: Application starting...
┌ Warning: Memory usage high
└ @ Main tutorial.md:52
Verbosity Levels
SciMLLogging provides several built-in verbosity levels:
using SciMLLogging
Silent() # No output
InfoLevel() # Informational messages
WarnLevel() # Warning messages
ErrorLevel() # Error messages
CustomLevel(-1000) # Custom log level with integer n
CustomLevel(-1000)
Dynamic Messages
Use functions for lazy evaluation of expensive message generation:
using SciMLLogging
using Logging
# Define the verbosity system (same as before)
struct MyVerbosity2{T} <: AbstractVerbositySpecifier{T}
progress::MessageLevel
MyVerbosity2{T}(progress = SciMLLogging.InfoLevel()) where T = new{T}(progress)
end
verbose = MyVerbosity2{true}()
# Variables from surrounding scope
iter = 5
total = 100
@SciMLMessage(verbose, :progress) do
percentage = iter / total * 100
"Progress: $iter/$total ($(round(percentage, digits=1))%)"
end
[ Info: Progress: 5/100 (5.0%)
The function is only evaluated if the message will actually be emitted.
Disabling Verbosity
For zero runtime cost when disabled:
using SciMLLogging
using Logging
struct MyVerbosity3{T} <: AbstractVerbositySpecifier{T}
startup::MessageLevel
MyVerbosity3{T}(startup = InfoLevel()) where T = new{T}(startup)
end
# Disabled verbosity
silent = MyVerbosity3{false}()
# This compiles to nothing - no runtime overhead
@SciMLMessage("This won't be shown", silent, :startup)
println("Message was not shown because verbosity is disabled")
Message was not shown because verbosity is disabled
Utility Functions
Converting to Integer
For compatibility with packages using integer verbosity levels:
using SciMLLogging
level = verbosity_to_int(WarnLevel()) # Returns 2
2
Converting to Boolean
For packages using boolean verbosity flags:
using SciMLLogging
is_verbose = verbosity_to_bool(InfoLevel()) # Returns true
println("SciMLLogging.InfoLevel() converts to: $is_verbose")
is_verbose = verbosity_to_bool(Silent()) # Returns false
println("SciMLLogging.Silent() converts to: $is_verbose")
SciMLLogging.InfoLevel() converts to: true
SciMLLogging.Silent() converts to: false
Custom Logger
Route messages to different destinations:
using SciMLLogging
using Logging
# Create a logger that sends warnings and errors to files
logger = SciMLLogger(
info_repl = true,
warn_repl = true,
error_repl = true,
warn_file = "warnings.log",
error_file = "errors.log"
)
# Define a simple verbosity system for testing
struct LoggerTestVerbosity{T} <: AbstractVerbositySpecifier{T}
test::MessageLevel
LoggerTestVerbosity{T}(test = WarnLevel()) where T = new{T}(test)
end
verbose = LoggerTestVerbosity{true}()
# Use the logger
with_logger(logger) do
@SciMLMessage("This warning is logged", verbose, :test)
end
# Clean up
isfile("warnings.log") && rm("warnings.log")
isfile("errors.log") && rm("errors.log")
┌ Warning: This warning is logged
└ @ Main tutorial.md:176
Complete Example
Here's a complete example showing a solver with verbosity:
using SciMLLogging
using Logging
using Random
Random.seed!(123) # For reproducibility
# Create verbosity type
struct SolverVerbosity{T} <: AbstractVerbositySpecifier{T}
initialization::MessageLevel
iterations::MessageLevel
convergence::MessageLevel
function SolverVerbosity{T}(;
initialization = InfoLevel(),
iterations = Silent(),
convergence = InfoLevel()
) where T
new{T}(initialization, iterations, convergence)
end
end
# Solver function
function my_solver(problem, verbose::SolverVerbosity)
@SciMLMessage("Initializing solver...", verbose, :initialization)
for i in 1:100
# Do iteration work...
@SciMLMessage(verbose, :iterations) do
"Iteration $i: residual = $(round(rand(), digits=4))"
end
if rand() < 0.05 # Converged (5% chance per iteration for demo)
@SciMLMessage("Converged at iteration $i", verbose, :convergence)
return i
end
end
@SciMLMessage("Failed to converge", verbose, :convergence)
return nothing
end
# Use the solver with verbosity
println("Running solver with verbosity enabled:")
verbose = SolverVerbosity{true}()
result = my_solver("problem", verbose)
println("Solver returned: $result")
println("\nRunning solver in silent mode:")
# Or with silent mode
silent = SolverVerbosity{false}()
result = my_solver("problem", silent) # No output
println("Solver returned: $result (no messages shown)")
Running solver with verbosity enabled:
[ Info: Initializing solver...
[ Info: Converged at iteration 7
Solver returned: 7
Running solver in silent mode:
Solver returned: 6 (no messages shown)
Testing with Verbosity
When testing code that uses SciMLLogging:
using SciMLLogging
using Logging
using Test
# Define a simple verbosity system for testing
struct TestVerbosity{T} <: AbstractVerbositySpecifier{T}
level::MessageLevel
TestVerbosity{T}(level = InfoLevel()) where T = new{T}(level)
end
@testset "Verbosity Tests" begin
verbose = TestVerbosity{true}()
# Test that message is logged at correct level
@test_logs (:info, "Test message") begin
@SciMLMessage("Test message", verbose, :level)
end
# Test that disabled verbosity produces no output
silent = TestVerbosity{false}()
@test_logs min_level=Logging.Debug begin
@SciMLMessage("Should not appear", silent, :level)
end
end
Test.DefaultTestSet("Verbosity Tests", Any[], 2, false, false, true, 1.758825802265028e9, 1.758825802502302e9, false, "tutorial.md")