using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using AndroidApplication = Android.App.Application;
using JObject = Java.Lang.Object;
using JStackTraceElement = Java.Lang.StackTraceElement;
using JThread = Java.Lang.Thread;
using JThrowable = Java.Lang.Throwable;
using NException = System.Exception;
using SQLite3 = SQLite.SQLite3;
using SQLiteException = SQLite.SQLiteException;
namespace System.Logging
{
public sealed class ThreadUncaughtExceptionHandler : JObject, JThread.IUncaughtExceptionHandler
{
readonly JThread.IUncaughtExceptionHandler? mDefaultHandler;
ThreadUncaughtExceptionHandler()
{
mDefaultHandler = JThread.DefaultUncaughtExceptionHandler;
JThread.DefaultUncaughtExceptionHandler = this;
}
public static ThreadUncaughtExceptionHandler? Instance { get; private set; }
public static void Initialize()
{
if (Instance == null) Instance = new ThreadUncaughtExceptionHandler();
}
static string GetLogDirPath()
{
var cacheDir = AndroidApplication.Context.CacheDir;
if (cacheDir == null) throw new NullReferenceException("Application.Context.CacheDir");
return Path.Combine(cacheDir.CanonicalPath, "catch_logs");
}
///
/// 读取上一次写入的日志
///
///
public static (DateTime fileCreationTime, string fileText) ReadLastExLog()
{
var dirPath = GetLogDirPath();
var dirInfo = new DirectoryInfo(dirPath);
if (dirInfo.Exists)
{
var lastExFile = dirInfo.GetFiles()
.Where(x => string.Equals(x.Extension, FileEx.LOG, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(x => x.Name).FirstOrDefault();
if (lastExFile != default)
{
using var fs = lastExFile.OpenText();
var fileCreationTime = lastExFile.CreationTime;
var fileText = fs.ReadToEnd();
return (fileCreationTime, fileText);
}
}
return default;
}
public static string ReadLastExLogFormat()
{
var temp = ReadLastExLog();
if (temp == default) return string.Empty;
var (time, @string) = temp;
try
{
var obj = Serializable.DJSON(@string);
var sb = new StringBuilder();
sb.AppendLine($"Time: {time.ToString(DateTimeFormat.Standard)}({time.DayOfWeek.ToString2()})");
sb.AppendLine($"Thread: {obj?.Thread?.Name}");
sb.AppendLine($"ProcessName: {obj?.ProcessName}");
ExceptionArgs? ex = obj?.Throwable;
while (ex != null)
{
sb.AppendLine($"ClassName: {ex.ClassName}");
sb.AppendLine($"Message: {ex.Message}");
if (ex.SQLiteExceptionResult.HasValue)
{
sb.AppendLine($"SQLiteExceptionResult: {ex.SQLiteExceptionResult.Value}({(int)ex.SQLiteExceptionResult.Value})");
}
if (ex.Message != null && !ex.Message.EndsWith(ex.NStackTrace))
{
sb.AppendLine($"StackTrace: {ex.NStackTrace}");
}
if (ex is ThrowableArgs throwable)
{
if (throwable.LocalizedMessage != ex.Message)
{
sb.AppendLine($"LocalizedMessage: {throwable.LocalizedMessage}");
}
sb.AppendLine($"JavaString: {throwable.JavaString}");
}
ex = ex.NInnerException;
sb.AppendLine();
}
return sb.ToString();
}
catch (NException ex)
{
return @string + Environment.NewLine + ex.Message;
}
}
public static StackTraceElementArgs? Convert(JStackTraceElement stackTraceElement)
{
if (stackTraceElement == default)
{
return default;
}
return new StackTraceElementArgs
{
ClassName = stackTraceElement.ClassName,
FileName = stackTraceElement.FileName,
LineNumber = stackTraceElement.LineNumber,
MethodName = stackTraceElement.MethodName,
IsNativeMethod = stackTraceElement.IsNativeMethod,
JavaString = stackTraceElement.ToString(),
};
}
public static StackTraceElementArgs?[]? Convert(IEnumerable stackTraceElements)
{
return stackTraceElements?.Select(x => Convert(x))?.ToArray();
}
static SQLiteException? InterceptSQLiteException(NException exception)
{
if (exception is SQLiteException _SQLiteException)
{
return _SQLiteException;
}
else if (exception.InnerException != default)
{
return InterceptSQLiteException(exception.InnerException);
}
else
{
return default;
}
}
static T? Convert(NException exception, T? model = default) where T : ExceptionArgs, new()
{
if (exception == default)
{
return default;
}
if (model == default)
{
model = new T { };
}
model.Message = exception.Message;
model.NSource = exception.Source;
model.NHelpLink = exception.HelpLink;
model.NHResult = exception.HResult;
model.NStackTrace = exception.StackTrace;
model.ClassName = exception.GetType().Name;
model.NInnerException = Convert(exception.InnerException);
return model;
}
public static ThrowableArgs? Convert(JThrowable? throwable)
{
if (throwable == default)
{
return default;
}
var result = new ThrowableArgs
{
LocalizedMessage = throwable.LocalizedMessage,
StackTrace = Convert(throwable.GetStackTrace()),
JavaString = throwable.ToString(),
};
var _SQLiteException = InterceptSQLiteException(throwable);
if (_SQLiteException != default)
{
result.SQLiteExceptionResult = _SQLiteException.Result;
}
result = Convert(throwable, result);
return result;
}
public static ThreadArgs? Convert(JThread? thread)
{
if (thread == default)
{
return default;
}
return new ThreadArgs
{
Id = thread.Id,
Name = thread.Name,
Priority = thread.Priority,
StackTrace = Convert(thread.GetStackTrace()),
JavaString = thread.ToString(),
};
}
public static UncaughtExceptionArgs? Convert(JThread thread, JThrowable throwable)
{
var has_thread = thread != default;
var has_throwable = throwable != default;
if (!has_thread && !has_throwable)
{
return default;
}
var result = new UncaughtExceptionArgs
{
ProcessName = AndroidApplication.Context.GetCurrentProcessName(),
};
if (has_thread)
{
result.Thread = Convert(thread);
}
if (has_throwable)
{
result.Throwable = Convert(throwable);
}
return result;
}
void JThread.IUncaughtExceptionHandler.UncaughtException(JThread thread, JThrowable throwable)
{
var now = DateTime.Now;
var dirPath = GetLogDirPath();
var filePath = Path.Combine(dirPath, now.ToUniversalTime().Ticks + FileEx.LOG);
var fileInfo = new FileInfo(filePath);
if (!fileInfo.Directory.Exists)
{
fileInfo.Directory.Create();
}
if (fileInfo.Exists)
{
fileInfo.Delete();
}
var model = Convert(thread, throwable);
if (model != null)
{
model.DateTime = now;
var @string = Serializable.SJSON(model);
using var fs = fileInfo.CreateText();
fs.WriteLine(@string);
fs.Flush();
}
mDefaultHandler?.UncaughtException(thread, throwable);
}
///
/// 传入参数纪录模型类
/// https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.UncaughtExceptionHandler.html#uncaughtException-java.lang.Thread-java.lang.Throwable-
///
public class UncaughtExceptionArgs
{
public ThreadArgs? Thread { get; set; }
public ThrowableArgs? Throwable { get; set; }
public DateTime DateTime { get; set; }
public string? ProcessName { get; set; }
}
///
/// 模型类
/// https://docs.oracle.com/javase/8/docs/api/java/lang/StackTraceElement.html
///
public class StackTraceElementArgs
{
public string? ClassName { get; set; }
public string? FileName { get; set; }
public int LineNumber { get; set; }
public string? MethodName { get; set; }
public bool IsNativeMethod { get; set; }
public string? JavaString { get; set; }
}
///
/// 模型类
/// https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html
///
public class ThreadArgs
{
public long Id { get; set; }
public string? Name { get; set; }
public int Priority { get; set; }
public StackTraceElementArgs?[]? StackTrace { get; set; }
public string? JavaString { get; set; }
}
///
/// 模型类
///
public class ExceptionArgs
{
public string? NSource { get; set; }
public string? NHelpLink { get; set; }
public int NHResult { get; set; }
public string? Message { get; set; }
public string? NStackTrace { get; set; }
public ExceptionArgs? NInnerException { get; set; }
public string? ClassName { get; set; }
public SQLite3.Result? SQLiteExceptionResult { get; set; }
}
///
/// 模型类
/// https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html
///
public class ThrowableArgs : ExceptionArgs
{
public string? LocalizedMessage { get; set; }
public StackTraceElementArgs?[]? StackTrace { get; set; }
public string? JavaString { get; set; }
}
}
}