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);
            }
        }
Exemple #2
0
        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);
            }
        }
Exemple #4
0
        /// <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);
        }
Exemple #5
0
        /// <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));
        }
Exemple #6
0
        /// <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));
        }