private async Task ProcessQueueItems(object[] queueItems)
        {
            try
            {
                var metadataJson = _payloadItemSerializer.SerializeObject(_metadata);
                var ndjson       = new StringBuilder();
                ndjson.AppendLine("{\"metadata\": " + metadataJson + "}");

                foreach (var item in queueItems)
                {
                    var serialized = _payloadItemSerializer.SerializeObject(item);
                    switch (item)
                    {
                    case Transaction _:
                        ndjson.AppendLine("{\"transaction\": " + serialized + "}");
                        break;

                    case Span _:
                        ndjson.AppendLine("{\"span\": " + serialized + "}");
                        break;

                    case Error _:
                        ndjson.AppendLine("{\"error\": " + serialized + "}");
                        break;

                    case MetricSet _:
                        ndjson.AppendLine("{\"metricset\": " + serialized + "}");
                        break;
                    }
                    _logger?.Trace()?.Log("Serialized item to send: {ItemToSend} as {SerializedItem}", item, serialized);
                }

                var content = new StringContent(ndjson.ToString(), Encoding.UTF8, "application/x-ndjson");

                var result = await _httpClient.PostAsync(Consts.IntakeV2Events, content, _cancellationTokenSource.Token);

                if (result != null && !result.IsSuccessStatusCode)
                {
                    _logger?.Error()
                    ?.Log("Failed sending event. " +
                          "APM Server response: status code: {ApmServerResponseStatusCode}, content: \n{ApmServerResponseContent}",
                          result.StatusCode, await result.Content.ReadAsStringAsync());
                }
                else
                {
                    _logger?.Debug()
                    ?.Log("Sent items to server:\n{SerializedItems}",
                          TextUtils.Indent(string.Join($",{Environment.NewLine}", queueItems.ToArray())));
                }
            }
            catch (Exception e)
            {
                _logger?.Warning()
                ?.LogException(
                    e,
                    "Failed sending events. Following events were not transferred successfully to the server ({ApmServerUrl}):\n{SerializedItems}",
                    _httpClient.BaseAddress,
                    TextUtils.Indent(string.Join($",{Environment.NewLine}", queueItems.ToArray())));
            }
        }
Exemple #2
0
        /// <summary>
        /// Cleans up the scheduler by indicating that no more tasks will be queued.
        /// This method blocks until all threads successfully shutdown.
        /// </summary>
        public void Dispose() =>
        _disposableHelper.DoOnce(_logger, DbgName, () =>
        {
            // Indicate that no new tasks will be coming in
            _taskQueue.CompleteAdding();

            _logger.Debug()?.Log("Waiting for thread `{ThreadName}' to exit... DbgCurrentState: {DbgCurrentState}", _thread.Name
                                 , DbgCurrentState());

            _thread.Join();

            _logger.Debug()?.Log("Disposing _taskQueue...");

            _taskQueue.Dispose();

            _logger.Debug()?.Log("Done");
        });
Exemple #3
0
        public IntakeV2EventsController(MockApmServer mockApmServer, Validator validator)
        {
            _mockApmServer = mockApmServer;
            _validator     = validator;
            _logger        = mockApmServer.InternalLogger.Scoped(ThisClassName + "#" + RuntimeHelpers.GetHashCode(this).ToString("X"));

            _logger.Debug()?.Log("Constructed with mock APM Server: {MockApmServer}", _mockApmServer);
        }
        internal Span StartSpanInternal(string name, string type, string subType = null, string action = null)
        {
            var retVal = new Span(name, type, Id, TraceId, _enclosingTransaction, IsSampled, _payloadSender, _logger);

            if (!string.IsNullOrEmpty(subType))
            {
                retVal.Subtype = subType;
            }

            if (!string.IsNullOrEmpty(action))
            {
                retVal.Action = action;
            }

            _logger.Debug()?.Log("Starting {SpanDetails}", retVal.ToString());
            return(retVal);
        }
Exemple #5
0
 public void End()
 {
     _logger.Debug()?.Log("Ending {SpanDetails}", ToString());
     if (!Duration.HasValue)
     {
         Duration = (DateTimeOffset.UtcNow - _start).TotalMilliseconds;
     }
     _payloadSender.QueueSpan(this);
 }
        public SystemTotalCpuProvider(IApmLogger logger)
        {
            _logger = logger.Scoped(nameof(SystemTotalCpuProvider));
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                var categoryName = "Processor";
                try
                {
                    try
                    {
                        _processorTimePerfCounter = new PerformanceCounter(categoryName, "% Processor Time", "_Total");
                    }
                    catch (InvalidOperationException e)
                    {
                        _logger.Debug()?.LogException(e, "Error instantiating '{CategoryName}' performance counter.", categoryName);
                        _processorTimePerfCounter?.Dispose();
                        // If the Processor performance counter category does not exist, try Processor Information.
                        categoryName = "Processor Information";
                        _processorTimePerfCounter = new PerformanceCounter(categoryName, "% Processor Time", "_Total");
                    }

                    //The perf. counter API returns 0 the for the 1. call (probably because there is no delta in the 1. call) - so we just call it here first
                    _processorTimePerfCounter.NextValue();
                }
                catch (Exception e)
                {
                    if (e is UnauthorizedAccessException)
                    {
                        _logger.Error()
                        ?.LogException(e, "Error instantiating '{CategoryName}' performance counter."
                                       + " Process does not have permissions to read performance counters."
                                       + " See https://www.elastic.co/guide/en/apm/agent/dotnet/current/metrics.html#metrics-system to see how to configure.", categoryName);
                    }
                    else
                    {
                        _logger.Error()
                        ?.LogException(e, "Error instantiating '{CategoryName}' performance counter", categoryName);
                    }

                    _logger.Warning()?.Log("System metrics won't be collected");
                    _processorTimePerfCounter?.Dispose();
                    _processorTimePerfCounter = null;
                }
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                var(success, idle, total) = ReadProcStat();
                if (!success)
                {
                    return;
                }

                _prevIdleTime  = idle;
                _prevTotalTime = total;
            }
        }
 internal IisAdministration(IApmLogger logger)
 {
     _logger = logger?.Scoped(nameof(IisAdministration));
     _policy = Policy
               .Handle <FileLoadException>()
               .Retry(3, (exception, retryCount) =>
     {
         _logger.Debug()?.LogException(exception, "Error commiting changes. retry {RetryCount}", retryCount);
     });
 }
Exemple #8
0
        internal int FindAvailablePortToListen()
        {
            var numberOfPortsTried       = 0;
            var numberOfPortsInScanRange = PortScanRange.End - PortScanRange.Begin;
            var currentPort = RandomGenerator.GetInstance().Next(PortScanRange.Begin, PortScanRange.End);

            while (true)
            {
                ++numberOfPortsTried;
                try
                {
                    _logger.Debug()?.Log("Trying to listen on port {PortNumber}...", currentPort);
                    var listener = new HttpListener();
                    listener.Prefixes.Add($"http://localhost:{currentPort}/");
                    listener.Start();
                    listener.Stop();
                    _logger.Debug()?.Log("Port {PortNumber} is available - it will be used to accept connections from the agent", currentPort);
                    return(currentPort);
                }
                catch (HttpListenerException ex)
                {
                    _logger.Debug()
                    ?.LogException(ex, "Failed to listen on port {PortNumber}. " +
                                   "Number of ports tried so far: {NumberOfPorts} out of {NumberOfPorts}",
                                   currentPort, numberOfPortsTried, numberOfPortsInScanRange);
                    if (numberOfPortsTried == numberOfPortsInScanRange)
                    {
                        throw new InvalidOperationException("Could not find an available port for Mock APM Server to listen. " +
                                                            $"Ports range that was tried: [{PortScanRange.Begin}, {PortScanRange.End})");
                    }
                }

                if (currentPort + 1 == PortScanRange.End)
                {
                    currentPort = PortScanRange.Begin;
                }
                else
                {
                    ++currentPort;
                }
            }
        }
Exemple #9
0
        public void QueueTransaction(ITransaction transaction)
        {
            var res = _eventQueue.Post(transaction);

            _logger.Debug()
            ?.Log(!res
                                        ? "Failed adding Transaction to the queue, {Transaction}"
                                        : "Transaction added to the queue, {Transaction}", transaction);

            _eventQueue.TriggerBatch();
        }
Exemple #10
0
 internal async Task UpdateAndWaitForAgentToApply(
     IReadOnlyDictionary <string, string> optionsToUpdate
     , TimeSpan?waitUntilNextRequest = null
     )
 {
     var(dbgConfig, agentAppliedEvent) = Update(optionsToUpdate, waitUntilNextRequest);
     _logger.Debug()
     ?.Log("Waiting for agent to apply updated central configuration..."
           + " Central config options: {CentralConfigOptions}", dbgConfig);
     await agentAppliedEvent.Task;
 }
        private IActionResult GetImpl()
        {
            _logger.Debug()
            ?.Log("Received get-agents-config request with query string: {QueryString}."
                  + " Current thread: {ThreadDesc}."
                  , Request.QueryString, DbgUtils.CurrentThreadDesc);

            var getAgentsConfig = _mockApmServer.GetAgentsConfig;

            if (getAgentsConfig == null)
            {
                return(NotFound("Get-agents-config API is not enabled"));
            }

            var result = getAgentsConfig(Request, Response);

            _logger.Debug()?.Log("Response to get-agents-config: {Response}", result);

            return(result);
        }
Exemple #12
0
        private async Task ProcessQueueItems(object[] queueItems)
        {
            try
            {
                var metadata = new Metadata {
                    Service = _service
                };
                var metadataJson = _payloadItemSerializer.SerializeObject(metadata);
                var ndjson       = new StringBuilder();
                ndjson.Append("{\"metadata\": " + metadataJson + "}" + "\n");

                foreach (var item in queueItems)
                {
                    var serialized = _payloadItemSerializer.SerializeObject(item);
                    switch (item)
                    {
                    case Transaction _:
                        ndjson.AppendLine("{\"transaction\": " + serialized + "}");
                        break;

                    case Span _:
                        ndjson.AppendLine("{\"span\": " + serialized + "}");
                        break;

                    case Error _:
                        ndjson.AppendLine("{\"error\": " + serialized + "}");
                        break;
                    }
                    _logger?.Trace()?.Log("Serialized item to send: {ItemToSend} as {SerializedItemToSend}", item, serialized);
                }

                var content = new StringContent(ndjson.ToString(), Encoding.UTF8, "application/x-ndjson");

                var result = await _httpClient.PostAsync(Consts.IntakeV2Events, content);

                if (result != null && !result.IsSuccessStatusCode)
                {
                    _logger?.Error()?.Log("Failed sending event. {ApmServerResponse}", await result.Content.ReadAsStringAsync());
                }
                else
                {
                    _logger?.Debug()
                    ?.Log($"Sent items to server: {Environment.NewLine}{{items}}", string.Join($",{Environment.NewLine}", queueItems.ToArray()));
                }
            }
            catch (Exception e)
            {
                _logger?.Warning()
                ?.LogException(
                    e, "Failed sending events. Following events were not transferred successfully to the server:\n{items}",
                    string.Join($",{Environment.NewLine}", queueItems.ToArray()));
            }
        }
        public PayloadSenderV2(IApmLogger logger, IConfigurationReader configurationReader, Service service, Api.System system,
                               HttpMessageHandler handler = null
                               )
        {
            _service  = service;
            _system   = system;
            _metadata = new Metadata {
                Service = _service, System = _system
            };
            _logger = logger?.Scoped(nameof(PayloadSenderV2));

            var serverUrlBase = configurationReader.ServerUrls.First();
            var servicePoint  = ServicePointManager.FindServicePoint(serverUrlBase);

            servicePoint.ConnectionLeaseTimeout = DnsTimeout;
            servicePoint.ConnectionLimit        = 20;

            _httpClient = new HttpClient(handler ?? new HttpClientHandler())
            {
                BaseAddress = serverUrlBase
            };
            _httpClient.DefaultRequestHeaders.UserAgent.Add(
                new ProductInfoHeaderValue($"elasticapm-{Consts.AgentName}", AdaptUserAgentValue(_service.Agent.Version)));
            _httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("System.Net.Http",
                                                                                       AdaptUserAgentValue(typeof(HttpClient).Assembly.GetCustomAttribute <AssemblyFileVersionAttribute>().Version)));
            _httpClient.DefaultRequestHeaders.UserAgent.Add(
                new ProductInfoHeaderValue(AdaptUserAgentValue(_service.Runtime.Name), AdaptUserAgentValue(_service.Runtime.Version)));

            if (configurationReader.SecretToken != null)
            {
                _httpClient.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue("Bearer", configurationReader.SecretToken);
            }
            Task.Factory.StartNew(
                () =>
            {
                try
                {
#pragma warning disable 4014
                    DoWork();
#pragma warning restore 4014
                }
                catch (TaskCanceledException ex)
                {
                    _logger?.Debug()?.LogExceptionWithCaller(ex);
                }
            }, CancellationToken.None, TaskCreationOptions.LongRunning, _singleThreadTaskScheduler);

            // Replace invalid characters by underscore. All invalid characters can be found at
            // https://github.com/dotnet/corefx/blob/e64cac6dcacf996f98f0b3f75fb7ad0c12f588f7/src/System.Net.Http/src/System/Net/Http/HttpRuleParser.cs#L41
            string AdaptUserAgentValue(string value) => Regex.Replace(value, "[ /()<>@,:;={}?\\[\\]\"\\\\]", "_");
        }
Exemple #14
0
        /// <summary>
        /// Gets the ASP.NET engine version
        /// </summary>
        /// <param name="logger">The logger</param>
        /// <returns>the engine version, or N/A if it cannot be found</returns>
        public static string GetEngineVersion(IApmLogger logger)
        {
            var aspNetVersion = "N/A";

            try
            {
                // We would like to report the same ASP.NET version as the one printed at the bottom of the error page
                // (see https://github.com/microsoft/referencesource/blob/master/System.Web/ErrorFormatter.cs#L431)
                // It is stored in VersionInfo.EngineVersion
                // (see https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System.Web/Util/versioninfo.cs#L91)
                // which is unfortunately an internal property of an internal class in System.Web assembly so we use reflection to get it
                const string versionInfoTypeName = "System.Web.Util.VersionInfo";
                var          versionInfoType     = typeof(HttpRuntime).Assembly.GetType(versionInfoTypeName);
                if (versionInfoType == null)
                {
                    logger.Error()
                    ?.Log("Type {TypeName} was not found in assembly {AssemblyFullName} - {AspNetVersion} will be used as ASP.NET version",
                          versionInfoTypeName, typeof(HttpRuntime).Assembly.FullName, aspNetVersion);
                    return(aspNetVersion);
                }

                const string engineVersionPropertyName = "EngineVersion";
                var          engineVersionProperty     = versionInfoType.GetProperty(engineVersionPropertyName,
                                                                                     BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
                if (engineVersionProperty == null)
                {
                    logger.Error()
                    ?.Log("Property {PropertyName} was not found in type {TypeName} - {AspNetVersion} will be used as ASP.NET version",
                          engineVersionPropertyName, versionInfoType.FullName, aspNetVersion);
                    return(aspNetVersion);
                }

                var engineVersionPropertyValue = (string)engineVersionProperty.GetValue(null);
                if (engineVersionPropertyValue == null)
                {
                    logger.Error()
                    ?.Log("Property {PropertyName} (in type {TypeName}) is of type {TypeName} and not a string as expected" +
                          " - {AspNetVersion} will be used as ASP.NET version",
                          engineVersionPropertyName, versionInfoType.FullName, engineVersionPropertyName.GetType().FullName, aspNetVersion);
                    return(aspNetVersion);
                }

                aspNetVersion = engineVersionPropertyValue;
            }
            catch (Exception ex)
            {
                logger.Error()?.LogException(ex, "Failed to obtain ASP.NET version - {AspNetVersion} will be used as ASP.NET version", aspNetVersion);
            }

            logger.Debug()?.Log("Found ASP.NET version: {AspNetVersion}", aspNetVersion);
            return(aspNetVersion);
        }
Exemple #15
0
        protected async Task <SampleAppResponse> SendGetRequestToSampleAppAndVerifyResponse(Uri uri, int expectedStatusCode,
                                                                                            bool timeHttpCall = true, bool addTraceContextHeaders = false
                                                                                            )
        {
            var startTime = DateTime.UtcNow;

            if (timeHttpCall)
            {
                _logger.Debug()
                ?.Log("HTTP call to sample application started at {Time} (as timestamp: {Timestamp})",
                      startTime, TimeUtils.ToTimestamp(startTime));
            }
            try
            {
                var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
                if (addTraceContextHeaders)
                {
                    httpRequestMessage.Headers.Add("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01");
                    httpRequestMessage.Headers.Add("tracestate", "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE");
                }

                var response = await SendGetRequestToSampleAppAndVerifyResponseImpl(httpRequestMessage, expectedStatusCode);

                return(new SampleAppResponse(response.Headers, await response.Content.ReadAsStringAsync()));
            }
            finally
            {
                if (timeHttpCall)
                {
                    _sampleAppClientCallTiming.Should().BeNull();
                    var endTime = DateTime.UtcNow;
                    _logger.Debug()
                    ?.Log("HTTP call to sample application ended at {Time} (as timestamp: {Timestamp}), Duration: {Duration}ms",
                          endTime, TimeUtils.ToTimestamp(endTime),
                          TimeUtils.DurationBetweenTimestamps(TimeUtils.ToTimestamp(startTime), TimeUtils.ToTimestamp(endTime)));
                    _sampleAppClientCallTiming = new TimedEvent(startTime, endTime);
                }
            }
        }
        public static IDictionary GetEnvironmentVariables(IApmLogger logger)
        {
            try
            {
                return(Environment.GetEnvironmentVariables());
            }
            catch (Exception e)
            {
                logger.Debug()?.LogException(e, "Error while getting environment variables");
            }

            return(null);
        }
 internal IisAdministration(IApmLogger logger)
 {
     _logger = logger?.Scoped(nameof(IisAdministration));
     _policy = Policy
               .Handle <FileLoadException>()
               .WaitAndRetry(
         20,
         (_, _) => TimeSpan.FromMilliseconds(500),
         (exception, _, ctx) =>
     {
         _logger.Debug()?.LogException(exception, "Error commiting changes. retry {RetryCount}", ctx.Count);
     });
 }
Exemple #18
0
        public PayloadSenderV2(IApmLogger logger, IConfigSnapshot config, Service service, Api.System system,
                               HttpMessageHandler httpMessageHandler = null, string dbgName = null
                               )
            : base(/* isEnabled: */ true, logger, ThisClassName, service, config, httpMessageHandler)
        {
            _logger = logger?.Scoped(ThisClassName + (dbgName == null ? "" : $" (dbgName: `{dbgName}')"));
            _payloadItemSerializer = new PayloadItemSerializer(config);

            _intakeV2EventsAbsoluteUrl = BackendCommUtils.ApmServerEndpoints.BuildIntakeV2EventsAbsoluteUrl(config.ServerUrls.First());

            System = system;

            _metadata = new Metadata {
                Service = service, System = System
            };
            foreach (var globalLabelKeyValue in config.GlobalLabels)
            {
                _metadata.Labels.Add(globalLabelKeyValue.Key, globalLabelKeyValue.Value);
            }

            if (config.MaxQueueEventCount < config.MaxBatchEventCount)
            {
                _logger?.Error()
                ?.Log(
                    "MaxQueueEventCount is less than MaxBatchEventCount - using MaxBatchEventCount as MaxQueueEventCount."
                    + " MaxQueueEventCount: {MaxQueueEventCount}."
                    + " MaxBatchEventCount: {MaxBatchEventCount}.",
                    config.MaxQueueEventCount, config.MaxBatchEventCount);

                _maxQueueEventCount = config.MaxBatchEventCount;
            }
            else
            {
                _maxQueueEventCount = config.MaxQueueEventCount;
            }

            _flushInterval = config.FlushInterval;

            _logger?.Debug()
            ?.Log(
                "Using the following configuration options:"
                + " Events intake API absolute URL: {EventsIntakeAbsoluteUrl}"
                + ", FlushInterval: {FlushInterval}"
                + ", MaxBatchEventCount: {MaxBatchEventCount}"
                + ", MaxQueueEventCount: {MaxQueueEventCount}"
                , _intakeV2EventsAbsoluteUrl, _flushInterval.ToHms(), config.MaxBatchEventCount, _maxQueueEventCount);

            _eventQueue = new BatchBlock <object>(config.MaxBatchEventCount);
            TransactionFilters.Add(new TransactionIgnoreUrlsFilter(config).Filter);
            StartWorkLoop();
        }
Exemple #19
0
        internal static void DoSwallowingExceptions(IApmLogger logger, Action action, bool shouldSwallowCancellation = true
                                                    , [CallerMemberName] string dbgCallerMethodName = null)
        {
            try
            {
                logger.Debug()?.Log(MethodStartingMsgFmt, dbgCallerMethodName);
                action();
                logger.Debug()?.Log(MethodExitingNormallyMsgFmt, dbgCallerMethodName);
            }
            catch (OperationCanceledException ex)
            {
                logger.Debug()?.LogException(ex, MethodExitingCancelledMsgFmt, dbgCallerMethodName);

                if (!shouldSwallowCancellation)
                {
                    throw;
                }
            }
            catch (Exception ex)
            {
                logger.Error()?.LogException(ex, MethodExitingExceptionMsgFmt, dbgCallerMethodName);
            }
        }
Exemple #20
0
        static ElasticApmModule()
        {
            var configReader    = new FullFrameworkConfigReader(ConsoleLogger.Instance);
            var agentComponents = new AgentComponents(configurationReader: configReader);

            SetServiceInformation(agentComponents.Service);
            Agent.Setup(agentComponents);
            Logger = Agent.Instance.Logger.Scoped(nameof(ElasticApmModule));

            Logger.Debug()
            ?.Log($"Entered {nameof(ElasticApmModule)} static ctor: ASP.NET: {AspNetVersion}, CLR: {ClrDescription}, IIS: {IisVersion}");

            IsCaptureHeadersEnabled = Agent.Instance.ConfigurationReader.CaptureHeaders;

            Agent.Instance.Subscribe(new HttpDiagnosticsSubscriber());
        }
        private void RunOnCurrentThread()
        {
            _logger.Debug()?.Log("`{ThreadName}' thread started", Thread.CurrentThread.Name);

            _isExecuting = true;

            ExceptionUtils.DoSwallowingExceptions(_logger, () =>
            {
                foreach (var task in _taskQueue.GetConsumingEnumerable(_cancellationToken))
                {
                    TryExecuteTask(task);
                }
            }
                                                  , dbgCallerMethodName: $"`{Thread.CurrentThread.Name}' (ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}) thread");

            _isExecuting = false;
        }
Exemple #22
0
        private void OnBeginRequest(object eventSender, EventArgs eventArgs)
        {
            Logger.Debug()?.Log("Incoming request processing started - starting trace");

            try
            {
                ProcessBeginRequest(eventSender);
            }
            catch (Exception ex)
            {
                Logger.Error()?.Log("Processing BeginRequest event failed. Exception: {Exception}", ex);
            }
        }
        public PayloadSenderV2(IApmLogger logger, IConfigSnapshot config, Service service, Api.System system,
                               HttpMessageHandler httpMessageHandler = null, string dbgName = null
                               )
            : base(/* isEnabled: */ true, logger, ThisClassName, service, config, httpMessageHandler)
        {
            _logger = logger?.Scoped(ThisClassName + (dbgName == null ? "" : $" (dbgName: `{dbgName}')"));

            System    = system;
            _metadata = new Metadata {
                Service = service, System = System
            };

            if (config.MaxQueueEventCount < config.MaxBatchEventCount)
            {
                _logger?.Error()
                ?.Log(
                    "MaxQueueEventCount is less than MaxBatchEventCount - using MaxBatchEventCount as MaxQueueEventCount."
                    + " MaxQueueEventCount: {MaxQueueEventCount}."
                    + " MaxBatchEventCount: {MaxBatchEventCount}.",
                    config.MaxQueueEventCount, config.MaxBatchEventCount);

                _maxQueueEventCount = config.MaxBatchEventCount;
            }
            else
            {
                _maxQueueEventCount = config.MaxQueueEventCount;
            }

            _flushInterval = config.FlushInterval;

            _logger?.Debug()
            ?.Log(
                "Using the following configuration options:"
                + " FlushInterval: {FlushInterval}"
                + ", MaxBatchEventCount: {MaxBatchEventCount}"
                + ", MaxQueueEventCount: {MaxQueueEventCount}"
                , _flushInterval.ToHms(), config.MaxBatchEventCount, _maxQueueEventCount);

            _eventQueue = new BatchBlock <object>(config.MaxBatchEventCount);

            StartWorkLoop();
        }
        internal bool EnqueueEvent(object eventObj, string dbgEventKind)
        {
            ThrowIfDisposed();

            // Enforce _maxQueueEventCount manually instead of using BatchBlock's BoundedCapacity
            // because of the issue of Post returning false when TriggerBatch is in progress. For more details see
            // https://stackoverflow.com/questions/35626955/unexpected-behaviour-tpl-dataflow-batchblock-rejects-items-while-triggerbatch
            var newEventQueueCount = Interlocked.Increment(ref _eventQueueCount);

            if (newEventQueueCount > _maxQueueEventCount)
            {
                _logger.Debug()
                ?.Log("Queue reached max capacity - " + dbgEventKind + " will be discarded."
                      + " newEventQueueCount: {EventQueueCount}."
                      + " MaxQueueEventCount: {MaxQueueEventCount}."
                      + " " + dbgEventKind + ": {" + dbgEventKind + "}."
                      , newEventQueueCount, _maxQueueEventCount, eventObj);
                Interlocked.Decrement(ref _eventQueueCount);
                return(false);
            }

            var enqueuedSuccessfully = _eventQueue.Post(eventObj);

            if (!enqueuedSuccessfully)
            {
                _logger.Debug()
                ?.Log("Failed to enqueue " + dbgEventKind + "."
                      + " newEventQueueCount: {EventQueueCount}."
                      + " MaxQueueEventCount: {MaxQueueEventCount}."
                      + " " + dbgEventKind + ": {" + dbgEventKind + "}."
                      , newEventQueueCount, _maxQueueEventCount, eventObj);
                Interlocked.Decrement(ref _eventQueueCount);
                return(false);
            }

            _logger.Debug()
            ?.Log("Enqueued " + dbgEventKind + "."
                  + " newEventQueueCount: {EventQueueCount}."
                  + " MaxQueueEventCount: {MaxQueueEventCount}."
                  + " " + dbgEventKind + ": {" + dbgEventKind + "}."
                  , newEventQueueCount, _maxQueueEventCount, eventObj);

            if (_flushInterval == TimeSpan.Zero)
            {
                _eventQueue.TriggerBatch();
            }

            return(true);
        }
Exemple #25
0
        /// <summary>
        ///  Turns an <see cref="Exception" /> into a <see cref="CapturedStackFrame" /> list which can be reported to the APM
        /// Server
        /// </summary>
        /// <param name="exception">The exception to rewrite into APM stack traces</param>
        /// <param name="logger">The logger to emit exceptions on should one occur</param>
        /// <param name="dbgCapturingFor">Just for logging.</param>
        /// <param name="configurationReader">
        /// Config reader - this controls the collection of stack traces (e.g. limit on frames,
        /// etc)
        /// </param>
        /// <param name="apmServerInfo">The server info instance to query the APM Server version</param>
        /// <returns>A prepared List that can be passed to the APM server</returns>
        internal static List <CapturedStackFrame> GenerateApmStackTrace(Exception exception, IApmLogger logger, string dbgCapturingFor,
                                                                        IConfigurationReader configurationReader, IApmServerInfo apmServerInfo
                                                                        )
        {
            var stackTraceLimit = configurationReader.StackTraceLimit;

            if (stackTraceLimit == 0)
            {
                return(null);
            }

            try
            {
                try
                {
                    return(GenerateApmStackTrace(
                               new EnhancedStackTrace(exception).GetFrames(), logger, configurationReader, apmServerInfo, dbgCapturingFor));
                }
                catch (Exception e)
                {
                    logger?.Debug()
                                                ?
                    .LogException(e, "Failed generating stack trace with EnhancedStackTrace - using fallback without demystification");
                    // Fallback, see https://github.com/elastic/apm-agent-dotnet/issues/957
                    // This callstack won't be demystified, but at least it'll be captured.
                    return(GenerateApmStackTrace(
                               new StackTrace(exception, true).GetFrames(), logger, configurationReader, apmServerInfo, dbgCapturingFor));
                }
            }
            catch (Exception e)
            {
                logger?.Warning()
                ?.Log("Failed extracting stack trace from exception for {ApmContext}."
                      + " Exception for failure to extract: {ExceptionForFailureToExtract}."
                      + " Exception to extract from: {ExceptionToExtractFrom}.",
                      dbgCapturingFor, e, exception);
            }

            return(null);
        }
Exemple #26
0
        public PayloadSenderV2(IApmLogger logger, IConfigurationReader configurationReader, Service service, Api.System system, HttpMessageHandler handler = null)
        {
            _service  = service;
            _system   = system;
            _metadata = new Metadata {
                Service = _service, System = _system
            };
            _logger = logger?.Scoped(nameof(PayloadSenderV2));

            var serverUrlBase = configurationReader.ServerUrls.First();
            var servicePoint  = ServicePointManager.FindServicePoint(serverUrlBase);

            servicePoint.ConnectionLeaseTimeout = DnsTimeout;
            servicePoint.ConnectionLimit        = 20;

            _httpClient = new HttpClient(handler ?? new HttpClientHandler())
            {
                BaseAddress = serverUrlBase
            };

            if (configurationReader.SecretToken != null)
            {
                _httpClient.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue("Bearer", configurationReader.SecretToken);
            }
            Task.Factory.StartNew(
                () =>
            {
                try
                {
#pragma warning disable 4014
                    DoWork();
#pragma warning restore 4014
                }
                catch (TaskCanceledException ex)
                {
                    _logger?.Debug()?.LogExceptionWithCaller(ex);
                }
            }, CancellationToken.None, TaskCreationOptions.LongRunning, _singleThreadTaskScheduler);
        }
        /// <summary>
        /// We need this private ctor to avoid calling configStore.CurrentSnapshot twice (and thus possibly using different
        /// snapshots)
        /// when passing isEnabled: initialConfigSnapshot.CentralConfig and config: initialConfigSnapshot to base
        /// </summary>
        private CentralConfigFetcher(IApmLogger logger, IConfigStore configStore, IConfigSnapshot initialConfigSnapshot, Service service
                                     , HttpMessageHandler httpMessageHandler, IAgentTimer agentTimer, string dbgName
                                     )
            : base(/* isEnabled: */ initialConfigSnapshot.CentralConfig, logger, ThisClassName, service, initialConfigSnapshot, httpMessageHandler)
        {
            _logger = logger?.Scoped(ThisClassName + (dbgName == null ? "" : $" (dbgName: `{dbgName}')"));

            _initialSnapshot = initialConfigSnapshot;

            var isCentralConfigOptEqDefault = _initialSnapshot.CentralConfig == ConfigConsts.DefaultValues.CentralConfig;
            var centralConfigStatus         = _initialSnapshot.CentralConfig ? "enabled" : "disabled";

            if (!isCentralConfigOptEqDefault)
            {
                centralConfigStatus = centralConfigStatus.ToUpper();
            }
            _logger.IfLevel(isCentralConfigOptEqDefault ? LogLevel.Debug : LogLevel.Information)
            ?.Log("Central configuration feature is {CentralConfigStatus} because CentralConfig option's value is {CentralConfigOptionValue}"
                  + " (default value is {CentralConfigOptionDefaultValue})"
                  , centralConfigStatus, _initialSnapshot.CentralConfig, ConfigConsts.DefaultValues.CentralConfig);

            if (!_initialSnapshot.CentralConfig)
            {
                return;
            }

            _configStore = configStore;

            _agentTimer = agentTimer ?? new AgentTimer();

            _getConfigAbsoluteUrl = BackendCommUtils.ApmServerEndpoints.BuildGetConfigAbsoluteUrl(initialConfigSnapshot.ServerUrls.First(), service);
            _logger.Debug()
            ?.Log("Combined absolute URL for APM Server get central configuration endpoint: `{Url}'. Service: {Service}."
                  , _getConfigAbsoluteUrl, service);

            StartWorkLoop();
        }
Exemple #28
0
        internal BackendCommComponentBase(bool isEnabled, IApmLogger logger, string dbgDerivedClassName, Service service
                                          , IConfiguration configuration, HttpMessageHandler httpMessageHandler = null
                                          )
        {
            _dbgName             = $"{ThisClassName} ({dbgDerivedClassName})";
            _dbgDerivedClassName = dbgDerivedClassName;
            _logger    = logger?.Scoped(_dbgName);
            _isEnabled = isEnabled;

            if (!_isEnabled)
            {
                _logger.Debug()?.Log("Disabled - exiting without initializing any members used by work loop");
                return;
            }

            CancellationTokenSource = new CancellationTokenSource();

            _disposableHelper = new DisposableHelper();

            _loopStarted   = new ManualResetEventSlim();
            _loopCompleted = new ManualResetEventSlim();

            HttpClient = BackendCommUtils.BuildHttpClient(logger, configuration, service, _dbgName, httpMessageHandler);
        }
        public async Task InvokeAsync(HttpContext context)
        {
            Transaction transaction;

            var transactionName = $"{context.Request.Method} {context.Request.Path}";

            if (context.Request.Headers.ContainsKey(TraceParent.TraceParentHeaderName))
            {
                var headerValue = context.Request.Headers[TraceParent.TraceParentHeaderName].ToString();

                var distributedTracingData = TraceParent.TryExtractTraceparent(headerValue);

                if (distributedTracingData != null)
                {
                    _logger.Debug()
                    ?.Log(
                        "Incoming request with {TraceParentHeaderName} header. DistributedTracingData: {DistributedTracingData}. Continuing trace.",
                        TraceParent.TraceParentHeaderName, distributedTracingData);

                    transaction = _tracer.StartTransactionInternal(
                        transactionName,
                        ApiConstants.TypeRequest,
                        distributedTracingData);
                }
                else
                {
                    _logger.Debug()
                    ?.Log(
                        "Incoming request with invalid {TraceParentHeaderName} header (received value: {TraceParentHeaderValue}). Starting trace with new trace id.",
                        TraceParent.TraceParentHeaderName, headerValue);

                    transaction = _tracer.StartTransactionInternal(transactionName,
                                                                   ApiConstants.TypeRequest);
                }
            }
            else
            {
                _logger.Debug()?.Log("Incoming request. Starting Trace.");
                transaction = _tracer.StartTransactionInternal(transactionName,
                                                               ApiConstants.TypeRequest);
            }

            if (transaction.IsSampled)
            {
                FillSampledTransactionContextRequest(context, transaction);
            }

            try
            {
                await _next(context);
            }
            catch (Exception e) when((Helpers.ExceptionFilter.Capture(e, transaction, context, _configurationReader, _logger)))
            {
            }
            finally
            {
                if (!transaction.HasCustomName)
                {
                    //fixup Transaction.Name - e.g. /user/profile/1 -> /user/profile/{id}
                    var routeData = (context.Features[typeof(IRoutingFeature)] as IRoutingFeature)?.RouteData;
                    if (routeData != null)
                    {
                        var name = GetNameFromRouteContext(routeData.Values);

                        if (!string.IsNullOrWhiteSpace(name))
                        {
                            transaction.Name = $"{context.Request.Method} {name}";
                        }
                    }
                }

                transaction.Result = Transaction.StatusCodeToResult(GetProtocolName(context.Request.Protocol), context.Response.StatusCode);

                if (transaction.IsSampled)
                {
                    FillSampledTransactionContextResponse(context, transaction);
                    FillSampledTransactionContextUser(context, transaction);
                }

                transaction.End();
            }
        }
Exemple #30
0
        private void PostToInternalTaskScheduler(string dbgActionDesc, Func <Task> asyncAction
                                                 , TaskCreationOptions taskCreationOptions = TaskCreationOptions.None
                                                 )
        {
#pragma warning disable 4014
            // We don't pass any CancellationToken on purpose because in some case (for example work loop)
            // we wait for asyncAction to start so we should never cancel it before it starts
            Task.Factory.StartNew(asyncAction, CancellationToken.None, taskCreationOptions, _singleThreadTaskScheduler);
#pragma warning restore 4014
            _logger.Debug()?.Log("Posted {DbgTaskDesc} to internal task scheduler", dbgActionDesc);
        }