手机中可以通过 Application.logMessageReceivedThreaded来监听日志的信息,在通过File.AppendAllText来将日志写在手机的Application.persistentDataPath目录中。日志可以按天写入,这样即使游戏闪退也可以把当天产生的日志取出来。
测试机取日志很麻烦,出现问题的时候周围也不一定有电脑可以取,而且测试机上也不一定安装飞书企业微信这种软件,就算安装了使用者也未必会取,未必会发出来。所以我就想是否能利用飞书开发平台的API自动将这个文件发出来。
1.需要在飞书开放平台上创建一个企业自建机器人应用,并且开通上传文件和发送消息权限(需要企业管理员审核)
2.通过app id和app secret 获取token,上传文件,并且发送到指定的群聊中(需要提前将企业自建机器人加入群聊)
string logPath = $"{Application.persistentDataPath}/1.log";
File.WriteAllText(logPath, "unity中产生的日志");
Feishu.Send("appid",
"appsecret",
"群聊chatid",
logPath, () =>
{
Debug.Log("发送成功");
}, (error) =>
{
Debug.Log("发送失败");
});
群聊的chatid在飞书开发平台中获取。由于日志文件可能比较大,我还做了一次压缩,接口我已经封装好了,使用起来很方便。
using System;
using System.Collections;
using System.IO;
using System.IO.Compression;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
public class Feishu:MonoBehaviour
{
static Feishu Inst;
static Feishu()
{
Inst = new GameObject().AddComponent<Feishu>();
GameObject.DontDestroyOnLoad(Inst.gameObject);
}
private const string REQUEST_TOKEN_URL =
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal";
private const string REQUEST_UPLOADFILE_URL = "https://open.feishu.cn/open-apis/im/v1/files";
private const string REQUEST_SENDCHAT_URL =
"https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id";
/// <summary>
/// 飞书聊天群发文件
/// </summary>
/// <param name="app_id">飞书平台id</param>
/// <param name="app_secret">飞书平台secret</param>
/// <param name="groupid">聊天窗口id</param>
/// <param name="path">文件路径</param>
/// <param name="success">成功</param>
/// <param name="error">失败</param>
public static void Send(string app_id, string app_secret, string groupid, string path, Action success,
Action<string> error)
{
Inst.StartCoroutine(UploadFile(app_id, app_secret, groupid, path, success, error));
}
static IEnumerator UploadFile(string app_id, string app_secret,string groupid,string path,Action success, Action<string>error)
{
string tenant_access_token=null;
string file_key = null;
string json = $"{{"app_id":"{app_id}","app_secret":"{app_secret}"}}";
bool isFinish = false;
using (UnityWebRequest www = UnityWebRequest.Post(REQUEST_TOKEN_URL, json, ""))
{
www.SetRequestHeader("Content-Type", "application/json; charset=utf-8");
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
error?.Invoke(www.error);
}
else
{
var token = JsonUtility.FromJson<Token>(www.downloadHandler.text);
if (token.code == 0)
{
tenant_access_token = token.tenant_access_token;
}
else
{
error?.Invoke(www.downloadHandler.text);
}
}
}
if (!string.IsNullOrEmpty(tenant_access_token))
{
WWWForm from = new WWWForm();
from.AddField("file_type", "stream");
from.AddField("file_name", Path.GetFileNameWithoutExtension(path)+".zip");
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var streamReader = new StreamReader(fileStream))
{
var txt = streamReader.ReadToEnd();
using (var outStream = new MemoryStream())
{
using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
{
var fileInArchive = archive.CreateEntry(Path.GetFileName(path));
using (var entryStream = fileInArchive.Open())
using (var fileToCompressStream = new MemoryStream(Encoding.UTF8.GetBytes(txt)))
{
fileToCompressStream.CopyTo(entryStream);
}
}
from.AddBinaryData("file",outStream.ToArray());
}
}
using (UnityWebRequest www = UnityWebRequest.Post(REQUEST_UPLOADFILE_URL, from))
{
www.SetRequestHeader("Authorization", $"Bearer {tenant_access_token}");
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
error?.Invoke(www.error);
}
else
{
var file = JsonUtility.FromJson<FileKey>(www.downloadHandler.text);
if (file.code == 0)
{
Debug.Log(www.downloadHandler.text);
file_key = file.data.file_key;
}
else
{
error?.Invoke(www.downloadHandler.text);
}
}
}
}
if (!string.IsNullOrEmpty(file_key))
{
json = $"{{"receive_id": "{groupid}","content": "{{\"file_key\":\"{file_key}\"}}","msg_type": "file"}}";
using (UnityWebRequest www = UnityWebRequest.Post(REQUEST_SENDCHAT_URL, json,""))
{
www.SetRequestHeader("Authorization", $"Bearer {tenant_access_token}");
www.SetRequestHeader("Content-Type", "application/json; charset=utf-8");
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
error?.Invoke(www.error);
}
else
{
var post = JsonUtility.FromJson<Post>(www.downloadHandler.text);
if(post.code == 0)
{
isFinish = true;
}else
error?.Invoke(www.downloadHandler.text);
}
}
}
if (isFinish)
{
success?.Invoke();
}
else
{
error?.Invoke("未知");
}
}
[System.Serializable]
class Token
{
public int code;
public string tenant_access_token;
}
[System.Serializable]
class Data
{
public string file_key;
}
[System.Serializable]
class FileKey
{
public int code;
public Data data;
}
[System.Serializable]
class Post
{
public int code;
}
}
从此以后我们和测试、策划之间的沟通就变成了, “把日志发群里” 无论它手机是否出现过报错、闪退,当天的日志都会写在一个文件中发出来,有了这个小工具,极大的提高了我们的开发调Bug的效率。
- 本文固定链接: https://www.xuanyusong.com/archives/5086
- 转载请注明: 雨松MOMO于 雨松MOMO程序研究院 发表
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!


说的不错
感谢雨松,我也把流程跑通了,主要有几个问题处理了下
1.需要注释掉以下代码,这会导致协议错误400
http://www.SetRequestHeader(“Content-Type”, “application/json; charset=utf-8”);
2.直接Post会提示属性错误,查了一下可能是URLEncode的问题,对应博主的文章在下面
https://blog.csdn.net/qq_38721111/article/details/130242496
可能是unity版本问题吗? 我用的是unitiy2022.3.20f 目前这段代码在PC和手机上都没有问题。我也在关注下你说的问题。
我的是2021.3.19,有可能新版本对这些情况进行了处理,毕竟我查下来发现还是不少这个问题的讨论的。而且post的接口也不太一样,你的代码里面多了第三个空字符串的参数,2021里只有两个参数