Beispiel #1
0
        public async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            try
            {
                // TODO: Ensure always disposed (TEST IT!)
                var scope = context.ServiceProvider.CreateScope();

                context.ReplaceServiceScope(scope);
                context.TransactionManager = new ConsumerTransactionManager(
                    context,
                    context.ServiceProvider
                    .GetRequiredService <ISilverbackIntegrationLogger <ConsumerTransactionManager> >());

                await next(context).ConfigureAwait(false);

                if (context.Sequence == null)
                {
                    await context.TransactionManager.CommitAsync().ConfigureAwait(false);

                    context.Dispose();
                }
                else
                {
                    if (context.IsSequenceStart)
                    {
                        StartSequenceProcessingAwaiter(context);
                    }

                    await AwaitProcessedIfNecessaryAsync(context).ConfigureAwait(false);
                }
            }
            catch (Exception exception)
            {
                // Sequence errors are handled in AwaitSequenceProcessingAsync, just await the rollback and rethrow
                if (context.Sequence != null)
                {
                    await context.Sequence.AbortAsync(SequenceAbortReason.Error, exception).ConfigureAwait(false);

                    if (context.Sequence.Length > 0 && context.Sequence is ISequenceImplementation sequenceImpl)
                    {
                        _logger.LogTraceWithMessageInfo(
                            IntegrationEventIds.LowLevelTracing,
                            "Awaiting sequence processing completed before rethrowing.",
                            context);

                        await sequenceImpl.ProcessingCompletedTask.ConfigureAwait(false);
                    }

                    throw;
                }

                if (!await HandleExceptionAsync(context, exception).ConfigureAwait(false))
                {
                    throw;
                }
            }
        }
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            if (context.Envelope.Endpoint.Encryption != null && context.Envelope.RawMessage != null)
            {
                string?keyIdentifier = null;

                if (context.Envelope.Endpoint.Encryption is SymmetricDecryptionSettings settings &&
                    settings.KeyProvider != null)
                {
                    keyIdentifier =
                        context.Envelope.Headers.GetValue(DefaultMessageHeaders.EncryptionKeyId);
                }

                context.Envelope.RawMessage = _streamFactory.GetDecryptStream(
                    context.Envelope.RawMessage,
                    context.Envelope.Endpoint.Encryption,
                    keyIdentifier);
            }

            await next(context).ConfigureAwait(false);
        }
        /// <inheritdoc cref="SequencerConsumerBehaviorBase.PublishSequenceAsync" />
        protected override Task PublishSequenceAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(next, nameof(next));

            return(next(context));
        }
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            _integrationSpy.AddRawInboundEnvelope(context.Envelope);

            return(next(context));
        }
Beispiel #5
0
        public Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            lock (_inboundEnvelopes)
            {
                _inboundEnvelopes.Add((IInboundEnvelope)context.Envelope);
            }

            return(next(context));
        }
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            context.Envelope = await HandleAsync(context.Envelope).ConfigureAwait(false);

            await next(context).ConfigureAwait(false);
        }
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            if (!await CheckIsAlreadyProcessedAsync(context).ConfigureAwait(false))
            {
                await next(context).ConfigureAwait(false);
            }
        }
Beispiel #8
0
        /// <inheritdoc cref="SequencerConsumerBehaviorBase.PublishSequenceAsync" />
        protected override Task PublishSequenceAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            var processingTask = Task.Run(async() => await next(context).ConfigureAwait(false));

            context.ProcessingTask ??= processingTask;

            return(Task.CompletedTask);
        }
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            if (_mappings != null && _mappings.Count > 0)
            {
                _mappings.Revert(context.Envelope.Headers);
            }

            await next(context).ConfigureAwait(false);
        }
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            using var activity = ActivitySources.StartConsumeActivity(context.Envelope);

            _activityEnricherFactory.GetActivityEnricher(context.Envelope.Endpoint)
            .EnrichInboundActivity(activity, context);

            await next(context).ConfigureAwait(false);
        }
Beispiel #11
0
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            if (context.Envelope is IInboundEnvelope inboundEnvelope)
            {
                HeaderAttributeHelper.SetFromHeaders(
                    inboundEnvelope.Message,
                    inboundEnvelope.Headers);
            }

            await next(context).ConfigureAwait(false);
        }
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            if (context.Envelope.Endpoint.Encryption != null &&
                !context.Envelope.Headers.Contains(DefaultMessageHeaders.Decrypted) &&
                context.Envelope.RawMessage != null)
            {
                context.Envelope.RawMessage = _streamFactory.GetDecryptStream(
                    context.Envelope.RawMessage,
                    context.Envelope.Endpoint.Encryption);
            }

            await next(context).ConfigureAwait(false);
        }
Beispiel #13
0
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            var newEnvelope = await DeserializeAsync(context).ConfigureAwait(false);

            if (newEnvelope == null)
            {
                _logger.LogNullMessageSkipped(context.Envelope);
                return;
            }

            context.Envelope = newEnvelope;

            await next(context).ConfigureAwait(false);
        }
Beispiel #14
0
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            _logger.LogProcessing(context);

            if (context.Sequence != null)
            {
                if (context.Sequence is RawSequence)
                {
                    await PublishEnvelopeAsync(context, context.Envelope.Endpoint.ThrowIfUnhandled)
                    .ConfigureAwait(false);
                }
                else
                {
                    await PublishSequenceAsync(context.Sequence, context).ConfigureAwait(false);
                }
            }
            else
            {
                var throwIfUnhandled = context.Envelope.Endpoint.ThrowIfUnhandled;

                if (context.Envelope is IInboundEnvelope envelope)
                {
                    // TODO: Create only if necessary?
                    var unboundedSequence = await GetUnboundedSequence(context).ConfigureAwait(false);

                    int pushedStreamsCount =
                        await unboundedSequence !.AddAsync(envelope, null, false).ConfigureAwait(false);

                    if (unboundedSequence.IsAborted && unboundedSequence.AbortException != null)
                    {
                        throw unboundedSequence.AbortException; // TODO: Wrap into another exception?
                    }
                    throwIfUnhandled &= pushedStreamsCount == 0;
                }

                await PublishEnvelopeAsync(context, throwIfUnhandled).ConfigureAwait(false);
            }

            await next(context).ConfigureAwait(false);
        }
Beispiel #15
0
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            try
            {
                await next(context).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _logger.LogProcessingFatalError(context.Envelope, ex);

                throw new ConsumerPipelineFatalException(
                          "Fatal error occurred processing the consumed message.",
                          ex);
            }
        }
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            if (context.Sequence != null)
            {
                if (context.Sequence is RawSequence)
                {
                    await PublishEnvelopeAsync(context, context.Envelope.Endpoint.ThrowIfUnhandled)
                    .ConfigureAwait(false);
                }
                else
                {
                    await PublishSequenceAsync(context.Sequence, context).ConfigureAwait(false);
                }
            }
            else
            {
                var throwIfUnhandled = context.Envelope.Endpoint.ThrowIfUnhandled;

                if (HasAnyMessageStreamSubscriber(context) && context.Envelope is IInboundEnvelope envelope)
                {
                    var unboundedSequence = await GetUnboundedSequenceAsync(context).ConfigureAwait(false);

                    int pushedStreamsCount =
                        await unboundedSequence !.AddAsync(envelope, null, false).ConfigureAwait(false);

                    if (unboundedSequence.IsAborted && unboundedSequence.AbortException != null)
                    {
                        throw unboundedSequence.AbortException;
                    }

                    throwIfUnhandled &= pushedStreamsCount == 0;
                }

                await PublishEnvelopeAsync(context, throwIfUnhandled).ConfigureAwait(false);
            }

            await next(context).ConfigureAwait(false);
        }
        /// <inheritdoc cref="SequencerConsumerBehaviorBase.HandleAsync" />
        public override async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
        {
            var rawSequence = Check.NotNull(context, nameof(context)).Sequence as ISequenceImplementation;

            try
            {
                await base.HandleAsync(context, next).ConfigureAwait(false);

                // Abort all pending sequences if the current message doesn't belong to a sequence
                if (context.Sequence == null)
                {
                    await context.SequenceStore
                    .GetPendingSequences()
                    .ForEachAsync(sequence => sequence.AbortAsync(SequenceAbortReason.IncompleteSequence))
                    .ConfigureAwait(false);
                }
            }
            finally
            {
                rawSequence?.CompleteSequencerBehaviorsTask();
            }
        }
Beispiel #18
0
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            try
            {
                await next(context).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _logger.LogCriticalWithMessageInfo(
                    IntegrationEventIds.ConsumerFatalError,
                    ex,
                    "Fatal error occurred processing the consumed message. The consumer will be stopped.",
                    context);

                throw;
            }
        }
Beispiel #19
0
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            if (context.Envelope.Endpoint.MessageValidationMode != MessageValidationMode.None &&
                context.Envelope is IInboundEnvelope deserializeEnvelope &&
                deserializeEnvelope.Message != null)
            {
                (bool isValid, string?validationErrors) = MessageValidator.CheckMessageIsValid(
                    deserializeEnvelope.Message,
                    context.Envelope.Endpoint.MessageValidationMode);

                if (!isValid)
                {
                    _logger.LogInvalidMessageProcessed(validationErrors !);
                }
            }

            await next(context).ConfigureAwait(false);
        }
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public async Task HandleAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            var activity = new Activity(DiagnosticsConstants.ActivityNameMessageConsuming);

            try
            {
                TryInitActivity(
                    context,
                    activity,
                    context.ServiceProvider.GetService <ISilverbackLogger <ActivityConsumerBehavior> >());

                await next(context).ConfigureAwait(false);
            }
            finally
            {
                activity.Stop();
            }
        }
Beispiel #21
0
        private async Task <ISequence?> GetSequenceAsync(
            ConsumerPipelineContext context,
            ConsumerBehaviorHandler next,
            ISequenceReader sequenceReader)
        {
            var sequence = await sequenceReader.GetSequenceAsync(context).ConfigureAwait(false);

            if (sequence.IsComplete)
            {
                return(null);
            }

            if (sequence is IncompleteSequence incompleteSequence)
            {
                _logger.LogSkippingIncompleteSequence(incompleteSequence);
                return(null);
            }

            context.SetSequence(sequence, sequence.IsNew);

            if (sequence.IsNew)
            {
                StartActivityIfNeeded(sequence);

                await PublishSequenceAsync(context, next).ConfigureAwait(false);

                if (context.ProcessingTask != null)
                {
                    MonitorProcessingTaskPrematureCompletion(context.ProcessingTask, sequence);
                }

                _logger.LogSequenceStarted(sequence);
            }

            return(sequence);
        }
Beispiel #22
0
 public Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next) => next(context);
Beispiel #23
0
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public virtual async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            var sequenceReader = await _sequenceReaders
                                 .FirstOrDefaultAsync(reader => reader.CanHandleAsync(context))
                                 .ConfigureAwait(false);

            if (sequenceReader == null)
            {
                await next(context).ConfigureAwait(false);

                return;
            }

            // Store the original envelope in case it is replaced in the GetSequence method (see ChunkSequenceReader)
            var originalEnvelope = context.Envelope;

            // Store the previous sequence since it must be added to the new one (e.g. ChunkSequence into BatchSequence)
            var previousSequence = context.Sequence;

            ISequence?sequence;

            // Loop to handle edge cases where the sequence gets completed between the calls to
            // GetSequenceAsync and AddAsync
            while (true)
            {
                sequence = await GetSequenceAsync(context, next, sequenceReader).ConfigureAwait(false);

                if (sequence == null)
                {
                    return;
                }

                if (!sequence.IsPending || sequence.IsCompleting)
                {
                    continue;
                }

                await sequence.AddAsync(originalEnvelope, previousSequence).ConfigureAwait(false);

                AddSequenceTagToActivity(sequence);

                break;
            }

            _logger.LogMessageAddedToSequence(context.Envelope, sequence);

            if (sequence.IsComplete)
            {
                await AwaitOtherBehaviorIfNeededAsync(sequence).ConfigureAwait(false);

                // Mark the envelope as the end of the sequence only if the sequence wasn't swapped (e.g. chunk -> batch)
                if (sequence.Context.Sequence == null || sequence == sequence.Context.Sequence ||
                    sequence.Context.Sequence.IsCompleting || sequence.Context.Sequence.IsComplete)
                {
                    context.SetIsSequenceEnd();
                }

                _logger.LogSequenceCompleted(sequence);
            }
        }
Beispiel #24
0
 /// <summary>
 ///     Forwards the new sequence to the next behavior in the pipeline.
 /// </summary>
 /// <param name="context">
 ///     The context that is passed along the behaviors pipeline.
 /// </param>
 /// <param name="next">
 ///     The next behavior in the pipeline.
 /// </param>
 /// <returns>
 ///     A <see cref="Task" /> representing the asynchronous operation.
 /// </returns>
 protected abstract Task PublishSequenceAsync(
     ConsumerPipelineContext context,
     ConsumerBehaviorHandler next);
        public async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            try
            {
                var scope = context.ServiceProvider.CreateScope();

                context.ReplaceServiceScope(scope);
                context.TransactionManager = new ConsumerTransactionManager(
                    context,
                    context.ServiceProvider
                    .GetRequiredService <IInboundLogger <ConsumerTransactionManager> >());

                _logger.LogProcessing(context.Envelope);

                await next(context).ConfigureAwait(false);

                if (context.Sequence == null)
                {
                    await context.TransactionManager.CommitAsync().ConfigureAwait(false);

                    context.Dispose();
                }
                else
                {
                    if (context.IsSequenceStart)
                    {
                        StartSequenceProcessingAwaiter(context);
                    }

                    await AwaitProcessedIfNecessaryAsync(context).ConfigureAwait(false);
                }
            }
            catch (Exception exception)
            {
                // Sequence errors are handled in AwaitSequenceProcessingAsync, just await the rollback and
                // rethrow (-> if the exception bubbled up till this point, it's because it failed to be
                // handled and it's safer to stop the consumer)
                if (context.Sequence != null)
                {
                    await context.Sequence.AbortAsync(SequenceAbortReason.Error, exception)
                    .ConfigureAwait(false);

                    if (context.Sequence.Length > 0)
                    {
                        _logger.LogInboundLowLevelTrace(
                            "Awaiting sequence processing completed before rethrowing.",
                            context.Envelope);

                        await context.Sequence.AwaitProcessingAsync(false).ConfigureAwait(false);
                    }

                    throw;
                }

                if (!await HandleExceptionAsync(context, exception).ConfigureAwait(false))
                {
                    throw;
                }
            }
        }
        public async Task Handle(ConsumerPipelineContext context, IServiceProvider serviceProvider, ConsumerBehaviorHandler next)
        {
            var envelope = context.Envelopes.Single();

            var operationName = $"Consuming Message on topic {envelope.Endpoint.Name}";

            ISpanBuilder spanBuilder;

            try
            {
                var headers       = envelope.Headers.ToDictionary(pair => pair.Key, pair => pair.Value.ToString());
                var parentSpanCtx = GlobalTracer.Instance.Extract(BuiltinFormats.TextMap, new TextMapExtractAdapter(headers));

                spanBuilder = GlobalTracer.Instance.BuildSpan(operationName);

                if (parentSpanCtx != null)
                {
                    spanBuilder = spanBuilder.AsChildOf(parentSpanCtx);
                }
            }
            catch
            {
                spanBuilder = GlobalTracer.Instance.BuildSpan(operationName);
            }

            spanBuilder
            .WithTag(Tags.SpanKind, Tags.SpanKindConsumer)
            .WithTag("endpoint", envelope.Endpoint.Name);

            using (spanBuilder.StartActive())
            {
                await next(context, serviceProvider);
            }
        }
Beispiel #27
0
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public virtual async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            var sequenceReader = await _sequenceReaders
                                 .FirstOrDefaultAsync(reader => reader.CanHandleAsync(context))
                                 .ConfigureAwait(false);

            if (sequenceReader == null)
            {
                await next(context).ConfigureAwait(false);

                return;
            }

            // Store the original envelope in case it is replaced in the GetSequence method (see ChunkSequenceReader)
            var originalEnvelope = context.Envelope;

            // Store the previous sequence since it must be added to the new one (e.g. ChunkSequence into BatchSequence)
            var previousSequence = context.Sequence;

            ISequence?sequence;

            // Loop to handle edge cases where the sequence gets completed between the calls to
            // GetSequenceAsync and AddAsync
            while (true)
            {
                sequence = await GetSequenceAsync(context, next, sequenceReader).ConfigureAwait(false);

                if (sequence == null)
                {
                    return;
                }

                // Loop again if the retrieved sequence has completed already in the meanwhile
                // ...unless it was a new sequence, in which case it can only mean that an error
                // occurred in the subscriber before consuming the actual first message and it doesn't
                // make sense to recreate and publish once again the sequence.
                if (!sequence.IsPending || sequence.IsCompleting)
                {
                    if (sequence.IsNew)
                    {
                        break;
                    }

                    continue;
                }

                await sequence.AddAsync(originalEnvelope, previousSequence).ConfigureAwait(false);

                _logger.LogMessageAddedToSequence(context.Envelope, sequence);

                AddSequenceTagToActivity(sequence);

                break;
            }

            if (sequence.IsComplete)
            {
                await AwaitOtherBehaviorIfNeededAsync(sequence).ConfigureAwait(false);

                // Mark the envelope as the end of the sequence only if the sequence wasn't swapped (e.g. chunk -> batch)
                if (sequence.Context.Sequence == null || sequence == sequence.Context.Sequence ||
                    sequence.Context.Sequence.IsCompleting || sequence.Context.Sequence.IsComplete)
                {
                    context.SetIsSequenceEnd();
                }

                _logger.LogSequenceCompleted(sequence);
            }
        }
Beispiel #28
0
        /// <inheritdoc cref="IConsumerBehavior.HandleAsync" />
        public virtual async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
        {
            Check.NotNull(context, nameof(context));
            Check.NotNull(next, nameof(next));

            var sequenceReader = await _sequenceReaders.FirstOrDefaultAsync(reader => reader.CanHandleAsync(context)).ConfigureAwait(false);

            if (sequenceReader == null)
            {
                await next(context).ConfigureAwait(false);

                return;
            }

            // Store the original envelope in case it is replaced in the GetSequence method (see ChunkSequenceReader)
            var originalEnvelope = context.Envelope;

            // Store the previous sequence since it must be added to the new one (e.g. ChunkSequence into BatchSequence)
            var previousSequence = context.Sequence;

            var sequence = await sequenceReader.GetSequenceAsync(context).ConfigureAwait(false);

            if (sequence == null)
            {
                _logger.LogWarningWithMessageInfo(
                    IntegrationEventIds.IncompleteSequenceDiscarded,
                    "The incomplete sequence is ignored (probably missing the first message).",
                    context);

                return;
            }

            context.SetSequence(sequence, sequence.IsNew);

            if (sequence.IsNew)
            {
                await PublishSequenceAsync(context, next).ConfigureAwait(false);

                if (context.ProcessingTask != null)
                {
                    MonitorProcessingTaskPrematureCompletion(context.ProcessingTask, sequence);
                }

                _logger.LogDebugWithMessageInfo(
                    IntegrationEventIds.SequenceStarted,
                    $"Started new {sequence.GetType().Name}.",
                    context);
            }

            await sequence.AddAsync(originalEnvelope, previousSequence).ConfigureAwait(false);

            _logger.LogDebugWithMessageInfo(
                IntegrationEventIds.MessageAddedToSequence,
                $"Message added to {sequence.GetType().Name}.",
                context);

            if (sequence.IsComplete)
            {
                await AwaitOtherBehaviorIfNeededAsync(sequence).ConfigureAwait(false);

                // Mark the envelope as the end of the sequence only if the sequence wasn't swapped (e.g. chunk -> batch)
                if (sequence.Context.Sequence == null || sequence == sequence.Context.Sequence ||
                    sequence.Context.Sequence.IsCompleting || sequence.Context.Sequence.IsComplete)
                {
                    context.SetIsSequenceEnd();
                }

                _logger.LogDebugWithMessageInfo(
                    IntegrationEventIds.SequenceCompleted,
                    $"{sequence.GetType().Name} '{sequence.SequenceId}' completed.",
                    context);
            }
        }