[Unity] Exception発生時に自動でSlackへレポート送信

本記事は、[サムザップ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/sumzap) の12/9の記事です。
# はじめに
開発中はExceptionって発生しがちなものですよね。
「マスタデータ入ったら直るから気にせんとこ」とか「サーバ側が手入れてくれたらこんなん発生せんはず」って感じで軽視されるExceptionって結構ある気がします。
けど実はそのException、アプリ側の操作方法によっては稀に起こるバグだったりするかも？
そんなExceptionをあとからでもしっかり見直せるようにどこかに記録を残しておきたいものです。
やり方は色々あると思いますが今回は`SRDebugger`を使って自動で`Slack`にExceptionを送信する方法をご紹介します！

# 準備
SRDebuggerをAssetStoreから手に入れます。
https://assetstore.unity.com/packages/tools/gui/srdebugger-console-tools-on-device-27688?locale=ja-JP
※使用方法はAssetStoreなどを参照ください

Unity-SRDebugger_BugReport_Send_To_SlackをGitHubから手に入れます。
https://github.com/Itoen/Unity-SRDebugger_BugReport_Send_To_Slack
※使用方法はGitHubを参照ください

# 実装
どうやったらSlackにレポートが送信できるのかなぁと調べるとすぐにBugReportSheetController.csというSRDebuggerの内包スクリプトを見つけることができると思います。

```csharp
var s = SRServiceManager.GetService<IBugReportService>();
var r = new BugReport();
r.Name = NameField.text;
r.UserDescription = DescriptionField.text;
r.ConsoleLog = Service.Console.AllEntries.ToList();
r.SystemInformation = SRServiceManager.GetService<ISystemInformationService>().CreateReport();
r.ScreenshotData = BugReportScreenshotUtil.ScreenshotData;
BugReportScreenshotUtil.ScreenshotData = null;
s.SendBugReport(r, OnBugReportComplete, OnBugReportProgress);
```
どうやらこの処理でレポートを送信しているようですね。
この処理を例外発生時に実行してやれば良さそう！

### レポート送信クラス
Exception発生時にSlackにレポートを送信するクラスを作成します。

```csharp
using SRDebugger.Internal;
using SRDebugger.Services;
using SRF.Service;
using System;
using System.Collections;
using System.Linq;
using UnityEngine;

class Reporter : MonoBehaviour
{
    void Start()
    {
        MyLogger.Initialize(this);
    }

    public void SendException(Exception exception)
    {
        StartCoroutine(SendExceptionAsync(exception));
    }

    IEnumerator SendExceptionAsync(Exception exception)
    {
        yield return new WaitForEndOfFrame();

        // 画面キャプチャ
        var tex = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
        tex.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
        tex.Apply();
        byte[] screenshotData = tex.EncodeToPNG();
        Destroy(tex);

        // レポート作成
        var report = new BugReport();
        report.Name = $"自動送信";
        report.UserDescription = exception.ToString();
        report.ConsoleLog = Service.Console.AllEntries.ToList();
        report.SystemInformation = SRServiceManager.GetService<ISystemInformationService>().CreateReport();
        report.ScreenshotData = screenshotData;

        // レポート送信
        SRServiceManager.GetService<IBugReportService>().SendBugReport(report, (didSuccess, errorMessage) => { });
    }
}
```
report.NameにはユーザーIDとかがあれば入れても良さそうですね。
画面のキャプチャも送りたかったので、画面をキャプチャするためにWaitForEndOfFrameでUnityの描画が終わるまで待っています。

### 例外検知クラス
UnityのDebug.unityLoggerを上書きして自作のLoggerを割り当てています。

```csharp
using System;
using UnityEngine;

class MyLogger : ILogHandler
{
    static ILogHandler _logHandler;
    static Reporter _reporter;

    public static void Initialize(Reporter reporter)
    {
        _logHandler = Debug.unityLogger.logHandler;
        Debug.unityLogger.logHandler = new MyLogger();
        _reporter = reporter;
    }

    public void LogFormat(LogType logType, UnityEngine.Object context, string format, params object[] args)
    {
        _logHandler.LogFormat(logType, context, format, args);
    }

    public void LogException(Exception exception, UnityEngine.Object context)
    {
        _logHandler.LogException(exception, context);
        _reporter.SendException(exception);
    }
}
```
Exception発生時にLogExceptionが呼び出されるため、ReporterのSendExceptionを実行します。

以上で完了です！

# おわりに
もう少し工夫するならExceptionが連続で発生した場合にはレポート送信中であれば弾く処理や、同じ内容のExceptionが連続で発生していたら送信しないなどの処理を入れても良いと思います。