もうとにかく今すぐデバッグのためのログを出力したいときのクラス(C++)

やりたいこと

以前の記事で、C++でログを残すメソッドを作った。

今回それを使おうと思ったのだが、マルチスレッドな処理で使いたいので、排他処理を入れたい。

また、ちょっと書き方がぐちゃぐちゃなので、多少でもマシなコードにしたい。

実験コード

最近知った諸々を取り入れて、こんな感じにしてみた。

#pragma once
#include <string>
#include <filesystem>
#include <mutex>

class LogOnDesktop
{
private:
    std::mutex mtx;
    std::filesystem::path AppendLogFilePathOnDesktopString(std::wstring filename);
    std::wstring GetTimeString();

public:
    void WriteLine(std::wstring txt);
};
#include <iostream>
#include <stdio.h>
#include <tchar.h>
#include <string>
#include <shlobj.h>
#include <time.h>
#include <thread>   // std::this_thread::get_id()を使うのに必要
#include <fstream>  // std::wofstreamを使うのに必要
#include <filesystem>
#include <mutex>

#include "LogOnDesktop.h"

// ※このコードをブラウザからコピーしてVisualStudioに貼り付けると、ビルドしたときに
//  文字コード云々でエラーになったことがあった。そういうときは、コード中のコメントを全部消すとエラー消えた。

std::filesystem::path LogOnDesktop::AppendLogFilePathOnDesktopString(std::wstring filename)
{
    WCHAR* buf = nullptr;
    std::filesystem::path desktop;

    auto hr = SHGetKnownFolderPath(FOLDERID_Desktop, KF_FLAG_DEFAULT, NULL, &buf);

    if (SUCCEEDED(hr)) {
        desktop = buf;
        desktop.append(filename);
    }

    CoTaskMemFree(buf);

    return desktop;
}

std::wstring LogOnDesktop::GetTimeString()
{
    WCHAR buf[64];
    auto t = time(nullptr);
    auto tmv = tm();
    auto error = localtime_s(&tmv, &t); // ローカル時間(タイムゾーンに合わせた時間)を取得

    wcsftime(buf, 64, L"%Y/%m/%d %H:%M:%S", &tmv);
    auto aika = std::wstring(buf);

    return aika;
}

void LogOnDesktop::WriteLine(std::wstring txt)
{
    std::lock_guard<std::mutex> lock(mtx);

    auto desktop = AppendLogFilePathOnDesktopString(L"mylog.log");

    DWORD processId = GetCurrentProcessId();

    auto aika = GetTimeString();

    // 現在のスレッドIDを出力
    auto thId = std::this_thread::get_id();

    // ファイルを開く(なければ作成)
    std::wofstream ofs(desktop, std::ios::app);

    if (!ofs)
        return;

    // 現在時刻とスレッドIDを付けたログをファイルに書き込み
    ofs << aika << L" " << processId <<  L" " <<  thId << L" " << txt << std::endl;

    // ファイル閉じる
    ofs.close();
}

メインのcpp

#include "LogOnDesktop.h"

int main()
{
    LogOnDesktop log;

    for (size_t i = 0; i < 10; i++)
    {
        log.WriteLine(std::to_wstring(i));
    }
}

出力はこんな感じ。

2024/01/25 22:04:13 16000 0
2024/01/25 22:04:13 16000 1
2024/01/25 22:04:13 16000 2
2024/01/25 22:04:13 16000 3
2024/01/25 22:04:13 16000 4
2024/01/25 22:04:13 16000 5
2024/01/25 22:04:13 16000 6
2024/01/25 22:04:13 16000 7
2024/01/25 22:04:13 16000 8
2024/01/25 22:04:13 16000 9

マルチスレッドでも大丈夫なことの確認

下記のように、main関数を変えて試した。

int main()
{
    static std::thread th_a;
    static std::thread th_b;

    LogOnDesktop log;

    th_a = std::thread([&log]
        {
            for (size_t i = 0; i < 10; i++)
            {
                log.WriteLine(std::to_wstring(i));
            }
        });

    th_b = std::thread([&log]
        {
            for (size_t i = 0; i < 10; i++)
            {
                log.WriteLine(std::to_wstring(i*100));
            }
        });

    // プログラム終了時にはjoinで終わるのを待つ
    th_a.join();
    th_b.join();
}

出力

2024/01/25 22:24:07 10844 0
2024/01/25 22:24:07 10844 100
2024/01/25 22:24:07 16516 0
2024/01/25 22:24:07 10844 200
2024/01/25 22:24:07 10844 300
2024/01/25 22:24:07 10844 400
2024/01/25 22:24:07 10844 500
2024/01/25 22:24:07 10844 600
2024/01/25 22:24:07 10844 700
2024/01/25 22:24:07 10844 800
2024/01/25 22:24:07 10844 900
2024/01/25 22:24:07 16516 1
2024/01/25 22:24:07 16516 2
2024/01/25 22:24:07 16516 3
2024/01/25 22:24:07 16516 4
2024/01/25 22:24:07 16516 5
2024/01/25 22:24:07 16516 6
2024/01/25 22:24:07 16516 7
2024/01/25 22:24:07 16516 8
2024/01/25 22:24:07 16516 9

スレッドAとBで、変に、1件のログの間に別のログが挟まったりすることなく、出力できてる。

※スレッドAで10件、Bで10件と連続して出ないのはOK。

参考

[C++] 特殊フォルダのパスを取得する

https://qiita.com/tera1707/private/9d79b31875c5d275c67f

std::filesystem::path::append

https://cpprefjp.github.io/reference/filesystem/path/append.html

ミューテックス(std::mutex)でlockする②

https://tera1707.com/entry/2024/01/24/225258