Ejemplo n.º 1
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);
            }
        }
Ejemplo n.º 2
0
 public void New_TraceState_Should_Have_Null_Header_And_SampleRate()
 {
     _traceState.ToTextHeader().Should().BeNull();
     _traceState.SampleRate.Should().BeNull();
 }