Skip to content

Commit c968350

Browse files
Merge pull request #104 from CliMA/ne/logging
Add logging documentation, `default_logger` function
2 parents 421a61f + a7e98aa commit c968350

File tree

14 files changed

+378
-27
lines changed

14 files changed

+378
-27
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ ClimaComms.jl Release Notes
44
main
55
-------
66

7+
v0.6.6
8+
-------
9+
- Replaced `MPIFileLogger` with `FileLogger` and added an `OnlyRootLogger` logger that silences non-root processes [PR 104](https://github.com/CliMA/ClimaComms.jl/pull/104).
10+
711
v0.6.5
812
-------
913

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ClimaComms"
22
uuid = "3a4d1b5c-c61d-41fd-a00a-5873ba7a1b0d"
33
authors = ["Kiran Pamnany <clima-software@caltech.edu>", "Simon Byrne <simonbyrne@caltech.edu>", "Charles Kawczynski <charliek@caltech.edu>", "Sriharsha Kandala <Sriharsha.kvs@gmail.com>", "Jake Bolewski <clima-software@caltech.edu>", "Gabriele Bozzola <gbozzola@caltech.edu>"]
4-
version = "0.6.5"
4+
version = "0.6.6"
55

66
[deps]
77
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"

docs/Manifest.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# This file is machine-generated - editing it directly is not advised
22

3-
julia_version = "1.11.2"
3+
julia_version = "1.11.0"
44
manifest_format = "2.0"
5-
project_hash = "c5b9e727593a1bc35ccae9b71e346465d8a7803c"
5+
project_hash = "d60839f726bd9115791d1a0807a21b61938765a9"
66

77
[[deps.ANSIColoredPrinters]]
88
git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c"

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
[deps]
22
ClimaComms = "3a4d1b5c-c61d-41fd-a00a-5873ba7a1b0d"
33
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
4+
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
5+
LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36"
46
MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ makedocs(
1515
pages = Any[
1616
"Home" => "index.md",
1717
"Developing with `ClimaComms`" => "internals.md",
18+
"Logging" => "logging.md",
1819
"Frequently Asked Questions" => "faqs.md",
1920
"APIs" => "apis.md",
2021
],

docs/src/apis.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ ClimaComms.graph_context
4949
Adapt.adapt_structure(::Type{<:AbstractArray}, ::ClimaComms.AbstractCommsContext)
5050
```
5151

52+
## Logging
53+
54+
```@docs
55+
ClimaComms.OnlyRootLogger
56+
ClimaComms.MPILogger
57+
ClimaComms.FileLogger
58+
```
59+
5260
## Context operations
5361

5462
```@docs

docs/src/logging.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Logging
2+
3+
## Overview
4+
5+
Logging is a crucial tool for debugging, monitoring, and understanding the behavior of your applications. ClimaComms extends Julia's built-in logging functionality (provided by [`Logging.jl`](https://docs.julialang.org/en/v1/stdlib/Logging/)) to work seamlessly in distributed computing environments, particularly with MPI.
6+
7+
### Julia's Logging System
8+
9+
Julia's standard library provides a flexible logging system through `Logging.jl`. Key concepts include:
10+
11+
- **Logger**: An object that handles log messages, determining how they are formatted and where they are sent
12+
- **Log Level**: Indicates the severity/importance of a message
13+
- **Log Record**: Contains the message and associated metadata (level, module, line number, etc.)
14+
15+
The default logger in Julia (as of v1.11) is `Logging.ConsoleLogger()`, which prints messages to the console. You can check your current logger using:
16+
17+
```julia
18+
using Logging; current_logger()
19+
```
20+
21+
## ClimaComms Logging Features
22+
23+
ClimaComms builds upon Julia's logging system by providing specialized loggers for distributed computing.
24+
25+
To set any of the loggers below as the global logger, use `Logging.global_logger(logger)`:
26+
27+
```julia
28+
using ClimaComms, Logging
29+
30+
ctx = ClimaComms.context()
31+
logger = ClimaComms.OnlyRootLogger(ctx)
32+
global_logger(logger)
33+
```
34+
35+
### OnlyRootLogger
36+
37+
`OnlyRootLogger(context)` returns a logger that silences non-root processes.
38+
If using MPI, this logger is enabled on the first [`ClimaComms.init`](@ref) call.
39+
40+
### FileLogger
41+
42+
`FileLogger(context, log_dir)` writes to `stdout` and to a file `log_dir/output.log` simultaneously.
43+
If using MPI, the `FileLogger` separates logs by MPI process into different files, so that process `i` will write to the `rank_$i.log` file in the `$log_dir` directory, where `$log_dir` is of your choosing.
44+
In this case, `$log_dir/output.log` is a [symbolic link](https://en.wikipedia.org/wiki/Symbolic_link) to the root process logs.
45+
In other words, you can always look at `$log_dir/output.log` for the output. Logging to `stdout` can be disabled by setting the keyword argument `log_stdout = false`.
46+
```julia
47+
using ClimaComms, Logging
48+
ctx = ClimaComms.context()
49+
logger = ClimaComms.FileLogger(ctx, "logs")
50+
51+
with_logger(logger) do
52+
@warn "Memory usage high" # Written to rank-specific log file
53+
end
54+
```
55+
This will output the following in both the REPL and `logs/rank_1.log`:
56+
```julia
57+
┌ Warning: Memory usage high
58+
└ @ Main REPL[6]:2
59+
```
60+
61+
62+
### MPILogger
63+
64+
`MPILogger(context)` adds an MPI rank prefix to all log messages:
65+
66+
```julia
67+
using ClimaComms, Logging
68+
ctx = ClimaComms.context()
69+
logger = MPILogger(ctx)
70+
71+
with_logger(logger) do
72+
@info "Processing data..." # Output: [P1] Info: Processing data...
73+
end
74+
```
75+
76+
## Log Levels
77+
78+
Julia provides four standard log levels, in order of increasing severity:
79+
80+
1. `Debug`: Detailed information for debugging
81+
2. `Info`: General information about program execution
82+
3. `Warn`: Warnings about potential issues
83+
4. `Error`: Error conditions that might still allow the program to continue
84+
85+
See [Julia documentation](https://docs.julialang.org/en/v1/stdlib/Logging/#Log-event-structure) for more detailed information.
86+
87+
You can define custom log levels using `LogLevel`:
88+
89+
```julia
90+
const Trace = LogLevel(-1000) # Lower number = less severe
91+
@macroexpand @logmsg Trace "Very detailed trace message"
92+
```
93+
94+
To disable all log messages at log levels equal to or less than a given `LogLevel`, use [`Logging.disable_logging(level)`](https://docs.julialang.org/en/v1/stdlib/Logging/#Logging.disable_logging).
95+
96+
## Filtering Log Messages
97+
98+
[LoggingExtras.jl](https://github.com/JuliaLogging/LoggingExtras.jl) provides powerful filtering capabilities through the `EarlyFilteredLogger(filter, logger)`, which takes two arguments:
99+
100+
- `filter(log_args)` is a function which takes in `log_args` and returns a Boolean determining if the message should be logged. `log_args` is a NamedTuple with fields `level`, `_module`, `id` and `group`. Example `filter` functions are provided below in the Common Use Cases.
101+
- `logger` is any existing logger, such as `Logging.ConsoleLogger()` or `MPILogger(ctx)`.
102+
103+
### Common Use Cases
104+
105+
#### How do I save log output to stdout and a file simultaneously?
106+
107+
`ClimaComms.FileLogger` logs to files and stdout simultaneously.
108+
For full customization, `LoggingExtras.TeeLogger(loggers)` composes multiple loggers, allowing for multiple loggers at once as shown below.
109+
110+
```julia
111+
using Logging, LoggingExtras
112+
113+
io = open("simulation.log", "w")
114+
loggers = (Logging.ConsoleLogger(stdout), Logging.ConsoleLogger(io))
115+
tee_logger = LoggingExtras.TeeLogger(loggers)
116+
117+
with_logger(tee_logger) do
118+
@warn "Log to stdout and file"
119+
end
120+
121+
close(io)
122+
```
123+
124+
#### How do I filter out warning messages?
125+
126+
```@example
127+
using Logging, LoggingExtras
128+
129+
function no_warnings(log_args)
130+
return log_args.level != Logging.Warn
131+
end
132+
133+
filtered_logger = EarlyFilteredLogger(no_warnings, Logging.current_logger())
134+
135+
with_logger(filtered_logger) do
136+
@warn "Hide this warning"
137+
@info "Display this message"
138+
end
139+
```
140+
141+
#### How do I filter out messages from certain modules?
142+
143+
We can create a custom filter that returns `false` if a log message originates from a list of excluded modules.
144+
145+
The same pattern can be reversed to filter messages only coming from certain modules.
146+
```
147+
using Logging, LoggingExtras
148+
149+
module_filter(excluded_modules) = log_args ->
150+
!(log_args._module in excluded_modules)
151+
152+
ModuleFilteredLogger(excluded) =
153+
EarlyFilteredLogger(module_filter(excluded), Logging.current_logger())
154+
# To test this logger:
155+
module TestModule
156+
using Logging
157+
function log_something()
158+
@info "This message will appear"
159+
end
160+
end
161+
162+
excluded = (Main, Base)
163+
with_logger(ModuleFilteredLogger(excluded)) do
164+
@info "Hide this message"
165+
TestModule.log_something()
166+
end
167+
```
168+
```julia
169+
[ Info: This message will appear
170+
```

ext/ClimaCommsMPIExt.jl

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module ClimaCommsMPIExt
22

3+
import Logging
34
import MPI
45
import ClimaComms
56

@@ -33,17 +34,21 @@ end
3334
function ClimaComms.init(ctx::ClimaComms.MPICommsContext)
3435
if !MPI.Initialized()
3536
MPI.Init()
37+
Logging.global_logger(ClimaComms.OnlyRootLogger(ctx))
3638
end
3739
# TODO: Generalize this to arbitrary accelerators
38-
if ctx.device isa ClimaComms.CUDADevice
40+
if ClimaComms.device(ctx) isa ClimaComms.CUDADevice
3941
if !MPI.has_cuda()
4042
error(
4143
"MPI implementation is not built with CUDA-aware interface. If your MPI is not OpenMPI, you have to set JULIA_MPI_HAS_CUDA to `true`",
4244
)
4345
end
4446
# assign GPUs based on local rank
4547
ClimaComms.local_communicator(ctx) do local_comm
46-
ClimaComms._assign_device(ctx.device, MPI.Comm_rank(local_comm))
48+
ClimaComms._assign_device(
49+
ClimaComms.device(ctx),
50+
MPI.Comm_rank(local_comm),
51+
)
4752
end
4853
end
4954
return ClimaComms.mypid(ctx), ClimaComms.nprocs(ctx)
@@ -300,19 +305,19 @@ function Base.summary(io::IO, ctx::ClimaComms.MPICommsContext)
300305

301306
if ClimaComms.iamroot(ctx)
302307
println(io, "Context: $(nameof(typeof(ctx)))")
303-
println(io, "Device: $(typeof(ctx.device))")
308+
println(io, "Device: $(typeof(ClimaComms.device(ctx)))")
304309
println(io, "Total Processes: $(ClimaComms.nprocs(ctx))")
305310
end
306311

307312
ClimaComms.barrier(ctx)
308313
rank = MPI.Comm_rank(ctx.mpicomm)
309314
node_name = MPI.Get_processor_name()
310315

311-
if ctx.device isa ClimaComms.CUDADevice
316+
if ClimaComms.device(ctx) isa ClimaComms.CUDADevice
312317
ClimaComms.local_communicator(ctx) do local_comm
313318
local_rank = MPI.Comm_rank(local_comm)
314319
local_size = MPI.Comm_size(local_comm)
315-
dev_summary = summary(io, ctx.device)
320+
dev_summary = summary(io, ClimaComms.device(ctx))
316321
println(
317322
io,
318323
"Rank: $rank, Local Rank: $local_rank, Node: $node_name, Device: $dev_summary",

0 commit comments

Comments
 (0)