internal Transaction( IApmLogger logger, string name, string type, Sampler sampler, DistributedTracingData distributedTracingData, IPayloadSender sender, IConfigurationReader configurationReader ) { Timestamp = TimeUtils.TimestampNow(); var idBytes = new byte[8]; Id = RandomGenerator.GenerateRandomBytesAsString(idBytes); _logger = logger?.Scoped($"{nameof(Transaction)}.{Id}"); _sender = sender; _configurationReader = configurationReader; Name = name; HasCustomName = false; Type = type; var isSamplingFromDistributedTracingData = false; if (distributedTracingData == null) { var traceIdBytes = new byte[16]; TraceId = RandomGenerator.GenerateRandomBytesAsString(traceIdBytes); IsSampled = sampler.DecideIfToSample(idBytes); } else { TraceId = distributedTracingData.TraceId; ParentId = distributedTracingData.ParentId; IsSampled = distributedTracingData.FlagRecorded; isSamplingFromDistributedTracingData = true; } SpanCount = new SpanCount(); 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); } }
internal Transaction( IApmLogger logger, string name, string type, Sampler sampler, DistributedTracingData distributedTracingData, IPayloadSender sender ) { _logger = logger?.Scoped(nameof(Transaction)); _sender = sender; _start = DateTimeOffset.UtcNow; Name = name; HasCustomName = false; Type = type; var idBytes = new byte[8]; Id = RandomGenerator.GenerateRandomBytesAsString(idBytes); var isSamplingFromDistributedTracingData = false; if (distributedTracingData == null) { var traceIdBytes = new byte[16]; TraceId = RandomGenerator.GenerateRandomBytesAsString(traceIdBytes); IsSampled = sampler.DecideIfToSample(idBytes); } else { TraceId = distributedTracingData.TraceId; ParentId = distributedTracingData.ParentId; IsSampled = distributedTracingData.FlagRecorded; isSamplingFromDistributedTracingData = true; } SpanCount = new SpanCount(); if (isSamplingFromDistributedTracingData) { _logger.Trace()?.Log("New Transaction instance created: {Transaction}. " + "IsSampled ({IsSampled}) is based on incoming distributed tracing data ({DistributedTracingData})", this, IsSampled, distributedTracingData); } else { _logger.Trace()?.Log("New Transaction instance created: {Transaction}. " + "IsSampled ({IsSampled}) is based on the given sampler ({Sampler})", this, IsSampled, sampler); } }
private Transaction(Context context, string name, string type, double duration, long timestamp, string id, string traceId, string parentId, bool isSampled, string result, SpanCount spanCount ) { _context = new Lazy <Context>(() => context); Name = name; Duration = duration; Timestamp = timestamp; Type = type; Id = id; TraceId = traceId; ParentId = parentId; IsSampled = isSampled; Result = result; SpanCount = spanCount; }
/// <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); } }
/// <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="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, bool ignoreActivity = false ) { ConfigSnapshot = configSnapshot; Timestamp = TimeUtils.TimestampNow(); _logger = logger?.Scoped($"{nameof(Transaction)}"); _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; } 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(); } }
internal Transaction( IApmLogger logger, string name, string type, Sampler sampler, DistributedTracingData distributedTracingData, IPayloadSender sender, IConfigSnapshot configSnapshot, ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer ) { ConfigSnapshot = configSnapshot; Timestamp = TimeUtils.TimestampNow(); var idBytes = new byte[8]; Id = RandomGenerator.GenerateRandomBytesAsString(idBytes); _logger = logger?.Scoped($"{nameof(Transaction)}.{Id}"); _sender = sender; _currentExecutionSegmentsContainer = currentExecutionSegmentsContainer; Name = name; HasCustomName = false; Type = type; 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. IsSampled = sampler.DecideIfToSample(idBytes); if (Activity.Current != null && Activity.Current.IdFormat == ActivityIdFormat.W3C) { TraceId = Activity.Current.TraceId.ToString(); ParentId = Activity.Current.ParentId; // Also mark the sampling decision on the Activity if (IsSampled) { Activity.Current.ActivityTraceFlags |= ActivityTraceFlags.Recorded; } } else { TraceId = _activity.TraceId.ToString(); } } else { TraceId = distributedTracingData.TraceId; ParentId = distributedTracingData.ParentId; IsSampled = distributedTracingData.FlagRecorded; isSamplingFromDistributedTracingData = true; _traceState = distributedTracingData.TraceState; } 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("ElasticApm.Transaction"); _activity.SetIdFormat(ActivityIdFormat.W3C); _activity.Start(); } }