Skip to content

Commit bc66bb4

Browse files
committed
Fix static initialization order bug (issue #4127)
1 parent 264c5de commit bc66bb4

File tree

1 file changed

+44
-31
lines changed

1 file changed

+44
-31
lines changed

OpenSim/Common/Logger.cpp

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,31 +35,43 @@
3535

3636
using namespace OpenSim;
3737

38+
// Static initialization of default and cout loggers.
39+
//
40+
// There are objects from other compilation units that call into the default
41+
// (and probably cout) logger objects during static initialization so use the
42+
// "Construct On First Use" idiom to ensure that each logger is allocated and
43+
// completely initialized before that happens the first time.
44+
45+
// Initialize a logger
3846
static void initializeLogger(spdlog::logger& l, const char* pattern) {
3947
l.set_level(spdlog::level::info);
48+
l.flush_on(spdlog::level::info);
4049
l.set_pattern(pattern);
4150
}
4251

43-
// cout logger will be initialized during static initialization time
44-
static std::shared_ptr<spdlog::logger> coutLogger =
45-
spdlog::stdout_color_mt("cout");
46-
47-
// default logger will be initialized during static initialization time
48-
static std::shared_ptr<spdlog::logger> defaultLogger =
49-
spdlog::default_logger();
50-
51-
// this function returns a dummy value so that it can be used in an assignment
52-
// expression (below) that *must* be executed in-order at static init time
53-
static bool initializeLogging() {
54-
initializeLogger(*coutLogger, "%v");
55-
initializeLogger(*defaultLogger, "[%l] %v");
56-
spdlog::flush_on(spdlog::level::info);
57-
return true;
52+
// Return a reference to the static cout logger object, allocating and
53+
// initializing it on the first call.
54+
static spdlog::logger& coutLoggerInternal() {
55+
static std::shared_ptr<spdlog::logger> l = spdlog::stdout_color_mt("cout");
56+
static bool first = true;
57+
if (first) {
58+
initializeLogger(*l, "%v");
59+
first = false;
60+
}
61+
return *l;
5862
}
5963

60-
// initialization of this variable will have the side-effect of completing the
61-
// initialization of logging
62-
static bool otherStaticInit = initializeLogging();
64+
// Return a reference to the static default logger object, allocating and
65+
// initializing it on the first call.
66+
static spdlog::logger& defaultLoggerInternal() {
67+
static std::shared_ptr<spdlog::logger> l = spdlog::default_logger();
68+
static bool first = true;
69+
if (first) {
70+
initializeLogger(*l, "[%l] %v");
71+
first = false;
72+
}
73+
return *l;
74+
}
6375

6476
// the file log sink (e.g. `opensim.log`) is lazily initialized.
6577
//
@@ -99,30 +111,30 @@ return true;
99111
// it should perform lazy initialization of the file sink
100112
spdlog::logger& Logger::getCoutLogger() {
101113
initFileLoggingAsNeeded();
102-
return *coutLogger;
114+
return coutLoggerInternal();
103115
}
104116

105117
// this function is only called when the caller is about to log something, so
106118
// it should perform lazy initialization of the file sink
107119
spdlog::logger& Logger::getDefaultLogger() {
108120
initFileLoggingAsNeeded();
109-
return *defaultLogger;
121+
return defaultLoggerInternal();
110122
}
111123

112124
static void addSinkInternal(std::shared_ptr<spdlog::sinks::sink> sink) {
113-
coutLogger->sinks().push_back(sink);
114-
defaultLogger->sinks().push_back(sink);
125+
coutLoggerInternal().sinks().push_back(sink);
126+
defaultLoggerInternal().sinks().push_back(sink);
115127
}
116128

117129
static void removeSinkInternal(const std::shared_ptr<spdlog::sinks::sink> sink)
118130
{
119131
{
120-
auto& sinks = defaultLogger->sinks();
132+
auto& sinks = defaultLoggerInternal().sinks();
121133
auto new_end = std::remove(sinks.begin(), sinks.end(), sink);
122134
sinks.erase(new_end, sinks.end());
123135
}
124136
{
125-
auto& sinks = coutLogger->sinks();
137+
auto& sinks = coutLoggerInternal().sinks();
126138
auto new_end = std::remove(sinks.begin(), sinks.end(), sink);
127139
sinks.erase(new_end, sinks.end());
128140
}
@@ -158,7 +170,7 @@ void Logger::setLevel(Level level) {
158170
}
159171

160172
Logger::Level Logger::getLevel() {
161-
switch (defaultLogger->level()) {
173+
switch (defaultLoggerInternal().level()) {
162174
case spdlog::level::off: return Level::Off;
163175
case spdlog::level::critical: return Level::Critical;
164176
case spdlog::level::err: return Level::Error;
@@ -218,7 +230,7 @@ bool Logger::shouldLog(Level level) {
218230
default:
219231
OPENSIM_THROW(Exception, "Internal error.");
220232
}
221-
return defaultLogger->should_log(spdlogLevel);
233+
return defaultLoggerInternal().should_log(spdlogLevel);
222234
}
223235

224236
void Logger::addFileSink(const std::string& filepath) {
@@ -229,10 +241,11 @@ void Logger::addFileSink(const std::string& filepath) {
229241
// downstream callers would find it quite surprising if the auto-initializer
230242
// runs *after* they manually specify a log, so just disable it
231243
fileSinkAutoInitDisabled = true;
244+
spdlog::logger& logger = defaultLoggerInternal();
232245

233246
if (m_filesink) {
234-
defaultLogger->warn("Already logging to file '{}'; log file not added. Call "
235-
"removeFileSink() first.", m_filesink->filename());
247+
logger.warn("Already logging to file '{}'; log file not added. Call "
248+
"removeFileSink() first.", m_filesink->filename());
236249
return;
237250
}
238251

@@ -243,9 +256,9 @@ void Logger::addFileSink(const std::string& filepath) {
243256
std::make_shared<spdlog::sinks::basic_file_sink_mt>(filepath);
244257
}
245258
catch (...) {
246-
defaultLogger->warn("Can't open file '{}' for writing. Log file will not be created. "
247-
"Check that you have write permissions to the specified path.",
248-
filepath);
259+
logger.warn("Can't open file '{}' for writing. Log file will not be created. "
260+
"Check that you have write permissions to the specified path.",
261+
filepath);
249262
return;
250263
}
251264
addSinkInternal(m_filesink);

0 commit comments

Comments
 (0)