485 lines
14 KiB
C#
485 lines
14 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// 安卓平台文件日志记录器,用于保存闪退前的关键信息
|
|
/// </summary>
|
|
public class AndroidFileLogger : MonoBehaviour
|
|
{
|
|
private static AndroidFileLogger _instance;
|
|
public static AndroidFileLogger Instance
|
|
{
|
|
get
|
|
{
|
|
if (_instance == null)
|
|
{
|
|
_instance = FindObjectOfType<AndroidFileLogger>();
|
|
if (_instance == null)
|
|
{
|
|
var go = new GameObject("AndroidFileLogger");
|
|
_instance = go.AddComponent<AndroidFileLogger>();
|
|
DontDestroyOnLoad(go);
|
|
}
|
|
}
|
|
return _instance;
|
|
}
|
|
}
|
|
|
|
private string _logDirectory;
|
|
private string _currentLogFile;
|
|
private Queue<string> _logBuffer;
|
|
private readonly object _logLock = new object();
|
|
private Coroutine _logFlushCoroutine;
|
|
private bool _isLoggerInitialized;
|
|
private bool _isLogReceiverRegistered;
|
|
|
|
// 日志设置
|
|
private const int MAX_LOG_LINES = 2000; // 内存中最多保存2000行日志
|
|
private const int MAX_LOG_FILE_SIZE = 5 * 1024 * 1024; // 5MB最大文件大小
|
|
private const int MAX_LOG_FILES = 5; // 最多保留5个日志文件
|
|
|
|
void Awake()
|
|
{
|
|
if (_instance != null && _instance != this)
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
|
|
_instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
|
|
// 只在安卓平台启用
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
InitializeLogger();
|
|
#endif
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
EnsureLoggerStarted();
|
|
|
|
LogInfo("AndroidFileLogger", "文件日志系统已启动");
|
|
LogInfo("AndroidFileLogger", $"日志目录: {_logDirectory}");
|
|
LogInfo("AndroidFileLogger", $"当前日志文件: {_currentLogFile}");
|
|
|
|
// 记录系统信息
|
|
LogSystemInfo();
|
|
#endif
|
|
}
|
|
|
|
private void InitializeLogger()
|
|
{
|
|
if (_isLoggerInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
// 获取安卓应用专用目录
|
|
_logDirectory = Path.Combine(Application.persistentDataPath, "logs");
|
|
|
|
// 确保目录存在
|
|
if (!Directory.Exists(_logDirectory))
|
|
{
|
|
Directory.CreateDirectory(_logDirectory);
|
|
}
|
|
|
|
// 创建新的日志文件
|
|
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
_currentLogFile = Path.Combine(_logDirectory, $"dcx_log_{timestamp}.txt");
|
|
|
|
// 初始化日志缓冲区
|
|
_logBuffer = new Queue<string>();
|
|
|
|
// 清理旧日志文件
|
|
CleanupOldLogFiles();
|
|
|
|
_isLoggerInitialized = true;
|
|
EnsureLogReceiverRegistered();
|
|
|
|
Debug.Log($"[AndroidFileLogger] 初始化完成,日志路径: {_currentLogFile}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[AndroidFileLogger] 初始化失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void EnsureLoggerStarted()
|
|
{
|
|
InitializeLogger();
|
|
EnsureLogReceiverRegistered();
|
|
|
|
if (_logFlushCoroutine == null)
|
|
{
|
|
_logFlushCoroutine = StartCoroutine(LogFlushCoroutine());
|
|
}
|
|
}
|
|
|
|
private void EnsureLogReceiverRegistered()
|
|
{
|
|
if (_isLogReceiverRegistered)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Application.logMessageReceived += OnLogMessageReceived;
|
|
_isLogReceiverRegistered = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unity日志消息回调
|
|
/// </summary>
|
|
private void OnLogMessageReceived(string condition, string stackTrace, LogType type)
|
|
{
|
|
if (string.IsNullOrEmpty(_logDirectory)) return;
|
|
|
|
// 过滤掉一些不重要的日志
|
|
if (ShouldSkipLog(condition, type)) return;
|
|
|
|
string logEntry = FormatLogEntry(condition, stackTrace, type);
|
|
|
|
lock (_logLock)
|
|
{
|
|
// 添加到缓冲区
|
|
_logBuffer.Enqueue(logEntry);
|
|
|
|
// 限制缓冲区大小
|
|
while (_logBuffer.Count > MAX_LOG_LINES)
|
|
{
|
|
_logBuffer.Dequeue();
|
|
}
|
|
|
|
// 如果是错误或异常,立即写入文件
|
|
if (type == LogType.Error || type == LogType.Exception || type == LogType.Assert)
|
|
{
|
|
FlushLogsToFile();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 判断是否跳过某些日志
|
|
/// </summary>
|
|
private bool ShouldSkipLog(string condition, LogType type)
|
|
{
|
|
// 跳过过于频繁的普通日志
|
|
if (type == LogType.Log)
|
|
{
|
|
if (condition.Contains("[帧处理]") ||
|
|
condition.Contains("[队列]") && !condition.Contains("警告") && !condition.Contains("错误") ||
|
|
condition.Contains("OptimizedLog") ||
|
|
condition.Contains("Memory:") && !condition.Contains("警告"))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 格式化日志条目
|
|
/// </summary>
|
|
private string FormatLogEntry(string condition, string stackTrace, LogType type)
|
|
{
|
|
var timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
|
|
var typeStr = type.ToString();
|
|
var entry = new StringBuilder();
|
|
|
|
entry.AppendLine($"[{timestamp}] [{typeStr}] {condition}");
|
|
|
|
// 对于错误和异常,包含堆栈跟踪
|
|
if ((type == LogType.Error || type == LogType.Exception) && !string.IsNullOrEmpty(stackTrace))
|
|
{
|
|
entry.AppendLine($"StackTrace: {stackTrace}");
|
|
}
|
|
|
|
entry.AppendLine("---");
|
|
|
|
return entry.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 记录自定义日志
|
|
/// </summary>
|
|
public void LogInfo(string tag, string message)
|
|
{
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
var logEntry = $"[{DateTime.Now:HH:mm:ss.fff}] [INFO] [{tag}] {message}\n---\n";
|
|
|
|
lock (_logLock)
|
|
{
|
|
_logBuffer.Enqueue(logEntry);
|
|
while (_logBuffer.Count > MAX_LOG_LINES)
|
|
{
|
|
_logBuffer.Dequeue();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// 记录错误日志(立即写入文件)
|
|
/// </summary>
|
|
public void LogError(string tag, string message)
|
|
{
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
var logEntry = $"[{DateTime.Now:HH:mm:ss.fff}] [ERROR] [{tag}] {message}\n---\n";
|
|
|
|
lock (_logLock)
|
|
{
|
|
_logBuffer.Enqueue(logEntry);
|
|
while (_logBuffer.Count > MAX_LOG_LINES)
|
|
{
|
|
_logBuffer.Dequeue();
|
|
}
|
|
|
|
// 立即刷写错误日志
|
|
FlushLogsToFile();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// 记录内存使用情况
|
|
/// </summary>
|
|
public void LogMemoryUsage(float memoryMB, string context = "")
|
|
{
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
var message = string.IsNullOrEmpty(context)
|
|
? $"内存使用: {memoryMB:F1}MB"
|
|
: $"内存使用: {memoryMB:F1}MB - {context}";
|
|
|
|
LogInfo("Memory", message);
|
|
|
|
// 内存超过警告阈值时立即写入
|
|
if (memoryMB > 250f)
|
|
{
|
|
lock (_logLock)
|
|
{
|
|
FlushLogsToFile();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// 记录系统信息
|
|
/// </summary>
|
|
private void LogSystemInfo()
|
|
{
|
|
LogInfo("System", $"Unity版本: {Application.unityVersion}");
|
|
LogInfo("System", $"平台: {Application.platform}");
|
|
LogInfo("System", $"设备型号: {SystemInfo.deviceModel}");
|
|
LogInfo("System", $"设备名称: {SystemInfo.deviceName}");
|
|
LogInfo("System", $"操作系统: {SystemInfo.operatingSystem}");
|
|
LogInfo("System", $"系统内存: {SystemInfo.systemMemorySize}MB");
|
|
LogInfo("System", $"显卡内存: {SystemInfo.graphicsMemorySize}MB");
|
|
LogInfo("System", $"处理器: {SystemInfo.processorType}");
|
|
LogInfo("System", $"处理器核心数: {SystemInfo.processorCount}");
|
|
LogInfo("System", $"应用数据路径: {Application.persistentDataPath}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 定期刷写日志协程
|
|
/// </summary>
|
|
private IEnumerator LogFlushCoroutine()
|
|
{
|
|
while (true)
|
|
{
|
|
yield return new WaitForSeconds(10f); // 每10秒刷写一次
|
|
|
|
lock (_logLock)
|
|
{
|
|
if (_logBuffer.Count > 50) // 缓冲区有超过50条日志时才写入
|
|
{
|
|
FlushLogsToFile();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 刷写日志到文件
|
|
/// </summary>
|
|
private void FlushLogsToFile()
|
|
{
|
|
try
|
|
{
|
|
if (_logBuffer.Count == 0 || string.IsNullOrEmpty(_currentLogFile)) return;
|
|
|
|
// 检查文件大小,如果过大则创建新文件
|
|
if (File.Exists(_currentLogFile))
|
|
{
|
|
var fileInfo = new FileInfo(_currentLogFile);
|
|
if (fileInfo.Length > MAX_LOG_FILE_SIZE)
|
|
{
|
|
CreateNewLogFile();
|
|
}
|
|
}
|
|
|
|
// 批量写入日志
|
|
var logsToWrite = new List<string>();
|
|
while (_logBuffer.Count > 0 && logsToWrite.Count < 100) // 每次最多写100条
|
|
{
|
|
logsToWrite.Add(_logBuffer.Dequeue());
|
|
}
|
|
|
|
if (logsToWrite.Count > 0)
|
|
{
|
|
File.AppendAllText(_currentLogFile, string.Join("", logsToWrite), Encoding.UTF8);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[AndroidFileLogger] 写入日志文件失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建新的日志文件
|
|
/// </summary>
|
|
private void CreateNewLogFile()
|
|
{
|
|
try
|
|
{
|
|
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
_currentLogFile = Path.Combine(_logDirectory, $"dcx_log_{timestamp}.txt");
|
|
|
|
LogInfo("AndroidFileLogger", $"创建新日志文件: {_currentLogFile}");
|
|
CleanupOldLogFiles();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[AndroidFileLogger] 创建新日志文件失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清理旧日志文件
|
|
/// </summary>
|
|
private void CleanupOldLogFiles()
|
|
{
|
|
try
|
|
{
|
|
if (!Directory.Exists(_logDirectory)) return;
|
|
|
|
var logFiles = Directory.GetFiles(_logDirectory, "dcx_log_*.txt");
|
|
|
|
// 按修改时间排序,保留最新的几个文件
|
|
Array.Sort(logFiles, (x, y) => File.GetLastWriteTime(y).CompareTo(File.GetLastWriteTime(x)));
|
|
|
|
// 删除多余的文件
|
|
for (int i = MAX_LOG_FILES; i < logFiles.Length; i++)
|
|
{
|
|
try
|
|
{
|
|
File.Delete(logFiles[i]);
|
|
Debug.Log($"[AndroidFileLogger] 删除旧日志文件: {Path.GetFileName(logFiles[i])}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[AndroidFileLogger] 删除日志文件失败: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[AndroidFileLogger] 清理旧日志文件失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 应用暂停时保存日志
|
|
/// </summary>
|
|
void OnApplicationPause(bool pauseStatus)
|
|
{
|
|
if (pauseStatus)
|
|
{
|
|
lock (_logLock)
|
|
{
|
|
FlushLogsToFile();
|
|
}
|
|
LogInfo("Application", "应用暂停,日志已保存");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 应用退出时保存日志
|
|
/// </summary>
|
|
void OnApplicationQuit()
|
|
{
|
|
lock (_logLock)
|
|
{
|
|
FlushLogsToFile();
|
|
}
|
|
LogInfo("Application", "应用退出,日志已保存");
|
|
|
|
if (_logFlushCoroutine != null)
|
|
{
|
|
StopCoroutine(_logFlushCoroutine);
|
|
}
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
if (_isLogReceiverRegistered)
|
|
{
|
|
Application.logMessageReceived -= OnLogMessageReceived;
|
|
_isLogReceiverRegistered = false;
|
|
}
|
|
|
|
// 最后一次保存日志
|
|
if (_logBuffer != null && _logBuffer.Count > 0)
|
|
{
|
|
try
|
|
{
|
|
FlushLogsToFile();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[AndroidFileLogger] OnDestroy保存日志失败: {ex.Message}");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取当前日志文件路径
|
|
/// </summary>
|
|
public string GetCurrentLogFilePath()
|
|
{
|
|
return _currentLogFile;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取所有日志文件路径
|
|
/// </summary>
|
|
public string[] GetAllLogFiles()
|
|
{
|
|
try
|
|
{
|
|
if (!Directory.Exists(_logDirectory)) return new string[0];
|
|
|
|
var logFiles = Directory.GetFiles(_logDirectory, "dcx_log_*.txt");
|
|
Array.Sort(logFiles, (x, y) => File.GetLastWriteTime(y).CompareTo(File.GetLastWriteTime(x)));
|
|
|
|
return logFiles;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[AndroidFileLogger] 获取日志文件列表失败: {ex.Message}");
|
|
return new string[0];
|
|
}
|
|
}
|
|
}
|