📝

Debug.Logを便利にするために工夫していること

2023/03/27に公開

概要

Unityでよく使うDebug.Logを便利に使えるようにするラッパークラスを紹介します。
OSSやAsset Storeを探したりもしたんですが、意外とないんですよね。

機能

カンマ区切りで複数パラメーター

複数の情報をログ出力したい際によく以下のように書くかと思います。

Debug.Log($"Log {obj} {data}");

これについて、以下のように普通に可変長引数で渡せるようにしています。

GLog.Log("Log", 1, 2, 3, true, null, 1.5f, "", data);


※ 標準ではnullと空文字で区別がつきませんが、以下の例では、null、空文字も内容がわかる形式で出力しています。

class GLog {
  public static void Log(params object[] messages)
  {
    var message = string.Join(',', messages.Select(FormatObject));
    Debug.Log(message);
  }

  public static string FormatObject(object o)
  {
    if (o is null) {
      return "(null)";
    }

    if (o as string == string.Empty) {
      return "(empty)";
    }

    return o.ToString();
  }
}

また、Burstコンパイルを利用する場合は可変長引数を回避する必要があります。

  public static void Log(object @object)
  {
    Log(new[] { @object });
  }

タグのカラーリング

タグの文字列に応じたカラーリングを行うことで、ログの視認性を向上しています。

  public static void Log(params object[] messages)
  {
    var message = string.Join(',', messages.Select(FormatObject));
    Debug.Log(DecorateColorTag(message));
  }

static string DecorateColorTag(object message)
  {
    var sb = new StringBuilder();
    var messageString = message.ToString();
    sb.Append(messageString);
    var reg = new Regex("\\[(?<tag>.*?)\\]", RegexOptions.IgnoreCase | RegexOptions.Singleline);

    var matches = reg.Matches(messageString);
    foreach (var match in matches.Reverse()) {
      var tag = match.Groups["tag"].Value;
      // HashCodeをもとに何らかの色を取得
      var color = Colors.SelectFromManyColors(tag.GetHashCode());
      sb.Insert(match.Index + match.Length, "</color></b>");
      sb.Insert(match.Index, $"<b><color={Colors.ConvertToRgbCode(color)}>");
    }

    return sb.ToString();
  }

StateLog

オブジェクトのFieldとPropertyをログ出力する機能です。

GLog.StateLog(new SpineData());

GLog.StateLog(typeof(Time));

ListLog

配列の中身を1つのDebugLogとして出力します。

GLog.ListLog(list);

↓ 要素が100件以上ある場合はこのように表示

↓ 要素ごとの出力をToString()以外にしたい場合は第2引数で指定できるようにしています。

GLog.ListLog(list, (index, item) => $"{index}, {item.r}");

DictLog

Dictionaryの中身を1つのDebugLogとして出力します。

GLog.DictLog("[DictLog]", dictionary);

↓ ListLogと同様に、要素ごとの出力を key => value 以外にしたい場合は第2引数で指定できるようにしています。

GLog.DictLog(dic, (k, v) => $"{k} - [{string.Join(",", v)}]";

WatchLog

値が変更されたときにだけログを出力します。

var val = "Start update value, per 2sec";
var handle = GLog.WatchLog(() => val);
for (var i = 0; i < 3; i++) {
  await UniTask.Delay(2000);
  val = $"{i + 1}";
}

handle.Dispose();

デフォルトではEqual()で比較していますが、意図しない形で検知される場合があるため、チェックの判定メソッドを第2引数でカスタムできるようにしています。

  • 例えば、以下の例だと文字列で比較するオプションを指定し、過剰な検知に対応しています。
var handle = GLog.WatchLog("[DateTime.Now]", () => DateTime.Now, GLog.CheckEquivalenceAsString);
await UniTask.Delay(5000);
handle.Dispose();

ビルド時のコードストリップ

以下のように書いておくことで、エディタおよびDEVELOPMENT_BUILD定義時にのみ処理されるようにしています。(Conditionalを使えば呼び出し箇所自体がビルドから省かれるのでパフォーマンスの劣化を防げます。)

#if !UNITY_EDITOR
  [Conditional("DEVELOPMENT_BUILD")]
#endif
  public static void Log(params object[] messages)
  {
    Debug.Log(message);
  }

SourceTemplate

EditorとしてRiderを使っている場合、JetBrains.Annotations.SourceTemplateを使うことで、スニペットのようにコードを出力できます。

以下のようにスニペットを登録

  [SourceTemplate]
  public static void statelog(this object self)
  {
    /*$ GLog.StateLog(self); */
  }

Rider上で以下のようにスニペットが利用できるようになります。

文字列をLogにする場合も簡単です。

レシーバーなしでlogと入力したときのスニペットはRiderのLiveTemplateという機能になります。
こちらの挙動も修正したい場合は以下のRider設定から修正できます。

おわり

GLogはGameLogの略です。
コードについてはあまり書けておらずアイデアのみの共有ではありますが、ご容赦ください。

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

Happy Elements

Discussion