kazuki’s blog

新人エンジニアのブログ

SRDebuggerのBugReportをSlackに送信する

こんにちは!
かずきです。
 
今回はUnityのアセットSRDebuggerのBugReport機能を改造して、
Slackに送信してみます!

バグレポートタブを有効化する

まず、BugReportTabControllerを改造します。
適当な名前(今回はMyBugReportTabControllerとします)のC#スクリプト作成し、
StompyRobot/SRDebugger/Scripts/UI/Tabs/BugReportController.csの内容をコピペします。

public bool IsEnabled
{
    get { return Settings.Instance.EnableBugReporter; }
}

ここを以下のように書き換えて保存します。

public bool IsEnabled
{
    get { return true; }
}



編集する為、StompyRobot/SRDebugger/Resources/SRDebugger/UI/Prefabs/Tabs内にあるBugReportTab.prefabをシーン上に配置します。
f:id:tc_kazuki:20180121162138p:plain

 
インスペクターをDebugモードに切り替えて、BugReportTabControllerを先程作ったスクリプトに差し替えます。

 
f:id:tc_kazuki:20180121162159p:plain

  
オブジェクト名・タブ名・キーを変更します。

f:id:tc_kazuki:20180121162232p:plain

StompyRobot/SRDebugger/Resources/SRDebugger/UI/Prefabs/Tabsにプレハブを保存します。(Reaource下のパスが同じなら他のディレクトリでOK)

f:id:tc_kazuki:20180121162245p:plain


f:id:tc_kazuki:20180121162255p:plain
これでバグレポートタブが表示されます。
ですが、このままではレポートの送信が出来ません(;ω;)

バグレポートをSlackに送信する

次はBugReportApiを改造します。
適当な名前(今回はMyBugReportApiとします)のC#スクリプト作成し、
StompyRobot/SRDebugger/Scripts/Internal/BugReportApi.csの内容をコピペします。

作成したスクリプト(MyBugReportApi)を編集していきます。

内容別で送信する為、レポート種別のEnumを作成します。

#region Enums

public enum ReportType : int
{
    ScreenShot = 0,
    SystemInfo,
    Console,
}

#endregion //Enums



次にSlack投稿時に使用する情報を定義します。

       #region Constants

        /// <summary>
        /// Slack API 
        /// </summary>
        private static readonly string SlackApiUrl = "https://slack.com/api";
        private static readonly string UploadEndPoint = "/files.upload";
        /// @note attachment上手行かない。。。
        //private static readonly string PostMessageEndPoint = "/chat.postMessage";
        private static readonly string AccessToken = "YourAPIToken";
        private static readonly string WebHookUrl = "https://hooks.slack.com/services/Txxxxx/Bxxxxxx/xxxxxxxxxxxxxxxxxx";
        private static readonly string Channels = "general";
        private static readonly string UserName = "SRDebuggerBugReport";

        /// <summary>
        /// Attachmentのカラー
        /// </summary>
        private static readonly string SystemInfoColor = "#000000";

        /// <summary>
        /// Log毎にAttachmentを分ける場合のカラー
        /// </summary>
        private static readonly string ErrorLogColor = "#FF0000";
        private static readonly string WarningLogColor = "#FFD700";
        private static readonly string NormalLogColor = "#708090";

        #endregion // Constants



3回に分けて投稿する為、ログIDを生成し保存するための変数を宣言します。

private static uint _logId;



BuildJsonRequestメソッドを削除して、以下の4つのメソッドを作成します。

static WWWForm BuildScrrenShotUploadRequest (BugReport report)
{
    var form = new WWWForm();

    form.AddField("token", AccessToken);
    form.AddField("channels", Channels);
    form.AddField("username", UserName);
    var title = string.Format("ScreenShot ({0})", _logId);
    form.AddField("title", title);
    form.AddBinaryData("file", report.ScreenshotData, "screenShot", "imag/png");
    form.AddField("initial_comment", report.UserDescription);

    return form;
}
static string BuildJsonRequest (int typeValue, BugReport report)
{
    string json = "";
    switch (typeValue)
    {
        case (int)ReportType.ScreenShot:
            var ht = new Hashtable();
            ht.Add("text", report.UserDescription);
            json = Json.Serialize(ht);
            break;
        case (int)ReportType.SystemInfo:
            json = BuildSystemInfoJsonRequest(report);
            break;
        case (int)ReportType.Console:
            json = BuildConsoleLogJsonRequest(report);
            break;
    }
    return json;
}
static string BuildSystemInfoJsonRequest (BugReport report)
{
    var ht = new Hashtable();
    ht.Add("username", UserName);
    var messageTitle = string.Format("SystemInfo ({0})", _logId);
    ht.Add("text", messageTitle);

    List<Hashtable> attachmentList = new List<Hashtable>();
    List<Hashtable> systemInfoList = new List<Hashtable>();
    foreach (var systemInfos in report.SystemInformation)
    {
        var systemInfoHashTable = new Hashtable();
        systemInfoHashTable.Add("color", SystemInfoColor);
        systemInfoHashTable.Add("title", systemInfos.Key);
        var sb = new StringBuilder();
        foreach (var obj in systemInfos.Value)
        {
            sb.AppendLine(obj.ToString());
        }
        systemInfoHashTable.Add("text", sb.ToString());

        attachmentList.Add(systemInfoHashTable);
    }

    ht.Add("attachments", attachmentList);

    var json = Json.Serialize(ht);

    return json;
}
private static string BuildConsoleLogJsonRequest (BugReport report)
{
    var ht = new Hashtable();
    ht.Add("username", UserName);
    var messageTitle = string.Format("Console ({0})", _logId);
    ht.Add("text", messageTitle);

    List<Hashtable> attachmentList = new List<Hashtable>();

    foreach (var logs in CreateConsoleDump())
    {
        StringBuilder sb = new StringBuilder();
        var consoleHashTable = new Hashtable();

        switch (logs[0])
        {
            case "Assert":
            case "Exception":
            case "Error":
                consoleHashTable.Add("color", ErrorLogColor);
                break;
            case "Warning":
                consoleHashTable.Add("color", WarningLogColor);
                break;
            default:
                consoleHashTable.Add("color", NormalLogColor);
                break;
        }

        // LogType + Log
        var title = logs[0] + " : " + logs[1];
        consoleHashTable.Add("title", title);
        // StackTrace
        for (int i = 2; i < logs.Count; i++)
        {
            sb.Append(logs[i]);
        }
        sb.AppendLine();
        consoleHashTable.Add("text", sb.ToString());
        attachmentList.Add(consoleHashTable);
    }

    ht.Add("attachments", attachmentList);

    var json = Json.Serialize(ht);

    return json;
}



Submitメソッドを以下のように編集します。

public IEnumerator Submit ()
{
    //Debug.Log("[BugReportApi] Submit()");

    if (_isBusy)
    {
        throw new InvalidOperationException("BugReportApi is already sending a bug report");
    }

    // Reset state
    _isBusy = true;
    ErrorMessage = "";
    IsComplete = false;
    WasSuccessful = false;
    _www = null;
    _logId = (uint)System.Security.Cryptography.MD5.Create().GetHashCode(); // ハッシュ値ならそうそう被らないだろうという安直な考え

    foreach (var type in Enum.GetValues(typeof(ReportType)))
    {
        try
        {
            if ((int)ReportType.ScreenShot == (int)type && _bugReport.ScreenshotData != null)
            {
                var form = BuildScrrenShotUploadRequest(_bugReport);
                var url = SlackApiUrl + UploadEndPoint;

                _www = new WWW(url, form);
            }
            else
            {
                string json = BuildJsonRequest((int)type, _bugReport);

                var jsonBytes = Encoding.UTF8.GetBytes(json);
                var headers = new Dictionary<string, string>();
                headers["Content-type"] = "application/json";
                headers["Accept"] = "application/json";
                headers["Method"] = "POST";
                headers["data"] = json;

                _www = new WWW(WebHookUrl, jsonBytes, headers);
            }

        }
        catch (Exception e)
        {
            ErrorMessage = e.Message;
        }

        if (_www == null)
        {
            SetCompletionState(false);
            yield break;
        }

        yield return _www;

        if (!string.IsNullOrEmpty(_www.error))
        {
            ErrorMessage = _www.error;
            SetCompletionState(false);

            yield break;
        }

        if (!_www.responseHeaders.ContainsKey("STATUS"))
        {
            ErrorMessage = "Completion State Unknown";
            SetCompletionState(false);
            yield break;
        }

        var status = _www.responseHeaders["STATUS"];

        if (!status.Contains("200"))
        {
            ErrorMessage = SRDebugApiUtil.ParseErrorResponse(_www.text, status);
            SetCompletionState(false);

            yield break;
        }
    }

    SetCompletionState(true);
}



StompyRobot/SRDebugger/Scripts/Services/Implementation内にあるBugReportApiServiceを編集します。

まずはメンバ変数_reportApiの型を作成したスクリプト(MyBugReportApi)の型に変更します。

private BugReportApi _reportApi;
// ↓
private MyBugReportApi _reportApi;



SendBugReportメソッド内でBugReportApiをnewしている場所を作成したスクリプト(MyBugReportApi)に変更します。

public void SendBugReport (BugReport report, BugReportCompleteCallback completeHandler,
    BugReportProgressCallback progressCallback = null)
{

    // 省略


    _reportApi = new BugReportApi(report, Settings.Instance.ApiKey);
    // ↓
    _reportApi = new MyBugReportApi(report, Settings.Instance.ApiKey);
}

これでSlackにバグレポートを送信できます!
f:id:tc_kazuki:20180121172322p:plain
f:id:tc_kazuki:20180121172421p:plain
f:id:tc_kazuki:20180121172426p:plain

追記

bitbucketにソースコードとunitypackage公開しました〜
tc_kazuki / SRDebugger BugReportSend To Slack — Bitbucket

まとめ(というか感想)

  • なるべくSRDebugger側のソースコードは編集しないように作りましたが、
    BugReportApiServiceのみ手を加えることになってしまった。

  • Slack Web API でAttachmentを使おうとしたが上手く表示出来きずWebhookで投稿してます(わかる方教えて下さい!)


    今回はログ種別などの見やすさを重視しましたが
    大きなプロダクトになると、逆に流れて見にくくなってしまうので、
    SystemInfoとConsoleはテキストファイルに1まとめにしてUploadする形にした方が良さそうなので、
    時間がある時作ってみます(後Reflectionも)。