Skip to content

Commit

Permalink
Add LogCat integration to Android via an EventProcessor (#2926)
Browse files Browse the repository at this point in the history
Co-authored-by: Bruno Garcia <bruno@brunogarcia.com>
  • Loading branch information
kanadaj and bruno-garcia authored Dec 2, 2023
1 parent 610f5fa commit 88bfc29
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
### Features

- Native crash reporting on NativeAOT published apps (Windows, Linux, macOS). ([#2887](https://github.com/getsentry/sentry-dotnet/pull/2887))
- Android: By default attaches LogCat logs to unhandled exceptions. Configurable via `SentryOptions.Android.LogCatIntegration` and `SentryOptions.Android.LogCatMaxLines`. Available when targeting `net7.0-android` or later, on API level 23 or later. ([#2926](https://github.com/getsentry/sentry-dotnet/pull/2926))

### API breaking Changes

Expand Down
4 changes: 4 additions & 0 deletions samples/Sentry.Samples.Android/MainActivity.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Sentry.Android;

namespace Sentry.Samples.Android;

[Activity(Label = "@string/app_name", MainLauncher = true)]
Expand All @@ -9,6 +11,8 @@ protected override void OnCreate(Bundle? savedInstanceState)
{
o.Dsn = "https://eb18e953812b41c3aeb042e666fd3b5c@o447951.ingest.sentry.io/5428537";
o.SendDefaultPii = true; // adds the user's IP address automatically
o.Android.LogCatIntegration = LogCatIntegrationType.Errors; // Get logcat logs for both handled and unhandled errors; default is unhandled only
o.Android.LogCatMaxLines = 1000; // Defaults to 1000
});

// Here's an example of adding custom scope information.
Expand Down
6 changes: 6 additions & 0 deletions src/Sentry/Platforms/Android/BindableSentryOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// ReSharper disable once CheckNamespace
using Sentry.Android;

namespace Sentry;

internal partial class BindableSentryOptions
Expand Down Expand Up @@ -33,6 +35,8 @@ public class AndroidOptions
public TimeSpan? ReadTimeout { get; set; }
public bool? EnableAndroidSdkTracing { get; set; }
public bool? EnableAndroidSdkBeforeSend { get; set; }
public LogCatIntegrationType? LogCatIntegration { get; set; }
public int? LogCatMaxLines { get; set; }

public void ApplyTo(SentryOptions.AndroidOptions options)
{
Expand All @@ -59,6 +63,8 @@ public void ApplyTo(SentryOptions.AndroidOptions options)
options.ReadTimeout = ReadTimeout ?? options.ReadTimeout;
options.EnableAndroidSdkTracing = EnableAndroidSdkTracing ?? options.EnableAndroidSdkTracing;
options.EnableAndroidSdkBeforeSend = EnableAndroidSdkBeforeSend ?? options.EnableAndroidSdkBeforeSend;
options.LogCatIntegration = LogCatIntegration ?? options.LogCatIntegration;
options.LogCatMaxLines = LogCatMaxLines ?? options.LogCatMaxLines;
}
}
}
105 changes: 105 additions & 0 deletions src/Sentry/Platforms/Android/LogCatAttachmentEventProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using Sentry.Extensibility;
using Runtime = Java.Lang.Runtime;

namespace Sentry.Android;

internal class LogCatAttachmentEventProcessor : ISentryEventProcessorWithHint
{
private static bool SendLogcatLogs = true;
private readonly LogCatIntegrationType _logCatIntegrationType;
private readonly IDiagnosticLogger? _diagnosticLogger;
private readonly int _maxLines;

public LogCatAttachmentEventProcessor(IDiagnosticLogger? diagnosticLogger, LogCatIntegrationType logCatIntegrationType, int maxLines = 1000)
{
if (maxLines <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxLines), "LogCatMaxLines must be greater than 0");
}

_diagnosticLogger = diagnosticLogger;
_logCatIntegrationType = logCatIntegrationType;
_maxLines = maxLines;
}

public SentryEvent Process(SentryEvent @event, Hint hint)
{
// If sending has failed once, we have to disable this feature to prevent infinite loops and to allow the SDK to work otherwise
if (!SendLogcatLogs)
{
return @event;
}

// The logcat command only works on Android API 23 and above
if (!OperatingSystem.IsAndroidVersionAtLeast(23))
{
SendLogcatLogs = false;
return @event;
}

// Just in case the setting got overridden at the scope level
if (_logCatIntegrationType == LogCatIntegrationType.None)
{
return @event;
}

try
{
if (_logCatIntegrationType != LogCatIntegrationType.All && !@event.HasException())
{
return @event;
}

// Only send logcat logs if the event is unhandled if the integration is set to Unhandled
if (_logCatIntegrationType == LogCatIntegrationType.Unhandled)
{
if (!@event.HasTerminalException())
{
return @event;
}
}

// We run the logcat command via the runtime
var process = Runtime.GetRuntime()?.Exec($"logcat -t {_maxLines} *:I");

// Strangely enough, process.InputStream is the *output* of the command
if (process?.InputStream is null)
{
return @event;
}

// We write the logcat logs to a memory stream so we can attach it
using var output = new MemoryStream();

process.InputStream.CopyTo(output);
process.WaitFor();

// Flush the stream to make sure all data is written
process.InputStream.Flush();

// Reset the position of the stream to the beginning so we can read it
output.Seek(0, SeekOrigin.Begin);
var bytes = output.ToArray();

hint.Attachments.Add(new Attachment(AttachmentType.Default, new ByteAttachmentContent(bytes), "logcat.log", "text/logcat"));

//hint.AddAttachment($"{filesDir.Path}/{fileName}", AttachmentType.Default, "text/logcat");

return @event;
}
catch (Exception e) // Catch all exceptions to prevent crashing the app during logging
{
// Disable the feature if it fails once
SendLogcatLogs = false;

// Log the failure to Sentry
_diagnosticLogger?.LogError(e, "Failed to send logcat logs");
return @event;
}
}

public SentryEvent Process(SentryEvent @event)
{
return Process(@event, new Hint());
}
}
28 changes: 28 additions & 0 deletions src/Sentry/Platforms/Android/LogCatIntegrationType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Sentry.Android;

/// <summary>
/// Configures when to attach LogCat logs to events.
/// </summary>
public enum LogCatIntegrationType
{
/// <summary>
/// The LogCat integration is disabled.
/// </summary>
None,
/// <summary>
/// LogCat logs are attached to events only when the event is unhandled.
/// </summary>
Unhandled,

/// <summary>
/// LogCat logs are attached to events with an exception.
/// </summary>
Errors,

/// <summary>
/// LogCat logs are attached to all events.
/// Use caution when enabling, as this may result in a lot of data being sent to Sentry
/// and performance issues if the SDK generates a lot of events.
/// </summary>
All
}
16 changes: 16 additions & 0 deletions src/Sentry/Platforms/Android/SentryOptions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Sentry.Android;

// ReSharper disable once CheckNamespace
namespace Sentry;

Expand Down Expand Up @@ -196,6 +198,20 @@ internal AndroidOptions(SentryOptions options)
/// </summary>
public TimeSpan ReadTimeout { get; set; } = TimeSpan.FromSeconds(5);

/// <summary>
/// Gets or sets whether when LogCat logs are attached to events.
/// The default is <see cref="LogCatIntegrationType.None"/>
/// </summary>
/// <seealso cref="LogCatMaxLines" />
public LogCatIntegrationType LogCatIntegration { get; set; } = LogCatIntegrationType.None;

/// <summary>
/// Gets or sets the maximum number of lines to read from LogCat logs.
/// The default value is 1000.
/// </summary>
/// <seealso cref="LogCatIntegration" />
public int LogCatMaxLines { get; set; } = 1000;


// ---------- Other ----------

Expand Down
5 changes: 5 additions & 0 deletions src/Sentry/Platforms/Android/SentrySdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Sentry.Android;
using Sentry.Android.Callbacks;
using Sentry.Android.Extensions;
using Sentry.Internal;
using Sentry.JavaSdk.Android.Core;

// Don't let the Sentry Android SDK auto-init, as we do that manually in SentrySdk.Init
Expand Down Expand Up @@ -187,6 +188,10 @@ private static void InitSentryAndroidSdk(SentryOptions options)

// Set options for the managed SDK that depend on the Android SDK. (The user will not be able to modify these.)
options.AddEventProcessor(new AndroidEventProcessor(androidOptions!));
if (options.Android.LogCatIntegration != LogCatIntegrationType.None)
{
options.AddEventProcessor(new LogCatAttachmentEventProcessor(options.DiagnosticLogger, options.Android.LogCatIntegration, options.Android.LogCatMaxLines));
}
options.CrashedLastRun = () => JavaSdk.Sentry.IsCrashedLastRun()?.BooleanValue() is true;
options.EnableScopeSync = true;
options.ScopeObserver = new AndroidScopeObserver(options);
Expand Down

0 comments on commit 88bfc29

Please sign in to comment.