シンプルで使いやすいカスタムLoggerをUnityで実装

# ■ 概要
Unityでログを出力する場合、「UnityEngine.Debug」の「Log関数」を使うのが一番標準的なやり方だと思います。（おそらくお馴染みであろう「Debug.Log」です。）
出力結果としては任意の文字列にタイムスタンプが付与された形でログ出力され、何かしらの結果をすぐ出力して確認する用途であれば十分なのですが、アプリ内の処理結果を追う目的などの意図的に残すログとしてはあまりにも不十分だと思っています。

![スクリーンショット 2025-04-28 123409.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/f8162eba-6ef8-44d6-ad70-9447a356c644.png)

今回は「UnityEngine」や「.NET Framework」の標準のライブラリのみを使ったカスタムLoggerを実装してみました。

# ■ 環境
動作環境：Windows 11 Pro
Windowsバージョン：23HS

Unityバージョン：Unity2022.3.44f1
プロジェクトテンプレート：Universal 3D

# ■ 実装
```c#:Common.cs
using UnityEngine;
using System.Text.RegularExpressions;
using System.IO;

public static class Common
{
    /// <summary>
    /// <para>呼び出し元のクラス名、 メソッド名をログ出力</para>
    /// <para>引数を付けた場合は、引数の中身を文字列として出力</para>
    /// </summary>
    /// <param name="message">ログ出力したい場合は文字列を入力 省略可能</param>
    public static void Log(string message = "")
    {
        // 1つ前のフレームを取得
        System.Diagnostics.StackFrame objStackFrame = new(1, true);

        // 呼び出し元のメソッド名を取得する
        string methodName = objStackFrame.GetMethod().Name;
        // 正規表現：2文字以上の英字
        methodName = Regex.Match(methodName, @"\b[a-zA-Z]{2,}\b").Value;

        // 呼び出し元のクラス名を取得する
        string className = objStackFrame.GetMethod().ReflectedType.FullName;
        // 正規表現：2文字以上の英字
        MatchCollection matches = Regex.Matches(className, @"\b[a-zA-Z]{2,}\b");
        // マッチした最後の要素を出力
        className = matches[^1].Value;

        // ファイル名と行番号を取得
        string fileName = Path.GetFileName(objStackFrame.GetFileName());
        int lineNumber = objStackFrame.GetFileLineNumber();

        string msg = "";
        if (message != "")
        {
            msg = "" + message;
        }

        Debug.Log($"*** [ {className} ] {methodName}() (at {fileName}:{lineNumber}) {msg} ***");
    }
}
```
呼び出し

```c#
Common.Log("ログ出力");
```
出力
![スクリーンショット 2025-04-28 123336.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/7c792929-a06d-44ae-82a8-02b41a54e0e8.png)

# ■ 解説

Loggerに必要な情報として

`1. 日付時間`
`2. クラス名`
`3. 関数名`
`4. ファイル名`
`5. 行数`
`6. 任意の文字列`

これらを出力するように実装しました。

UnityEngineの「Debug.Log関数」をラップする形で実装することで、軽量かつ専用のLoggerライブラリのインストールなど、特定環境に依存しないメリットがあります。また上記の項目以外にも必要な情報があれば追加して、自由に出力のフォーマットを変更できるのも良いポイントです。

下記では実装の要所を解説します。

## 1. System.Diagnostics.StackFrameから情報を取得する

「System.Diagnostics.StackFrame」クラスは、「.NET Framework」におけるプログラムの実行時の呼び出し履歴（コールスタック）の1つのフレーム（階層）を表すオブジェクトです。

簡単に言えば、「どのメソッドが、どのメソッドを呼び出したのか」という情報を記録したスタック構造の各要素が「StackFrame」オブジェクトとして対応しています。

```c#
// 1つ前のフレームを取得
System.Diagnostics.StackFrame objStackFrame = new(1, true);
```

「StackFrame」クラスのコンストラクターの第一引数には、スタック上の取得したいフレーム数を指定します。今回はこの「Common.Log」関数の呼び出し元の情報を取得したいため1つ前のフレームである「1」を指定します。（現在のフレームを取得したい場合は「0」を指定してあげれば良いわけです。）
第二引数のフラグはファイル名や行番号の情報を取得したい場合にはtrueを指定します。

https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.stackframe.-ctor?view=net-8.0#system-diagnostics-stackframe-ctor(system-int32-system-boolean)


## 2. 引数にデフォルト値を設定

「Common.Log」関数の引数には、デフォルト値として空の文字列が設定しているため、引数を省略して呼び出すことも可能です。

```c#
Common.Log();
```

このように呼び出しすることで、単に呼び出し元の情報のみを出力するログとしても使用できます。

![スクリーンショット 2025-04-28 131333.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/7f5582ed-23db-4471-8727-5910024dd5a1.png)

# ■ 最後に （感想）

「Common.Log();」というこの1行を各要所に残しておくだけでも、アプリ内の処理の順序を追う際に役立ちそうかなと思います。

最後まで読んでいただきありがとうございました。