-
Notifications
You must be signed in to change notification settings - Fork 4.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
after_open file-event handler firing twice for some reason? #3213
Comments
The fourth argument First open in constructor: spdlog/include/spdlog/sinks/rotating_file_sink-inl.h Lines 44 to 49 in e593f66
spdlog/include/spdlog/sinks/rotating_file_sink-inl.h Lines 102 to 107 in e593f66
spdlog/include/spdlog/sinks/rotating_file_sink-inl.h Lines 130 to 131 in e593f66
|
I've been trying to work around this in a number of ways, because I don't want the header to be duplicated in the logfile, but I want it also on the console... of course, one option would be to create an initial logger that outputs ONLY to the logfile for just the header, but that feels hacky. After your last comment @tt4g, I decided to move the console logging out of the file-open handler, to be one of the last things in the SpdLogger constructor. However, it's become apparent that there is an issue here, either some data race, or the way it's implemented. Here's the updated constructor: SpdLogger::SpdLogger (spdlog::level::level_enum const& consoleLevel) {
Profiler prof("spdLogger-init");
spdlog::init_thread_pool(2048, 1);
spdlog::file_event_handlers handlers;
auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::string logHeader(fmt::format(
"{0:*^80}\n"
"{1:*^80}\n",
fmt::format(" {} {} starting, {:%Y/%m/%d @ %H:%M:%S} ", PACKAGE, VERSION, fmt::localtime(time)),
fmt::format(" Logging any events of level {}, or higher. ", spdlog::level::to_string_view(consoleLevel))));
std::clog << "logger/debug: " << logHeader << std::endl;
spdlog::filename_t filename = getLogFilename_new().u8string();
// auto stderror_graber_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>();
m_sink = std::make_shared<spdlog::sinks::dist_sink_mt>();
auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
stdout_sink->set_color(spdlog::level::critical, logger_colors(bold_on_red)); // Error.
stdout_sink->set_color(spdlog::level::err, logger_colors(yellow_bold)); // Warning.
stdout_sink->set_color(spdlog::level::warn, logger_colors(green)); // Notice.
stdout_sink->set_color(spdlog::level::info, logger_colors(white));
stdout_sink->set_color(spdlog::level::debug, logger_colors(blue));
stdout_sink->set_color(spdlog::level::trace, logger_colors(cyan));
m_defaultLogger = std::make_shared<spdlog::async_logger>(Logger::subsystemToString(Logger::System::LOGGER), m_sink, spdlog::thread_pool(), spdlog::async_overflow_policy::block);
handlers.after_open = [this, logHeader](spdlog::filename_t filename, std::FILE *fstream) {
std::clog << "logger/debug: will call writeLogHeader()" << std::endl;
writeLogHeader(filename, fstream, logHeader);
};
// handlers.after_close = [this](spdlog::filename_t filename) { warn(Logger::System::LOGGER, "More details might be available in {}", filename); };
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(filename, 1024 * 1024 * 2, 5, true, handlers);
file_sink->set_level(spdlog::level::off);
m_sink->add_sink(file_sink);
m_sink->add_sink(stdout_sink);
m_sink->set_pattern("[%T]:::%^%n / %l%$::: %v");
const std::string _fl = std::string{spdlog::level::to_string_view(file_sink->level()).begin(),spdlog::level::to_string_view(file_sink->level()).end()};
const std::string _sl = std::string{spdlog::level::to_string_view(stdout_sink->level()).begin(), spdlog::level::to_string_view(stdout_sink->level()).end()};
const std::string file_level = std::string{_fl};
const std::string stdout_level = std::string{_sl};
std::clog << "logger/debug: file_sink->level(): " << file_level << std::endl;
std::clog << "logger/debug: stdout_sink->level(): " << stdout_level << std::endl;
notice(Logger::System::LOGGER, logHeader);
m_sink->flush();
file_sink->set_level(spdlog::level::debug);
} What is happening now is that the header message is still written to both the console and the file (twice to the file), even though the level of the file sink should be That could possibly be abscribed to the async logging of course, but there's also the fact that all the old log files are also being opened due to the rotation, and this means that they get the header appended at the end. |
The situation is so complex that the cause cannot be determined by looking only at the I think it is because log level filtering by sink is an asynchronous operation when using async_logger.
|
I think there is a race between modifying the level and writing to the file, but there is only a single file sink, and that goes inside the dist sink (which is a class member of SpdLogger), which is what everything will use to access the file eventually. The only access to the file sink other than through the I wonder... might this be worked around by allowing sinks to be set to a different level at construction time? |
If the headers are being written from |
I just realized my oversight. Calling
Suppose an exception is thrown due to a lock conflict, spdlog/include/spdlog/async_logger-inl.h Lines 63 to 67 in e593f66
Since spdlog's sink is not guaranteed to be reentrant, it is best not to call sink's logging and flush methods from callbacks. |
Those methods are just shortcuts to |
I don't really want to flush there, I added it to see if it made any difference. |
Then please remove the I don't think this leaves any questions, but what is the reason for not closing? |
I am sorry, actually, because I should have posted the entire code here, but when that was posted, the flush had already been removed from
[15:18:16]:::LOGGER / NOTICE::: ******** Performous 1.3.1-40-gcb369ffb3 starting, 2024/10/14 @ 15:18:16 ******** And then,
Here's how the log from the next-to-last run is currently looking like:
|
Note you construct the file sink with rotate_on_open=true which might lead to this behavior |
I understand that is the direct cause of the behavior, but my point is that it shouldn't behave like that. Or, come to think of it... why does the file even need to be opened for rotating it? Shouldn't it be possible to rotate it just by checking if the filename already exists? |
Also, I think it should be possible to change the initial level of a sink, or reading/modifying the level should use a mutex, or both. The way spdlog is now, even if I comment-out the line defining the handler, it's evident there is a data race in This: SpdLogger::SpdLogger (spdlog::level::level_enum const& consoleLevel) {
Profiler prof("spdLogger-init");
spdlog::init_thread_pool(2048, 1);
spdlog::file_event_handlers handlers;
auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::string logHeader(fmt::format(
"{0:*^80}\n"
"{1:*^80}\n",
fmt::format(" {} {} starting, {:%Y/%m/%d @ %H:%M:%S} ", PACKAGE, VERSION, fmt::localtime(time)),
fmt::format(" Logging any events of level {}, or higher. ", spdlog::level::to_string_view(consoleLevel))));
std::clog << "logger/debug: " << logHeader << std::endl;
spdlog::filename_t filename = getLogFilename_new().u8string();
m_sink = std::make_shared<spdlog::sinks::dist_sink_mt>();
auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
stdout_sink->set_color(spdlog::level::critical, logger_colors(bold_on_red)); // Error.
stdout_sink->set_color(spdlog::level::err, logger_colors(yellow_bold)); // Warning.
stdout_sink->set_color(spdlog::level::warn, logger_colors(green)); // Notice.
stdout_sink->set_color(spdlog::level::info, logger_colors(white));
stdout_sink->set_color(spdlog::level::debug, logger_colors(blue));
stdout_sink->set_color(spdlog::level::trace, logger_colors(cyan));
m_defaultLogger = std::make_shared<spdlog::async_logger>(Logger::subsystemToString(Logger::System::LOGGER), m_sink, spdlog::thread_pool(), spdlog::async_overflow_policy::block);
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(filename, 1024 * 1024 * 2, 5, true, handlers);
file_sink->set_level(spdlog::level::off);
m_sink->add_sink(file_sink);
m_sink->add_sink(stdout_sink);
m_sink->set_pattern("[%T]:::%^%n / %l%$::: %v");
const std::string _fl = std::string{spdlog::level::to_string_view(file_sink->level()).begin(),spdlog::level::to_string_view(file_sink->level()).end()};
const std::string _sl = std::string{spdlog::level::to_string_view(stdout_sink->level()).begin(), spdlog::level::to_string_view(stdout_sink->level()).end()};
const std::string file_level = std::string{_fl};
const std::string stdout_level = std::string{_sl};
std::clog << "logger/debug: file_sink->level(): " << file_level << std::endl;
std::clog << "logger/debug: stdout_sink->level(): " << stdout_level << std::endl;
notice(Logger::System::LOGGER, logHeader);
file_sink->set_level(spdlog::level::debug);
} produces this on the console:
And the log-file begins with
despite the file_sink being set to |
Assuming that some users are doing
As already answered, this is an expected behavior that occurs because of the use of async_logger (level change is a synchronous operation, log writing is an asynchronous operation). |
I don't know the conditions under which you write headers to the log, but if you assume it is when a new log file is created, then simply writing the headers only when you get the size of the file in the |
I am still in the playing around and working out kinks phase.
I got this constructor:
And here's
writeLogHeader()
:This function results in
"logger/debug: will call writeLogHeader()"
being printed twice to the console: One with the default spdlog format, and then once with my own custom format.And then, it's also appended to my log file, which will look like this:
P.S. both the
flush()
and theset_level
calls inwriteLogHeader
were put in there trying to work around the issue, and neither seems to have worked.The text was updated successfully, but these errors were encountered: