/// <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);
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
Example #4
0
        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();
            }
        }