【C++】SPDLOGを用いて高速かつ安全にログを出力する

# はじめに
タイムスタンプ付きログを高速・安全に出力できる「SPDLOG」を業務で使う機会があったため、仕様・使い方をまとめました。



# SPDLOGとは？

GitHubで公開されているヘッダオンリーのC++用のログ出力ライブラリで、簡単に導入・高機能なログ出力を行うことができます。
https://github.com/gabime/spdlog

##  メリット
* iostream の代替であるオープンソースのフォーマットライブラリ「[{fmt}](https://github.com/fmtlib/fmt)」を使用しているため、pythonのformat()メソッドのようにシンプルに記述できる
* 信頼性・安全性が高い
* パフォーマンスが良い(速い)
* MITライセンスである

## デメリット
* テンプレートをたくさん使用しているためコンパイルが遅くなる

# 導入方法

ヘッダーのみのバージョンと、コンパイル済みバージョンの２種類の方法があります。
本記事では導入が簡単なヘッダーのみのバージョンを用いた方法を紹介します。

1.　Gitを用いてspdlogをクローンする。
2.　以下の構造になるようにspdlog\include\spdlogをコピーする。
```
.
├── Project1.sln
├── main.cpp
└── spdlog　←コピー
     ├── async.h
     ├── async_logger-inl.h
     ├── async_logger.h
     ├── common-inl.h
     ├── common.h      
     :
```
3.　プロジェクトのプロパティを開き、[C/C++]-[全般]の「追加のインクルードディレクトリ」で「$(SolutionDir);」を指定する。

# 使用例

## 実行環境
* Windows11　22h2
* VisualStudio2022

## コンソールにログ出力を行う
```
#include "spdlog/spdlog.h"

int main()
{
    spdlog::set_level(spdlog::level::trace);
    spdlog::trace("spdlog trace");
    spdlog::set_level(spdlog::level::debug);
    spdlog::debug("spdlog debug");
    spdlog::info("spdlog info");
    spdlog::error("spdlog warning");
    spdlog::warn("spdlog error");
    spdlog::critical("spdlog critical");

    // change log pattern
    spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
    spdlog::info("spdlog info");
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840516/8fe7b3b4-d68f-4582-5c07-a8f2a52e8bac.png)
trace、debugを出力するためにはspdlog::set_levelが必要でした。
デフォルトでタイムスタンプ付きログが出力されますが、タイムスタンプの書式を変えて出力することも可能です。
## ファイルにログ出力を行う
```
#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"

void main() {
	auto file_logger = spdlog::basic_logger_mt("basic_logger", "C:\\log\\log.txt");
	spdlog::set_level(spdlog::level::trace);
	file_logger->trace("spdlog trace");
	spdlog::set_level(spdlog::level::debug);
	file_logger->debug("spdlog debug");
	file_logger->info("spdlog info");
	file_logger->warn("spdlog warning");
	file_logger->error("spdlog error");
	file_logger->critical("spdlog critical");
	file_logger->flush();
}
```
```:log.txt
[2023-05-30 17:29:09.092] [basic_logger] [trace] spdlog trace
[2023-05-30 17:29:09.094] [basic_logger] [debug] spdlog debug
[2023-05-30 17:29:09.094] [basic_logger] [info] spdlog info
[2023-05-30 17:29:09.094] [basic_logger] [warning] spdlog warning
[2023-05-30 17:29:09.094] [basic_logger] [error] spdlog error
[2023-05-30 17:29:09.094] [basic_logger] [critical] spdlog critical
```

# 躓いたエラーなど

## ファイルにログが出力できない
```
1. my_logger->flush(); 
2. spdlog::flush_on(spdlog::level::info); 
3. spdlog::flush_every(std::chrono::seconds(3));
```
ファイルに出力する場合は上記のような方法でflushする必要があります。
1. フラッシュしていないすべてのログをフラッシュする。
2. ログレベルごとにflushさせることも可能。
3. 指定した時間ごとに定期的にフラッシュする。

## WCHARを使用できない
```
#define SPDLOG_WCHAR_FILENAMES //ファイルパスでWCHARを使用できるようにする
#define SPDLOG_WCHAR_TO_UTF8_SUPPORT //出力内容でWCHARを使用できるようにする
```
上記の内容を定義する必要があります。
```
#define SPDLOG_WCHAR_FILENAMES
#define SPDLOG_WCHAR_TO_UTF8_SUPPORT
#include <iostream>
#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"
int main() {
　　//ファイル出力
	auto file_logger = spdlog::basic_logger_mt("basic_logger", L"C:\\ログ\\ログ.txt");

	file_logger->info(L"ＡＢＣあいう");
	file_logger->info(L"①2⃣➌㊃㈤６");
	file_logger->info(L"＝￥＊％（）！");
	file_logger->info(L"🐶😢👼🌸");
	file_logger->flush();
}
```
日本語文字列を含むファイルへのログ出力、全角文字の出力ができました。(notepad.exeで確認)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840516/d7209968-2600-e1f3-c95e-f003af10fb09.png)

コンソール出力で文字化けしてしまう場合は、`setlocale()`などを用いて適切な文字コードの設定を行ってください。

## おまけ：{fmt}フォーマット指定子まとめ
個人的によく使うprintfのフォーマット指定子と{fmt}のフォーマット指定子をまとめました。

こちらのサイトに{fmt}のフォーマット指定子の詳細な仕様がまとめられていました。
https://fmt.dev/latest/syntax.html

### {fmt}構文
```
{:[整列][符号][プレフィックス][幅][精度][型]}
```

|オプション|機能|{fmt}の記号|{fmt}|printf|実行結果例|
|-|-|-|-|-|-|
|整列|左詰め|<|{:<5}|%#‐5s|abc‗‗|
|整列|右詰め|>|{:>5}|%#5s|‗‗abc|
|整列|中央揃え|^|{:^5}|‐|‗abc‗|
|符号|正数/負数表記|+|{:+b}|%+d|+10,-10|
|符号|負数表記|-|{:+b}|‐|10,-10|
|プレフィックス|2進数、16進数の場合有効|#|{:#x}、{:#b}|%#x|0xf，0b1111|
|幅|空白埋め|[10進数の数値]|{:#5x}|%#5x|‗‗0xf<br>※「0x」も含んだ総桁数|
|幅|0埋め|0[10進数の数値]|{:.2x}|%#05x|0x00f<br>※「0x」も含んだ総桁数|
|精度|数値の精度指定|.[表示する小数の桁数]|{:#.2f}|%.2f|3.14|
|型|2進数|b|{:b}|‐|1111|
|型|10進数|d|{:d}|%d|15|
|型|16進数(小文字)|x|{:x}|%x|f|
|型|16進数(大文字)|X|{:X}|%X|F|
|型|実数|f|{:f}|%f|3.141593|
|型|文字|c|{:c}|%c|a|
|型|文字列|s|{:s}|%s|abc|

※実行結果では、空白1マスを「‗」で表現しています。

### 使用例
```:main.cpp
#include "spdlog/spdlog.h"

int main() {
	spdlog::info("整列");
	spdlog::info("左詰め   : {:<5}", "abc");
	spdlog::info("右詰め   : {:>5}", "abc");
	spdlog::info("中央詰め : {:^5}", "abc");

	spdlog::info("符号");
	spdlog::info("デフォルト : {:d}, {:d}", +10,-10);
	spdlog::info("正数/負数表示 : {:+d}, {:+d}", +10, -10);
	spdlog::info("負数表示 : {:-d}, {:-d}", +10, -10);

	spdlog::info("総桁数");
	spdlog::info("空白埋め(5桁) : {:#5x}", 15);
	spdlog::info("0埋め(5桁) : {:#05x}", 15);

	spdlog::info("精度");
	spdlog::info(".2f : {:.2f}", 3.141593);
	spdlog::info("010.2f : {:010.2f}", 3.141593);
	spdlog::info("3.4f : {:3.4f}", 3.141593);
	spdlog::info("3.8f : {:3.8f}", 3.141593);

	spdlog::info("型・プレフィックス");
	spdlog::info("2進数 : {:b}", 15);
	spdlog::info("2進数プレフィックス付き : {:#b}", 15);
	spdlog::info("10進数 : {:d}",15);
	spdlog::info("16進数(小文字) : {:x}", 15);
	spdlog::info("16進数(大文字) : {:X}", 15);
	spdlog::info("16進数プレフィックス付き : {:#x}", 15);
	spdlog::info("実数 : {:f}", 3.141593);
	spdlog::info("文字 : {:c}", 'a');
	spdlog::info("文字列 : {:s}", "abc");
}
```

```:実行結果
[2023-05-31 14:48:27.596] [info] 整列
[2023-05-31 14:48:27.601] [info] 左詰め   : abc
[2023-05-31 14:48:27.601] [info] 右詰め   :   abc
[2023-05-31 14:48:27.601] [info] 中央詰め :  abc

[2023-05-31 14:47:33.861] [info] 符号
[2023-05-31 14:47:33.861] [info] デフォルト : 10, -10
[2023-05-31 14:47:33.861] [info] 正数/負数表示 : +10, -10
[2023-05-31 14:47:33.861] [info] 負数表示 : 10, -10

[2023-05-31 14:47:33.861] [info] 総桁数
[2023-05-31 14:47:33.862] [info] 空白埋め(5桁) :   0xf
[2023-05-31 14:47:33.862] [info] 0埋め(5桁) : 0x00f

[2023-05-31 14:47:33.862] [info] 精度
[2023-05-31 14:47:33.862] [info] .2f : 3.14
[2023-05-31 14:47:33.862] [info] 010.2f : 0000003.14
[2023-05-31 14:47:33.862] [info] 3.4f : 3.1416
[2023-05-31 14:47:33.862] [info] 3.8f : 3.14159300

[2023-05-31 14:47:33.862] [info] 型・プレフィックス
[2023-05-31 14:47:33.862] [info] 2進数 : 1111
[2023-05-31 14:47:33.862] [info] 2進数プレフィックス付き : 0b1111
[2023-05-31 14:47:33.862] [info] 10進数 : 15
[2023-05-31 14:47:33.863] [info] 16進数(小文字) : f
[2023-05-31 14:47:33.863] [info] 16進数(大文字) : F
[2023-05-31 14:47:33.863] [info] 16進数プレフィックス付き : 0xf
[2023-05-31 14:47:33.863] [info] 実数 : 3.141593
[2023-05-31 14:47:33.863] [info] 文字 : a
[2023-05-31 14:47:33.863] [info] 文字列 : abc
```
# おわりに
spdlogを使用することで、タイムスタンプ付きのログ出力や引数の出力が簡単にできるのが魅力的だと思いました。
spdlogを用いて、ログ追加作業、デバッグ作業を効率的に行っていきたいと思います。

# 参考
## SPDLOG
https://github.com/gabime/spdlog
https://qiita.com/kozamurai/items/32b7e2912379359e6286
https://tadaoyamaoka.hatenablog.com/entry/2018/02/10/175237
https://blog.systemjp.net/entry/2020/03/02/152211
https://blog.csdn.net/tutou_gou/article/details/121284474

## {fmt}
https://github.com/fmtlib/fmt
https://fmt.dev/latest/syntax.html
https://tech.drecom.co.jp/fmt/
https://qiita.com/luftfararen/items/36f811e72a996a45a33f

## その他
https://renoji.com/IT.php?Contents=Program_C/Function_InOut_Specifier_Output.html
https://qiita.com/keitean/items/7cd52af571d27a1173d0
https://qiita.com/Kogia_sima/items/80598029683175755efd

最後までご覧いただきありがとうございます。