Skip to content

Commit 11c4f95

Browse files
authored
fix: Structured Logging follow-up (#2390)
1 parent 2d8a378 commit 11c4f95

File tree

8 files changed

+169
-198
lines changed

8 files changed

+169
-198
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
## Unreleased
44

5-
### Fixes
5+
### Fixes
66

7+
- Structured logs now have the `origin` and `sdk` attributes correctly set ([#2390](https://github.com/getsentry/sentry-unity/pull/2390))
78
- Resolved possible startup crashes on Android VR platforms like the Oculus Quest. The SDK no longer natively subscribes to interaction hooks for automatic tracing and breadcrumb creation. ([#2393](https://github.com/getsentry/sentry-unity/pull/2393))
89

910
### Dependencies

src/Sentry.Unity/Integrations/UnityApplicationLoggingIntegration.cs

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using Sentry.Extensibility;
3+
using Sentry.Infrastructure;
34
using Sentry.Integrations;
45
using UnityEngine;
56

@@ -12,26 +13,28 @@ namespace Sentry.Unity.Integrations;
1213
/// </summary>
1314
internal class UnityApplicationLoggingIntegration : ISdkIntegration
1415
{
15-
private readonly IApplication _application;
1616
private readonly bool _captureExceptions;
17+
private readonly IApplication _application;
18+
private readonly ISystemClock _clock;
1719

1820
private ErrorTimeDebounce _errorTimeDebounce = null!; // Set in Register
1921
private LogTimeDebounce _logTimeDebounce = null!; // Set in Register
2022
private WarningTimeDebounce _warningTimeDebounce = null!; // Set in Register
2123

22-
private IHub? _hub;
24+
private IHub _hub = null!; // Set in Register
2325
private SentryUnityOptions _options = null!; // Set in Register
2426

25-
internal UnityApplicationLoggingIntegration(bool captureExceptions = false, IApplication? application = null)
27+
internal UnityApplicationLoggingIntegration(bool captureExceptions = false, IApplication? application = null, ISystemClock? clock = null)
2628
{
2729
_captureExceptions = captureExceptions;
2830
_application = application ?? ApplicationAdapter.Instance;
31+
_clock = clock ?? SystemClock.Clock;
2932
}
3033

3134
public void Register(IHub hub, SentryOptions sentryOptions)
3235
{
33-
_hub = hub;
34-
// This should never throw
36+
// These should never throw but in case they do...
37+
_hub = hub ?? throw new ArgumentException("Hub is null.");
3538
_options = sentryOptions as SentryUnityOptions ?? throw new ArgumentException("Options is not of type 'SentryUnityOptions'.");
3639

3740
_logTimeDebounce = new LogTimeDebounce(_options.DebounceTimeLog);
@@ -44,11 +47,6 @@ public void Register(IHub hub, SentryOptions sentryOptions)
4447

4548
internal void OnLogMessageReceived(string message, string stacktrace, LogType logType)
4649
{
47-
if (_hub is null)
48-
{
49-
return;
50-
}
51-
5250
// We're not capturing the SDK's own logs
5351
if (message.StartsWith(UnityLogger.LogTag))
5452
{
@@ -64,6 +62,7 @@ internal void OnLogMessageReceived(string message, string stacktrace, LogType lo
6462
ProcessException(message, stacktrace, logType);
6563
ProcessError(message, stacktrace, logType);
6664
ProcessBreadcrumbs(message, logType);
65+
ProcessStructuredLog(message, logType);
6766
}
6867

6968
private bool IsGettingDebounced(LogType logType)
@@ -92,7 +91,7 @@ private void ProcessException(string message, string stacktrace, LogType logType
9291
_options.LogDebug("Exception capture has been enabled. Capturing exception through '{0}'.", nameof(UnityApplicationLoggingIntegration));
9392

9493
var evt = UnityLogEventFactory.CreateExceptionEvent(message, stacktrace, false, _options);
95-
_hub?.CaptureEvent(evt);
94+
_hub.CaptureEvent(evt);
9695
}
9796
}
9897

@@ -108,11 +107,11 @@ private void ProcessError(string message, string stacktrace, LogType logType)
108107
if (_options.AttachStacktrace && !string.IsNullOrEmpty(stacktrace))
109108
{
110109
var evt = UnityLogEventFactory.CreateMessageEvent(message, stacktrace, SentryLevel.Error, _options);
111-
_hub?.CaptureEvent(evt);
110+
_hub.CaptureEvent(evt);
112111
}
113112
else
114113
{
115-
_hub?.CaptureMessage(message, level: SentryLevel.Error);
114+
_hub.CaptureMessage(message, level: SentryLevel.Error);
116115
}
117116
}
118117

@@ -133,8 +132,26 @@ private void ProcessBreadcrumbs(string message, LogType logType)
133132
if (_options.AddBreadcrumbsForLogType.TryGetValue(logType, out var value) && value)
134133
{
135134
_options.LogDebug("Adding breadcrumb for log message of type: {0}", logType);
136-
_hub?.AddBreadcrumb(message: message, category: "unity.logger", level: ToBreadcrumbLevel(logType));
135+
_hub.AddBreadcrumb(message: message, category: "unity.logger", level: ToBreadcrumbLevel(logType));
136+
}
137+
}
138+
139+
private void ProcessStructuredLog(string message, LogType logType)
140+
{
141+
if (!_options.Experimental.EnableLogs || !_options.Experimental.CaptureStructuredLogsForLogType.TryGetValue(logType, out var captureLog) || !captureLog)
142+
{
143+
return;
137144
}
145+
146+
_options.LogDebug("Capturing structured log message of type '{0}'.", logType);
147+
148+
SentryLog.GetTraceIdAndSpanId(_hub, out var traceId, out var spanId);
149+
SentryLog log = new(_clock.GetUtcNow(), traceId, ToLogLevel(logType), message) { ParentSpanId = spanId };
150+
151+
log.SetDefaultAttributes(_options, UnitySdkInfo.Sdk);
152+
log.SetOrigin("auto.log.unity");
153+
154+
_hub.Logger.CaptureLog(log);
138155
}
139156

140157
private void OnQuitting() => _application.LogMessageReceived -= OnLogMessageReceived;
@@ -149,4 +166,15 @@ private static BreadcrumbLevel ToBreadcrumbLevel(LogType logType)
149166
LogType.Warning => BreadcrumbLevel.Warning,
150167
_ => BreadcrumbLevel.Info
151168
};
169+
170+
private static SentryLogLevel ToLogLevel(LogType logType)
171+
=> logType switch
172+
{
173+
LogType.Assert => SentryLogLevel.Error,
174+
LogType.Error => SentryLogLevel.Error,
175+
LogType.Exception => SentryLogLevel.Error,
176+
LogType.Log => SentryLogLevel.Info,
177+
LogType.Warning => SentryLogLevel.Warning,
178+
_ => SentryLogLevel.Info
179+
};
152180
}

src/Sentry.Unity/Integrations/UnityLogHandlerIntegration.cs

Lines changed: 4 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,15 @@ namespace Sentry.Unity.Integrations;
1212
/// </summary>
1313
internal sealed class UnityLogHandlerIntegration : ISdkIntegration, ILogHandler
1414
{
15-
private readonly Func<SentryStructuredLogger>? _loggerFactory;
1615
private IHub? _hub;
1716
private SentryUnityOptions _options = null!; // Set during register
1817
private ILogHandler _unityLogHandler = null!; // Set during register
19-
private SentryStructuredLogger _structuredLogger = null!; // Set during register
20-
21-
// For testing: allows injecting a custom logger factory
22-
internal UnityLogHandlerIntegration(Func<SentryStructuredLogger>? loggerFactory = null)
23-
{
24-
_loggerFactory = loggerFactory;
25-
}
2618

2719
public void Register(IHub hub, SentryOptions sentryOptions)
2820
{
2921
_hub = hub;
3022
// This should never happen, but if it does...
3123
_options = sentryOptions as SentryUnityOptions ?? throw new ArgumentException("Options is not of type 'SentryUnityOptions'.");
32-
_structuredLogger = _loggerFactory?.Invoke() ?? _hub.Logger;
3324

3425
// If called twice (i.e. init with the same options object) the integration will reference itself as the
3526
// original handler loghandler and endlessly forward to itself
@@ -70,66 +61,13 @@ internal void ProcessException(Exception exception, UnityEngine.Object? context)
7061
// https://docs.sentry.io/platforms/unity/troubleshooting/#unhandled-exceptions---debuglogexception
7162
exception.SetSentryMechanism("Unity.LogException", handled: false, terminal: false);
7263
_ = _hub.CaptureException(exception);
73-
74-
if (_options.Experimental.CaptureStructuredLogsForLogType.TryGetValue(LogType.Exception, out var captureException) && captureException)
75-
{
76-
_options.LogDebug("Capturing structured log message of type '{0}'.", LogType.Exception);
77-
_structuredLogger.LogError(exception.Message);
78-
}
7964
}
8065

8166
public void LogFormat(LogType logType, UnityEngine.Object? context, string format, params object[] args)
8267
{
83-
try
84-
{
85-
ProcessLog(logType, context, format, args);
86-
}
87-
finally
88-
{
89-
// Always pass the log back to Unity
90-
// Capturing of `Debug`, `Warning`, and `Error` happens in the Application Logging Integration.
91-
// The LogHandler does not have access to the stacktrace information required
92-
_unityLogHandler.LogFormat(logType, context, format, args);
93-
}
94-
}
95-
96-
private void ProcessLog(LogType logType, UnityEngine.Object? context, string format, params object[] args)
97-
{
98-
if (_hub?.IsEnabled is not true || !_options.Experimental.EnableLogs)
99-
{
100-
return;
101-
}
102-
103-
// We're not capturing the SDK's own logs.
104-
if (args.Length > 1 && Equals(args[0], UnityLogger.LogTag))
105-
{
106-
return;
107-
}
108-
109-
ProcessStructuredLog(logType, format, args);
110-
}
111-
112-
private void ProcessStructuredLog(LogType logType, string format, params object[] args)
113-
{
114-
if (!_options.Experimental.CaptureStructuredLogsForLogType.TryGetValue(logType, out var captureLog) || !captureLog)
115-
{
116-
return;
117-
}
118-
119-
_options.LogDebug("Capturing structured log message of type '{0}'.", logType);
120-
121-
switch (logType)
122-
{
123-
case LogType.Log:
124-
_structuredLogger.LogInfo(format, args);
125-
break;
126-
case LogType.Warning:
127-
_structuredLogger.LogWarning(format, args);
128-
break;
129-
case LogType.Assert:
130-
case LogType.Error:
131-
_structuredLogger.LogError(format, args);
132-
break;
133-
}
68+
// Always pass the log back to Unity
69+
// Capturing of `Debug`, `Warning`, and `Error` happens in the Application Logging Integration.
70+
// The LogHandler does not have access to the stacktrace information required
71+
_unityLogHandler.LogFormat(logType, context, format, args);
13472
}
13573
}

src/Sentry.Unity/Integrations/UnityScopeIntegration.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ internal static class UnitySdkInfo
1313
public static string Version { get; } = typeof(UnitySdkInfo).Assembly.GetNameAndVersion().Version ?? "0.0.0";
1414
public const string Name = "sentry.dotnet.unity";
1515
public const string PackageName = "upm:sentry.unity";
16+
17+
public static readonly SdkVersion Sdk = new() { Name = Name, Version = Version };
1618
}
1719

1820
internal class UnityScopeIntegration : ISdkIntegration

test/Sentry.Unity.Tests/Stubs/TestHub.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@ internal sealed class TestHub : IHub
1616
public IReadOnlyList<SentryTransaction> CapturedTransactions => _capturedTransactions;
1717
public IReadOnlyList<Action<Scope>> ConfigureScopeCalls => _configureScopeCalls;
1818

19-
public TestHub(bool isEnabled = true)
19+
public TestHub(bool isEnabled = true, SentryStructuredLogger? logger = null)
2020
{
21-
#pragma warning disable SENTRY0001
22-
Logger = null!;
23-
#pragma warning restore SENTRY0001
2421
IsEnabled = isEnabled;
22+
Logger = logger ?? new TestStructuredLogger();
2523
}
2624
public bool IsEnabled { get; }
2725

@@ -134,10 +132,7 @@ public void BindException(Exception exception, ISpan span)
134132
throw new NotImplementedException();
135133
}
136134

137-
ISpan? IHub.GetSpan()
138-
{
139-
throw new NotImplementedException();
140-
}
135+
ISpan? IHub.GetSpan() => null;
141136

142137
public SentryTraceHeader? GetTraceHeader()
143138
{

test/Sentry.Unity.Tests/Stubs/TestStructuredLogger.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,24 @@ namespace Sentry.Unity.Tests.Stubs;
66
internal sealed class TestStructuredLogger : SentryStructuredLogger
77
{
88
public List<(string level, string message, object[] args)> LogCalls { get; } = new();
9+
public List<SentryLog> CapturedLogs { get; } = new();
910

1011
private protected override void CaptureLog(SentryLogLevel level, string template, object[]? parameters, Action<SentryLog>? configureLog)
1112
=> LogCalls.Add((level.ToString(), template, parameters ?? []));
1213

1314
protected internal override void CaptureLog(SentryLog log)
1415
{
15-
// Not needed for our tests
16+
CapturedLogs.Add(log);
1617
}
1718

1819
protected internal override void Flush()
1920
{
2021
// Not needed for our tests
2122
}
2223

23-
public void Clear() => LogCalls.Clear();
24+
public void Clear()
25+
{
26+
LogCalls.Clear();
27+
CapturedLogs.Clear();
28+
}
2429
}

0 commit comments

Comments
 (0)