3030
3131using namespace OpenSim ;
3232
33- std::shared_ptr<spdlog::logger> Logger::m_cout_logger =
33+ static void initializeLogger (spdlog::logger& l, const char * pattern) {
34+ l.set_level (spdlog::level::info);
35+ l.set_pattern (pattern);
36+ }
37+
38+ // cout logger will be initialized during static initialization time
39+ static std::shared_ptr<spdlog::logger> coutLogger =
3440 spdlog::stdout_color_mt (" cout" );
35- std::shared_ptr<spdlog::sinks::basic_file_sink_mt> Logger::m_filesink = {};
36- std::shared_ptr<spdlog::logger> Logger::m_default_logger;
37-
38- // Force creation of the Logger instane to initialize spdlog::loggers
39- std::shared_ptr<OpenSim::Logger> Logger::m_osimLogger = Logger::getInstance();
40-
41- Logger::Logger () {
42- m_default_logger = spdlog::default_logger ();
43- m_default_logger->set_level (spdlog::level::info);
44- m_default_logger->set_pattern (" [%l] %v" );
45- m_cout_logger->set_level (spdlog::level::info);
46- m_cout_logger->set_pattern (" %v" );
47- // This ensures log files are updated regularly, instead of only when the
48- // program shuts down.
41+
42+ // default logger will be initialized during static initialization time
43+ static std::shared_ptr<spdlog::logger> defaultLogger =
44+ spdlog::default_logger ();
45+
46+ // this function returns a dummy value so that it can be used in an assignment
47+ // expression (below) that *must* be executed in-order at static init time
48+ static bool initializeLogging () {
49+ initializeLogger (*coutLogger, " %v" );
50+ initializeLogger (*defaultLogger, " [%l] %v" );
4951 spdlog::flush_on (spdlog::level::info);
52+ return true ;
53+ }
54+
55+ // initialization of this variable will have the side-effect of completing the
56+ // initialization of logging
57+ static bool otherStaticInit = initializeLogging();
58+
59+ // the file log sink (e.g. `opensim.log`) is lazily initialized.
60+ //
61+ // it is only initialized when the first log message is about to be written to
62+ // it. Users *may* disable this functionality before the first log message is
63+ // written (or disable it statically, by setting OPENSIM_DISABLE_LOG_FILE)
64+ static std::shared_ptr<spdlog::sinks::basic_file_sink_mt> m_filesink = nullptr ;
65+
66+ // if a user manually calls `Logger::(remove|add)FileSink`, auto-initialization
67+ // should be disabled. Manual usage "overrides" lazy auto-initialization.
68+ static bool fileSinkAutoInitDisabled = false ;
69+
70+ // attempt to auto-initialize the file log, if applicable
71+ static bool initFileLoggingAsNeeded () {
72+ #ifdef OPENSIM_DISABLE_LOG_FILE
73+ // software builders may want to statically ensure that automatic file logging
74+ // *cannot* happen - even during static initialization. This compiler define
75+ // outright disables the behavior, which is important in Windows applications
76+ // that run multiple instances of OpenSim-linked binaries. In Windows, the
77+ // logs files collide and cause a "multiple processes cannot open the same
78+ // file" error).
79+ return true ;
80+ #else
81+ static bool initialized = []() {
82+ if (fileSinkAutoInitDisabled) {
83+ return true ;
84+ }
85+ Logger::addFileSink ();
86+ return true ;
87+ }();
88+
89+ return initialized;
90+ #endif
91+ }
92+
93+ // this function is only called when the caller is about to log something, so
94+ // it should perform lazy initialization of the file sink
95+ spdlog::logger& Logger::getCoutLogger () {
96+ initFileLoggingAsNeeded ();
97+ return *coutLogger;
98+ }
99+
100+ // this function is only called when the caller is about to log something, so
101+ // it should perform lazy initialization of the file sink
102+ spdlog::logger& Logger::getDefaultLogger () {
103+ initFileLoggingAsNeeded ();
104+ return *defaultLogger;
105+ }
106+
107+ static void addSinkInternal (std::shared_ptr<spdlog::sinks::sink> sink) {
108+ coutLogger->sinks ().push_back (sink);
109+ defaultLogger->sinks ().push_back (sink);
110+ }
111+
112+ static void removeSinkInternal (const std::shared_ptr<spdlog::sinks::sink> sink)
113+ {
114+ {
115+ auto & sinks = defaultLogger->sinks ();
116+ auto new_end = std::remove (sinks.begin (), sinks.end (), sink);
117+ sinks.erase (new_end, sinks.end ());
118+ }
119+ {
120+ auto & sinks = coutLogger->sinks ();
121+ auto new_end = std::remove (sinks.begin (), sinks.end (), sink);
122+ sinks.erase (new_end, sinks.end ());
123+ }
50124}
51125
52126void Logger::setLevel (Level level) {
@@ -79,8 +153,7 @@ void Logger::setLevel(Level level) {
79153}
80154
81155Logger::Level Logger::getLevel () {
82- const auto level = m_default_logger->level ();
83- switch (level) {
156+ switch (defaultLogger->level ()) {
84157 case spdlog::level::off: return Level::Off;
85158 case spdlog::level::critical: return Level::Critical;
86159 case spdlog::level::err: return Level::Error;
@@ -140,23 +213,32 @@ bool Logger::shouldLog(Level level) {
140213 default :
141214 OPENSIM_THROW (Exception, " Internal error." );
142215 }
143- return m_default_logger ->should_log (spdlogLevel);
216+ return defaultLogger ->should_log (spdlogLevel);
144217}
145218
146219void Logger::addFileSink (const std::string& filepath) {
220+ // this method is either called by the file log auto-initializer, which
221+ // should now be disabled, or by downstream code trying to manually specify
222+ // a file sink
223+ //
224+ // downstream callers would find it quite surprising if the auto-initializer
225+ // runs *after* they manually specify a log, so just disable it
226+ fileSinkAutoInitDisabled = true ;
227+
147228 if (m_filesink) {
148- warn (" Already logging to file '{}'; log file not added. Call "
229+ defaultLogger-> warn (" Already logging to file '{}'; log file not added. Call "
149230 " removeFileSink() first." , m_filesink->filename ());
150231 return ;
151232 }
233+
152234 // check if file can be opened at the specified path if not return meaningful
153235 // warning rather than bubble the exception up.
154236 try {
155237 m_filesink =
156238 std::make_shared<spdlog::sinks::basic_file_sink_mt>(filepath);
157239 }
158240 catch (...) {
159- warn (" Can't open file '{}' for writing. Log file will not be created. "
241+ defaultLogger-> warn (" Can't open file '{}' for writing. Log file will not be created. "
160242 " Check that you have write permissions to the specified path." ,
161243 filepath);
162244 return ;
@@ -165,6 +247,19 @@ void Logger::addFileSink(const std::string& filepath) {
165247}
166248
167249void Logger::removeFileSink () {
250+ // if this method is called, then we are probably at a point in the
251+ // application's lifetime where automatic log allocation is going to cause
252+ // confusion.
253+ //
254+ // callers will be surpised if, after calling this method, auto
255+ // initialization happens afterwards and the log file still exists - even
256+ // if they called it to remove some manually-specified log
257+ fileSinkAutoInitDisabled = true ;
258+
259+ if (m_filesink == nullptr ) {
260+ return ;
261+ }
262+
168263 removeSinkInternal (
169264 std::static_pointer_cast<spdlog::sinks::sink>(m_filesink));
170265 m_filesink.reset ();
@@ -178,24 +273,4 @@ void Logger::removeSink(const std::shared_ptr<LogSink> sink) {
178273 removeSinkInternal (std::static_pointer_cast<spdlog::sinks::sink>(sink));
179274}
180275
181- void Logger::addSinkInternal (std::shared_ptr<spdlog::sinks::sink> sink) {
182- m_default_logger->sinks ().push_back (sink);
183- m_cout_logger->sinks ().push_back (sink);
184- }
185-
186- void Logger::removeSinkInternal (const std::shared_ptr<spdlog::sinks::sink> sink)
187- {
188- {
189- auto & sinks = m_default_logger->sinks ();
190- auto to_erase = std::find (sinks.cbegin (), sinks.cend (), sink);
191- if (to_erase != sinks.cend ()) sinks.erase (to_erase);
192- }
193- {
194- auto & sinks = m_cout_logger->sinks ();
195- auto to_erase = std::find (sinks.cbegin (), sinks.cend (), sink);
196- if (to_erase != sinks.cend ()) sinks.erase (to_erase);
197- }
198- }
199-
200-
201276
0 commit comments