Skip to content

Commit 1ecab82

Browse files
authored
fix: Android - make sure logcat.log is attached to unhandled event (SIGSEGV Segfault) (#3694)
1 parent b283298 commit 1ecab82

File tree

4 files changed

+199
-6
lines changed

4 files changed

+199
-6
lines changed

CHANGELOG.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
### Fixes
1111
- Fixed JNI Error when accessing Android device data from multiple threads ([#3802](https://github.com/getsentry/sentry-dotnet/pull/3802))
12+
- Android - fix bug that prevents logcat.log from getting attached to unhandled events (SIGSEGV Segfault) ([#3694](https://github.com/getsentry/sentry-dotnet/pull/3694))
1213
- Fix "System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'idData')" error propagating OpenTelemetry span ids ([#3850](https://github.com/getsentry/sentry-dotnet/pull/3850))
1314

1415
### Dependencies
@@ -44,6 +45,7 @@
4445

4546
### Fixes
4647

48+
- Fixed ArgumentNullException in FormRequestPayloadExtractor when handling invalid form data on ASP.NET ([#3734](https://github.com/getsentry/sentry-dotnet/pull/3734))
4749
- Fixed NullReferenceException in SentryTraceHeader when parsing null or empty values ([#3757](https://github.com/getsentry/sentry-dotnet/pull/3757))
4850
- ArgumentNullException in FormRequestPayloadExtractor when handling invalid form data on ASP.NET ([#3734](https://github.com/getsentry/sentry-dotnet/pull/3734))
4951
- Crash when using NLog with FailedRequestStatusCodes options in a Maui app with Trimming enabled ([#3743](https://github.com/getsentry/sentry-dotnet/pull/3743))
@@ -81,16 +83,16 @@
8183

8284
## 4.12.2
8385

86+
### Fixes
87+
88+
- Events from NDK on Android will report sdk.name `sentry.native.android.dotnet` ([#3682](https://github.com/getsentry/sentry-dotnet/pull/3682))
89+
8490
### Features
8591

8692
- Android - allow logcat attachments to be previewed in Sentry ([#3711](https://github.com/getsentry/sentry-dotnet/pull/3711))
8793
- Added a `SetBeforeScreenshotCapture` callback to the options: allowing the user to set an action before the screenshot is taken ([#3661](https://github.com/getsentry/sentry-dotnet/pull/3661))
8894
- Make `Sentry.AspNetCore.Blazor.WebAssembly` generally available. ([#3674](https://github.com/getsentry/sentry-dotnet/pull/3674))
8995

90-
### Fixes
91-
92-
- Events from NDK on Android will report sdk.name `sentry.native.android.dotnet` ([#3682](https://github.com/getsentry/sentry-dotnet/pull/3682))
93-
9496
### Dependencies
9597

9698
- Bump Java SDK from v7.14.0 to v7.16.0 ([#3670](https://github.com/getsentry/sentry-dotnet/pull/3670), [#3707](https://github.com/getsentry/sentry-dotnet/pull/3707))

src/Sentry/Platforms/Android/SentrySdk.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ private static void InitSentryAndroidSdk(SentryOptions options)
3131

3232
// Define the configuration for the Android SDK
3333
SentryAndroidOptions? nativeOptions = null;
34+
3435
var configuration = new OptionsConfigurationCallback(o =>
3536
{
3637
// Capture the android options reference on the outer scope
@@ -59,6 +60,7 @@ private static void InitSentryAndroidSdk(SentryOptions options)
5960
o.ServerName = options.ServerName;
6061
o.SessionTrackingIntervalMillis = (long)options.AutoSessionTrackingInterval.TotalMilliseconds;
6162
o.ShutdownTimeoutMillis = (long)options.ShutdownTimeout.TotalMilliseconds;
63+
o.SetNativeHandlerStrategy(JavaSdk.Android.Core.NdkHandlerStrategy.SentryHandlerStrategyChainAtStart);
6264

6365
if (options.CacheDirectoryPath is { } cacheDirectoryPath)
6466
{

test/Sentry.Maui.Tests/SentryMauiLogcatsTests.cs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,165 @@ public Fixture()
2929
options.CacheDirectoryPath = null; //Do not wrap our FakeTransport with a caching transport
3030
options.FlushTimeout = TimeSpan.FromSeconds(10);
3131
});
32+
3233
Builder = builder;
3334
}
3435
}
3536

3637
private readonly Fixture _fixture = new();
3738

39+
3840
#if ANDROID
41+
[SkippableFact]
42+
public async Task CaptureException_WhenAttachLogcats_DefaultAsync()
43+
{
44+
// Arrange
45+
var builder = _fixture.Builder.UseSentry();
46+
47+
// Act
48+
using var app = builder.Build();
49+
var client = app.Services.GetRequiredService<ISentryClient>();
50+
var sentryId = client.CaptureException(new Exception());
51+
await client.FlushAsync();
52+
53+
var options = app.Services.GetRequiredService<IOptions<SentryMauiOptions>>().Value;
54+
55+
var envelope = _fixture.Transport.GetSentEnvelopes().FirstOrDefault(e => e.TryGetEventId() == sentryId);
56+
envelope.Should().NotBeNull("Envelope with sentryId {0} should be sent", sentryId);
57+
var envelopeItem = envelope!.Items.FirstOrDefault(item => item.TryGetType() == "attachment");
58+
59+
// Assert
60+
envelopeItem.Should().BeNull();
61+
}
62+
63+
[SkippableFact]
64+
public async Task CaptureException_WhenAttachLogcats_AllExceptionsAsync()
65+
{
66+
67+
// Arrange
68+
var builder = _fixture.Builder.UseSentry(options =>
69+
{
70+
options.Android.LogCatIntegration = Android.LogCatIntegrationType.All;
71+
});
72+
73+
// Act
74+
using var app = builder.Build();
75+
var client = app.Services.GetRequiredService<ISentryClient>();
76+
var sentryId = client.CaptureException(new Exception());
77+
await client.FlushAsync();
78+
79+
var options = app.Services.GetRequiredService<IOptions<SentryMauiOptions>>().Value;
80+
81+
var envelope = _fixture.Transport.GetSentEnvelopes().FirstOrDefault(e => e.TryGetEventId() == sentryId);
82+
envelope.Should().NotBeNull("Envelope with sentryId {0} should be sent", sentryId);
83+
84+
// Assert
85+
envelope!.Items.Any(env => env.TryGetFileName() == "logcat.log").Should().BeTrue();
86+
}
87+
88+
[SkippableFact]
89+
public async Task CaptureException_WhenAttachLogcats_UnhandledExceptionsAsync()
90+
{
91+
92+
// Arrange
93+
var builder = _fixture.Builder.UseSentry(options =>
94+
{
95+
options.Android.LogCatIntegration = Android.LogCatIntegrationType.Unhandled;
96+
});
97+
98+
// Act
99+
using var app = builder.Build();
100+
var client = app.Services.GetRequiredService<ISentryClient>();
101+
var sentryId = client.CaptureException(BuildUnhandledException());
102+
103+
await client.FlushAsync();
104+
105+
var envelope = _fixture.Transport.GetSentEnvelopes().FirstOrDefault(e => e.TryGetEventId() == sentryId);
106+
envelope.Should().NotBeNull("Envelope with sentryId {0} should be sent", sentryId);
107+
var envelopeItem = envelope!.Items.FirstOrDefault(item => item.TryGetType() == "attachment");
108+
109+
// Assert
110+
envelopeItem.Should().NotBeNull();
111+
envelopeItem!.TryGetFileName().Should().Be("logcat.log");
112+
}
113+
114+
[SkippableFact]
115+
public async Task CaptureException_WhenAttachLogcats_HandledExceptionsAsync()
116+
{
117+
118+
// Arrange
119+
var builder = _fixture.Builder.UseSentry(options =>
120+
{
121+
options.Android.LogCatIntegration = Android.LogCatIntegrationType.Unhandled;
122+
});
123+
124+
// Act
125+
using var app = builder.Build();
126+
var client = app.Services.GetRequiredService<ISentryClient>();
127+
var sentryId = client.CaptureException(new Exception());
128+
129+
await client.FlushAsync();
130+
131+
var envelope = _fixture.Transport.GetSentEnvelopes().FirstOrDefault(e => e.TryGetEventId() == sentryId);
132+
envelope.Should().NotBeNull("Envelope with sentryId {0} should be sent", sentryId);
133+
134+
// Assert
135+
envelope!.Items.Any(item => item.TryGetType() == "attachment").Should().BeFalse();
136+
}
137+
138+
[SkippableFact]
139+
public async Task CaptureException_WhenAttachLogcats_ErrorsAsync()
140+
{
141+
142+
// Arrange
143+
var builder = _fixture.Builder.UseSentry(options =>
144+
{
145+
options.Android.LogCatIntegration = Android.LogCatIntegrationType.Errors;
146+
});
147+
148+
// Act
149+
using var app = builder.Build();
150+
var client = app.Services.GetRequiredService<ISentryClient>();
151+
var sentryId = client.CaptureException(new Exception());
152+
await client.FlushAsync();
153+
154+
var options = app.Services.GetRequiredService<IOptions<SentryMauiOptions>>().Value;
155+
156+
var envelope = _fixture.Transport.GetSentEnvelopes().FirstOrDefault(e => e.TryGetEventId() == sentryId);
157+
envelope.Should().NotBeNull("Envelope with sentryId {0} should be sent", sentryId);
158+
var envelopeItem = envelope!.Items.FirstOrDefault(item => item.TryGetType() == "attachment");
159+
160+
// Assert
161+
envelopeItem.Should().NotBeNull();
162+
envelopeItem!.TryGetFileName().Should().Be("logcat.log");
163+
}
164+
165+
[SkippableFact]
166+
public async Task CaptureException_WhenAttachLogcats_NoneAsync()
167+
{
168+
169+
// Arrange
170+
var builder = _fixture.Builder.UseSentry(options =>
171+
{
172+
options.Android.LogCatIntegration = Android.LogCatIntegrationType.None;
173+
});
174+
175+
// Act
176+
using var app = builder.Build();
177+
var client = app.Services.GetRequiredService<ISentryClient>();
178+
var sentryId = client.CaptureException(new Exception());
179+
await client.FlushAsync();
180+
181+
var options = app.Services.GetRequiredService<IOptions<SentryMauiOptions>>().Value;
182+
183+
var envelope = _fixture.Transport.GetSentEnvelopes().FirstOrDefault(e => e.TryGetEventId() == sentryId);
184+
envelope.Should().NotBeNull("Envelope with sentryId {0} should be sent", sentryId);
185+
var envelopeItem = envelope!.Items.FirstOrDefault(item => item.TryGetType() == "attachment");
186+
187+
// Assert
188+
envelopeItem.Should().BeNull();
189+
}
190+
39191
[Fact]
40192
public void CaptureException_CheckLogcatType()
41193
{
@@ -63,5 +215,22 @@ public void CaptureException_CheckLogcatType()
63215
hint.Should().NotBeNull();
64216
hint.Attachments.First().ContentType.Should().Be("text/plain", hint.Attachments.First().ContentType);
65217
}
218+
219+
private static Exception BuildUnhandledException()
220+
{
221+
try
222+
{
223+
// Throwing will put a stack trace on the exception
224+
throw new Exception("Error");
225+
}
226+
catch (Exception exception)
227+
{
228+
// Add extra data to test fully
229+
exception.Data[Mechanism.HandledKey] = false;
230+
exception.Data[Mechanism.MechanismKey] = "AppDomain.UnhandledException";
231+
exception.Data["foo"] = "bar";
232+
return exception;
233+
}
234+
}
66235
#endif
67236
}

test/Sentry.Maui.Tests/SentryMauiOptionsTests.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,32 @@ public void CacheDirectoryPath_Default()
5252
#endif
5353
}
5454

55+
#if ANDROID
5556
[Fact]
56-
public void AttachScreenshots_Default()
57+
public void HandlerStrategy_Default()
5758
{
59+
// Arrange
60+
var expected = Android.LogCatIntegrationType.None;
61+
var options = new SentryMauiOptions();
62+
63+
// Assert
64+
Assert.Equal(expected, options.Android.LogCatIntegration);
65+
}
66+
67+
[Fact]
68+
public void HandlerStrategy_Set()
69+
{
70+
// Arrange
71+
var expected = Android.LogCatIntegrationType.None;
5872
var options = new SentryMauiOptions();
59-
Assert.False(options.AttachScreenshot);
73+
74+
// Act
75+
options.Android.LogCatIntegration = Android.LogCatIntegrationType.All;
76+
77+
// Assert
78+
Assert.NotEqual(expected, options.Android.LogCatIntegration);
6079
}
80+
#endif
6181

6282
[Fact]
6383
public void BeforeCaptureScreenshot_Set()

0 commit comments

Comments
 (0)