簡體   English   中英

根據全局日志級別優化離開日志功能調用

[英]Optimizing away log function calls depending on global log level

我是設計納米衛星的大學團隊的成員。 我們決定實現自己的(更精簡的)日志記錄庫,以代替Google的glogspdlogplogBoost::Log

  • 引入了不同日志級別的概念,以根據日志消息的嚴重性和是否預期發生將日志消息划分為子類別。
  • 此外,將可以定義“全局日志級別”。 嚴重性低於設置為全局日志級別的嚴重性的所有內容都不會被記錄。

由於明顯的限制,必須在編譯時優化全局日志級別以下的日志調用。

第一次嘗試是這樣的(單個頭文件):日志級別:

// We can set the global log level by defining one of these
#if defined LOGLEVEL_TRACE
#define LOGLEVEL Logger::trace
#elif defined LOGLEVEL_DEBUG
#define LOGLEVEL Logger::debug
#elif defined LOGLEVEL_INFO
[...]
#else
#define LOGLEVEL Logger::disabled
#endif

這些級別本身是enum成員:

enum LogLevel {
        trace = 32, // Very detailed information, useful for tracking the individual steps of an operation
        debug = 64, // General debugging information
        info = 96, // Noteworthy or periodical events
[...]
};

operator<<重載以提高可讀性:

template <class T>
Logger::LogEntry& operator<<(Logger::LogEntry& entry, const T value) {
    etl::to_string(value, entry.message, entry.format, true);

    return entry;
}

constexpr使編譯器執行我們想要的操作:

#define LOG(level)
    if (Logger::isLogged(level)) \
        if (Logger::LogEntry entry(level); true) \
            entry
// [...]
static constexpr bool isLogged(LogLevelType level) {
        return static_cast<LogLevelType>(LOGLEVEL) <= level;
    }

此代碼有很多問題(有關更多信息,請參見MR討論 )。

  • enum LogLevel調用運算符已添加,以返回新的static LogEntry
  • inline d強制const-O1傳播。
  • 已創建兩個LogEntry enums
  • 第二個是inline所有內容的nop
  • if constexpr已添加if constexpr語法。

以及更多內容(請參閱此處和下面的說明)。

那是當前代碼的(切碎)狀態:

#include <cstdint>
#include <string>

#define LOGLEVEL_EMERGENCY

#if defined LOGLEVEL_TRACE
#define LOGLEVEL Logger::trace
#elif defined LOGLEVEL_DEBUG
#define LOGLEVEL Logger::debug
#elif defined LOGLEVEL_INFO
#define LOGLEVEL Logger::info
#elif defined LOGLEVEL_NOTICE
#define LOGLEVEL Logger::notice
#elif defined LOGLEVEL_WARNING
#define LOGLEVEL Logger::warning
#elif defined LOGLEVEL_ERROR
#define LOGLEVEL Logger::error
#elif defined LOGLEVEL_EMERGENCY
#define LOGLEVEL Logger::emergency
#else
#define LOGLEVEL Logger::disabled
#endif

#define LOG_TRACE     (LOG<Logger::trace>())
#define LOG_DEBUG     (LOG<Logger::debug>())
#define LOG_INFO      (LOG<Logger::info>())
#define LOG_NOTICE    (LOG<Logger::notice>())
#define LOG_WARNING   (LOG<Logger::warning>())
#define LOG_ERROR     (LOG<Logger::error>())
#define LOG_EMERGENCY (LOG<Logger::emergency>())

class Logger {
public:

    Logger() = delete;

    typedef uint8_t LogLevelType;

    enum LogLevel : LogLevelType {
        trace = 32,
        debug = 64,
        info = 96,
        notice = 128,
        warning = 160,
        error = 192,
        emergency = 254,
        disabled = 255, 
    };

    enum class NoLogEntry {};

    struct LogEntry {
        std::string message = "";
        LogLevel level;

        explicit LogEntry(LogLevel level);

        ~LogEntry();

        LogEntry(LogEntry const&) = delete;

        template <class T>
        Logger::LogEntry& operator<<(const T value) noexcept {
            message.append(value);

            return *this;
        }

        Logger::LogEntry& operator<<(const std::string& value);
    };

    static constexpr bool isLogged(LogLevelType level) {
        return static_cast<LogLevelType>(LOGLEVEL) <= level;
    }

    static void log(LogLevel level, std::string & message);
};

template <Logger::LogLevel level>
constexpr inline auto LOG() {
    if constexpr (Logger::isLogged(level)) {
        return Logger::LogEntry(level);
    } else {
        return Logger::NoLogEntry();
    }
};

template <typename T>
[[maybe_unused]] constexpr Logger::NoLogEntry operator<<(const Logger::NoLogEntry noLogEntry, T value) {
    return noLogEntry;
}

int main() {
    LOG_NOTICE << "I am getting optimized away!";
    LOG_EMERGENCY << "I am not getting optimized away, and rightfully so";

    return 0;
}

正如你可以看到如編譯器資源管理器中, LOG_NOTICE是越來越在優化掉-O1

你有什么建議嗎?

我遇到了這個SO問題 ,但是由於我選擇了自定義的,從零開始的日志記錄庫實現,因此它無關緊要。

老實說,我不相信全局模式,例如全局記錄器或單例模式。 我也不認為強烈需要這樣的編譯時間記錄器優化。 只要不需要編寫消息(在不需要時將其轉換("The ratio is {}:{}", 20, 50) "The ratio is 20:50" ("The ratio is {}:{}", 20, 50)轉換為"The ratio is 20:50" ),您就不會受到性能的影響由於記錄問題。

實際上,我想說的是您不應在編譯時對其進行優化,否則您將無法重新配置就無法配置記錄器。 如果您必須重新編譯整個程序只是為了調整記錄器配置,這將是一個調試地獄,因為它可能會由於重新編譯而改變行為,並且在您想要在沒有經過用戶設計的用戶設備上測試程序的情況下,情況尤其糟糕有開發者環境。

我的建議是使用一個可配置的記錄器類,該類的實例傳遞給項目的所有主要組件。 並為其提供簡單的消息功能來處理日志消息。 我寫了一個示例(只需將消息級別變成枚舉之類)。

#include <iostream>
#include <mutex>
#include <memory>
#include <sstream>

using namespace std;

class CGlobalLogger
{
    public:
    void Print(const std::string& str)
    {
        std::lock_guard g(m_mutex);
        std::cout << str;
    }
    int  GetMessageLevel()
    {
        return m_messageLevel;
    }
    private:
    int        m_messageLevel = 5;
    std::mutex m_mutex;
};

void Print(std::stringstream& ss){};

template<typename... Args, typename Arg>
void Print(std::stringstream& ss, Arg&& arg,  Args&&... args)
{
    ss << arg;
    Print(ss, std::forward<Args>(args)...);
}


class CLocalLogger
{
    public:
    CLocalLogger() = default;
    CLocalLogger(std::shared_ptr<CGlobalLogger> global_logger, std::string local_name)
    {
        m_log           = std::move(global_logger);
        m_local_name    = std::move(local_name);
    }

    template<typename... Args>
    void Log(int messageLevel, Args&&... args)
    {
        if(messageLevel < m_log->GetMessageLevel())
        {
            return;
        }

        std::stringstream ss;

        Print(ss, m_local_name, std::forward<Args>(args)...);

        m_log->Print(ss.str());
    }
    private:
    std::string                     m_local_name;
    std::shared_ptr<CGlobalLogger>  m_log;
};

int main()
{
    CLocalLogger logger(std::make_shared<CGlobalLogger>(), "main: ");

    logger.Log(7, "Hello World! ", 5, " ", 7.);
    logger.Log(3, "I won't be printed... ", 5, "abs");

    return 0;
}

另外,您可以操縱本地記錄器,以便確定誰打印了消息,這在某些情況下對於全局記錄器來說可能很難。 假設您有一個普通的class A並且里面有一個印刷品。 所以,你給你的每個實例class A本地記錄儀具有獨特的本地名字,那么你可以告訴他們的情況下class A在日志中打印消息,而不是剛才說的class A打印出來。

PS有一些低級別的日志記錄,在任何發行版本中都不應出現,但通常您可以將其注釋掉,除非您處理的是大型項目,在這種情況下,您會將它們隱藏在定義或其他內容之后。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM