private async Task OnMessageReceivedAsync <TEvent, TConsumer>(EventRegistration ereg, EventConsumerRegistration creg, ProcessMessageEventArgs args) where TEvent : class where TConsumer : IEventConsumer <TEvent> { var message = args.Message; var messageId = message.MessageId; var cancellationToken = args.CancellationToken; message.ApplicationProperties.TryGetValue(AttributeNames.ActivityId, out var parentActivityId); using var log_scope = Logger.BeginScopeForConsume(id: messageId, correlationId: message.CorrelationId, sequenceNumber: message.SequenceNumber, extras: new Dictionary <string, string> { ["EnqueuedSequenceNumber"] = message.EnqueuedSequenceNumber.ToString(), }); // Instrumentation using var activity = EventBusActivitySource.StartActivity(ActivityNames.Consume, ActivityKind.Consumer, parentActivityId?.ToString()); activity?.AddTag(ActivityTagNames.EventBusEventType, typeof(TEvent).FullName); activity?.AddTag(ActivityTagNames.EventBusConsumerType, typeof(TConsumer).FullName); activity?.AddTag(ActivityTagNames.MessagingSystem, Name); var destination = TransportOptions.UseBasicTier || ereg.UseQueueInsteadOfTopic() ? ereg.EventName : creg.ConsumerName; activity?.AddTag(ActivityTagNames.MessagingDestination, destination); // name of the queue/subscription activity?.AddTag(ActivityTagNames.MessagingDestinationKind, "queue"); // the spec does not know subscription so we can only use queue for both try { Logger.LogDebug("Processing '{MessageId}'", messageId); using var scope = CreateScope(); using var ms = message.Body.ToStream(); var contentType = new ContentType(message.ContentType); var context = await DeserializeAsync <TEvent>(body : ms, contentType : contentType, registration : ereg, scope : scope, cancellationToken : cancellationToken); Logger.LogInformation("Received message: '{MessageId}' containing Event '{Id}'", messageId, context.Id); await ConsumeAsync <TEvent, TConsumer>(@event : context, scope : scope, cancellationToken : cancellationToken); // Complete the message Logger.LogDebug("Completing message: {MessageId}.", messageId); await args.CompleteMessageAsync(message : message, cancellationToken : cancellationToken); } catch (Exception ex) { Logger.LogError(ex, "Event processing failed. Moving to deadletter."); await args.DeadLetterMessageAsync(message : message, cancellationToken : cancellationToken); } }
private async Task OnMessageReceivedAsync <TEvent, TConsumer>(EventRegistration ereg, EventConsumerRegistration creg, IModel channel, BasicDeliverEventArgs args, CancellationToken cancellationToken) where TEvent : class where TConsumer : IEventConsumer <TEvent> { var messageId = args.BasicProperties?.MessageId; using var log_scope = Logger.BeginScopeForConsume(id: messageId, correlationId: args.BasicProperties?.CorrelationId, extras: new Dictionary <string, string> { ["RoutingKey"] = args.RoutingKey, ["DeliveryTag"] = args.DeliveryTag.ToString(), }); args.BasicProperties.Headers.TryGetValue(AttributeNames.ActivityId, out var parentActivityId); // Instrumentation using var activity = EventBusActivitySource.StartActivity(ActivityNames.Consume, ActivityKind.Consumer, parentActivityId?.ToString()); activity?.AddTag(ActivityTagNames.EventBusEventType, typeof(TEvent).FullName); activity?.AddTag(ActivityTagNames.EventBusConsumerType, typeof(TConsumer).FullName); activity?.AddTag(ActivityTagNames.MessagingSystem, Name); activity?.AddTag(ActivityTagNames.MessagingDestination, creg.ConsumerName); activity?.AddTag(ActivityTagNames.MessagingDestinationKind, "queue"); // only queues are possible try { Logger.LogDebug("Processing '{MessageId}'", messageId); using var scope = CreateScope(); using var ms = new MemoryStream(args.Body.ToArray()); var contentType = GetContentType(args.BasicProperties); var context = await DeserializeAsync <TEvent>(body : ms, contentType : contentType, registration : ereg, scope : scope, cancellationToken : cancellationToken); Logger.LogInformation("Received message: '{MessageId}' containing Event '{Id}'", messageId, context.Id); await ConsumeAsync <TEvent, TConsumer>(@event : context, scope : scope, cancellationToken : cancellationToken); // Acknowlege the message Logger.LogDebug("Completing message: {MessageId}, {DeliveryTag}.", messageId, args.DeliveryTag); channel.BasicAck(deliveryTag: args.DeliveryTag, multiple: false); } catch (Exception ex) { Logger.LogError(ex, "Event processing failed. Moving to deadletter."); channel.BasicNack(deliveryTag: args.DeliveryTag, multiple: false, requeue: false); } }
private async Task OnEventReceivedAsync <TEvent, TConsumer>(EventRegistration reg, Message <string, byte[]> message, CancellationToken cancellationToken) where TEvent : class where TConsumer : IEventConsumer <TEvent> { var messageKey = message.Key; message.Headers.TryGetValue(AttributeNames.CorrelationId, out var correlationId); message.Headers.TryGetValue(AttributeNames.ContentType, out var contentType_str); message.Headers.TryGetValue(AttributeNames.ActivityId, out var parentActivityId); using var log_scope = Logger.BeginScopeForConsume(id: messageKey, correlationId: correlationId); // Instrumentation using var activity = EventBusActivitySource.StartActivity(ActivityNames.Consume, ActivityKind.Consumer, parentActivityId?.ToString()); activity?.AddTag(ActivityTagNames.EventBusEventType, typeof(TEvent).FullName); activity?.AddTag(ActivityTagNames.EventBusConsumerType, typeof(TConsumer).FullName); activity?.AddTag(ActivityTagNames.MessagingSystem, Name); try { Logger.LogDebug("Processing '{MessageKey}", messageKey); using var scope = CreateScope(); using var ms = new MemoryStream(message.Value); var contentType = contentType_str == null ? null : new ContentType(contentType_str.ToString()); var context = await DeserializeAsync <TEvent>(body : ms, contentType : contentType, registration : reg, scope : scope, cancellationToken : cancellationToken); Logger.LogInformation("Received event: '{MessageKey}' containing Event '{Id}'", messageKey, context.Id); await ConsumeAsync <TEvent, TConsumer>(@event : context, scope : scope, cancellationToken : cancellationToken); } catch (Exception ex) { Logger.LogError(ex, "Event processing failed. Moving to deadletter."); // produce message on deadletter topic var dlt = reg.EventName += TransportOptions.DeadLetterSuffix; await producer.ProduceAsync(topic : dlt, message : message, cancellationToken : cancellationToken); } }
/// <summary> /// Serialize an event into a stream of bytes. /// </summary> /// <typeparam name="TEvent">The event type to be serialized.</typeparam> /// <param name="body"> /// The stream to serialize to. /// (It must be writeable, i.e. <see cref="Stream.CanWrite"/> must be true). /// </param> /// <param name="event">The context of the event to be serialized.</param> /// <param name="registration">The bus registration for this event.</param> /// <param name="scope">The scope in which to resolve required services.</param> /// <param name="cancellationToken"></param> /// <returns></returns> protected async Task SerializeAsync <TEvent>(Stream body, EventContext <TEvent> @event, EventRegistration registration, IServiceScope scope, CancellationToken cancellationToken = default) where TEvent : class { // Instrumentation using var activity = EventBusActivitySource.StartActivity(ActivityNames.Serialize); activity?.AddTag(ActivityTagNames.EventBusEventType, typeof(TEvent).FullName); activity?.AddTag(ActivityTagNames.EventBusSerializerType, registration.EventSerializerType.FullName); // Get the serializer var serializer = (IEventSerializer)scope.ServiceProvider.GetRequiredService(registration.EventSerializerType); // do actual serialization and return the content type await serializer.SerializeAsync(body, @event, BusOptions.HostInfo, cancellationToken); }
/// <summary> /// Deserialize an event from a stream of bytes. /// </summary> /// <typeparam name="TEvent">The event type to be deserialized.</typeparam> /// <param name="body"> /// The <see cref="Stream"/> containing the raw data. /// (It must be readable, i.e. <see cref="Stream.CanRead"/> must be true). /// </param> /// <param name="contentType">The type of content contained in the <paramref name="body"/>.</param> /// <param name="registration">The bus registration for this event.</param> /// <param name="scope">The scope in which to resolve required services.</param> /// <param name="cancellationToken"></param> /// <returns></returns> protected async Task <EventContext <TEvent> > DeserializeAsync <TEvent>(Stream body, ContentType contentType, EventRegistration registration, IServiceScope scope, CancellationToken cancellationToken = default) where TEvent : class { // Instrumentation using var activity = EventBusActivitySource.StartActivity(ActivityNames.Deserialize); activity?.AddTag(ActivityTagNames.EventBusEventType, typeof(TEvent).FullName); activity?.AddTag(ActivityTagNames.EventBusSerializerType, registration.EventSerializerType.FullName); // Get the serializer var serializer = (IEventSerializer)scope.ServiceProvider.GetRequiredService(registration.EventSerializerType); // Deserialize the content into a context return(await serializer.DeserializeAsync <TEvent>(body, contentType, cancellationToken)); }
/// <summary> /// Publish a batch of events. /// </summary> /// <typeparam name="TEvent">The event type.</typeparam> /// <param name="events">The events to publish.</param> /// <param name="scheduled"> /// The time at which the event should be availed for consumption. /// Set null for immediate availability. /// </param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <IList <string> > PublishAsync <TEvent>(IList <EventContext <TEvent> > events, DateTimeOffset?scheduled = null, CancellationToken cancellationToken = default) where TEvent : class { if (scheduled != null && scheduled <= DateTimeOffset.UtcNow) { throw new ArgumentException("Scheduled time cannot be in the past."); } // Instrumentation using var activity = EventBusActivitySource.StartActivity(ActivityNames.Publish, ActivityKind.Producer); activity?.AddTag(ActivityTagNames.EventBusEventType, typeof(TEvent).FullName); foreach (var @event in events) { // Add diagnostics headers @event.Headers.AddIfNotDefault(HeaderNames.EventType, typeof(TEvent).FullName); @event.Headers.AddIfNotDefault(HeaderNames.ActivityId, Activity.Current?.Id); // Set properties that may be missing @event.Id ??= Guid.NewGuid().ToString(); @event.Sent ??= DateTimeOffset.UtcNow; } // Add message specific activity tags activity?.AddTag(ActivityTagNames.MessagingMessageId, string.Join(",", events.Select(e => e.Id))); activity?.AddTag(ActivityTagNames.MessagingConversationId, string.Join(",", events.Select(e => e.CorrelationId))); // Get the transport and add transport specific activity tags var(reg, transport) = GetTransportForEvent <TEvent>(); activity?.AddTag(ActivityTagNames.MessagingSystem, transport.Name); // Publish on the transport logger.LogInformation("Sending {EventsCount} events using '{TransportName}' transport. Scheduled: {Scheduled}. Events:\r\n- {Ids}", events.Count, transport.Name, scheduled, string.Join("\r\n- ", events.Select(e => e.Id))); return(await transport.PublishAsync(events : events, registration : reg, scheduled : scheduled, cancellationToken : cancellationToken)); }