【C++】spdlog光速入門,C++logger最簡單最快的庫

来源:https://www.cnblogs.com/jinyunshaobing/archive/2022/10/16/16797330.html
-Advertisement-
Play Games

參考文檔:https://spdlog.docsforge.com/master/ spdlog簡介 Very fast, header only, C++ logging library. 一個header-only的C++日誌庫,十分高效且易用。 獲取安裝方式 https://github.co ...


參考文檔:https://spdlog.docsforge.com/master/

spdlog簡介

Very fast, header only, C++ logging library.
一個header-only的C++日誌庫,十分高效且易用。

獲取安裝方式

https://github.com/gabime/spdlog
使用時只需要將git項目內的/include/spdlog文件夾整個放入項目的include目錄下即可

使用樣例

#include "spdlog/spdlog.h"

int main() 
{
    spdlog::info("Welcome to spdlog!");
    spdlog::error("Some error message with arg: {}", 1);
    
    spdlog::warn("Easy padding in numbers like {:08d}", 12);
    spdlog::critical("Support for int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42);
    spdlog::info("Support for floats {:03.2f}", 1.23456);
    spdlog::info("Positional args are {1} {0}..", "too", "supported");
    spdlog::info("{:<30}", "left aligned");
    
    spdlog::set_level(spdlog::level::debug); // Set global log level to debug
    spdlog::debug("This message should be displayed..");    
    
    // change log pattern
    spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
    
    // Compile time log levels
    // define SPDLOG_ACTIVE_LEVEL to desired level
    SPDLOG_TRACE("Some trace message with param {}", 42);
    SPDLOG_DEBUG("Some debug message");
}

快速入門

幾個核心概念

  • logger:日誌對象,每個日誌內包含一個sink組成的vector,每個sink可以分別設置優先順序,logger本身也可設置優先順序
  • sink:直譯是水槽,實際上是引流的對象或者可以認為是輸出目標,spdlog庫內置了多種不同類型的logger可供選擇
  • formatter:格式化對象,絕大部分情況下spdlog預設的格式就足夠用了,但是如果有個性化需求,可以進行自定義格式
  • level:日誌級別,不同的日誌庫可能會有不同的設置,但是基本情況下都會有debug、info、warn、error等的級別劃分來處理不同的情況,具體各個級別的情況可以根據自己的實際情況選取

邏輯關係:每個logger包含一個vector,該vector由一個或多個std::shared_ptr<sink>組成,logger的每條日誌都會調用sink對象,由sink對象按照formatter的格式輸出到sink指定的地方(有可能是控制台、文件等),接下來我們從內到外的講解spdlog的這三個核心組件

formatter

formatter也即格式化對象,用於控制日誌的輸出格式,spdlog自帶了預設的formatter,一般情況下,我們無需任何修改,直接使用即可。註意,每個sink會有一個formatter

預設formatter

預設formatter的格式為:[日期時間] [logger名] [log級別] log內容

[2022-10-13 17:00:55.795] [sidecar] [debug] found env KAFKA_PARTITION_VALUE : -1
[2022-10-13 17:00:55.795] [sidecar_config] [debug] kafka_config.kafka_brokers : localhost:9092
[2022-10-13 17:00:55.795] [sidecar_config] [debug] kafka_config.kafka_main_topic : workflow_queue
[2022-10-13 17:00:55.795] [sidecar_config] [debug] kafka_config.kafka_partition_value : -1
[2022-10-13 17:00:55.795] [sidecar] [info] SidecarConfig initialized

自定義formatter

如果預設的formatter不符合需求,可以自定義formatter,具體方式如下

  • set_parrtern(pattern_string);
    • 例如:
    • 全局級別的:spdlog::set_pattern(" [%H:%M:%S %z] [thread %t] %v ");
    • 單個logger級別的:some_logger->set_parttern(">>> %H:%M:%S %z %v <<<");
    • 單個sink級別的:some_sink-> set_parttern(".. %H: %M ..");
      其中用到了%H %M這些占位符,事實上它們都是預先設定好的,想要查看所有的占位符情況,可以參考以下網站:
      https://spdlog.docsforge.com/v1.x/3.custom-formatting/#pattern-flags

sink

每個sink對應著一個輸出目標和輸出格式,它內部包含一個formatter,輸出目標可以是控制台、文件等地方。
所有的sink都在命名空間spdlog::sinks下,可以自行探索

控制台sink

spdlog中創建控制台sink非常簡單,該方式創建的sink會輸出到命令行終端,且是彩色的(也可以選非彩色的,但是有彩色的應該都會選彩色的吧……)。尾碼的_mt代表多線程,_st代表單線程

auto sink1 = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();

文件sink

文件sink的類型有很多,這裡展示幾種經典類型

auto sink1 = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file_name);//最簡單的文件sink,只需要指定文件名

auto sink2 = std::make_shared<spdlog::sinks::daily_file_sink_mt>(log_file_name, path, 14, 22);//每天的14點22分在path下創建新的文件

auto sink3 = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_file_name, 1024 * 1024 * 10, 100, false);//輪轉文件,一個文件滿了會寫到下一個文件,第二個參數是單文件大小上限,第三個參數是文件數量最大值

其他sink

ostream_sink
syslog_sink
......
也可以通過繼承base_sink創建子類來自定義sink,具體可以參考:
https://spdlog.docsforge.com/v1.x/4.sinks/#implementing-your-own-sink

sink的flush問題

創建好sink後建議設置flush方式,否則可能無法立刻在file中看到logger的內容
以下為兩種重要的flush方式設置(直接設置全局)

spdlog::flush_every(std::chrono::seconds(1));
spdlog::flush_on(spdlog::level::debug);

logger

日誌對象,每個logger內包含了一個vector用於存放sink,每個sink都是相互獨立
因此一個日誌對象在輸出日誌時可以同時輸出到控制台和文件等位置

使用預設logger

如果整個項目中只需要一個logger,spdlog提供了最為便捷的預設logger,註意,該logger在全局公用,輸出到控制台、多線程、彩色

//Use the default logger (stdout, multi-threaded, colored)
spdlog::info("Hello, {}!", "World");

創建特定的logger

大部分情況下預設logger是不夠用的,因為我們可能需要做不同項目模塊各自的logger,可能需要logger輸出到文件進行持久化,所以創建logger是很重要的一件事。好在創建logger也是非常簡單的!

方式一:直接創建

與創建sink類似,我們可以非常便捷的創建logger
由於大部分時候一個logger只會有一個sink,所以spdlog提供了創建logger的介面並封裝了創建sink的過程

auto console = spdlog::stdout_color_mt("some_unique_name");//一個輸出到控制台的彩色多線程logger,可以指定名字
auto file_logger = spdlog::rotating_logger_mt("file_logger", "logs/mylogfile", 1048576 * 5, 3);//一個輸出到指定文件的輪轉文件logger,後面的參數指定了文件的信息

方式二:組合sinks方式創建

有時候,單sink的logger不夠用,那麼可以先創建sink的vector,然後使用sinks_vector創建logger
以下樣例中,首先創建了sink的vector,然後創建了兩個sink並放入vector,最後使用該vector創建了logger,其中,set_level的過程不是必須的,register_logger一般是必須的,否則只能在創建logger的地方使用該logger,關於register的問題可以往下看

    std::vector<spdlog::sink_ptr> sinks;

    auto sink1 = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    sink1->set_level(SidecarLoggers::getGlobalLevel());
    sinks.push_back(sink1);

    auto sink2 = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_file_name, 1024 * 1024 * 10, 100, false);
    sink2->set_level(spdlog::level::debug);
    sinks.push_back(sink2);
    
    auto logger = std::make_shared<spdlog::logger>("logger_name", begin(sinks), end(sinks));
    logger->set_level(spdlog::level::debug);
    spdlog::register_logger(logger);

logger的註冊與獲取

在一個地方創建了logger卻只能在該處使用肯定是不好用的,所以spdlog提供了一個全局註冊和獲取logger,我們只需要在某處先創建logger並註冊,那麼後面在其他地方使用時直接獲取就可以了
註冊:spdlog::register_logger()
獲取:spdlog::get()

//上面的代碼中我們註冊了一個logger,名字是logger_name,接下來嘗試獲取
auto logger = SidecarLoggers::getLogger("logger_name");

關於註冊與獲取需要註意的事

  • 必須先創建註冊才能獲取,建議每個模塊的logger都在整個模塊最開始初始化時創建並註冊。如果在全局嘗試獲取不存在的logger,會返回空指針,如果恰好又使用空指針嘗試輸出logger,會造成整個程式的崩潰(訪問非法記憶體了,segment fault)
  • 通過上述的方式一創建的logger是自動註冊的,不需要手動註冊,但是方式二創建的logger需要手動註冊
  • 一旦註冊,全局使用,名字標識logger,在各個模塊獲取同一個名字的logger會獲取到同一個logger的指針

logger的使用

獲取到一個logger之後,就可以愉快的使用它了,使用起來很簡單

logger->debug("this is a debug msg");
logger->warn("warn!!!!");
logger->info("hello world");
logger->error("燙燙燙燙");

logger的level設置

logger的預設level是info,如果處於開發環境或者生產環境,會只需要debug級別以上或者warn級別以上的log
要設置logger的級別,很簡單:

logger->set_level(spdlog::level::debug);

可以設置全局logger級別

spdlog::set_level(spdlog::level::warn);

可以設置sink級別的logger

sink1->set_level(spdlog::level::info);

註意:一個logger內假如有多個sink,那麼這些sink分別設置level是可以不同的,但是由於logger本身也有level,所以真正使用時,logger的level如果高於某個sink,會覆蓋該sink的level,所以建議此時把logger的level手動設置為debug(預設為info)

樣例代碼

以下代碼為本人對spdlog的簡單使用封裝,主要功能有:

  • 一鍵初始化,根據環境變數可設置修改logger級別等
  • 一鍵創建雙sink的logger(控制台和文件)
  • 一鍵獲取logger,假如logger不存在則創建

頭文件

#ifndef SIDECAR_LOGGER_H
#define SIDECAR_LOGGER_H

#include <stdlib.h>
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/daily_file_sink.h"
#include <vector>

class SidecarLoggers
{
public:
    static void init();
    static spdlog::level::level_enum getGlobalLevel();
    static std::vector<spdlog::sink_ptr> createSinks(const std::string &log_file_name);
    static void createLogger(const std::string &logger_name);
    static std::shared_ptr<spdlog::logger> getLogger(const std::string &logger_name);

private:
    static spdlog::level::level_enum global_level;
};

#endif

源文件

#include "sidecar_logger.h"

spdlog::level::level_enum SidecarLoggers::global_level = spdlog::level::info;

spdlog::level::level_enum SidecarLoggers::getGlobalLevel()
{
    return global_level;
}

std::vector<spdlog::sink_ptr> SidecarLoggers::createSinks(const std::string &log_file_name)
{
    std::vector<spdlog::sink_ptr> sinks;

    auto sink1 = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    sink1->set_level(SidecarLoggers::getGlobalLevel());
    sinks.push_back(sink1);

    auto sink2 = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_file_name, 1024 * 1024 * 10, 100, false);
    sink2->set_level(spdlog::level::debug);
    sinks.push_back(sink2);
    return sinks;
}

void SidecarLoggers::createLogger(const std::string &logger_name)
{
    std::string log_file_name = logger_name + "_log.txt";
    auto sinks = SidecarLoggers::createSinks(log_file_name);

    auto logger = std::make_shared<spdlog::logger>(logger_name, begin(sinks), end(sinks));
    logger->set_level(spdlog::level::debug);
    spdlog::register_logger(logger);
}

std::shared_ptr<spdlog::logger> SidecarLoggers::getLogger(const std::string &logger_name){
    auto logger = spdlog::get(logger_name);
    if(!logger){//looger指向為空
        createLogger(logger_name);
        logger = spdlog::get(logger_name);
    }
    return logger;
}


void SidecarLoggers::init()
{
    auto level = spdlog::level::debug;
    if (std::getenv("STAGE") != NULL)
    {
        std::string stage = std::getenv("STAGE");
        if (stage == "dev")
            level = spdlog::level::debug;
    }
    SidecarLoggers::global_level = level;

    spdlog::flush_every(std::chrono::seconds(1));
    spdlog::flush_on(spdlog::level::debug);

    SidecarLoggers::createLogger("sidecar");
}


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我們在安卓開發學習中會遇到需要返回數據的情況,這裡我們使用了幾個方法 1、startActivityForResult通過這個方法我們可以啟動另外一個活動 2、onBasePressed使用這個方法我們可以 點擊返回鍵返回數據到上一個活動 3、onActivityResult我們在需要接收返回數據的 ...
  • 語法規範 JavaScript嚴格區分大小寫,對空格、換行、縮進不敏感,建議語句結束加‘;’ JavaScript 會忽略多個空格。您可以向腳本添加空格,以增強可讀性。 JavaScript 程式員傾向於使用以小寫字母開頭的駝峰大小寫 firstName, lastName, masterCard, ...
  • Openlayers介紹 ​ Openlayers是一個基於Javacript開發,免費、開源的前端地圖開發庫,使用它,可以很容易的開發出WebGIS系統。目前Openlayers支持地圖瓦片、矢量數據等眾多地圖數據格式,支持比較完整的地圖交互操作。目前OpenLayers已經成為一個擁有眾多開發者 ...
  • 一篇文章帶你掌握主流辦公框架——SpringBoot 在之前的文章中我們已經學習了SSM的全部內容以及相關整合 SSM是Spring的產品,主要用來簡化開發,但我們現在所介紹的這款框架——SpringBoot,卻是用來簡化Spring開發的框架 SpringBoot是由Pivowtal團隊提供的全新 ...
  • 1.前言 首先回顧下代理模式(Proxy Pattern)的定義:代理模式指為其他對象提供一種代理,以控制這個對象的訪問,屬於結構型設計模式。其適用於在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端於目標對象之間起到中介的作用。 代理模式主要分為靜態代理和動態代理兩種方 ...
  • 挺早以前就刷了裡面一些題,結果不知道為啥登錄賬號刷題記錄又沒了,強迫症又讓我不想從中間開始刷。既然如此,那就從頭開始刷吧。QWQ Step one 第一題,沒啥好說的。 module top_module( output one ); // Insert your code here assign ...
  • 背景介紹: 最近在搭建一個公共項目,類似業務操作記錄上報的功能,就想著給業務方提供統一的sdk,在sdk中實現客戶端和服務端的交互封裝,對業務方幾乎是無感的。訪問關係如下圖: 訪問關係示意圖 這裡採用了http的方式進行交互,但是,如果每次介面調用都需要感知http的封裝,一來代碼重覆度較高,二來新 ...
  • JDBC和連接池04 10.資料庫連接池 10.1傳統連接弊端分析 傳統獲取Connection問題分析 傳統的 JDBC 資料庫連接使用DriverManager來獲取,每次向資料庫建立連接的時候都要將Connection載入到記憶體中,再驗證IP地址,用戶名和密碼(約0.05s~1s時間)。需要數 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...