/// <summary> /// Try to inject the trace context into the Kafka message headers /// </summary> /// <param name="agent">The agent</param> /// <param name="segment">The outgoing distributed tracing data to propagate</param> /// <param name="message">The duck-typed Kafka Message object</param> /// <typeparam name="TTopicPartitionMarker">The TopicPartition type (used optimisation purposes)</typeparam> /// <typeparam name="TMessage">The type of the duck-type proxy</typeparam> internal static void TryInjectHeaders <TTopicPartitionMarker, TMessage>(IApmAgent agent, IExecutionSegment segment, TMessage message) where TMessage : IMessage { if (!HeadersInjectionEnabled) { return; } try { message.Headers ??= CachedMessageHeadersHelper <TTopicPartitionMarker> .CreateHeaders(); var adapter = new KafkaHeadersCollection(message.Headers, agent.Logger); var distributedTracingData = segment.OutgoingDistributedTracingData; adapter.Set(TraceContext.TraceParentBinaryHeaderName, distributedTracingData.SerializeToString()); adapter.Set(TraceContext.TraceStateHeaderName, distributedTracingData.TraceState.ToTextHeader()); } catch (Exception ex) { // don't keep trying if we run into problems HeadersInjectionEnabled = false; agent.Logger.Warning()?.LogException(ex, "There was a problem injecting headers into the Kafka record. Disabling headers injection"); } }
internal static ITransaction CreateConsumerTransaction( IApmAgent agent, string topic, Partition?partition, Offset?offset, IMessage message) { ITransaction transaction = null; try { if (agent.Tracer.CurrentTransaction is not null) { return(null); } if (agent is ApmAgent apmAgent) { var matcher = WildcardMatcher.AnyMatch(apmAgent.ConfigurationStore.CurrentSnapshot.IgnoreMessageQueues, topic); if (matcher != null) { agent.Logger.Trace() ?.Log( "Not tracing message from {Queue} because it matched IgnoreMessageQueues pattern {Matcher}", topic, matcher.GetMatcher()); return(null); } } DistributedTracingData distributedTracingData = null; if (message?.Headers != null) { var headers = new KafkaHeadersCollection(message.Headers, agent.Logger); try { var traceParent = string.Join(",", headers.GetValues(TraceContext.TraceParentBinaryHeaderName)); var traceState = headers.GetValues(TraceContext.TraceStateHeaderName).FirstOrDefault(); distributedTracingData = TraceContext.TryExtractTracingData(traceParent, traceState); } catch (Exception ex) { agent.Logger.Error()?.LogException(ex, "Error extracting propagated headers from Kafka message"); } } var name = string.IsNullOrEmpty(topic) ? "Kafka RECEIVE" : $"Kafka RECEIVE from {topic}"; transaction = agent.Tracer.StartTransaction(name, ApiConstants.TypeMessaging, distributedTracingData); if (partition is not null) { transaction.SetLabel("partition", partition.ToString()); } if (offset is not null) { transaction.SetLabel("offset", offset.ToString()); } // record only queue topic name and age on context for now. capture body and headers potentially in future transaction.Context.Message = new Message { Queue = new Queue { Name = topic } }; if (transaction is Transaction realTransaction && message is not null && message.Timestamp.Type != 0) { var consumeTime = TimeUtils.ToDateTime(realTransaction.Timestamp); var produceTime = message.Timestamp.UtcDateTime; var age = Math.Max(0, (consumeTime - produceTime).TotalMilliseconds); if (age > 0 && age < MaxAge) { transaction.Context.Message.Age = new Age { Ms = (long)age } } ; } if (message is not null && message.Value is null) { transaction.SetLabel("tombstone", "true"); } } catch (Exception ex) { agent.Logger.Error()?.LogException(ex, "Error creating or populating transaction."); } return(transaction); }