public void StartsActivitySourceActivity() { using var _ = SetAppConfigSwitch(); // Bug: there is no way to set activity type to W3C // https://github.com/dotnet/runtime/issues/43853 var oldDefault = Activity.DefaultIdFormat; Activity.DefaultIdFormat = ActivityIdFormat.W3C; try { using var activityListener = new TestActivitySourceListener("Azure.Clients.ClientName"); DiagnosticScopeFactory clientDiagnostics = new DiagnosticScopeFactory("Azure.Clients", "Microsoft.Azure.Core.Cool.Tests", true); DiagnosticScope scope = clientDiagnostics.CreateScope("ClientName.ActivityName"); scope.AddAttribute("Attribute1", "Value1"); scope.AddAttribute("Attribute2", 2, i => i.ToString()); scope.AddAttribute("Attribute3", 3); scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00"); scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00", new Dictionary <string, string>() { { "linkAttribute", "linkAttributeValue" } }); Assert.IsTrue(scope.IsEnabled); scope.Start(); scope.Dispose(); Assert.AreEqual(1, activityListener.Activities.Count); var activity = activityListener.Activities.Dequeue(); Assert.AreEqual("ClientName.ActivityName", activity.DisplayName); Assert.AreEqual("Value1", activity.TagObjects.Single(o => o.Key == "Attribute1").Value); Assert.AreEqual("2", activity.TagObjects.Single(o => o.Key == "Attribute2").Value); Assert.AreEqual("3", activity.TagObjects.Single(o => o.Key == "Attribute3").Value); var links = activity.Links.ToArray(); Assert.AreEqual(2, links.Length); Assert.AreEqual(ActivityContext.Parse("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00", null), links[0].Context); Assert.AreEqual(ActivityContext.Parse("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00", null), links[1].Context); Assert.AreEqual(ActivityIdFormat.W3C, activity.IdFormat); } finally { Activity.DefaultIdFormat = oldDefault; } }
public void AddLinkCreatesLinkedActivityWithTags() { using var testListener = new TestDiagnosticListener("Azure.Clients"); DiagnosticScopeFactory clientDiagnostics = new DiagnosticScopeFactory("Azure.Clients", "Microsoft.Azure.Core.Cool.Tests", true); DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName"); var expectedTags = new Dictionary <string, string>() { { "key1", "value1" }, { "key2", "value2" } }; scope.AddLink("id", expectedTags); scope.Start(); (string Key, object Value, DiagnosticListener)startEvent = testListener.Events.Dequeue(); scope.Dispose(); (string Key, object Value, DiagnosticListener)stopEvent = testListener.Events.Dequeue(); Assert.Null(Activity.Current); Assert.AreEqual("ActivityName.Start", startEvent.Key); Assert.AreEqual("ActivityName.Stop", stopEvent.Key); var activities = (IEnumerable <Activity>)startEvent.Value.GetType().GetTypeInfo().GetDeclaredProperty("Links").GetValue(startEvent.Value); Activity linkedActivity = activities.Single(); Assert.AreEqual(ActivityIdFormat.W3C, linkedActivity.IdFormat); Assert.AreEqual("id", linkedActivity.ParentId); CollectionAssert.AreEquivalent(expectedTags, linkedActivity.Tags); }
public void AddLinkCallsPassesLinksAsPartOfStartPayload() { using var testListener = new TestDiagnosticListener("Azure.Clients"); DiagnosticScopeFactory clientDiagnostics = new DiagnosticScopeFactory("Azure.Clients", "Microsoft.Azure.Core.Cool.Tests", true); DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName"); scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00", "foo=bar"); scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00", null); scope.Start(); (string Key, object Value, DiagnosticListener)startEvent = testListener.Events.Dequeue(); Activity activity = Activity.Current; scope.Dispose(); (string Key, object Value, DiagnosticListener)stopEvent = testListener.Events.Dequeue(); var isEnabledCall = testListener.IsEnabledCalls.Dequeue(); Assert.NotNull(activity); Assert.Null(Activity.Current); Assert.AreEqual("ActivityName.Start", startEvent.Key); Assert.AreEqual("ActivityName.Stop", stopEvent.Key); Assert.AreEqual("ActivityName", isEnabledCall.Name); var activities = (IEnumerable <Activity>)startEvent.Value.GetType().GetTypeInfo().GetDeclaredProperty("Links").GetValue(startEvent.Value); Activity[] activitiesArray = activities.ToArray(); Assert.AreEqual(activitiesArray.Length, 2); Activity linkedActivity1 = activitiesArray[0]; Activity linkedActivity2 = activitiesArray[1]; Assert.AreEqual(ActivityIdFormat.W3C, linkedActivity1.IdFormat); Assert.AreEqual("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00", linkedActivity1.ParentId); Assert.AreEqual("foo=bar", linkedActivity1.TraceStateString); Assert.AreEqual(ActivityIdFormat.W3C, linkedActivity2.IdFormat); Assert.AreEqual("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00", linkedActivity2.ParentId); Assert.Null(linkedActivity2.TraceStateString); Assert.AreEqual(0, testListener.Events.Count); }
private static void AddLinkedDiagnostics(this DiagnosticScope scope, IDictionary <string, object> properties) { if (EntityScopeFactory.TryExtractDiagnosticId( properties, out string diagnosticId)) { scope.AddLink(diagnosticId); } }
public void AddLinkCallsPassesLinksAsPartOfStartPayload() { using var testListener = new TestDiagnosticListener("Azure.Clients"); DiagnosticScopeFactory clientDiagnostics = new DiagnosticScopeFactory("Azure.Clients", "Microsoft.Azure.Core.Cool.Tests", true); DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName"); scope.AddLink("id"); scope.AddLink("id2"); scope.Start(); (string Key, object Value, DiagnosticListener)startEvent = testListener.Events.Dequeue(); Activity activity = Activity.Current; scope.Dispose(); (string Key, object Value, DiagnosticListener)stopEvent = testListener.Events.Dequeue(); Assert.Null(Activity.Current); Assert.AreEqual("ActivityName.Start", startEvent.Key); Assert.AreEqual("ActivityName.Stop", stopEvent.Key); var activities = (IEnumerable <Activity>)startEvent.Value.GetType().GetTypeInfo().GetDeclaredProperty("Links").GetValue(startEvent.Value); Activity[] activitiesArray = activities.ToArray(); Assert.AreEqual(activitiesArray.Length, 2); Activity linkedActivity1 = activitiesArray[0]; Activity linkedActivity2 = activitiesArray[1]; Assert.AreEqual(ActivityIdFormat.W3C, linkedActivity1.IdFormat); Assert.AreEqual("id", linkedActivity1.ParentId); Assert.AreEqual(ActivityIdFormat.W3C, linkedActivity2.IdFormat); Assert.AreEqual("id2", linkedActivity2.ParentId); Assert.AreEqual(0, testListener.Events.Count); }
private static void AddLinkIfEventHasContext(DiagnosticScope scope, JToken evnt) { if (evnt is JObject eventObj && eventObj.TryGetValue("traceparent", out JToken traceparent) && traceparent.Type == JTokenType.String) { string tracestateStr = null; if (eventObj.TryGetValue("tracestate", out JToken tracestate) && tracestate.Type == JTokenType.String) { tracestateStr = tracestate.Value <string>(); } scope.AddLink(traceparent.Value <string>(), tracestateStr); } }
public void StartActivitySourceActivityIgnoresInvalidLinkParent() { using var _ = SetAppConfigSwitch(); using var activityListener = new TestActivitySourceListener("Azure.Clients.ClientName"); DiagnosticScopeFactory clientDiagnostics = new DiagnosticScopeFactory("Azure.Clients", "Microsoft.Azure.Core.Cool.Tests", true); DiagnosticScope scope = clientDiagnostics.CreateScope("ClientName.ActivityName"); scope.AddLink("test"); scope.Start(); scope.Dispose(); Assert.AreEqual(0, activityListener.Activities.Single().Links.Count()); }
/// <summary> /// Creates and configures a diagnostics scope to be used for instrumenting /// events. /// </summary> /// /// <param name="diagnosticIdentifiers">The set of diagnostic identifiers to which the scope will be linked.</param> /// /// <returns>The requested <see cref="DiagnosticScope" />.</returns> /// private DiagnosticScope CreateDiagnosticScope(IEnumerable <string> diagnosticIdentifiers) { DiagnosticScope scope = EventDataInstrumentation.ScopeFactory.CreateScope(DiagnosticProperty.ProducerActivityName); scope.AddAttribute(DiagnosticProperty.KindAttribute, DiagnosticProperty.ClientKind); scope.AddAttribute(DiagnosticProperty.ServiceContextAttribute, DiagnosticProperty.EventHubsServiceContext); scope.AddAttribute(DiagnosticProperty.EventHubAttribute, EventHubName); scope.AddAttribute(DiagnosticProperty.EndpointAttribute, FullyQualifiedNamespace); if (scope.IsEnabled) { foreach (var identifier in diagnosticIdentifiers) { scope.AddLink(identifier); } } scope.Start(); return(scope); }
/// <summary> /// Starts running a task responsible for receiving and processing events in the context of a specified partition. /// </summary> /// /// <param name="partitionId">The identifier of the Event Hub partition the task is associated with. Events will be read only from this partition.</param> /// <param name="startingPosition">The position within the partition where the task should begin reading events.</param> /// <param name="maximumReceiveWaitTime">The maximum amount of time to wait to for an event to be available before emitting an empty item; if <c>null</c>, empty items will not be published.</param> /// <param name="retryOptions">The set of options to use for determining whether a failed operation should be retried and, if so, the amount of time to wait between retry attempts.</param> /// <param name="trackLastEnqueuedEventInformation">Indicates whether or not the task should request information on the last enqueued event on the partition associated with a given event, and track that information as events are received.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>The running task that is currently receiving and processing events in the context of the specified partition.</returns> /// protected virtual Task RunPartitionProcessingAsync(string partitionId, EventPosition startingPosition, TimeSpan?maximumReceiveWaitTime, RetryOptions retryOptions, bool trackLastEnqueuedEventInformation, CancellationToken cancellationToken = default) { // TODO: should the retry options used here be the same for the abstract RetryPolicy property? Argument.AssertNotNullOrEmpty(partitionId, nameof(partitionId)); Argument.AssertNotNull(retryOptions, nameof(retryOptions)); return(Task.Run(async() => { // TODO: should we double check if a previous run already exists and close it? We have a race condition. Maybe we should throw in case another task exists. var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var taskCancellationToken = cancellationSource.Token; ActivePartitionProcessorTokenSources[partitionId] = cancellationSource; // Context is set to default if operation fails. This shouldn't fail unless the user tries processing // a partition they don't own. PartitionContexts.TryGetValue(partitionId, out var context); var options = new EventHubConsumerClientOptions { RetryOptions = retryOptions, TrackLastEnqueuedEventInformation = trackLastEnqueuedEventInformation }; await using var connection = CreateConnection(); await using (var consumer = new EventHubConsumerClient(ConsumerGroup, connection, options)) { await foreach (var partitionEvent in consumer.ReadEventsFromPartitionAsync(partitionId, startingPosition, maximumReceiveWaitTime, taskCancellationToken)) { using DiagnosticScope diagnosticScope = EventDataInstrumentation.ClientDiagnostics.CreateScope(DiagnosticProperty.EventProcessorProcessingActivityName); diagnosticScope.AddAttribute("kind", "server"); if (diagnosticScope.IsEnabled && partitionEvent.Data != null && EventDataInstrumentation.TryExtractDiagnosticId(partitionEvent.Data, out string diagnosticId)) { diagnosticScope.AddLink(diagnosticId); } diagnosticScope.Start(); try { await ProcessEventAsync(partitionEvent, context).ConfigureAwait(false); } catch (Exception eventProcessingException) { diagnosticScope.Failed(eventProcessingException); throw; } } } })); }
/// <summary> /// The main loop of a partition pump. It receives events from the Azure Event Hubs service /// and delegates their processing to the event processor processing handlers. /// </summary> /// /// <param name="cancellationToken">A <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task RunAsync(CancellationToken cancellationToken) { List <EventData> receivedEvents; Exception unrecoverableException = null; // We'll break from the loop upon encountering a non-retriable exception. The event processor periodically // checks its pumps' status, so it should be aware of when one of them stops working. while (!cancellationToken.IsCancellationRequested) { try { receivedEvents = (await InnerConsumer.ReceiveAsync(MaximumMessageCount, Options.MaximumReceiveWaitTime, cancellationToken).ConfigureAwait(false)).ToList(); using DiagnosticScope diagnosticScope = EventDataInstrumentation.ClientDiagnostics.CreateScope(DiagnosticProperty.EventProcessorProcessingActivityName); diagnosticScope.AddAttribute("kind", "server"); if (diagnosticScope.IsEnabled) { foreach (var eventData in receivedEvents) { if (EventDataInstrumentation.TryExtractDiagnosticId(eventData, out string diagnosticId)) { diagnosticScope.AddLink(diagnosticId); } } } // Small workaround to make sure we call ProcessEvent with EventData = null when no events have been received. // The code is expected to get simpler when we start using the async enumerator internally to receive events. if (receivedEvents.Count == 0) { receivedEvents.Add(null); } diagnosticScope.Start(); foreach (var eventData in receivedEvents) { try { var processorEvent = new EventProcessorEvent(Context, eventData, UpdateCheckpointAsync); await ProcessEventAsync(processorEvent).ConfigureAwait(false); } catch (Exception eventProcessingException) { diagnosticScope.Failed(eventProcessingException); unrecoverableException = eventProcessingException; break; } } } catch (Exception eventHubException) { // Stop running only if it's not a retriable exception. if (RetryPolicy.CalculateRetryDelay(eventHubException, 1) == null) { throw eventHubException; } } if (unrecoverableException != null) { throw unrecoverableException; } } }
/// <summary> /// The main loop of a partition pump. It receives events from the Azure Event Hubs service /// and delegates their processing to the inner partition processor. /// </summary> /// /// <param name="cancellationToken">A <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task RunAsync(CancellationToken cancellationToken) { IEnumerable <EventData> receivedEvents; Exception unrecoverableException = null; // We'll break from the loop upon encountering a non-retriable exception. The event processor periodically // checks its pumps' status, so it should be aware of when one of them stops working. while (!cancellationToken.IsCancellationRequested) { try { receivedEvents = await InnerConsumer.ReceiveAsync(Options.MaximumMessageCount, Options.MaximumReceiveWaitTime, cancellationToken).ConfigureAwait(false); using DiagnosticScope diagnosticScope = EventDataInstrumentation.ClientDiagnostics.CreateScope(DiagnosticProperty.EventProcessorProcessingActivityName); diagnosticScope.AddAttribute("kind", "server"); if (diagnosticScope.IsEnabled) { foreach (var eventData in receivedEvents) { if (EventDataInstrumentation.TryExtractDiagnosticId(eventData, out string diagnosticId)) { diagnosticScope.AddLink(diagnosticId); } } } diagnosticScope.Start(); try { await PartitionProcessor.ProcessEventsAsync(Context, receivedEvents, cancellationToken).ConfigureAwait(false); } catch (Exception partitionProcessorException) { diagnosticScope.Failed(partitionProcessorException); unrecoverableException = partitionProcessorException; CloseReason = PartitionProcessorCloseReason.PartitionProcessorException; break; } } catch (Exception eventHubException) { // Stop running only if it's not a retriable exception. if (s_retryPolicy.CalculateRetryDelay(eventHubException, 1) == null) { unrecoverableException = eventHubException; CloseReason = PartitionProcessorCloseReason.EventHubException; break; } } } if (unrecoverableException != null) { // In case an exception is encountered while partition processor is processing the error, don't // catch it and let the calling method (StopAsync) handle it. await PartitionProcessor.ProcessErrorAsync(Context, unrecoverableException, cancellationToken).ConfigureAwait(false); } }