OGS
Logging.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "Logging.h"
5
6#include <spdlog/common.h>
7#include <spdlog/sinks/stdout_color_sinks.h>
8#include <spdlog/spdlog.h>
9
10#if defined(_WIN32) && defined(OGS_BUILD_WHEEL)
11#include <io.h>
12#include <spdlog/details/null_mutex.h>
13#include <spdlog/sinks/base_sink.h>
14
15#include <cstdio>
16#include <mutex>
17#endif
18
19#include <exception>
20#include <iostream>
21#include <map>
22
23#include "Error.h"
24#include "MPI.h"
25
26namespace
27{
28#if defined(_WIN32) && defined(OGS_BUILD_WHEEL)
29// Stdout sink for Windows wheel builds resolving the target on every write.
30//
31// spdlog's built-in Windows stdout sinks (wincolor_sink and
32// stdout_sink_base) cache the stdout OS HANDLE once at sink creation and
33// WriteFile() to that value forever. When OGS runs in-process (e.g. via the
34// pybind wheel) and the host redirects fd 1/2 afterwards (pytest fd capture
35// does dup2 per test), the CRT closes the previously cached handle. Windows
36// recycles handle values, so subsequent console writes can land in an
37// arbitrary file OGS opened later. Standalone executables are unaffected -
38// their std handles are fixed at process startup - so the stock colour sink
39// is kept there.
40//
41// _write(_fileno(stdout), ...) goes through the live CRT fd table at call
42// time, so it always follows the current redirection target. spdlog's
43// formatter already appends the platform eol, which survives the raw
44// _write. Trade-off: no Windows console colour.
45//
46// Another option would be running the wheel pytests in a subprocess but that
47// hides the defect.
48template <typename Mutex>
49class Fd1StdoutSink final : public spdlog::sinks::base_sink<Mutex>
50{
51protected:
52 void sink_it_(spdlog::details::log_msg const& msg) override
53 {
54 spdlog::memory_buf_t formatted;
55 this->formatter_->format(msg, formatted);
56 _write(_fileno(stdout), formatted.data(),
57 static_cast<unsigned int>(formatted.size()));
58 }
59
60 void flush_() override
61 {
62 // _write is unbuffered; nothing to flush.
63 }
64};
65#endif // defined(_WIN32) && defined(OGS_BUILD_WHEEL)
66
67#ifdef USE_PETSC
68static int mpi_rank = -1;
69void mpi_error_handler(const std::string& msg)
70{
71 assert(mpi_rank != -1);
72 std::cerr << "[" << mpi_rank << "] spdlog error: " << msg << std::endl;
73 std::abort();
74}
75#endif // USE_PETSC
76void error_handler(const std::string& msg)
77{
78 std::cerr << "spdlog error: " << msg << std::endl;
79 std::abort();
80}
81} // namespace
82
83namespace BaseLib
84{
85std::shared_ptr<spdlog::logger> console;
86
87void setConsoleLogLevel(std::string const& level_string)
88{
89 using namespace spdlog::level;
90 std::map<std::string, level_enum> string_to_log_level = {
91 {"none", off}, {"critical", critical}, {"error", err}, {"warn", warn},
92 {"info", info}, {"debug", debug}, {"all", trace}};
93
94 auto const level = string_to_log_level.find(level_string);
95 if (level == string_to_log_level.end())
96 {
97 ERR("'{:s}' is not a valid log level!", level_string);
98 OGS_FATAL("Wrong log level string.");
99 }
100 console->set_level(level->second);
101 spdlog::set_default_logger(console);
102}
103
104void initOGSLogger(std::string const& log_level)
105{
106 if (!console)
107 {
108#if defined(_WIN32) && defined(OGS_BUILD_WHEEL)
109 // Custom sink re-resolving stdout per write; see Fd1StdoutSink above.
110 // No colour on Windows console as a consequence.
111#if defined(USE_PETSC) || defined(_OPENMP)
112 console = std::make_shared<spdlog::logger>(
113 "ogs", std::make_shared<Fd1StdoutSink<std::mutex>>());
114#else
115 console = std::make_shared<spdlog::logger>(
116 "ogs",
117 std::make_shared<Fd1StdoutSink<spdlog::details::null_mutex>>());
118#endif
119 spdlog::register_logger(console);
120#else
121#if defined(USE_PETSC) || defined(_OPENMP)
122 // Thread-safe sink is required when OGS logs concurrently, e.g. from
123 // OpenMP assembly threads. Otherwise the non-thread-safe sink races and
124 // may corrupt output (e.g. console output interleaved into other
125 // files).
126 console = spdlog::stdout_color_mt("ogs");
127#else
128 console = spdlog::stdout_color_st("ogs");
129#endif
130#endif // defined(_WIN32) && defined(OGS_BUILD_WHEEL)
131 // Default pattern and error handler both for MPI and non-MPI builds.
132 spdlog::set_pattern("%^%l:%$ %v");
133 spdlog::set_error_handler(error_handler);
134
135#ifdef USE_PETSC
136 int mpi_init;
137 MPI_Initialized(&mpi_init);
138 if (mpi_init == 1)
139 {
140 MPI_Comm_rank(BaseLib::MPI::OGS_COMM_WORLD, &mpi_rank);
141 spdlog::set_pattern(fmt::format("[{}] %^%l:%$ %v", mpi_rank));
142 spdlog::set_error_handler(mpi_error_handler);
143 }
144#endif // USE_PETSC
145 }
147}
148} // namespace BaseLib
#define OGS_FATAL(...)
Definition Error.h:10
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
MPI_Comm OGS_COMM_WORLD
Definition MPI.cpp:9
void initOGSLogger(std::string const &log_level)
Definition Logging.cpp:104
void setConsoleLogLevel(std::string const &level_string)
Definition Logging.cpp:87
std::shared_ptr< spdlog::logger > console
Definition Logging.cpp:85
void error_handler(const std::string &msg)
Definition Logging.cpp:76
void mpi_error_handler(const std::string &msg)
Definition Logging.cpp:69