internal static void SetUpFilters(
     List <Func <ITransaction, ITransaction> > transactionFilters,
     List <Func <ISpan, ISpan> > spanFilters,
     IApmServerInfo apmServerInfo,
     IApmLogger logger)
 {
     transactionFilters.Add(new TransactionIgnoreUrlsFilter().Filter);
     transactionFilters.Add(new HeaderDictionarySanitizerFilter().Filter);
     // with this, stack trace demystification and conversion to the intake API model happens on a non-application thread:
     spanFilters.Add(new SpanStackTraceCapturingFilter(logger, apmServerInfo).Filter);
 }
        internal AgentComponents(
            IApmLogger logger,
            IConfigurationReader configurationReader,
            IPayloadSender payloadSender,
            IMetricsCollector metricsCollector,
            ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer,
            ICentralConfigFetcher centralConfigFetcher,
            IApmServerInfo apmServerInfo,
            BreakdownMetricsProvider breakdownMetricsProvider = null
            )
        {
            try
            {
                var tempLogger = logger ?? ConsoleLogger.LoggerOrDefault(configurationReader?.LogLevel);
                ConfigurationReader = configurationReader ?? new EnvironmentConfigurationReader(tempLogger);
                Logger  = logger ?? ConsoleLogger.LoggerOrDefault(ConfigurationReader.LogLevel);
                Service = Service.GetDefaultService(ConfigurationReader, Logger);

                var systemInfoHelper = new SystemInfoHelper(Logger);
                var system           = systemInfoHelper.ParseSystemInfo(ConfigurationReader.HostName);

                ConfigStore = new ConfigStore(new ConfigSnapshotFromReader(ConfigurationReader, "local"), Logger);

                ApmServerInfo = apmServerInfo ?? new ApmServerInfo();

                PayloadSender = payloadSender
                                ?? new PayloadSenderV2(Logger, ConfigStore.CurrentSnapshot, Service, system, ApmServerInfo,
                                                       isEnabled: ConfigurationReader.Enabled);

                HttpTraceConfiguration = new HttpTraceConfiguration();

                if (ConfigurationReader.Enabled)
                {
                    breakdownMetricsProvider ??= new BreakdownMetricsProvider(Logger);

                    CentralConfigFetcher = centralConfigFetcher ?? new CentralConfigFetcher(Logger, ConfigStore, Service);
                    MetricsCollector     = metricsCollector ?? new MetricsCollector(Logger, PayloadSender, ConfigStore, breakdownMetricsProvider);
                    MetricsCollector.StartCollecting();
                }
                else
                {
                    Logger.Info()?.Log("The Elastic APM .NET Agent is disabled - the agent won't capture traces and metrics.");
                }

                TracerInternal = new Tracer(Logger, Service, PayloadSender, ConfigStore,
                                            currentExecutionSegmentsContainer ?? new CurrentExecutionSegmentsContainer(), ApmServerInfo, breakdownMetricsProvider);
            }
            catch (Exception e)
            {
                Logger.Error()?.LogException(e, "Failed initializing agent.");
            }
        }
        internal static async Task FillApmServerInfo(IApmServerInfo apmServerInfo, IApmLogger logger, IConfiguration configuration,
                                                     HttpClient httpClient, Action <bool, IApmServerInfo> callbackOnFinish
                                                     )
        {
            try
            {
                using var requestMessage = new HttpRequestMessage(HttpMethod.Get, configuration.ServerUrl);
                requestMessage.Headers.Add("Metadata", "true");

                var responseMessage = await httpClient.SendAsync(requestMessage).ConfigureAwait(false);

                if (responseMessage.IsSuccessStatusCode)
                {
                    using var stream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false);

                    using var streamReader = new StreamReader(stream, Encoding.UTF8);
                    using var jsonReader   = new JsonTextReader(streamReader);

                    var serializer = new JsonSerializer();
                    var metadata   = serializer.Deserialize <JObject>(jsonReader);
                    var version    = metadata["version"];
                    var strVersion = version?.Value <string>();
                    if (strVersion != null)
                    {
                        try
                        {
                            apmServerInfo.Version = new ElasticVersion(strVersion);
                            callbackOnFinish?.Invoke(true, apmServerInfo);
                        }
                        catch (Exception e)
                        {
                            logger.Warning()?.LogException(e, "Failed parsing APM Server version - version string: {VersionString}", strVersion);
                            callbackOnFinish?.Invoke(false, apmServerInfo);
                        }
                    }
                }
                else
                {
                    logger.Warning()?.Log("Failed reading APM server info, response from server: {ResponseCode}", responseMessage.StatusCode);
                    callbackOnFinish?.Invoke(false, apmServerInfo);
                }
            }
            catch (Exception e)
            {
                logger.Warning()?.LogException(e, "Failed reading APM server info");
                callbackOnFinish?.Invoke(false, apmServerInfo);
            }
        }
示例#4
0
 public Tracer(
     IApmLogger logger,
     Service service,
     IPayloadSender payloadSender,
     IConfigSnapshotProvider configProvider,
     ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer,
     IApmServerInfo apmServerInfo
     )
 {
     _logger         = logger?.Scoped(nameof(Tracer));
     _service        = service;
     _sender         = payloadSender.ThrowIfArgumentNull(nameof(payloadSender));
     _configProvider = configProvider.ThrowIfArgumentNull(nameof(configProvider));
     CurrentExecutionSegmentsContainer = currentExecutionSegmentsContainer.ThrowIfArgumentNull(nameof(currentExecutionSegmentsContainer));
     DbSpanCommon   = new DbSpanCommon(logger);
     _apmServerInfo = apmServerInfo;
 }
示例#5
0
 /// <inheritdoc />
 public TestAgentComponents(
     IApmLogger logger            = null,
     IConfiguration configuration = null,
     IPayloadSender payloadSender = null,
     ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer = null,
     ICentralConfigurationFetcher centralConfigurationFetcher             = null,
     IApmServerInfo apmServerInfo = null
     ) : base(
         logger ?? new NoopLogger(),
         configuration ?? new MockConfiguration(logger ?? new NoopLogger()),
         payloadSender ?? new MockPayloadSender(),
         new FakeMetricsCollector(),
         currentExecutionSegmentsContainer,
         centralConfigurationFetcher ?? new NoopCentralConfigurationFetcher(),
         apmServerInfo ?? MockApmServerInfo.Version710
         )
 {
 }
 /// <inheritdoc />
 public TestAgentComponents(
     IApmLogger logger            = null,
     IConfigSnapshot config       = null,
     IPayloadSender payloadSender = null,
     ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer = null,
     ICentralConfigFetcher centralConfigFetcher = null,
     IApmServerInfo apmServerInfo = null
     ) : base(
         logger ?? new NoopLogger(),
         config ?? new MockConfigSnapshot(logger ?? new NoopLogger()),
         payloadSender ?? new MockPayloadSender(),
         new FakeMetricsCollector(),
         currentExecutionSegmentsContainer,
         centralConfigFetcher ?? new NoopCentralConfigFetcher(),
         apmServerInfo ?? new MockApmServerInfo()
         )
 {
 }
        internal AgentComponents(
            IApmLogger logger,
            IConfigurationReader configurationReader,
            IPayloadSender payloadSender,
            IMetricsCollector metricsCollector,
            ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer,
            ICentralConfigurationFetcher centralConfigurationFetcher,
            IApmServerInfo apmServerInfo,
            BreakdownMetricsProvider breakdownMetricsProvider = null
            )
        {
            try
            {
                var tempLogger = logger ?? ConsoleLogger.LoggerOrDefault(configurationReader?.LogLevel);
                ConfigurationReader = configurationReader ?? new EnvironmentConfigurationReader(tempLogger);
                Logger  = logger ?? ConsoleLogger.LoggerOrDefault(ConfigurationReader.LogLevel);
                Service = Service.GetDefaultService(ConfigurationReader, Logger);

                var systemInfoHelper = new SystemInfoHelper(Logger);
                var system           = systemInfoHelper.GetSystemInfo(ConfigurationReader.HostName);

                ConfigurationStore = new ConfigurationStore(new ConfigurationSnapshotFromReader(ConfigurationReader, "local"), Logger);

                ApmServerInfo = apmServerInfo ?? new ApmServerInfo();

                PayloadSender = payloadSender
                                ?? new PayloadSenderV2(Logger, ConfigurationStore.CurrentSnapshot, Service, system, ApmServerInfo,
                                                       isEnabled: ConfigurationReader.Enabled);

                if (ConfigurationReader.Enabled)
                {
                    breakdownMetricsProvider ??= new BreakdownMetricsProvider(Logger);
                }

                HttpTraceConfiguration = new HttpTraceConfiguration();
                SubscribedListeners    = new HashSet <Type>();

                // initialize the tracer before central configuration or metric collection is started
                TracerInternal = new Tracer(Logger, Service, PayloadSender, ConfigurationStore,
                                            currentExecutionSegmentsContainer ?? new CurrentExecutionSegmentsContainer(), ApmServerInfo, breakdownMetricsProvider);

                if (ConfigurationReader.Enabled)
                {
                    CentralConfigurationFetcher = centralConfigurationFetcher ?? new CentralConfigurationFetcher(Logger, ConfigurationStore, Service);
                    MetricsCollector            = metricsCollector ?? new MetricsCollector(Logger, PayloadSender, ConfigurationStore, breakdownMetricsProvider);
                    MetricsCollector.StartCollecting();
                }
                else
                {
                    Logger.Info()?.Log("The Elastic APM .NET Agent is disabled - the agent won't capture traces and metrics.");
                }

                TracerInternal = new Tracer(Logger, Service, PayloadSender, ConfigurationStore,
                                            currentExecutionSegmentsContainer ?? new CurrentExecutionSegmentsContainer(), ApmServerInfo, breakdownMetricsProvider);


                if (ConfigurationReader.EnableOpenTelemetryBridge)
                {
#if NET5_0 || NET6_0
                    if (ApmServerInfo.Version >= new ElasticVersion(7, 16, 0, string.Empty))
                    {
                        new OpenTelemetry.ElasticActivityListener(this, TracerInternal);
                    }
                    else
                    {
                        Logger.Warning()?.Log("OpenTelemetry (Activity) bridge is only supported with APM Server 7.16.0 or newer - bridge won't be enabled..");
                    }
#else
                    Logger.Warning()?.Log("OpenTelemetry (Activity) bridge is only supported on .NET5 or newer - bridge won't be enabled.");
#endif
                }
            }
            catch (Exception e)
            {
                Logger.Error()?.LogException(e, "Failed initializing agent.");
            }
        }
示例#8
0
        public Span(
            string name,
            string type,
            string parentId,
            string traceId,
            Transaction enclosingTransaction,
            IPayloadSender payloadSender,
            IApmLogger logger,
            ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer,
            IApmServerInfo apmServerInfo,
            Span parentSpan = null,
            InstrumentationFlag instrumentationFlag = InstrumentationFlag.None,
            bool captureStackTraceOnStart           = false
            )
        {
            InstrumentationFlag = instrumentationFlag;
            Timestamp           = TimeUtils.TimestampNow();
            Id      = RandomGenerator.GenerateRandomBytesAsString(new byte[8]);
            _logger = logger?.Scoped($"{nameof(Span)}.{Id}");

            _payloadSender = payloadSender;
            _currentExecutionSegmentsContainer = currentExecutionSegmentsContainer;
            _parentSpan           = parentSpan;
            _enclosingTransaction = enclosingTransaction;
            _apmServerInfo        = apmServerInfo;
            Name = name;
            Type = type;

            ParentId = parentId;
            TraceId  = traceId;

            if (IsSampled)
            {
                SampleRate = enclosingTransaction.SampleRate;
                // Started and dropped spans should be counted only for sampled transactions
                if (enclosingTransaction.SpanCount.IncrementTotal() > ConfigSnapshot.TransactionMaxSpans &&
                    ConfigSnapshot.TransactionMaxSpans >= 0)
                {
                    _isDropped = true;
                    enclosingTransaction.SpanCount.IncrementDropped();
                }
                else
                {
                    enclosingTransaction.SpanCount.IncrementStarted();

                    if (captureStackTraceOnStart)
                    {
                        var stackTrace = new StackTrace(true);
                        try
                        {
                            // I saw EnhancedStackTrace throwing exceptions in some environments
                            // therefore we try-catch and fall back to a non-demystified call stack.
                            _stackFrames = new EnhancedStackTrace(stackTrace).GetFrames();
                        }
                        catch
                        {
                            _stackFrames = stackTrace.GetFrames();
                        }
                    }
                }
            }
            else
            {
                SampleRate = 0;
            }

            _currentExecutionSegmentsContainer.CurrentSpan = this;

            _logger.Trace()
            ?.Log("New Span instance created: {Span}. Start time: {Time} (as timestamp: {Timestamp}). Parent span: {Span}",
                  this, TimeUtils.FormatTimestampForLog(Timestamp), Timestamp, _parentSpan);
        }
示例#9
0
        /// <summary>
        /// Creates a new transaction
        /// </summary>
        /// <param name="logger">The logger which logs debug information during the transaction  creation process</param>
        /// <param name="name">The name of the transaction</param>
        /// <param name="type">The type of the transaction</param>
        /// <param name="sampler">The sampler implementation which makes the sampling decision</param>
        /// <param name="distributedTracingData">Distributed tracing data, in case this transaction is part of a distributed trace</param>
        /// <param name="sender">The IPayloadSender implementation which will record this transaction</param>
        /// <param name="configSnapshot">The current configuration snapshot which contains the up-do-date config setting values</param>
        /// <param name="currentExecutionSegmentsContainer" />
        /// The ExecutionSegmentsContainer which makes sure this transaction flows
        /// <param name="apmServerInfo">Component to fetch info about APM Server (e.g. APM Server version)</param>
        /// <param name="ignoreActivity">
        /// If set the transaction will ignore Activity.Current and it's trace id,
        /// otherwise the agent will try to keep ids in-sync across async work-flows
        /// </param>
        internal Transaction(
            IApmLogger logger,
            string name,
            string type,
            Sampler sampler,
            DistributedTracingData distributedTracingData,
            IPayloadSender sender,
            IConfigSnapshot configSnapshot,
            ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer,
            IApmServerInfo apmServerInfo,
            bool ignoreActivity = false
            )
        {
            ConfigSnapshot = configSnapshot;
            Timestamp      = TimeUtils.TimestampNow();

            _logger        = logger?.Scoped(nameof(Transaction));
            _apmServerInfo = apmServerInfo;
            _sender        = sender;
            _currentExecutionSegmentsContainer = currentExecutionSegmentsContainer;

            Name          = name;
            HasCustomName = false;
            Type          = type;

            // For each new transaction, start an Activity if we're not ignoring them.
            // If Activity.Current is not null, the started activity will be a child activity,
            // so the traceid and tracestate of the parent will flow to it.
            if (!ignoreActivity)
            {
                _activity = StartActivity();
            }

            var isSamplingFromDistributedTracingData = false;

            if (distributedTracingData == null)
            {
                // We consider a newly created transaction **without** explicitly passed distributed tracing data
                // to be a root transaction.
                // Ignore the created activity ActivityTraceFlags because it starts out without setting the IsSampled flag,
                // so relying on that would mean a transaction is never sampled.
                if (_activity != null)
                {
                    // If an activity was created, reuse its id
                    Id      = _activity.SpanId.ToHexString();
                    TraceId = _activity.TraceId.ToHexString();

                    var idBytesFromActivity = new Span <byte>(new byte[16]);
                    _activity.TraceId.CopyTo(idBytesFromActivity);

                    // Read right most bits. From W3C TraceContext: "it is important for trace-id to carry "uniqueness" and "randomness"
                    // in the right part of the trace-id..."
                    idBytesFromActivity = idBytesFromActivity.Slice(8);

                    _traceState = new TraceState();

                    // If activity has a tracestate, populate the transaction tracestate with it.
                    if (!string.IsNullOrEmpty(_activity.TraceStateString))
                    {
                        _traceState.AddTextHeader(_activity.TraceStateString);
                    }

                    IsSampled = sampler.DecideIfToSample(idBytesFromActivity.ToArray());

                    // In the unlikely event that tracestate populated from activity contains an es vendor key, the tracestate
                    // is mutated to set the sample rate defined by the sampler, because we consider a transaction without
                    // explicitly passed distributedTracingData to be a **root** transaction. The end result
                    // is that activity tracestate will be propagated, along with the sample rate defined by this transaction.
                    if (IsSampled)
                    {
                        SampleRate = sampler.Rate;
                        _traceState.SetSampleRate(sampler.Rate);
                    }
                    else
                    {
                        SampleRate = 0;
                        _traceState.SetSampleRate(0);
                    }

                    // sync the activity tracestate with the tracestate of the transaction
                    _activity.TraceStateString = _traceState.ToTextHeader();
                }
                else
                {
                    // If no activity is created, create new random ids
                    var idBytes = new byte[8];
                    Id        = RandomGenerator.GenerateRandomBytesAsString(idBytes);
                    IsSampled = sampler.DecideIfToSample(idBytes);

                    idBytes = new byte[16];
                    TraceId = RandomGenerator.GenerateRandomBytesAsString(idBytes);

                    if (IsSampled)
                    {
                        _traceState = new TraceState(sampler.Rate);
                        SampleRate  = sampler.Rate;
                    }
                    else
                    {
                        _traceState = new TraceState(0);
                        SampleRate  = 0;
                    }
                }

                // ParentId could be also set here, but currently in the UI each trace must start with a transaction where the ParentId is null,
                // so to avoid https://github.com/elastic/apm-agent-dotnet/issues/883 we don't set it yet.
            }
            else
            {
                if (_activity != null)
                {
                    Id = _activity.SpanId.ToHexString();

                    // try to set the parent id and tracestate on the created activity, based on passed distributed tracing data.
                    // This is so that the distributed tracing data will flow to any child activities
                    try
                    {
                        _activity.SetParentId(
                            ActivityTraceId.CreateFromString(distributedTracingData.TraceId.AsSpan()),
                            ActivitySpanId.CreateFromString(distributedTracingData.ParentId.AsSpan()),
                            distributedTracingData.FlagRecorded ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None);

                        if (distributedTracingData.HasTraceState)
                        {
                            _activity.TraceStateString = distributedTracingData.TraceState.ToTextHeader();
                        }
                    }
                    catch (Exception e)
                    {
                        _logger.Error()?.LogException(e, "Error setting trace context on created activity");
                    }
                }
                else
                {
                    var idBytes = new byte[8];
                    Id = RandomGenerator.GenerateRandomBytesAsString(idBytes);
                }

                TraceId   = distributedTracingData.TraceId;
                ParentId  = distributedTracingData.ParentId;
                IsSampled = distributedTracingData.FlagRecorded;
                isSamplingFromDistributedTracingData = true;
                _traceState = distributedTracingData.TraceState;

                // If there is no tracestate or no valid "es" vendor entry with an "s" (sample rate) attribute, then the agent must
                // omit sample rate from non-root transactions and their spans.
                // See https://github.com/elastic/apm/blob/master/specs/agents/tracing-sampling.md#propagation
                if (_traceState?.SampleRate is null)
                {
                    SampleRate = null;
                }
                else
                {
                    SampleRate = _traceState.SampleRate.Value;
                }
            }

            // Also mark the sampling decision on the Activity
            if (IsSampled && _activity != null)
            {
                _activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
            }

            SpanCount = new SpanCount();
            _currentExecutionSegmentsContainer.CurrentTransaction = this;

            if (isSamplingFromDistributedTracingData)
            {
                _logger.Trace()
                ?.Log("New Transaction instance created: {Transaction}. " +
                      "IsSampled ({IsSampled}) and SampleRate ({SampleRate}) is based on incoming distributed tracing data ({DistributedTracingData})." +
                      " Start time: {Time} (as timestamp: {Timestamp})",
                      this, IsSampled, SampleRate, distributedTracingData, TimeUtils.FormatTimestampForLog(Timestamp), Timestamp);
            }
            else
            {
                _logger.Trace()
                ?.Log("New Transaction instance created: {Transaction}. " +
                      "IsSampled ({IsSampled}) is based on the given sampler ({Sampler})." +
                      " Start time: {Time} (as timestamp: {Timestamp})",
                      this, IsSampled, sampler, TimeUtils.FormatTimestampForLog(Timestamp), Timestamp);
            }
        }
示例#10
0
        public PayloadSenderV2(
            IApmLogger logger,
            IConfigSnapshot config,
            Service service,
            Api.System system,
            IApmServerInfo apmServerInfo,
            HttpMessageHandler httpMessageHandler = null,
            string dbgName = null,
            bool isEnabled = true,
            IEnvironmentVariables environmentVariables = null
            )
            : base(isEnabled, logger, ThisClassName, service, config, httpMessageHandler)
        {
            if (!isEnabled)
            {
                return;
            }

            _logger = logger?.Scoped(ThisClassName + (dbgName == null ? "" : $" (dbgName: `{dbgName}')"));
            _payloadItemSerializer = new PayloadItemSerializer();
            _configSnapshot        = config;

            _intakeV2EventsAbsoluteUrl = BackendCommUtils.ApmServerEndpoints.BuildIntakeV2EventsAbsoluteUrl(config.ServerUrl);

            System = system;

            _cloudMetadataProviderCollection = new CloudMetadataProviderCollection(config.CloudProvider, _logger, environmentVariables);
            _apmServerInfo = apmServerInfo;
            _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);

            SetUpFilters(TransactionFilters, SpanFilters, ErrorFilters, apmServerInfo, logger);
            StartWorkLoop();
        }
示例#11
0
        public Span(
            string name,
            string type,
            string parentId,
            string traceId,
            Transaction enclosingTransaction,
            IPayloadSender payloadSender,
            IApmLogger logger,
            ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer,
            IApmServerInfo apmServerInfo,
            Span parentSpan = null,
            InstrumentationFlag instrumentationFlag = InstrumentationFlag.None,
            bool captureStackTraceOnStart           = false
            )
        {
            InstrumentationFlag = instrumentationFlag;
            Timestamp           = TimeUtils.TimestampNow();
            Id      = RandomGenerator.GenerateRandomBytesAsString(new byte[8]);
            _logger = logger?.Scoped($"{nameof(Span)}.{Id}");

            _payloadSender = payloadSender;
            _currentExecutionSegmentsContainer = currentExecutionSegmentsContainer;
            _parentSpan           = parentSpan;
            _enclosingTransaction = enclosingTransaction;
            _apmServerInfo        = apmServerInfo;
            Name = name;
            Type = type;

            ParentId = parentId;
            TraceId  = traceId;

            if (IsSampled)
            {
                SampleRate = enclosingTransaction.SampleRate;
                // Started and dropped spans should be counted only for sampled transactions
                if (enclosingTransaction.SpanCount.IncrementTotal() > ConfigSnapshot.TransactionMaxSpans &&
                    ConfigSnapshot.TransactionMaxSpans >= 0)
                {
                    _isDropped = true;
                    enclosingTransaction.SpanCount.IncrementDropped();
                }
                else
                {
                    enclosingTransaction.SpanCount.IncrementStarted();

                    // In some cases capturing the stacktrace in End() results in a stack trace which is not very useful.
                    // In such cases we capture the stacktrace on span start.
                    // These are typically async calls - e.g. capturing stacktrace for outgoing HTTP requests in the
                    // System.Net.Http.HttpRequestOut.Stop
                    // diagnostic source event produces a stack trace that does not contain the caller method in user code - therefore we
                    // capture the stacktrace is .Start
                    if (captureStackTraceOnStart && ConfigSnapshot.StackTraceLimit != 0 && ConfigSnapshot.SpanFramesMinDurationInMilliseconds != 0)
                    {
                        RawStackTrace = new StackTrace(true);
                    }
                }
            }
            else
            {
                SampleRate = 0;
            }

            _currentExecutionSegmentsContainer.CurrentSpan = this;

            _logger.Trace()
            ?.Log("New Span instance created: {Span}. Start time: {Time} (as timestamp: {Timestamp}). Parent span: {Span}",
                  this, TimeUtils.FormatTimestampForLog(Timestamp), Timestamp, _parentSpan);
        }
示例#12
0
        internal AgentComponents(
            IApmLogger logger,
            IConfigurationReader configurationReader,
            IPayloadSender payloadSender,
            IMetricsCollector metricsCollector,
            ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer,
            ICentralConfigurationFetcher centralConfigurationFetcher,
            IApmServerInfo apmServerInfo,
            BreakdownMetricsProvider breakdownMetricsProvider = null
            )
        {
            try
            {
                var tempLogger = logger ?? ConsoleLogger.LoggerOrDefault(configurationReader?.LogLevel);
                ConfigurationReader = configurationReader ?? new EnvironmentConfigurationReader(tempLogger);
                Logger  = logger ?? ConsoleLogger.LoggerOrDefault(ConfigurationReader.LogLevel);
                Service = Service.GetDefaultService(ConfigurationReader, Logger);

                var systemInfoHelper = new SystemInfoHelper(Logger);
                var system           = systemInfoHelper.GetSystemInfo(ConfigurationReader.HostName);

                ConfigurationStore = new ConfigurationStore(new ConfigurationSnapshotFromReader(ConfigurationReader, "local"), Logger);

                ApmServerInfo = apmServerInfo ?? new ApmServerInfo();

                // Called by PayloadSenderV2 after the ServerInfo is fetched
                Action <bool, IApmServerInfo> serverInfoCallback = null;

#if NET5_0 || NET6_0
                ElasticActivityListener activityListener = null;
                if (ConfigurationReader.EnableOpenTelemetryBridge)
                {
                    activityListener = new OpenTelemetry.ElasticActivityListener(this);

                    serverInfoCallback = (success, serverInfo) =>
                    {
                        if (success)
                        {
                            if (serverInfo.Version >= new ElasticVersion(7, 16, 0, string.Empty))
                            {
                                Logger.Info()
                                ?.Log("APM Server version ready - OpenTelemetry (Activity) bridge is active. Current Server version: {version}",
                                      serverInfo.Version.ToString());
                            }
                            else
                            {
                                Logger.Warning()
                                ?.Log(
                                    "OpenTelemetry (Activity) bridge is only supported with APM Server 7.16.0 or newer - bridge won't be enabled. Current Server version: {version}",
                                    serverInfo.Version.ToString());
                                activityListener?.Dispose();
                            }
                        }
                        else
                        {
                            Logger.Warning()
                            ?.Log(
                                "Unable to read server version - OpenTelemetry (Activity) bridge is only supported with APM Server 7.16.0 or newer. "
                                + "The bridge remains active, but due to unknown server version it may not work as expected.");
                        }
                    };
                }
#endif
                PayloadSender = payloadSender
                                ?? new PayloadSenderV2(Logger, ConfigurationStore.CurrentSnapshot, Service, system, ApmServerInfo,
                                                       isEnabled: ConfigurationReader.Enabled, serverInfoCallback: serverInfoCallback);

                if (ConfigurationReader.Enabled)
                {
                    breakdownMetricsProvider ??= new BreakdownMetricsProvider(Logger);
                }

                HttpTraceConfiguration = new HttpTraceConfiguration();
                SubscribedListeners    = new HashSet <Type>();

                // initialize the tracer before central configuration or metric collection is started
                TracerInternal = new Tracer(Logger, Service, PayloadSender, ConfigurationStore,
                                            currentExecutionSegmentsContainer ?? new CurrentExecutionSegmentsContainer(), ApmServerInfo, breakdownMetricsProvider);

#if NET5_0 || NET6_0
                if (ConfigurationReader.EnableOpenTelemetryBridge)
                {
                    // If the server version is not known yet, we enable the listener - and then the callback will do the version check once we have the version
                    if (ApmServerInfo.Version == null || ApmServerInfo?.Version == new ElasticVersion(0, 0, 0, null))
                    {
                        activityListener?.Start(TracerInternal);
                    }
                    // Otherwise do a version check
                    else if (ApmServerInfo.Version >= new ElasticVersion(7, 16, 0, string.Empty))
                    {
                        Logger.Info()
                        ?.Log("Starting OpenTelemetry (Activity) bridge");

                        activityListener?.Start(TracerInternal);
                    }
                    else
                    {
                        Logger.Warning()
                        ?.Log(
                            "OpenTelemetry (Activity) bridge is only supported with APM Server 7.16.0 or newer - bridge won't be enabled. Current Server version: {version}",
                            ApmServerInfo.Version.ToString());
                        activityListener?.Dispose();
                    }
                }
#endif

                if (ConfigurationReader.Enabled)
                {
                    CentralConfigurationFetcher = centralConfigurationFetcher ?? new CentralConfigurationFetcher(Logger, ConfigurationStore, Service);
                    MetricsCollector            = metricsCollector ?? new MetricsCollector(Logger, PayloadSender, ConfigurationStore, breakdownMetricsProvider);
                    MetricsCollector.StartCollecting();
                }
                else
                {
                    Logger.Info()?.Log("The Elastic APM .NET Agent is disabled - the agent won't capture traces and metrics.");
                }
            }
            catch (Exception e)
            {
                Logger.Error()?.LogException(e, "Failed initializing agent.");
            }
        }
示例#13
0
        /// <summary>
        /// Creates a new transaction
        /// </summary>
        /// <param name="logger">The logger which logs debug information during the transaction  creation process</param>
        /// <param name="name">The name of the transaction</param>
        /// <param name="type">The type of the transaction</param>
        /// <param name="sampler">The sampler implementation which makes the sampling decision</param>
        /// <param name="distributedTracingData">Distributed tracing data, in case this transaction is part of a distributed trace</param>
        /// <param name="sender">The IPayloadSender implementation which will record this transaction</param>
        /// <param name="configSnapshot">The current configuration snapshot which contains the up-do-date config setting values</param>
        /// <param name="currentExecutionSegmentsContainer" />
        /// The ExecutionSegmentsContainer which makes sure this transaction flows
        /// <param name="apmServerInfo">Component to fetch info about APM Server (e.g. APM Server version)</param>
        /// <param name="ignoreActivity">
        /// If set the transaction will ignore Activity.Current and it's trace id,
        /// otherwise the agent will try to keep ids in-sync across async work-flows
        /// </param>
        internal Transaction(
            IApmLogger logger,
            string name,
            string type,
            Sampler sampler,
            DistributedTracingData distributedTracingData,
            IPayloadSender sender,
            IConfigSnapshot configSnapshot,
            ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer,
            IApmServerInfo apmServerInfo,
            bool ignoreActivity = false
            )
        {
            ConfigSnapshot = configSnapshot;
            Timestamp      = TimeUtils.TimestampNow();

            _logger        = logger?.Scoped($"{nameof(Transaction)}");
            _apmServerInfo = apmServerInfo;

            _sender = sender;
            _currentExecutionSegmentsContainer = currentExecutionSegmentsContainer;

            Name          = name;
            HasCustomName = false;
            Type          = type;

            // For each transaction start, we fire an Activity
            // If Activity.Current is null, then we create one with this and set its traceid, which will flow to all child activities and we also reuse it in Elastic APM, so it'll be the same on all Activities and in Elastic
            // If Activity.Current is not null, we pick up its traceid and apply it in Elastic APM
            if (!ignoreActivity)
            {
                StartActivity();
            }

            var isSamplingFromDistributedTracingData = false;

            if (distributedTracingData == null)
            {
                // Here we ignore Activity.Current.ActivityTraceFlags because it starts out without setting the IsSampled flag, so relying on that would mean a transaction is never sampled.
                // To be sure activity creation was successful let's check on it
                if (_activity != null)
                {
                    // In case activity creation was successful, let's reuse the ids
                    Id      = _activity.SpanId.ToHexString();
                    TraceId = _activity.TraceId.ToHexString();

                    var idBytesFromActivity = new Span <byte>(new byte[16]);
                    _activity.TraceId.CopyTo(idBytesFromActivity);
                    // Read right most bits. From W3C TraceContext: "it is important for trace-id to carry "uniqueness" and "randomness" in the right part of the trace-id..."
                    idBytesFromActivity = idBytesFromActivity.Slice(8);
                    IsSampled           = sampler.DecideIfToSample(idBytesFromActivity.ToArray());
                }
                else
                {
                    // In case from some reason the activity creation was not successful, let's create new random ids
                    var idBytes = new byte[8];
                    Id        = RandomGenerator.GenerateRandomBytesAsString(idBytes);
                    IsSampled = sampler.DecideIfToSample(idBytes);

                    idBytes = new byte[16];
                    TraceId = RandomGenerator.GenerateRandomBytesAsString(idBytes);
                }

                // PrentId could be also set here, but currently in the UI each trace must start with a transaction where the ParentId is null,
                // so to avoid https://github.com/elastic/apm-agent-dotnet/issues/883 we don't set it yet.
            }
            else
            {
                if (_activity != null)
                {
                    Id = _activity.SpanId.ToHexString();
                }
                else
                {
                    var idBytes = new byte[8];
                    Id = RandomGenerator.GenerateRandomBytesAsString(idBytes);
                }
                TraceId   = distributedTracingData.TraceId;
                ParentId  = distributedTracingData.ParentId;
                IsSampled = distributedTracingData.FlagRecorded;
                isSamplingFromDistributedTracingData = true;
                _traceState = distributedTracingData.TraceState;
            }

            // Also mark the sampling decision on the Activity
            if (IsSampled && _activity != null && !ignoreActivity)
            {
                _activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
            }

            SampleRate = IsSampled ? sampler.Rate : 0;

            SpanCount = new SpanCount();
            _currentExecutionSegmentsContainer.CurrentTransaction = this;

            if (isSamplingFromDistributedTracingData)
            {
                _logger.Trace()
                ?.Log("New Transaction instance created: {Transaction}. " +
                      "IsSampled ({IsSampled}) is based on incoming distributed tracing data ({DistributedTracingData})." +
                      " Start time: {Time} (as timestamp: {Timestamp})",
                      this, IsSampled, distributedTracingData, TimeUtils.FormatTimestampForLog(Timestamp), Timestamp);
            }
            else
            {
                _logger.Trace()
                ?.Log("New Transaction instance created: {Transaction}. " +
                      "IsSampled ({IsSampled}) is based on the given sampler ({Sampler})." +
                      " Start time: {Time} (as timestamp: {Timestamp})",
                      this, IsSampled, sampler, TimeUtils.FormatTimestampForLog(Timestamp), Timestamp);
            }

            void StartActivity()
            {
                _activity = new Activity(ApmTransactionActivityName);
                _activity.SetIdFormat(ActivityIdFormat.W3C);
                _activity.Start();
            }
        }
 public SpanStackTraceCapturingFilter(IApmLogger logger, IApmServerInfo apmServerInfo) =>
 (_logger, _apmServerInfo) = (logger, apmServerInfo);
示例#15
0
        /// <summary>
        /// Turns a System.Diagnostic.StackFrame[] into a <see cref="CapturedStackFrame" /> list which can be reported to the APM
        /// Server
        /// </summary>
        /// <param name="frames">The stack frames to rewrite into APM stack traces</param>
        /// <param name="logger">The logger to emit exceptions on should one occur</param>
        /// <param name="apmServerInfo">The ServerInfo instance to query the server version</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>
        /// <returns>A prepared List that can be passed to the APM server</returns>
        internal static List <CapturedStackFrame> GenerateApmStackTrace(StackFrame[] frames, IApmLogger logger,
                                                                        IConfigurationReader configurationReader, IApmServerInfo apmServerInfo, string dbgCapturingFor
                                                                        )
        {
            var stackTraceLimit = configurationReader.StackTraceLimit;

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

            if (stackTraceLimit > 0)
            {
                // new StackTrace(skipFrames: n) skips frames from the top of the stack (currently executing method is top)
                // the StackTraceLimit feature takes the top n frames, so unfortunately we currently capture the whole stack trace and just take
                // the top `configurationReader.StackTraceLimit` frames. - This could be optimized.
                frames = frames.Take(stackTraceLimit).ToArray();
            }

            var retVal = new List <CapturedStackFrame>(frames.Length);

            logger.Trace()?.Log("transform stack frames");

            try
            {
                foreach (var frame in frames)
                {
                    var className = frame?.GetMethod()
                                    ?.DeclaringType?.FullName;             //see: https://github.com/elastic/apm-agent-dotnet/pull/240#discussion_r289619196

                    var functionName = GetRealMethodName(frame?.GetMethod());
                    var fileName     = frame?.GetFileName();

                    logger.Trace()?.Log("{MethodName}, {lineNo}", functionName, frame?.GetFileLineNumber());

                    var capturedStackFrame = new CapturedStackFrame
                    {
                        Function = functionName ?? "N/A",
                        Module   = frame?.GetMethod()?.ReflectedType?.Assembly.FullName,
                        LineNo   = frame?.GetFileLineNumber() ?? 0,
                        AbsPath  = frame?.GetFileName()                        // optional property
                    };

                    if (apmServerInfo?.Version < V710)
                    {
                        // In pre 7.10, Kibana shows stack traces in format: `[FileName] in [MethodName]` and there is no way to show ClassName.
                        // For .NET that format is less useful especially because in some cases we only have a `.dll` file as filename.
                        // Therefore as a workaround we send the real classname in the file name field and
                        // we don't send anything in the ClassName field, since that's not used.
                        // If versions 7.09 is out of support, this code can be removed.
                        capturedStackFrame.FileName = string.IsNullOrWhiteSpace(className) ? "N/A" : className;
                    }
                    else
                    {
                        // FileName is either the .cs file or the assembly location as fallback
                        capturedStackFrame.FileName =
                            string.IsNullOrWhiteSpace(fileName)
                                                                ? string.IsNullOrEmpty(frame?.GetMethod()?.GetType().Assembly.Location) ? "n/a" :
                            frame.GetMethod()?.GetType().Assembly.Location
                                                                : fileName;

                        capturedStackFrame.ClassName = string.IsNullOrWhiteSpace(className) ? "N/A" : className;
                    }

                    retVal.Add(capturedStackFrame);
                }
            }
            catch (Exception e)
            {
                logger?.Warning()?.LogException(e, "Failed capturing stacktrace for {ApmContext}", dbgCapturingFor);
            }

            return(retVal);
        }
示例#16
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);
        }