gabime / spdlog

Fast C++ logging library.
Other
24.39k stars 4.55k forks source link

[question] Interval based flush #3192

Closed yalov closed 1 month ago

yalov commented 1 month ago

from the wiki:

Interval based flush spdlog supports setting flush interval. This is implemented by a single worker thread that periodically calls flush() on each logger. For example, turn on periodic flush with interval of 5 seconds for all registered loggers: spdlog::flush_every(std::chrono::seconds(5));

Note Use this only on thread safe loggers, since the periodic flush happens from a different thread.

Does the note mean that flush_every() can be used only if all loggers in the application are *****_mt ?

What will happened if some of sinks are *_mt and some **_st ?

For example, one of my logger is a file logger, that log csv in one thread, wherefore it uses ****_st sink. How to make it flush, for example, every 5 seconds?

tt4g commented 1 month ago

Does the note mean that flush_every() can be used only if all loggers in the application are *****_mt ?

Right.

***_st sink is not thread-safe and is not guaranteed to work.

yalov commented 1 month ago

***_st sink is not thread-safe and is not guaranteed to work.

"not guaranteed to work" mean:

tt4g commented 1 month ago

The C++ specification defines that access from multiple threads to a variable that is not thread-safe causes a race condition, resulting to undefined behavior.

See also: https://stackoverflow.com/a/48447270

yalov commented 1 month ago

So, there is several options:

std::thread loggers_flush_thread([]() {
    auto logger = spdlog::get("logger_mt_that_need_periodic_flush");

    while (true) {
        std::this_thread::sleep_for(std::chrono::milliseconds(5000));

        if (logger)
            logger->flush();
        else
            logger = spdlog::get("logger_mt_that_need_periodic_flush");
    }
}).join();
tt4g commented 1 month ago

Yes, but both of the two methods you have presented do the same thing.

yalov commented 1 month ago

Yes, but both of the two methods you have presented do the same thing.

I do not understand, for the first one I need to make all logger _mt, even though I need to flush periodically only one of them, but for the second I can make only one logger _mt, and all other leave as *_st, isn't that a difference?

tt4g commented 1 month ago

You are correct in your opinion. It is true that the loggers_flush_thread thread flushes only logger_mt_that_need_periodic_flush loggers, so it is not exactly the same behavior as spdlog::flush_every(), which flushes all loggers. However, having one thread just to flush one logger is cost prohibitive.

BTW, it is not exactly true that logger_mt is thread-safe. The spdlog logger is always thread-safe. `spdlog::_logger_mt()andspdlog::***_logger_st()` are APIs to create a set of logger and sink, the only difference is whether the sink that the logger has is thread-safe (_mt) or not thread-safe (_st).

yalov commented 1 month ago

Thank you, to summarize and with real code samples: I have 1 default logger (sink_mt)

main(){
    //....
    std::vector<spdlog::sink_ptr> sink_list;
    auto sink1 = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("sink1", "file1");
    sink_list.push_back(sink);
    auto sink2 = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    sink_list.push_back(stdout_sink);
    auto dup_sink = std::make_shared<spdlog::sinks::dup_filter_sink_mt>(std::chrono::milliseconds(2000));
    dup_sink->set_sinks(sink_list);
    auto logger = std::make_shared<spdlog::logger>("main", dup_sink);
    spdlog::set_default_logger(logger);
}

And let say 10 simple file loggers (sink_st), every one writes into one file in a separate thread its own csv thing. Two of them are the most important, so I want to make sure they periodically flush to file. Every logger is a member of a class.

class class1{
    std::shared_ptr<spdlog::logger> logger1;

    void create(){
        auto sink = std::make_shared<spdlog::sinks::rotating_file_sink_st>("sink1", "file1");
        logger1 = std::make_shared<spdlog::logger>("logger1", sink);
    }

    run(){
        while(true){
            // .......
            logger1->info(message);
        }
    }
}

So, that is the recommended way to make it happen?

tt4g commented 1 month ago

First, spdlog::flush_every() will only flush loggers registered in the spdlog registry.

As far as I can see from the code provided, only logger is registered in the spdlog registry by spdlog::default_logger() API. That is, spdlog::flush_every() does nothing for rotating_file_sink_st.

Change sink to _mt to at least be able to flush from another thread. If there is a reason why you cannot use _mt, give up interval flushes.