Loading...
Searching...
No Matches
logger.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * spdlog-based logging wrapper.
5 *
6 * Usage:
7 * #include "io/logger.hpp"
8 * log::info("lattice parameter = {:.4f} a.u.", alat);
9 * log::debug("iteration {} delta = {:.2e}", iter, de);
10 */
11
12#include <spdlog/sinks/basic_file_sink.h>
13#include <spdlog/sinks/stdout_color_sinks.h>
14#include <spdlog/spdlog.h>
15
16#include <cstdio>
17#include <memory>
18#include <string>
19#include <vector>
20
21namespace q3 {
22namespace log {
23
24/**
25 * Initialize the logger. Call once at startup.
26 */
27inline std::shared_ptr<spdlog::sinks::stdout_color_sink_mt>& console_sink() {
28 static std::shared_ptr<spdlog::sinks::stdout_color_sink_mt> sink;
29 return sink;
30}
31
32/**
33 * The level init() left the logger at, so a later change can be undone.
34 *
35 * Not simply info: a run that asked for a log file deliberately drops the
36 * logger to debug so the file sink sees records the console does not, and
37 * restoring info instead would empty that file. The console sink needs no
38 * such memory -- init() always pins it at info.
39 */
40inline spdlog::level::level_enum& baseline_logger_level() {
41 static spdlog::level::level_enum level = spdlog::level::info;
42 return level;
43}
44
45/**
46 * Move the console sink's own threshold, unless this process was silenced.
47 *
48 * The sink filters after the logger does, so a logger-level change on its own
49 * never reaches the terminal. Moving the sink unconditionally would have the
50 * opposite fault: silence() takes it to off on a non-root MPI rank, and that
51 * decision must outlive any later level change.
52 */
53inline void set_console_level_unless_silenced(spdlog::level::level_enum level) {
54 if (console_sink() && console_sink()->level() != spdlog::level::off) {
55 console_sink()->set_level(level);
56 }
57}
58
59inline void init(const std::string& log_file = "") {
60 console_sink() = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
61 console_sink()->set_pattern("%v"); // Clean output — no timestamp/level prefix
62 console_sink()->set_level(spdlog::level::info);
63
64 std::vector<spdlog::sink_ptr> sinks{console_sink()};
65
66 /**
67 * The file is a second sink on the same logger, not a logger of its own.
68 * It used to be the latter -- registered under its own name and never
69 * written to, because every log:: call goes to the default logger -- so a
70 * requested log file was reliably created and reliably empty.
71 *
72 * It is given a lower threshold than the console on purpose: a file is
73 * asked for when the console has already proved too terse, so it carries
74 * debug records the console drops. That is also why the logger's own
75 * threshold moves down with it -- a logger filters before its sinks do, so
76 * leaving it at info would discard those records before the file sink ever
77 * saw them.
78 */
79 if (!log_file.empty()) {
80 auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file, true);
81 file_sink->set_pattern("%v");
82 file_sink->set_level(spdlog::level::debug);
83 sinks.push_back(file_sink);
84 }
85
86 auto logger = std::make_shared<spdlog::logger>("q3", sinks.begin(), sinks.end());
87 baseline_logger_level() = log_file.empty() ? spdlog::level::info : spdlog::level::debug;
88 logger->set_level(baseline_logger_level());
89 logger->flush_on(spdlog::level::warn);
90 spdlog::set_default_logger(logger);
91}
92
93/// Set log level: "trace", "debug", "info", "warn", "error", "off"
94inline void set_level(const std::string& level) {
95 spdlog::level::level_enum parsed_level = spdlog::level::from_str(level);
96 spdlog::set_level(parsed_level);
98}
99
100/**
101 * Apply an input deck's control.verbosity to the log level.
102 *
103 * `explicit_log_level` is whatever --log-level was given, empty when it was
104 * not. An explicit flag always wins: it is the more specific request, and a
105 * deck carried from machine to machine must not talk over what the command
106 * line asked for.
107 *
108 * "high" opens up the per-iteration and per-phase diagnostics, which are
109 * logged at debug. "default" puts the level back where init() left it rather
110 * than assuming info, so that a log file keeps receiving those records and so
111 * that one in-process run cannot inherit the previous deck's verbosity.
112 */
113inline void apply_verbosity(const std::string& verbosity, const std::string& explicit_log_level) {
114 if (!explicit_log_level.empty()) {
115 return;
116 }
117 if (verbosity == "high") {
118 set_level("debug");
119 return;
120 }
121 spdlog::set_level(baseline_logger_level());
122 set_console_level_unless_silenced(spdlog::level::info);
123}
124
125/**
126 * Push every buffered record to its sink.
127 *
128 * Needed before any exit that skips destructors -- MPI_Abort, std::_Exit --
129 * because spdlog buffers, and a process killed with records still in the
130 * buffer loses them. The message being lost is invariably the one explaining
131 * why the process is exiting, which is the one worth keeping.
132 */
133inline void flush() {
134 spdlog::default_logger()->flush();
135}
136
137/**
138 * Silence this process's console output (e.g. non-root MPI ranks).
139 *
140 * Silences the console sink, not the logger. Setting the logger's own level to
141 * off would silence its file sink too, and the one thing a non-root rank has
142 * to be able to do is write its own log file -- which is the entire reason
143 * init_mpi() gives each rank a numbered one. Falls back to the logger-wide
144 * setting only if init() has not run.
145 */
146inline void silence() {
147 if (console_sink()) {
148 console_sink()->set_level(spdlog::level::off);
149 return;
150 }
151 spdlog::set_level(spdlog::level::off);
152}
153
154/**
155 * Report a fatal error on stderr, whatever the current log level.
156 *
157 * error() routes through spdlog, and silence() takes the console sink off --
158 * which means that on a silenced rank a fatal error still reaches that rank's
159 * log file, if it has one, but never the console the user is watching. A
160 * process about to die owes its user a reason on the console whether or not a
161 * log file was asked for, so this bypasses spdlog and
162 * writes straight to stderr, flushing before it returns: MPI_Abort neither
163 * unwinds nor flushes, and a rank that exits nonzero takes its peers down with
164 * it, so anything still sitting in a buffer at that point is never seen.
165 */
166inline void fatal(const std::string& message) {
167 std::fputs(message.c_str(), stderr);
168 std::fputc('\n', stderr);
169 std::fflush(stderr);
170}
171
172// Convenience wrappers that forward to spdlog
173template <typename... Args>
174inline void trace(spdlog::format_string_t<Args...> fmt, Args&&... args) {
175 spdlog::trace(fmt, std::forward<Args>(args)...);
176}
177
178template <typename... Args>
179inline void debug(spdlog::format_string_t<Args...> fmt, Args&&... args) {
180 spdlog::debug(fmt, std::forward<Args>(args)...);
181}
182
183template <typename... Args> inline void info(spdlog::format_string_t<Args...> fmt, Args&&... args) {
184 spdlog::info(fmt, std::forward<Args>(args)...);
185}
186
187template <typename... Args> inline void warn(spdlog::format_string_t<Args...> fmt, Args&&... args) {
188 spdlog::warn(fmt, std::forward<Args>(args)...);
189}
190
191template <typename... Args>
192inline void error(spdlog::format_string_t<Args...> fmt, Args&&... args) {
193 spdlog::error(fmt, std::forward<Args>(args)...);
194}
195
196/**
197 * A blank line, then a section title at the common margin, underlined.
198 *
199 * Every block-level heading a run prints goes through here, so the margin and
200 * the separation between blocks are decided once rather than at each print
201 * site -- which is how the output came to carry three competing left margins
202 * and three header styles. The title text itself is passed through unchanged,
203 * because several of these headings are what the validation suite's stdout
204 * parsers key on.
205 */
206inline void section(const std::string& title) {
207 info("");
208 info(" {}", title);
209 info(" {}", std::string(title.size(), '-'));
210}
211
212} // namespace log
213} // namespace q3
void section(const std::string &title)
Definition logger.hpp:206
void fatal(const std::string &message)
Definition logger.hpp:166
void silence()
Definition logger.hpp:146
void set_console_level_unless_silenced(spdlog::level::level_enum level)
Definition logger.hpp:53
void debug(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:179
void init(const std::string &log_file="")
Definition logger.hpp:59
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
spdlog::level::level_enum & baseline_logger_level()
Definition logger.hpp:40
void error(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:192
void flush()
Definition logger.hpp:133
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
void trace(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:174
void apply_verbosity(const std::string &verbosity, const std::string &explicit_log_level)
Definition logger.hpp:113
void set_level(const std::string &level)
Set log level: "trace", "debug", "info", "warn", "error", "off".
Definition logger.hpp:94
std::shared_ptr< spdlog::sinks::stdout_color_sink_mt > & console_sink()
Definition logger.hpp:27