UnityからSlackにエラーを投げるときの情報についての考察

本記事は[Cocone Advent Calendar 2022](https://qiita.com/advent-calendar/2022/cocone)の15日目の記事となります。
昨日は @ka_ik さんの[新卒の方に教え継ぎたいこと](https://qiita.com/ka_ik/items/67119de9e52ef8250dff)でした。(新卒の方だけでなくチームで働くうえで大事なことが書かれていましたね！)

# はじめに
皆さんは普段スマホ向けにビルドされたclientのエラーをどのように検知していますでしょうか？
UnityEditor上で発生したエラーであればもちろんConsoleウィンドウに表示されるのですが、ios/android向けビルドし各メンバーやQAさんが触っているときに発生したエラーはどうしても検知するのが遅れてしまいますよね…
そして明らかにバグっているときはちゃんと報告が上がるのですが、内部的にエラーは発生しているが動作的には問題ないといったエラーに関してはどうしても見逃されてしまうことがあると思います。

そこでclientで発生したエラーをSlackへ送ることで普通であれば見逃されてしまうようなエラーをいち早く検知しようという趣旨なのですが、
本記事では単純にSlackにエラーを投げるという内容ではなく（UnityからSlackに投稿するという記事はたくさんあると思うので）
<b>どのような内容を投稿すればよりエラーの原因を見つけやすくなるのか</b>という視点の考察を書こうと思います。

# Unityでエラーを検知しSlackに投稿する
Slackに投げるという内容は書かないといいましたが前提となるので簡単にだけ触れておきましょう

## Unityでエラーを検知する
```c#
// UnityのLogMessageを取得する
Application.logMessageReceived += LogMessageReceived;
```
```c#
private void LogMessageReceived(string body, string stackTrace, LogType logType)
{
    // LogTypeを判断して処理を変える
    switch (logType)
    {
        case LogType.Log:
            break;
        case LogType.Warning:
            break;
        case LogType.Assert:
            break;
        case LogType.Error:
            break;
        case LogType.Exception:
            break;
    }
}
```

## UnityからSlackに投稿する
SlackのIncoming Webhooksなどを使用しWebHookURLを取得します
取得については `slack webhook` や `unity slack 投稿` などとGoogle先生に聞けばたくさん記事が出てくると思うのでそちらからどうぞ

webhook urlが取得できればこのようにslackに投稿することができます
```c#
private void LogMessageReceived(string body, string stackTrace, LogType logType)
{
    Post(body);
}

[Serializable]
private class PostData
{
    public string text;
}

private async void Post(string body)
{
    // slackから取得したwebhook url
    var url = "https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyyy/zzzzzzzzzzzzzzzzzzzzzzzzz";
    var post = new PostData
    {
        text = body,
    };
    var postBytes = Encoding.UTF8.GetBytes(JsonUtility.ToJson(post));

    var header = new Dictionary<string, string>
    {
        { "Content-Type", "application/json; charset=UTF-8" },
    };
    using (var request = UnityWebRequest.Post(url, header))
    {
        request.uploadHandler = new UploadHandlerRaw(postBytes);
        await request.SendWebRequest();
    }
}
```

ここまでできれば下準備は完了ですね！

# Slackに送る情報について考えてみる
前置きが長くなってしまいましたがここからがようやく本題です
どのような情報をSlackに送ればいいのかを考えていきます。

## 1. 端末情報
スマホ向けに配信するということは様々なデバイスで動作するということです
端末の種類、OSのversionなど変数となる条件が非常に多いので端末情報は必須
```c#
    var model = SystemInfo.deviceModel;
#if UNITY_IOS
    model = $"{UnityEngine.iOS.Device.generation.ToString()}";
#endif
    var deviceInfo = $"{SystemInfo.operatingSystem}, {model}";
```

## 2. ビルド番号、gitブランチ情報
開発環境であれば複数のエンジニアが同時に開発を進行しながらビルドしているため様々なビルドが生まれていると思います
そこでどのブランチでビルドしたのか、ビルド番号は何番なのかをビルド時にclientに渡しておきその情報をエラーと一緒に送ることで判断しやすくなります

## 3. ユーザーID
特定のアカウントの状態によって発生するエラーもあります
そこでどのユーザーでエラーが発生したかの情報を送ることで特定のアカウントでしか起こっていないなどがわかります

## 4. Server情報
開発環境であれば複数のServerが存在していることがあると思います
なので特定のServerでだけ発生しているエラーであることがわかればserverにバグがあるということが判断できます

## 5. ユーザー時間
オンラインゲームなどを開発していると、あるデータの公開時間の確認をするためにユーザーの現在時間をずらすDebug機能を作ることがあると思います
しかし時間をいじるというのはなかなか難しく、時間を変更していることでエラーが発生するということもあります。
そこで現在時間ではなく、そのユーザーが現在どういう時間設定なのかがわかることで時間をずらしてることで発生してるエラーなのでは？という判断ができるようになります。

# まとめ
全てまとめると以下のようなコードになります
```c#
private void LogMessageReceived(string body, string stackTrace, LogType logType)
{
    // ErrorとException以外は無視
    if (logType != LogType.Error && logType != LogType.Exception)
    {
        return;
    }

    // 端末情報を取得
    var model = SystemInfo.deviceModel;
#if UNITY_IOS
    model = $"{UnityEngine.iOS.Device.generation.ToString()}";
#endif
    var deviceInfo = $"{SystemInfo.operatingSystem}, {model}";

    var localTime = ユーザー時間を取得;

    // 端末情報、ビルド時に設定された情報、現在のユーザー情報、Error情報をまとめる
    var customInfo = $"{deviceInfo}\n" + 
        $"build no: {buildNo} | branch: {branch} | server: {serverName}\n" +
        $"mycode: {userId}, name: {userName} | local time: {localTime:yyyy/MM/dd HH:mm:ss}\n" +
        $"{body};

    Post(body, stackTrace);
}

[Serializable]
private class PostData
{
    public string text;
    public Attachments[] attachments;
}

[Serializable]
private class Attachments
{
    public string text;
    public string color;
}

private async void Post(string body, string stackTrace)
{
    // slackから取得したwebhook url
    var url = "https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyyy/zzzzzzzzzzzzzzzzzzzzzzzzz";
    var post = new PostData
    {
        text = body,

        // Attachmentsを指定することでSlack上で色を付けて表示できる
        attachments = new[]
        {
            new Attachments
            {
                text = stackTrace,
                color = "#D00000",
            },
        },
    };

    var postBytes = Encoding.UTF8.GetBytes(JsonUtility.ToJson(post));

    var header = new Dictionary<string, string>
    {
        { "Content-Type", "application/json; charset=UTF-8" },
    };
    using (var request = UnityWebRequest.Post(url, header))
    {
        request.uploadHandler = new UploadHandlerRaw(postBytes);
        await request.SendWebRequest();
    }
}
```

そしてこのようなコードで投稿したものが以下になります
（モザイクが多くなってしまいましたが下の赤い部分がStackTraceの部分です）
![mosaic_20221212131241.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2990927/23a87f97-f4b2-f59f-8538-36c9bea9a6c2.png)

このような情報が自動でSlackに流れてくるので誰がどこでエラーを踏んだのかがすぐに把握できます。
簡単なエラーであれば報告がくるころには「あ、そのエラーはもう修正済みですー」と言えるようになり非常に優秀なエンジニアとして評価されることでしょう。

# 大事なポイント
最後にすごく大事なポイントを書いておきます。
エンジニアであれば誰しもエラーというものが好きにはなれないと思いますし、ましてや自分が何もしなくてもslackに自動でエラーが投稿されるという状況は非常に辛いものがあります…。
そこでslack botのアイコンはできるだけカワイイ猫ちゃんにしておき、少しでも安らかな心でエラーを確認できるようにしておきましょう。
![mosaic_20221212132340.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2990927/9f695564-247d-fde7-2983-8688b50b8df0.png)

# 最後に
UnityからSlackへ投稿するという記事はよく見かけるのですが、どのような内容を投稿するべきなのかという視点の記事をあまり見かけなかったので書いてみました。

clientのエラーはもちろんですが通信エラーが発生した際もSlackへ通知されるようになっていればserverのエラーを検知することが出来たり、
UnityのLogを自分でハンドリングしているので特定のエラーは無視するなど細かい調整も可能だと思います。

<b>※Slackへの通知は本番で不特定多数の人が触る状況で行うととんでもない数の投稿になってしまうと思うので人数が限られた開発環境内でだけ使用することをお勧めします。</b>

この記事はあくまでも一例であり、もっといい方法もたくさんあるとは思いますが少しでも何かのいいきっかけになると幸いです。


明日は @kim_hyoungdae さんの記事となります。
こちらもぜひ読んでみてください！