Skip to content

Commit

Permalink
spotlight (#2961)
Browse files Browse the repository at this point in the history
  • Loading branch information
bruno-garcia authored Dec 13, 2023
1 parent 3968458 commit a9e8acd
Show file tree
Hide file tree
Showing 18 changed files with 322 additions and 70 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

- iOS profiling support (alpha). ([#2930](https://github.com/getsentry/sentry-dotnet/pull/2930))

### Feature

- Support for [Spotlight](https://spotlightjs.com/). Debug tool for local development. ([#2961](https://github.com/getsentry/sentry-dotnet/pull/2961))
- Enable it with option `EnableSpotlight`
- Optionally configure the URL to connect via `SpotlightUrl`. Defaults to `http://localhost:8969/stream`.

### Fixes

- Stop Sentry for MacCatalyst from creating `default.profraw` in the app bundle using xcodebuild archive to build sentry-cocoa ([#2960](https://github.com/getsentry/sentry-dotnet/pull/2960))
Expand Down
4 changes: 4 additions & 0 deletions src/Sentry/BindableSentryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ internal partial class BindableSentryOptions
public bool? AutoSessionTracking { get; set; }
public bool? UseAsyncFileIO { get; set; }
public bool? JsonPreserveReferences { get; set; }
public bool? EnableSpotlight { get; set; }
public string? SpotlightUrl { get; set; }

public void ApplyTo(SentryOptions options)
{
Expand Down Expand Up @@ -92,6 +94,8 @@ public void ApplyTo(SentryOptions options)
options.AutoSessionTracking = AutoSessionTracking ?? options.AutoSessionTracking;
options.UseAsyncFileIO = UseAsyncFileIO ?? options.UseAsyncFileIO;
options.JsonPreserveReferences = JsonPreserveReferences ?? options.JsonPreserveReferences;
options.EnableSpotlight = EnableSpotlight ?? options.EnableSpotlight;
options.SpotlightUrl = SpotlightUrl ?? options.SpotlightUrl;

#if ANDROID
Android.ApplyTo(options.Android);
Expand Down
62 changes: 62 additions & 0 deletions src/Sentry/Extensibility/DiagnosticLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ internal static void LogDebug<TArg, TArg2>(
TArg2 arg2)
=> options.DiagnosticLogger?.LogIfEnabled(SentryLevel.Debug, null, message, arg, arg2);

/// <summary>
/// Log a debug message.
/// </summary>
internal static void LogDebug<TArg, TArg2, TArg3>(
this SentryOptions options,
string message,
TArg arg,
TArg2 arg2,
TArg3 arg3)
=> options.DiagnosticLogger?.LogIfEnabled(SentryLevel.Debug, null, message, arg, arg2, arg3);

/// <summary>
/// Log a debug message.
/// </summary>
Expand Down Expand Up @@ -206,6 +217,17 @@ internal static void LogWarning<TArg, TArg2>(
TArg2 arg2)
=> options.DiagnosticLogger?.LogIfEnabled(SentryLevel.Warning, null, message, arg, arg2);

/// <summary>
/// Log a warning message.
/// </summary>
internal static void LogWarning<TArg, TArg2, TArg3>(
this SentryOptions options,
string message,
TArg arg,
TArg2 arg2,
TArg3 arg3)
=> options.DiagnosticLogger?.LogIfEnabled(SentryLevel.Warning, null, message, arg, arg2, arg3);

/// <summary>
/// Log a error message.
/// </summary>
Expand Down Expand Up @@ -300,6 +322,29 @@ internal static void LogError<TArg, TArg2, TArg3, TArg4>(this SentryOptions opti
TArg4 arg4)
=> options.DiagnosticLogger?.LogIfEnabled(SentryLevel.Error, exception, message, arg, arg2, arg3, arg4);

/// <summary>
/// Log a error message.
/// </summary>
internal static void LogError<TArg, TArg2, TArg3, TArg4, TArg5>(this SentryOptions options,
string message,
TArg arg,
TArg2 arg2,
TArg3 arg3,
TArg4 arg4,
TArg5 arg5)
=> options.DiagnosticLogger?.LogIfEnabled(SentryLevel.Error, null, message, arg, arg2, arg3, arg4, arg5);

/// <summary>
/// Log a error message.
/// </summary>
internal static void LogError<TArg, TArg2, TArg3, TArg4>(this SentryOptions options,
string message,
TArg arg,
TArg2 arg2,
TArg3 arg3,
TArg4 arg4)
=> options.DiagnosticLogger?.LogIfEnabled(SentryLevel.Error, null, message, arg, arg2, arg3, arg4);

/// <summary>
/// Log an error message.
/// </summary>
Expand Down Expand Up @@ -504,6 +549,23 @@ internal static void LogIfEnabled<TArg, TArg2, TArg3, TArg4>(
}
}

internal static void LogIfEnabled<TArg, TArg2, TArg3, TArg4, TArg5>(
this IDiagnosticLogger logger,
SentryLevel level,
Exception? exception,
string message,
TArg arg,
TArg2 arg2,
TArg3 arg3,
TArg4 arg4,
TArg5 arg5)
{
if (logger.IsEnabled(level))
{
logger.Log(level, message, exception, arg, arg2, arg3, arg4, arg5);
}
}

internal static void LogIfEnabled<TArg, TArg2, TArg3, TArg4>(
this SentryOptions options,
SentryLevel level,
Expand Down
71 changes: 39 additions & 32 deletions src/Sentry/Http/HttpTransportBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public abstract class HttpTransportBase
// Using string instead of SentryId here so that we can use Interlocked.Exchange(...).
private string? _lastDiscardedSessionInitId;

private string _typeName;

/// <summary>
/// Constructor for this class.
/// </summary>
Expand All @@ -37,6 +39,7 @@ protected HttpTransportBase(SentryOptions options,
_options = options;
_clock = clock ?? SystemClock.Clock;
_getEnvironmentVariable = getEnvironmentVariable ?? options.SettingLocator.GetEnvironmentVariable;
_typeName = GetType().Name;
}

// Keep track of rate limits and their expiry dates.
Expand Down Expand Up @@ -66,20 +69,22 @@ protected internal Envelope ProcessEnvelope(Envelope envelope)
if (clientReport != null)
{
envelopeItems.Add(EnvelopeItem.FromClientReport(clientReport));
_options.LogDebug("Attached client report to envelope {0}.", eventId);
_options.LogDebug("{0}: Attached client report to envelope {1}.", _typeName, eventId);
}

if (envelopeItems.Count == 0)
{
if (_options.SendClientReports)
{
_options.LogInfo("Envelope {0} was discarded because all contained items are rate-limited " +
_options.LogInfo("{0}: Envelope '{1}' was discarded because all contained items are rate-limited " +
"and there are no client reports to send.",
_typeName,
eventId);
}
else
{
_options.LogInfo("Envelope {0} was discarded because all contained items are rate-limited.",
_options.LogInfo("{0}: Envelope '{1}' was discarded because all contained items are rate-limited.",
_typeName,
eventId);
}
}
Expand All @@ -99,7 +104,8 @@ private void ProcessEnvelopeItem(DateTimeOffset now, EnvelopeItem item, List<Env
.RecordDiscardedEvent(DiscardReason.RateLimitBackoff, item.DataCategory);

_options.LogDebug(
"Envelope item of type {0} was discarded because it's rate-limited.",
"{0}: Envelope item of type {1} was discarded because it's rate-limited.",
_typeName,
item.TryGetType());

// Check if session update with init=true
Expand All @@ -111,7 +117,8 @@ private void ProcessEnvelopeItem(DateTimeOffset now, EnvelopeItem item, List<Env
_lastDiscardedSessionInitId = discardedSessionUpdate.Id.ToString();

_options.LogDebug(
"Discarded envelope item containing initial session update (SID: {0}).",
"{0}: Discarded envelope item containing initial session update (SID: {1}).",
_typeName,
discardedSessionUpdate.Id);
}

Expand All @@ -125,7 +132,8 @@ private void ProcessEnvelopeItem(DateTimeOffset now, EnvelopeItem item, List<Env
// note: attachment drops are not currently counted in discarded events

_options.LogWarning(
"Attachment '{0}' dropped because it's too large ({1} bytes).",
"{0}: Attachment '{1}' dropped because it's too large ({2} bytes).",
_typeName,
item.TryGetFileName(),
item.TryGetLength());

Expand All @@ -146,7 +154,8 @@ private void ProcessEnvelopeItem(DateTimeOffset now, EnvelopeItem item, List<Env
items.Add(modifiedEnvelopeItem);

_options.LogDebug(
"Promoted envelope item with session update to initial following a discarded update (SID: {0}).",
"{0}: Promoted envelope item with session update to initial following a discarded update (SID: {1}).",
_typeName,
sessionUpdate.Id);

return;
Expand All @@ -162,7 +171,7 @@ private void ProcessEnvelopeItem(DateTimeOffset now, EnvelopeItem item, List<Env
/// <param name="envelope">The envelope.</param>
/// <returns>An HTTP request message, with the proper headers and body set.</returns>
/// <exception cref="InvalidOperationException">Throws if the DSN is not set in the options.</exception>
protected internal HttpRequestMessage CreateRequest(Envelope envelope)
protected internal virtual HttpRequestMessage CreateRequest(Envelope envelope)
{
if (string.IsNullOrWhiteSpace(_options.Dsn))
{
Expand Down Expand Up @@ -302,22 +311,22 @@ private void LogEnvelopeSent(Envelope envelope, string? payload = null)
{
if (eventId == null)
{
_options.LogInfo("Envelope successfully sent.", eventId);
_options.LogInfo("{0}: Envelope successfully sent.", _typeName);
}
else
{
_options.LogInfo("Envelope '{0}' successfully sent.", eventId);
_options.LogInfo("{0}: Envelope '{1}' successfully sent.", _typeName, eventId);
}
}
else
{
if (eventId == null)
{
_options.LogDebug("Envelope successfully sent. Content: {1}", eventId, payload);
_options.LogDebug("{0}: Envelope successfully sent. Content: {1}", _typeName, payload);
}
else
{
_options.LogDebug("Envelope '{0}' successfully sent. Content: {1}", eventId, payload);
_options.LogDebug("{0}: Envelope '{1}' successfully sent. Content: {2}", _typeName, eventId, payload);
}
}
}
Expand Down Expand Up @@ -347,17 +356,17 @@ private void HandleFailure(HttpResponseMessage response, Envelope envelope)
if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Debug) is true)
{
var payload = envelope.SerializeToString(_options.DiagnosticLogger, _clock);
_options.LogDebug("Failed envelope '{0}' has payload:\n{1}\n", eventId, payload);
_options.LogDebug("{0}: Failed envelope '{1}' has payload:\n{2}\n", _typeName, eventId, payload);

// SDK is in debug mode, and envelope was too large. To help troubleshoot:
const string persistLargeEnvelopePathEnvVar = "SENTRY_KEEP_LARGE_ENVELOPE_PATH";
if (response.StatusCode == HttpStatusCode.RequestEntityTooLarge
&& _getEnvironmentVariable(persistLargeEnvelopePathEnvVar) is { } destinationDirectory)
{
_options.DiagnosticLogger?
.LogDebug("Environment variable '{0}' set. Writing envelope to {1}",
persistLargeEnvelopePathEnvVar,
destinationDirectory);
_options.LogDebug("{0}: Environment variable '{1}' set. Writing envelope to {2}",
_typeName,
persistLargeEnvelopePathEnvVar,
destinationDirectory);

var destination = Path.Combine(destinationDirectory, "envelope_too_large",
(eventId ?? SentryId.Create()).ToString());
Expand All @@ -370,8 +379,8 @@ private void HandleFailure(HttpResponseMessage response, Envelope envelope)
{
envelope.Serialize(envelopeFile, _options.DiagnosticLogger);
envelopeFile.Flush();
_options.LogInfo("Envelope's {0} bytes written to: {1}",
envelopeFile.Length, destination);
_options.LogInfo("{0}: Envelope's {1} bytes written to: {2}",
_typeName, envelopeFile.Length, destination);
}
}
}
Expand Down Expand Up @@ -403,17 +412,17 @@ private async Task HandleFailureAsync(HttpResponseMessage response, Envelope env
{
var payload = await envelope
.SerializeToStringAsync(_options.DiagnosticLogger, _clock, cancellationToken).ConfigureAwait(false);
_options.LogDebug("Failed envelope '{0}' has payload:\n{1}\n", eventId, payload);
_options.LogDebug("{0}: Failed envelope '{1}' has payload:\n{2}\n", _typeName, eventId, payload);

// SDK is in debug mode, and envelope was too large. To help troubleshoot:
const string persistLargeEnvelopePathEnvVar = "SENTRY_KEEP_LARGE_ENVELOPE_PATH";
if (response.StatusCode == HttpStatusCode.RequestEntityTooLarge
&& _getEnvironmentVariable(persistLargeEnvelopePathEnvVar) is { } destinationDirectory)
{
_options.DiagnosticLogger?
.LogDebug("Environment variable '{0}' set. Writing envelope to {1}",
persistLargeEnvelopePathEnvVar,
destinationDirectory);
_options.LogDebug("{0}: Environment variable '{1}' set. Writing envelope to {2}",
_typeName,
persistLargeEnvelopePathEnvVar,
destinationDirectory);

var destination = Path.Combine(destinationDirectory, "envelope_too_large",
(eventId ?? SentryId.Create()).ToString());
Expand All @@ -431,8 +440,8 @@ await envelope
.SerializeAsync(envelopeFile, _options.DiagnosticLogger, cancellationToken)
.ConfigureAwait(false);
await envelopeFile.FlushAsync(cancellationToken).ConfigureAwait(false);
_options.LogInfo("Envelope's {0} bytes written to: {1}",
envelopeFile.Length, destination);
_options.LogInfo("{0}: Envelope's {1} bytes written to: {2}",
_typeName, envelopeFile.Length, destination);
}
}
}
Expand Down Expand Up @@ -460,9 +469,8 @@ private void IncrementDiscardsForHttpFailure(HttpStatusCode responseStatusCode,

private void LogFailure(string responseString, HttpStatusCode responseStatusCode, SentryId? eventId)
{
_options.Log(SentryLevel.Error,
"Sentry rejected the envelope {0}. Status code: {1}. Error detail: {2}.",
null,
_options.LogError("{0}: Sentry rejected the envelope '{1}'. Status code: {2}. Error detail: {3}.",
_typeName,
eventId,
responseStatusCode,
responseString);
Expand All @@ -478,9 +486,8 @@ private void LogFailure(JsonElement responseJson, HttpStatusCode responseStatusC
responseJson.GetPropertyOrNull("causes")?.EnumerateArray().Select(j => j.GetString()).ToArray()
?? Array.Empty<string>();

_options.Log(SentryLevel.Error,
"Sentry rejected the envelope {0}. Status code: {1}. Error detail: {2}. Error causes: {3}.",
null,
_options.LogError("{0}: Sentry rejected the envelope '{1}'. Status code: {2}. Error detail: {3}. Error causes: {4}.",
_typeName,
eventId,
responseStatusCode,
errorMessage,
Expand Down
60 changes: 60 additions & 0 deletions src/Sentry/Http/SpotlightHttpTransport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Sentry.Extensibility;
using Sentry.Infrastructure;
using Sentry.Internal.Http;
using Sentry.Protocol.Envelopes;

namespace Sentry.Http;

internal class SpotlightHttpTransport : HttpTransport
{
private readonly ITransport _inner;
private readonly SentryOptions _options;
private readonly HttpClient _httpClient;
private readonly Uri _spotlightUrl;
private readonly ISystemClock _clock;

public SpotlightHttpTransport(ITransport inner, SentryOptions options, HttpClient httpClient, Uri spotlightUrl, ISystemClock clock)
: base(options, httpClient)
{
_options = options;
_httpClient = httpClient;
_spotlightUrl = spotlightUrl;
_inner = inner;
_clock = clock;
}

protected internal override HttpRequestMessage CreateRequest(Envelope envelope)
{
return new HttpRequestMessage
{
RequestUri = _spotlightUrl,
Method = HttpMethod.Post,
Content = new EnvelopeHttpContent(envelope, _options.DiagnosticLogger, _clock)
{ Headers = { ContentType = MediaTypeHeaderValue.Parse("application/x-sentry-envelope") } }
};
}

public override async Task SendEnvelopeAsync(Envelope envelope, CancellationToken cancellationToken = default)
{
var sentryTask = _inner.SendEnvelopeAsync(envelope, cancellationToken);

try
{
// Send to spotlight
using var processedEnvelope = ProcessEnvelope(envelope);
if (processedEnvelope.Items.Count > 0)
{
using var request = CreateRequest(processedEnvelope);
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
await HandleResponseAsync(response, processedEnvelope, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception e)
{
_options.LogError(e, "Failed sending envelope to Spotlight.");
}

// await the Sentry request before returning
await sentryTask.ConfigureAwait(false);
}
}
Loading

0 comments on commit a9e8acd

Please sign in to comment.