Skip to content
45 changes: 45 additions & 0 deletions Appegy.UniLogger.Lab/Assets/Scenes/ExampleScene.unity
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,50 @@ NavMeshSettings:
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &315014944
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 315014946}
- component: {fileID: 315014945}
m_Layer: 0
m_Name: ExceptionInAggregate
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &315014945
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 315014944}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 52aa6e75cb376c29aa4b9a0c2a76ac86, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Appegy.UniLogger.Example.ExceptionInAggregate
--- !u!4 &315014946
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 315014944}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &519420028
GameObject:
m_ObjectHideFlags: 0
Expand Down Expand Up @@ -793,3 +837,4 @@ SceneRoots:
- {fileID: 1449897689}
- {fileID: 1073955932}
- {fileID: 1464717865}
- {fileID: 315014946}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace Appegy.UniLogger.Example
{
public class ExceptionInAggregate : MonoBehaviour
{
private void Start()
{
ULogger.LogException(BuildAggregate());
}

[MethodImpl(MethodImplOptions.NoInlining)]
private AggregateException BuildAggregate()
{
try
{
ThrowInner();
}
catch (Exception inner)
{
return new AggregateException("Wrapped by aggregate", inner);
}
return null;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void ThrowInner()
{
throw new InvalidOperationException("Real cause inside aggregate");
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace Appegy.UniLogger.Example
Expand All @@ -10,6 +11,7 @@ private void Start()
ThrowMethod();
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void ThrowMethod()
{
throw new Exception("Throw exception in method");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace Appegy.UniLogger.Example
Expand All @@ -12,6 +13,7 @@ private void Start()
ThrowMethod();
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void ThrowMethod()
{
Debug.LogException(new Exception("Custom exception"), this);
Expand Down
15 changes: 15 additions & 0 deletions src/Runtime/Internal/LogRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal readonly struct LogRecord
public readonly Object Context;
public readonly DateTime LogTime;
public readonly int ThreadId;
public readonly Exception Exception;

public LogRecord(IReadOnlyList<string> tags, LogLevel logLevel, string message, string stackTrace, Color color, Object context, DateTime logTime, int threadId)
{
Expand All @@ -26,6 +27,20 @@ public LogRecord(IReadOnlyList<string> tags, LogLevel logLevel, string message,
Context = context;
LogTime = logTime;
ThreadId = threadId;
Exception = null;
}

public LogRecord(Exception exception, string message)
{
Exception = exception;
Message = message;
Tags = null;
LogLevel = default;
StackTrace = null;
Color = default;
Context = null;
LogTime = default;
ThreadId = 0;
}
}
}
138 changes: 138 additions & 0 deletions src/Runtime/Internal/UnityExceptionFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using UnityEngine;

namespace Appegy.UniLogger
{
internal static class UnityExceptionFormatter
{
private static readonly string[] InternalFramePrefixes =
{
"UnityEngine.StackTraceUtility",
"UnityEngine.Debug",
"UnityEngine.Logger",
"UnityEngine.UnityLogger",
"Appegy.UniLogger.ULogger",
"Appegy.UniLogger.ExtendedULogger",
"Appegy.UniLogger.UnityExceptionFormatter",
"Appegy.UniLogger.LogDispatcher",
};

private static readonly Func<StackTrace, string> ExtractFormatted = CreateExtractDelegate();

private static Func<StackTrace, string> CreateExtractDelegate()
{
try
{
var method = typeof(StackTraceUtility).GetMethod(
"ExtractFormattedStackTrace",
BindingFlags.Static | BindingFlags.NonPublic,
null, new[] { typeof(StackTrace) }, null);
return method == null
? null
: (Func<StackTrace, string>)method.CreateDelegate(typeof(Func<StackTrace, string>));
}
catch
{
return null;
}
}

public static string Format(Exception exception)
{
if (exception == null) return string.Empty;

var builder = StringBuilderPool.GetBuilder();
try
{
var stack = FormatTrace(new StackTrace(exception, true));

builder.Append(exception.GetType().Name).Append(": ").Append(exception.Message).Append('\n');

if (string.IsNullOrEmpty(stack))
{
AppendStrippedInternalFrames(builder, StackTraceUtility.ExtractStackTrace());
}
else
{
builder.Append(stack);
}

return builder.ToString();
}
catch
{
return exception.ToString();
}
finally
{
StringBuilderPool.ReturnBuilder(builder);
}
}

private static string FormatTrace(StackTrace trace)
{
if (ExtractFormatted == null || trace.FrameCount == 0)
{
return null;
}
return ExtractFormatted(trace);
}

private static void AppendStrippedInternalFrames(StringBuilder builder, string stack)
{
if (string.IsNullOrEmpty(stack))
{
return;
}

var start = 0;
var length = stack.Length;
while (start < length)
{
var lineEnd = stack.IndexOf('\n', start);
var lineLength = (lineEnd < 0 ? length : lineEnd) - start;
if (!IsInternalFrame(stack, start, lineLength))
{
break;
}
if (lineEnd < 0)
{
start = length;
break;
}
start = lineEnd + 1;
}

if (start >= length)
{
builder.Append(stack);
}
else
{
builder.Append(stack, start, length - start);
}
}

private static bool IsInternalFrame(string stack, int lineStart, int lineLength)
{
foreach (var prefix in InternalFramePrefixes)
{
if (prefix.Length <= lineLength &&
string.CompareOrdinal(stack, lineStart, prefix, 0, prefix.Length) == 0 &&
(prefix.Length == lineLength || IsFrameBoundary(stack[lineStart + prefix.Length])))
{
return true;
}
}
return false;
}

private static bool IsFrameBoundary(char c)
{
return c == '.' || c == ':' || c == '(' || c == ' ';
}
}
}
2 changes: 2 additions & 0 deletions src/Runtime/Internal/UnityExceptionFormatter.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/Runtime/Internal/UnityLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public UnityLogger(ILogHandler defaultLogger)
public void LogException(Exception exception, Object context)
{
Default.LogException(exception, context);
ULogger.EnqueueException(exception);
}

public void LogFormat(LogType logType, Object context, string format, params object[] args)
Expand Down
5 changes: 4 additions & 1 deletion src/Runtime/Services/Target.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using JetBrains.Annotations;
using System;
using JetBrains.Annotations;
using static Appegy.UniLogger.LogLevelExtensions;

namespace Appegy.UniLogger
Expand Down Expand Up @@ -52,6 +53,8 @@ public bool GetStackTraceEnabled(LogLevel logLevel)

protected internal abstract void Log(string message, [CanBeNull] string stackTrace);

protected internal abstract void LogException([NotNull] Exception exception, string message);

protected internal virtual void Flush()
{
}
Expand Down
5 changes: 5 additions & 0 deletions src/Runtime/Targets/FileTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ protected internal override void Log(string message, string stackTrace)
}
}

protected internal override void LogException(Exception exception, string message)
{
Log(message, null);
}

protected internal override void Flush()
{
if (_disposed || _writer == null) return;
Expand Down
5 changes: 5 additions & 0 deletions src/Runtime/Targets/InMemoryTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ protected internal override void Log(string message, string stackTrace)
}
}

protected internal override void LogException(Exception exception, string message)
{
Log(message, null);
}

public string GetContent()
{
lock (_gate)
Expand Down
6 changes: 1 addition & 5 deletions src/Runtime/Types/LogLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public enum LogLevel
Log = 1,
Warning = 2,
Error = 3,
Exception = 4,
}

public static class LogLevelExtensions
Expand All @@ -26,7 +25,7 @@ public static LogLevel ConvertToLogLevel(this LogType original)
LogType.Assert => LogLevel.Error,
LogType.Warning => LogLevel.Warning,
LogType.Log => LogLevel.Log,
LogType.Exception => LogLevel.Exception,
LogType.Exception => LogLevel.Error,
_ => throw new ArgumentOutOfRangeException(nameof(original), original, null)
};
}
Expand All @@ -39,7 +38,6 @@ public static LogType ConvertToLogType(this LogLevel original)
LogLevel.Log => LogType.Log,
LogLevel.Warning => LogType.Warning,
LogLevel.Error => LogType.Error,
LogLevel.Exception => LogType.Exception,
_ => throw new ArgumentOutOfRangeException(nameof(original), original, null)
};
}
Expand All @@ -52,7 +50,6 @@ public static string ToShortString(this LogLevel logLevel)
LogLevel.Log => "LG",
LogLevel.Warning => "WN",
LogLevel.Error => "ER",
LogLevel.Exception => "EX",
_ => throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null)
};
}
Expand All @@ -65,7 +62,6 @@ public static string ToMessageColor(this LogLevel logLevel)
LogLevel.Log => "white",
LogLevel.Warning => "orange",
LogLevel.Error => "red",
LogLevel.Exception => "#FF5349FF", // (orange red)
_ => throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null)
};
}
Expand Down
Loading
Loading