4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-06-05

はじめに

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

SPDLOGとは?

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

メリット

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

デメリット

  • テンプレートをたくさん使用しているためコンパイルが遅くなる

導入方法

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

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
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"ABCあいう");
	file_logger->info(L"①2⃣➌㊃㈤6");
	file_logger->info(L"=¥*%()!");
	file_logger->info(L"🐶😢👼🌸");
	file_logger->flush();
}

日本語文字列を含むファイルへのログ出力、全角文字の出力ができました。(notepad.exeで確認)
image.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
※「0x」も含んだ総桁数
0埋め 0[10進数の数値] {:.2x} %#05x 0x00f
※「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

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

4
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?