/// <summary> /// Creates an <see cref="T:System.Net.Http.HttpClient" /> configure to call Sentry for the specified <see cref="T:Sentry.Dsn" /> /// </summary> /// <param name="options">The HTTP options.</param> /// <returns></returns> public HttpClient Create(SentryOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } var httpClientHandler = options.CreateHttpClientHandler?.Invoke() ?? new HttpClientHandler(); if (options.HttpProxy != null) { httpClientHandler.Proxy = options.HttpProxy; options.LogInfo("Using Proxy: {0}", options.HttpProxy); } // If the platform supports automatic decompression if (httpClientHandler.SupportsAutomaticDecompression) { // if the SDK is configured to accept compressed data httpClientHandler.AutomaticDecompression = options.DecompressionMethods; } else { options.LogDebug("No response compression supported by HttpClientHandler."); } HttpMessageHandler handler = httpClientHandler; if (options.RequestBodyCompressionLevel != CompressionLevel.NoCompression) { if (options.RequestBodyCompressionBuffered) { handler = new GzipBufferedRequestBodyHandler(handler, options.RequestBodyCompressionLevel); options.LogDebug("Using 'GzipBufferedRequestBodyHandler' body compression strategy with level {0}.", options.RequestBodyCompressionLevel); } else { handler = new GzipRequestBodyHandler(handler, options.RequestBodyCompressionLevel); options.LogDebug("Using 'GzipRequestBodyHandler' body compression strategy with level {0}.", options.RequestBodyCompressionLevel); } } else { options.LogDebug("Using no request body compression strategy."); } // Adding retry after last for it to run first in the pipeline handler = new RetryAfterHandler(handler); var client = new HttpClient(handler); client.DefaultRequestHeaders.Add("Accept", "application/json"); if (options.ConfigureClient is { } configureClient) { options.LogDebug("Invoking user-defined HttpClient configuration action."); configureClient.Invoke(client); } return(client); }
public SentryEvent?Process(SentryEvent? @event) { var context = HttpContext.Current; if (context is null || @event is null) { return(@event); } try { // During Application initialization we might have an event to send but no HTTP Request. // Request getter throws and doesn't seem there's a way to query for it. _ = context.Request; } catch (HttpException) { _options.LogDebug("HttpException not available to retrieve context."); return(@event); } @event.Request.Method = context.Request.HttpMethod; @event.Request.Url = context.Request.Url.AbsoluteUri; try { // ReSharper disable once ConstantConditionalAccessQualifier @event.Request.QueryString = context.Request.QueryString?.ToString(); } catch (NullReferenceException) { // Ignored since it can throw on WCF on the first event. // See #390 _options.LogDebug("Ignored NRE thrown on System.Web.HttpContext.Request.QueryString"); } foreach (var key in context.Request.Headers.AllKeys) { if (!_options.SendDefaultPii // Don't add headers which might contain PII && (key == "Cookie" || key == "Authorization")) { continue; } @event.Request.Headers[key] = context.Request.Headers[key]; } if (_options?.SendDefaultPii == true) { if (@event.User.Username == Environment.UserName) { // if SendDefaultPii is true, Sentry SDK will send the current logged on user // which doesn't make sense in a server apps @event.User.Username = null; } @event.User.IpAddress = context.Request.UserHostAddress; if (context.User.Identity is { } identity) { @event.User.Username = identity.Name; @event.User.Other.Add("IsAuthenticated", identity.IsAuthenticated.ToString()); } if (context.User is ClaimsPrincipal claimsPrincipal) { if (claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier) is { } claim) { @event.User.Id = claim.Value; } } } @event.ServerName = Environment.MachineName; // Move 'runtime' under key 'server-runtime' as User-Agent parsing done at // Sentry will represent the client's if (@event.Contexts.TryRemove(Runtime.Type, out var runtime)) { @event.Contexts["server-runtime"] = runtime; } if (@event.Contexts.TryRemove(Protocol.OperatingSystem.Type, out var os)) { @event.Contexts["server-os"] = os; } var body = PayloadExtractor.ExtractPayload(new SystemWebHttpRequest(context.Request)); if (body != null) { @event.Request.Data = body; } if (@event.Sdk.Version is null && @event.Sdk.Name is null) { @event.Sdk.Name = "sentry.dotnet.aspnet"; @event.Sdk.Version = SdkVersion.Version; } if (SdkVersion.Version != null) { @event.Sdk.AddPackage( $"nuget:{SdkVersion.Name}", SdkVersion.Version); } return(@event); }
public SentryEvent Process(SentryEvent @event) { _options.LogDebug("Running main event processor on: Event {0}", @event.EventId); if (TimeZoneInfo.Local is { } timeZoneInfo) { @event.Contexts.Device.Timezone = timeZoneInfo; } IDictionary <string, string>?cultureInfoMapped = null; if ([email protected](CultureInfoKey) && CultureInfoToDictionary(CultureInfo.CurrentCulture) is { } currentCultureMap) { cultureInfoMapped = currentCultureMap; @event.Contexts[CultureInfoKey] = currentCultureMap; } if ([email protected](CurrentUiCultureKey) && CultureInfoToDictionary(CultureInfo.CurrentUICulture) is { } currentUiCultureMap && (cultureInfoMapped is null || currentUiCultureMap.Any(p => !cultureInfoMapped.Contains(p)))) { @event.Contexts[CurrentUiCultureKey] = currentUiCultureMap; } #if NETCOREAPP3_0_OR_GREATER @event.Contexts[IsDynamicCodeKey] = new Dictionary <string, bool> { { IsDynamicCodeCompiledKey, RuntimeFeature.IsDynamicCodeCompiled }, { IsDynamicCodeSupportedKey, RuntimeFeature.IsDynamicCodeSupported } }; #endif AddMemoryInfo(@event.Contexts); AddThreadPoolInfo(@event.Contexts); if (@event.ServerName == null) { // Value set on the options take precedence over device name. if (!string.IsNullOrEmpty(_options.ServerName)) { @event.ServerName = _options.ServerName; } else if (_options.SendDefaultPii) { @event.ServerName = Environment.MachineName; } } if (@event.Level == null) { @event.Level = SentryLevel.Error; } if (@event.Release == null) { @event.Release = Release; } if (@event.Exception == null) { var stackTrace = SentryStackTraceFactoryAccessor().Create(@event.Exception); if (stackTrace != null) { var currentThread = Thread.CurrentThread; var thread = new SentryThread { Crashed = false, Current = true, Name = currentThread.Name, Id = currentThread.ManagedThreadId, Stacktrace = stackTrace }; @event.SentryThreads = @event.SentryThreads?.Any() == true ? new List <SentryThread>(@event.SentryThreads) { thread } : new[] { thread }.AsEnumerable(); } } if (_options.ReportAssembliesMode != ReportAssembliesMode.None) { foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { if (assembly.IsDynamic) { continue; } var asmName = assembly.GetName(); if (asmName.Name is null) { continue; } var asmVersion = _options.ReportAssembliesMode switch { ReportAssembliesMode.Version => asmName.Version?.ToString() ?? string.Empty, ReportAssembliesMode.InformationalVersion => assembly.GetVersion() ?? string.Empty, _ => throw new ArgumentOutOfRangeException( $"Report assemblies mode '{_options.ReportAssembliesMode}' is not yet supported") }; if (!string.IsNullOrWhiteSpace(asmVersion)) { @event.Modules[asmName.Name] = asmVersion; } } } // Run enricher to fill in the gaps _enricher.Apply(@event); return(@event); }
private async Task WorkerAsync() { var cancellation = _shutdownSource.Token; using var shutdownTimeout = new CancellationTokenSource(); var shutdownRequested = false; try { while (!shutdownTimeout.IsCancellationRequested) { // If the cancellation was signaled, // set the latest we can keep reading off of the queue (while there's still stuff to read) // No longer synchronized with queuedEnvelopeSemaphore (Enqueue will throw object disposed), // run until the end of the queue or shutdownTimeout if (!shutdownRequested) { try { await _queuedEnvelopeSemaphore.WaitAsync(cancellation).ConfigureAwait(false); } // Cancellation requested, scheduled shutdown but continue in case there are more items catch (OperationCanceledException) { if (_options.ShutdownTimeout == TimeSpan.Zero) { _options.LogDebug( "Exiting immediately due to 0 shutdown timeout. #{0} in queue.", _queue.Count); return; } else { _options.LogDebug( "Shutdown scheduled. Stopping by: {0}. #{1} in queue.", _options.ShutdownTimeout, _queue.Count); shutdownTimeout.CancelAfter(_options.ShutdownTimeout); } shutdownRequested = true; } } if (_queue.TryPeek(out var envelope)) // Work with the envelope while it's in the queue { try { // Dispose inside try/catch using var _ = envelope; var task = _transport.SendEnvelopeAsync(envelope, shutdownTimeout.Token); _options.LogDebug( "Envelope {0} handed off to transport. #{1} in queue.", envelope.TryGetEventId(), _queue.Count); await task.ConfigureAwait(false); } catch (OperationCanceledException) { _options.LogInfo( "Shutdown token triggered. Time to exit. #{0} in queue.", _queue.Count); return; } catch (Exception exception) { _options.LogError( "Error while processing envelope (event ID: '{0}'). #{1} in queue.", exception, envelope.TryGetEventId(), _queue.Count); } finally { _ = _queue.TryDequeue(out _); _ = Interlocked.Decrement(ref _currentItems); OnFlushObjectReceived?.Invoke(envelope, EventArgs.Empty); } } else { Debug.Assert(shutdownRequested); _options.LogInfo("Exiting the worker with an empty queue."); // Empty queue. Exit. return; } } } catch (Exception e) { _options.LogFatal("Exception in the background worker.", e); throw; } finally { _queuedEnvelopeSemaphore.Dispose(); } }