Developer Tutorial: Adding SciMLLogging to Your Package

This tutorial is for Julia package developers who want to integrate SciMLLogging.jl into their packages to provide users with fine-grained verbosity control.

Overview

SciMLLogging.jl provides four main components for package developers:

  1. AbstractVerbositySpecifier - Base type for creating custom verbosity types
  2. @SciMLMessage - Macro for emitting conditional log messages
  3. Log levels - Predefined log levels (Silent, DebugLevel, InfoLevel, WarnLevel, ErrorLevel, CustomLevel(n)). These are the fields of the AbstractVerbositySpecifiers that determine which messages get logged, and at what log level.
  4. Verbosity preset levels - None, Minimal, Standard, Detailed, All. These represent predefined sets of log levels.

AbstractVerbositySpecifier

`AbstractVerbositySpecifier` is the base type that package developers implement a subtype of to create custom verbosity type for their packages.

@SciMLMessage

In order to use the the @SciMLMessage macro, simply choose which of the fields of your AbstractVerbositySpecifier should control that particular message. Then when the macro is called, the field of the verbosity object corresponding with the option argument to the macro is used to control the logging of the message.

Step 1: Design Your Verbosity Interface

First, decide what aspects of your package should be controllable by users. For example, a solver might have:

  • Initialization messages
  • Iteration progress
  • Convergence information
  • Error control information

Step 2: Create Your Verbosity Type

Define a struct that subtypes AbstractVerbositySpecifier:

using SciMLLogging
using ConcreteStructs: @concrete

@concrete struct MySolverVerbosity <: AbstractVerbositySpecifier
    initialization
    iterations
    convergence
    warnings
end

# Constructor with defaults
function MySolverVerbosity(;
        initialization = InfoLevel(),
        iterations = Silent(),
        convergence = InfoLevel(),
        warnings = WarnLevel()
)
    MySolverVerbosity(initialization, iterations, convergence, warnings)
end
  • Use @concrete from ConcreteStructs.jl for better performance
  • Each field represents a category of messages your package can emit

Step 3: Add Convenience Constructors

Make it easy for users to create verbosity instances. Perhaps include a constructor that can take a AbstractVerbosityPreset, and use it to set the rest of the fields, and a constructor that takes all keyword arguments:

# Preset-based constructor (optional)
function MySolverVerbosity(preset::AbstractVerbosityPreset)
    if preset isa None
        MySolverVerbosity(
            initialization = Silent(),
            iterations = Silent(),
            convergence = Silent(),
            warnings = Silent()
        )
    elseif preset isa All
        MySolverVerbosity(
            initialization = InfoLevel(),
            iterations = InfoLevel(),
            convergence = InfoLevel(),
            error_control = WarnLevel()
        )
    elseif preset isa Minimal
        MySolverVerbosity(
            initialization = Silent(),
            iterations = Silent(),
            convergence = ErrorLevel(),
            error_control = ErrorLevel()
        )
    else
        MySolverVerbosity()  # Default
    end
end

Step 4: Integrate Messages Into Your Code

Use @SciMLMessage throughout your package code:

function my_solve(problem, verbose::MySolverVerbosity)
    @SciMLMessage("Initializing solver for $(typeof(problem))", verbose, :initialization)

    # Setup code here...

    for iteration in 1:maxiters
        # Solver iteration...

        @SciMLMessage(verbose, :iterations) do
            "Iteration $iteration: residual = $(compute_residual())"
        end

        if converged
            @SciMLMessage("Converged after $iteration iterations", verbose, :convergence)
            return solution
        end

        if should_warn_about_something()
            @SciMLMessage("Convergence is slow, consider adjusting parameters", verbose, :error_control)
        end
    end

    @SciMLMessage("Failed to converge after $maxiters iterations", verbose, :convergence)
    return nothing
end

Step 5: Document for Users

Provide clear documentation for your users:

"""
    MySolverVerbosity(; kwargs...)

Controls verbosity output from MySolver functions.

# Keyword Arguments
- `initialization = InfoLevel()`: Messages about solver setup
- `iterations = Silent()`: Per-iteration progress messages
- `convergence = InfoLevel()`: Convergence/failure notifications
- `error_control = WarnLevel()`: Messages about solver error control

# Constructors
- `MySolverVerbosity()`: Default enabled verbosity
- `MySolverVerbosity(None())`: Disabled (zero overhead)
- `MySolverVerbosity(All())`: Enable all message categories
- `MySolverVerbosity(Minimal())`: Only errors and convergence

# Example

julia

Default verbosity

verbose = MySolverVerbosity()

Custom verbosity - show everything except iterations

verbose = MySolverVerbosity(iterations = Silent())

Silent mode

verbose = MySolverVerbosity( initialization = Silent(), iterations = Silent(), convergence = Silent(), warnings = Silent() )

"""

Complete Example

Here's a complete minimal example:

using SciMLLogging
using ConcreteStructs: @concrete
import SciMLLogging: AbstractVerbositySpecifier

@concrete struct ExampleVerbosity <: AbstractVerbositySpecifier
    progress
end

# Constructor with default
ExampleVerbosity(; progress = InfoLevel()) = ExampleVerbosity(progress)

function solve_example(n::Int, verbose::ExampleVerbosity)
    result = 0
    for i in 1:n
        result += i
        @SciMLMessage("Step $i: sum = $result", verbose, :progress)
    end
    return result
end
solve_example (generic function with 1 method)

This example shows the minimal structure needed to integrate SciMLLogging into a package.