Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spotlight #2961

Merged
merged 8 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

### 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 @@ -47,6 +47,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 @@ -90,6 +92,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