diff --git a/samples/WebAPI/WebAPI_Dapper/GreetingsWeb/Greetings.db b/samples/WebAPI/WebAPI_Dapper/GreetingsWeb/Greetings.db index 7cdbf2b439..20798f4364 100644 Binary files a/samples/WebAPI/WebAPI_Dapper/GreetingsWeb/Greetings.db and b/samples/WebAPI/WebAPI_Dapper/GreetingsWeb/Greetings.db differ diff --git a/samples/WebAPI/WebAPI_Dapper/GreetingsWeb/Startup.cs b/samples/WebAPI/WebAPI_Dapper/GreetingsWeb/Startup.cs index cf330a0e25..9039fcaa06 100644 --- a/samples/WebAPI/WebAPI_Dapper/GreetingsWeb/Startup.cs +++ b/samples/WebAPI/WebAPI_Dapper/GreetingsWeb/Startup.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using DbMaker; using GreetingsApp.Handlers; using GreetingsApp.Policies; @@ -9,20 +8,20 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; -using OpenTelemetry; using OpenTelemetry.Exporter; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using Paramore.Brighter; using Paramore.Brighter.Extensions.DependencyInjection; -using Paramore.Brighter.Observability; +using Paramore.Brighter.Extensions.Diagnostics; using Paramore.Darker.AspNetCore; using Paramore.Darker.Policies; using Paramore.Darker.QueryLogging; using TransportMaker; -using ExportProcessorType = OpenTelemetry.ExportProcessorType; namespace GreetingsWeb; @@ -132,8 +131,20 @@ private void ConfigureDarker(IServiceCollection services) private void ConfigureObservability(IServiceCollection services) { - var brighterTracer = new BrighterTracer(TimeProvider.System); - services.AddSingleton(brighterTracer); + services.AddLogging(loggingBuilder => + { + loggingBuilder.AddConsole(); + loggingBuilder.AddOpenTelemetry(options => + { + options.IncludeScopes = true; + options.AddOtlpExporter((exporterOptions, _) => + { + exporterOptions.Protocol = OtlpExportProtocol.Grpc; + }) + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("GreetingsWeb")) + .IncludeScopes = true; + }); + }); services.AddOpenTelemetry() .ConfigureResource(builder => @@ -145,7 +156,7 @@ private void ConfigureObservability(IServiceCollection services) }).WithTracing(builder => { builder - .AddSource(brighterTracer.ActivitySource.Name) + .AddBrighterInstrumentation() .AddSource("RabbitMQ.Client.*") .SetSampler(new AlwaysOnSampler()) .AddAspNetCoreInstrumentation() @@ -161,4 +172,6 @@ private void ConfigureObservability(IServiceCollection services) .AddOtlpExporter() ); } + + } diff --git a/samples/WebAPI/WebAPI_Dapper/Greetings_Sweeper/Program.cs b/samples/WebAPI/WebAPI_Dapper/Greetings_Sweeper/Program.cs index e0f4cac93b..0df8fbab99 100644 --- a/samples/WebAPI/WebAPI_Dapper/Greetings_Sweeper/Program.cs +++ b/samples/WebAPI/WebAPI_Dapper/Greetings_Sweeper/Program.cs @@ -4,6 +4,7 @@ using GreetingsApp.Requests; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using OpenTelemetry.Exporter; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; @@ -21,7 +22,17 @@ builder.Services.AddSingleton(brighterTracer); builder.Logging.ClearProviders(); -builder.Logging.AddConsole().AddDebug(); +builder.Logging.AddConsole(); +builder.Logging.AddOpenTelemetry(otel => +{ + otel.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Greetings Sweeper")) + .AddOtlpExporter(options => + { + options.Protocol = OtlpExportProtocol.Grpc; + }) + .IncludeScopes = true; +}); + builder.Services.AddOpenTelemetry() .ConfigureResource(builder => { diff --git a/samples/WebAPI/WebAPI_Dapper/README.md b/samples/WebAPI/WebAPI_Dapper/README.md index 9815ef573e..5f2293808d 100644 --- a/samples/WebAPI/WebAPI_Dapper/README.md +++ b/samples/WebAPI/WebAPI_Dapper/README.md @@ -40,9 +40,13 @@ We also add an Inbox here. The Inbox can be used to de-duplicate messages. In me ## Telemetry -The apps use OpenTelemetry to provide observability. This is configured in the `Program.cs` or `Startup.cs` file. Docker files are supported for an Open Telemetry Collector that exports telemetry to Jaeger and metrics to Prometheus. There is a supported config file for the Open Telemetry collector at the root. +The apps use OpenTelemetry to provide observability. This is configured in the `Program.cs` or `Startup.cs` file. -You can view the telemetry in the [Jaeger UI](http://localhost:16686) +Docker files are supported for an Open Telemetry Collector that exports telemetry to Jaeger and metrics to Prometheus. There is a supported config file for the Open Telemetry collector at the root. You can view the telemetry in the [Jaeger UI](http://localhost:16686) + +Alternatively you can use [.NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/overview?tabs=bash) dashbaord to view the telemetry, which removes the need for running the collector and Jaeger. You can run it using Docker with: + +`docker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard -e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS='true' mcr.microsoft.com/dotnet/aspire-dashboard:8.0.0` ### Configuration diff --git a/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/Program.cs b/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/Program.cs index 2001d39cdc..ddd65405ed 100644 --- a/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/Program.cs +++ b/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/Program.cs @@ -7,11 +7,13 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using OpenTelemetry.Exporter; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using Paramore.Brighter; using Paramore.Brighter.Extensions.DependencyInjection; +using Paramore.Brighter.Extensions.Diagnostics; using Paramore.Brighter.MsSql; using Paramore.Brighter.MySql; using Paramore.Brighter.Observability; @@ -139,8 +141,20 @@ static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCollection static void ConfigureObservability(IServiceCollection services) { - var brighterTracer = new BrighterTracer(TimeProvider.System); - services.AddSingleton(brighterTracer); + services.AddLogging(loggingBuilder => + { + loggingBuilder.AddConsole(); + loggingBuilder.AddOpenTelemetry(options => + { + options.IncludeScopes = true; + options.AddOtlpExporter((exporterOptions, processorOptions) => + { + exporterOptions.Protocol = OtlpExportProtocol.Grpc; + }) + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Salutation Analytics")) + .IncludeScopes = true; + }); + }); services.AddOpenTelemetry() .ConfigureResource(builder => @@ -152,7 +166,7 @@ static void ConfigureObservability(IServiceCollection services) }).WithTracing(builder => { builder - .AddSource(brighterTracer.ActivitySource.Name) + .AddBrighterInstrumentation() .AddSource("RabbitMQ.Client.*") .SetSampler(new AlwaysOnSampler()) .AddAspNetCoreInstrumentation() diff --git a/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json b/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json index 639defffb1..75fbb37fc9 100644 --- a/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json +++ b/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json @@ -3,6 +3,9 @@ "profiles": { "Development": { "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "BRIGHTER_GREETINGS_DATABASE": "Sqlite", diff --git a/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj b/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj index 9f3df19a33..c6bb2647a4 100644 --- a/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj +++ b/samples/WebAPI/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj @@ -7,6 +7,7 @@ + diff --git a/samples/WebAPI/WebAPI_Dapper/Salutation_Sweeper/Program.cs b/samples/WebAPI/WebAPI_Dapper/Salutation_Sweeper/Program.cs index 48f3baff5b..4f7e7843ce 100644 --- a/samples/WebAPI/WebAPI_Dapper/Salutation_Sweeper/Program.cs +++ b/samples/WebAPI/WebAPI_Dapper/Salutation_Sweeper/Program.cs @@ -4,6 +4,7 @@ using SalutationApp.Requests; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using OpenTelemetry.Exporter; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; @@ -20,7 +21,16 @@ builder.Services.AddSingleton(brighterTracer); builder.Logging.ClearProviders(); -builder.Logging.AddConsole().AddDebug(); +builder.Logging.AddConsole(); +builder.Logging.AddOpenTelemetry(otel => +{ + otel.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Greetings Sweeper")) + .AddOtlpExporter(options => + { + options.Protocol = OtlpExportProtocol.Grpc; + }) + .IncludeScopes = true; +}); builder.Services.AddOpenTelemetry() .ConfigureResource(builder => { diff --git a/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionExtensions.cs index 0b2f1498e0..00db1d6bfb 100644 --- a/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionExtensions.cs @@ -63,7 +63,6 @@ public static IBrighterBuilder AddBrighter( var options = new BrighterOptions(); configure?.Invoke(options); services.TryAddSingleton(options); - services.TryAddSingleton(new BrighterTracer()); return BrighterHandlerBuilder(services, options); } diff --git a/src/Paramore.Brighter.Extensions.Diagnostics/BrighterTracerBuilderExtensions.cs b/src/Paramore.Brighter.Extensions.Diagnostics/BrighterTracerBuilderExtensions.cs index ed286150c4..a06471a99f 100644 --- a/src/Paramore.Brighter.Extensions.Diagnostics/BrighterTracerBuilderExtensions.cs +++ b/src/Paramore.Brighter.Extensions.Diagnostics/BrighterTracerBuilderExtensions.cs @@ -1,4 +1,7 @@ -using OpenTelemetry.Trace; +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using OpenTelemetry.Trace; using Paramore.Brighter.Observability; namespace Paramore.Brighter.Extensions.Diagnostics; @@ -6,5 +9,17 @@ namespace Paramore.Brighter.Extensions.Diagnostics; public static class BrighterTracerBuilderExtensions { public static TracerProviderBuilder AddBrighterInstrumentation(this TracerProviderBuilder builder) - => builder.AddSource("Paramore.Brighter"); + { + builder.ConfigureServices(services => + { + var brighterTracer = new BrighterTracer(TimeProvider.System); + services.TryAddSingleton(brighterTracer); + + + builder.AddSource(brighterTracer.ActivitySource.Name); + }); + + return builder; + } + } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index 9386cfc3d7..6e30df5829 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -85,7 +85,8 @@ public IAmAChannel CreateChannel(Subscription subscription) EnsureQueue(); return new Channel( - subscription.ChannelName.ToValidSQSQueueName(), + subscription.ChannelName.ToValidSQSQueueName(), + subscription.RoutingKey.ToValidSNSTopicName(), _messageConsumerFactory.Create(subscription), subscription.BufferSize ); diff --git a/src/Paramore.Brighter.MessagingGateway.AzureServiceBus/AzureServiceBusChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AzureServiceBus/AzureServiceBusChannelFactory.cs index 902b1f024e..f09f5dfd20 100644 --- a/src/Paramore.Brighter.MessagingGateway.AzureServiceBus/AzureServiceBusChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AzureServiceBus/AzureServiceBusChannelFactory.cs @@ -39,7 +39,8 @@ public IAmAChannel CreateChannel(Subscription subscription) _azureServiceBusConsumerFactory.Create(azureServiceBusSubscription); return new Channel( - channelName: subscription.ChannelName, + channelName: subscription.ChannelName, + routingKey: subscription.RoutingKey, messageConsumer: messageConsumer, maxQueueLength: subscription.BufferSize ); diff --git a/src/Paramore.Brighter.MessagingGateway.Kafka/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.Kafka/ChannelFactory.cs index 2fcba311db..299950fd36 100644 --- a/src/Paramore.Brighter.MessagingGateway.Kafka/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.Kafka/ChannelFactory.cs @@ -52,6 +52,7 @@ public IAmAChannel CreateChannel(Subscription subscription) return new Channel( subscription.ChannelName, + subscription.RoutingKey, _kafkaMessageConsumerFactory.Create(subscription), subscription.BufferSize); } diff --git a/src/Paramore.Brighter.MessagingGateway.MsSql/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.MsSql/ChannelFactory.cs index ecee9924f1..ada4a3cee9 100644 --- a/src/Paramore.Brighter.MessagingGateway.MsSql/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.MsSql/ChannelFactory.cs @@ -32,7 +32,8 @@ public IAmAChannel CreateChannel(Subscription subscription) s_logger.LogDebug("MsSqlInputChannelFactory: create input channel {ChannelName} for topic {Topic}", subscription.ChannelName, subscription.RoutingKey); return new Channel( - subscription.ChannelName, + subscription.ChannelName, + subscription.RoutingKey, _msSqlMessageConsumerFactory.Create(subscription), subscription.BufferSize); } diff --git a/src/Paramore.Brighter.MessagingGateway.RMQ/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.RMQ/ChannelFactory.cs index 9c3159801f..cf05e38ac1 100644 --- a/src/Paramore.Brighter.MessagingGateway.RMQ/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.RMQ/ChannelFactory.cs @@ -57,9 +57,10 @@ public IAmAChannel CreateChannel(Subscription subscription) var messageConsumer = _messageConsumerFactory.Create(rmqSubscription); return new Channel( - channelName:subscription.ChannelName, - messageConsumer:messageConsumer, - maxQueueLength:subscription.BufferSize + channelName: subscription.ChannelName, + routingKey: subscription.RoutingKey, + messageConsumer: messageConsumer, + maxQueueLength: subscription.BufferSize ); } } diff --git a/src/Paramore.Brighter.MessagingGateway.Redis/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.Redis/ChannelFactory.cs index dca6cc1361..7decc5d595 100644 --- a/src/Paramore.Brighter.MessagingGateway.Redis/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.Redis/ChannelFactory.cs @@ -54,6 +54,7 @@ public IAmAChannel CreateChannel(Subscription subscription) return new Channel( subscription.ChannelName, + subscription.RoutingKey, _messageConsumerFactory.Create(subscription), subscription.BufferSize ); diff --git a/src/Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs index 1333bca527..66b9b56363 100644 --- a/src/Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection/ServiceCollectionExtensions.cs @@ -34,7 +34,6 @@ public static IBrighterBuilder AddServiceActivator( configure?.Invoke(options); services.TryAddSingleton(options); services.TryAddSingleton(options); - services.TryAddSingleton(); services.TryAdd(new ServiceDescriptor(typeof(IDispatcher), (serviceProvider) => (IDispatcher)BuildDispatcher(serviceProvider), @@ -70,17 +69,21 @@ private static Dispatcher BuildDispatcher(IServiceProvider serviceProvider) var requestContextFactory = serviceProvider.GetService(); var dispatcherBuilder = DispatchBuilder - .With() + .StartNew() .CommandProcessorFactory(providerFactory, requestContextFactory); var messageMapperRegistry = ServiceCollectionExtensions.MessageMapperRegistry(serviceProvider); var messageTransformFactory = ServiceCollectionExtensions.TransformFactory(serviceProvider); var messageTransformFactoryAsync = ServiceCollectionExtensions.TransformFactoryAsync(serviceProvider); + var tracer = serviceProvider.GetService(); + return dispatcherBuilder .MessageMappers(messageMapperRegistry, messageMapperRegistry, messageTransformFactory, messageTransformFactoryAsync) .ChannelFactory(options.DefaultChannelFactory) - .Subscriptions(options.Subscriptions).Build(); + .Subscriptions(options.Subscriptions) + .ConfigureInstrumentation(tracer, options.InstrumentationOptions) + .Build(); } } diff --git a/src/Paramore.Brighter.ServiceActivator.Extensions.Diagnostics/HealthChecks/BrighterServiceActivatorHealthCheck.cs b/src/Paramore.Brighter.ServiceActivator.Extensions.Diagnostics/HealthChecks/BrighterServiceActivatorHealthCheck.cs index 56a56c15c9..25ad7f9283 100644 --- a/src/Paramore.Brighter.ServiceActivator.Extensions.Diagnostics/HealthChecks/BrighterServiceActivatorHealthCheck.cs +++ b/src/Paramore.Brighter.ServiceActivator.Extensions.Diagnostics/HealthChecks/BrighterServiceActivatorHealthCheck.cs @@ -17,7 +17,7 @@ public BrighterServiceActivatorHealthCheck(IDispatcher dispatcher) public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new()) { - var expectedConsumers = ((Dispatcher)_dispatcher).Connections.Sum(c => c.NoOfPerformers); + var expectedConsumers = ((Dispatcher)_dispatcher).Subscriptions.Sum(c => c.NoOfPerformers); var activeConsumers = _dispatcher.Consumers.Count(); if (expectedConsumers != activeConsumers) @@ -33,12 +33,12 @@ public BrighterServiceActivatorHealthCheck(IDispatcher dispatcher) private string GenerateUnhealthyMessage() { - var config = ((Dispatcher)_dispatcher).Connections; + var config = ((Dispatcher)_dispatcher).Subscriptions; var unhealthyHosts = new List(); foreach (var cfg in config) { - var sub = _dispatcher.Consumers.Where(c => c.SubscriptionName == cfg.Name).ToArray(); + var sub = _dispatcher.Consumers.Where(c => c.Subscription.Name == cfg.Name).ToArray(); if (sub.Length != cfg?.NoOfPerformers) unhealthyHosts.Add($"{cfg.Name} has {sub.Count()} of {cfg.NoOfPerformers} expected consumers"); } diff --git a/src/Paramore.Brighter.ServiceActivator/BrighterSynchronizationContext.cs b/src/Paramore.Brighter.ServiceActivator/BrighterSynchronizationContext.cs index 3b46845904..0f00b67048 100644 --- a/src/Paramore.Brighter.ServiceActivator/BrighterSynchronizationContext.cs +++ b/src/Paramore.Brighter.ServiceActivator/BrighterSynchronizationContext.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; -using System.Threading.Tasks; //Based on https://devblogs.microsoft.com/pfxteam/await-synchronizationcontext-and-console-apps/ @@ -10,9 +9,8 @@ namespace Paramore.Brighter.ServiceActivator { internal class BrighterSynchronizationContext : SynchronizationContext { - private readonly BlockingCollection> _queue = new BlockingCollection>(); - private readonly Thread _thread = Thread.CurrentThread; - private int _operationCount = 0; + private readonly BlockingCollection> _queue = new(); + private int _operationCount; /// /// When we have completed the operations, we can exit @@ -24,7 +22,7 @@ public override void OperationCompleted() } /// - /// Tracks the number of ongoing operations so we know when 'done' + /// Tracks the number of ongoing operations, so we know when 'done' /// public override void OperationStarted() { @@ -36,7 +34,7 @@ public override void OperationStarted() /// The object passed to the delegate. public override void Post(SendOrPostCallback d, object state) { - if (d == null) throw new ArgumentNullException("d"); + if (d == null) throw new ArgumentNullException(nameof(d)); _queue.Add(new KeyValuePair(d, state)); } @@ -46,7 +44,7 @@ public override void Send(SendOrPostCallback d, object state) throw new NotSupportedException("Synchronously sending is not supported."); } - /// Runs an loop to process all queued work items. + /// Runs a loop to process all queued work items. public void RunOnCurrentThread() { foreach (var workItem in _queue.GetConsumingEnumerable()) @@ -54,6 +52,6 @@ public void RunOnCurrentThread() } /// Notifies the context that no more work will arrive. - public void Complete() { _queue.CompleteAdding(); } + private void Complete() { _queue.CompleteAdding(); } } } diff --git a/src/Paramore.Brighter.ServiceActivator/Consumer.cs b/src/Paramore.Brighter.ServiceActivator/Consumer.cs index 47cb467857..776f4f43e9 100644 --- a/src/Paramore.Brighter.ServiceActivator/Consumer.cs +++ b/src/Paramore.Brighter.ServiceActivator/Consumer.cs @@ -59,7 +59,7 @@ public class Consumer : IAmAConsumer, IEquatable /// The name. public ConsumerName Name { get; } - public SubscriptionName SubscriptionName { get; set; } + public Subscription Subscription { get; set; } /// /// Gets the performer. /// @@ -83,13 +83,13 @@ public class Consumer : IAmAConsumer, IEquatable /// Initializes a new instance of the class. /// /// The name. - /// The name of the associated subscription. + /// /// The channel. /// The message pump. - public Consumer(ConsumerName name, SubscriptionName subscriptionName, IAmAChannel channel, IAmAMessagePump messagePump) + public Consumer(ConsumerName name, Subscription subscription, IAmAChannel channel, IAmAMessagePump messagePump) { Name = name; - SubscriptionName = subscriptionName; + Subscription = subscription; Performer = new Performer(channel, messagePump); State = ConsumerState.Shut; } @@ -107,11 +107,12 @@ public void Open() /// /// Shuts the task, which will not receive messages. /// - public void Shut() + /// The topic we post the quit message to, in order to shut the perfomer + public void Shut(RoutingKey topic) { if (State == ConsumerState.Open) { - Performer.Stop(); + Performer.Stop(topic); State = ConsumerState.Shut; } } diff --git a/src/Paramore.Brighter.ServiceActivator/ConsumerFactory.cs b/src/Paramore.Brighter.ServiceActivator/ConsumerFactory.cs index 463a2a1433..661672f3d9 100644 --- a/src/Paramore.Brighter.ServiceActivator/ConsumerFactory.cs +++ b/src/Paramore.Brighter.ServiceActivator/ConsumerFactory.cs @@ -23,6 +23,7 @@ THE SOFTWARE. */ #endregion using System; +using Paramore.Brighter.Observability; namespace Paramore.Brighter.ServiceActivator { @@ -33,6 +34,8 @@ internal class ConsumerFactory : IConsumerFactory where TRequest : cla private readonly Subscription _subscription; private readonly IAmAMessageTransformerFactory _messageTransformerFactory; private readonly IAmARequestContextFactory _requestContextFactory; + private readonly IAmABrighterTracer _tracer; + private readonly InstrumentationOptions _instrumentationOptions; private readonly ConsumerName _consumerName; private readonly IAmAMessageMapperRegistryAsync _messageMapperRegistryAsync; private readonly IAmAMessageTransformerFactoryAsync _messageTransformerFactoryAsync; @@ -42,13 +45,17 @@ public ConsumerFactory( Subscription subscription, IAmAMessageMapperRegistry messageMapperRegistry, IAmAMessageTransformerFactory messageTransformerFactory, - IAmARequestContextFactory requestContextFactory) + IAmARequestContextFactory requestContextFactory, + IAmABrighterTracer tracer, + InstrumentationOptions instrumentationOptions = InstrumentationOptions.All) { _commandProcessorProvider = commandProcessorProvider; _messageMapperRegistry = messageMapperRegistry; _subscription = subscription; _messageTransformerFactory = messageTransformerFactory; _requestContextFactory = requestContextFactory; + _tracer = tracer; + _instrumentationOptions = instrumentationOptions; _consumerName = new ConsumerName($"{_subscription.Name}-{Guid.NewGuid()}"); } @@ -57,13 +64,17 @@ public ConsumerFactory( Subscription subscription, IAmAMessageMapperRegistryAsync messageMapperRegistryAsync, IAmAMessageTransformerFactoryAsync messageTransformerFactoryAsync, - IAmARequestContextFactory requestContextFactory) + IAmARequestContextFactory requestContextFactory, + IAmABrighterTracer tracer, + InstrumentationOptions instrumentationOptions = InstrumentationOptions.All) { _commandProcessorProvider = commandProcessorProvider; _messageMapperRegistryAsync = messageMapperRegistryAsync; _subscription = subscription; _messageTransformerFactoryAsync = messageTransformerFactoryAsync; _requestContextFactory = requestContextFactory; + _tracer = tracer; + _instrumentationOptions = instrumentationOptions; _consumerName = new ConsumerName($"{_subscription.Name}-{Guid.NewGuid()}"); } @@ -78,7 +89,8 @@ public Consumer Create() private Consumer CreateBlocking() { var channel = _subscription.ChannelFactory.CreateChannel(_subscription); - var messagePump = new MessagePumpBlocking(_commandProcessorProvider, _messageMapperRegistry, _messageTransformerFactory, _requestContextFactory) + var messagePump = new MessagePumpBlocking(_commandProcessorProvider, _messageMapperRegistry, + _messageTransformerFactory, _requestContextFactory, _tracer, _instrumentationOptions) { Channel = channel, TimeoutInMilliseconds = _subscription.TimeoutInMilliseconds, @@ -87,13 +99,14 @@ private Consumer CreateBlocking() UnacceptableMessageLimit = _subscription.UnacceptableMessageLimit }; - return new Consumer(_consumerName, _subscription.Name, channel, messagePump); + return new Consumer(_consumerName, _subscription, channel, messagePump); } private Consumer CreateAsync() { var channel = _subscription.ChannelFactory.CreateChannel(_subscription); - var messagePump = new MessagePumpAsync(_commandProcessorProvider, _messageMapperRegistryAsync, _messageTransformerFactoryAsync, _requestContextFactory) + var messagePump = new MessagePumpAsync(_commandProcessorProvider, _messageMapperRegistryAsync, + _messageTransformerFactoryAsync, _requestContextFactory, _tracer, _instrumentationOptions) { Channel = channel, TimeoutInMilliseconds = _subscription.TimeoutInMilliseconds, @@ -104,7 +117,7 @@ private Consumer CreateAsync() ChannelFailureDelay = _subscription.ChannelFailureDelay }; - return new Consumer(_consumerName, _subscription.Name, channel, messagePump); + return new Consumer(_consumerName, _subscription, channel, messagePump); } } } diff --git a/src/Paramore.Brighter.ServiceActivator/ControlBus/ControlBusReceiverBuilder.cs b/src/Paramore.Brighter.ServiceActivator/ControlBus/ControlBusReceiverBuilder.cs index 9bce2ffded..928aed921d 100644 --- a/src/Paramore.Brighter.ServiceActivator/ControlBus/ControlBusReceiverBuilder.cs +++ b/src/Paramore.Brighter.ServiceActivator/ControlBus/ControlBusReceiverBuilder.cs @@ -189,13 +189,14 @@ public Dispatcher Build(string hostName) new RoutingKey($"{hostName}.{HEARTBEAT}")) }; - return DispatchBuilder.With() + return DispatchBuilder.StartNew() .CommandProcessorFactory(() => new CommandProcessorProvider(commandProcessor), new InMemoryRequestContextFactory() ) .MessageMappers(incomingMessageMapperRegistry, null, null, null) .ChannelFactory(_channelFactory) .Subscriptions(subscriptions) + .NoInstrumentation() .Build(); } diff --git a/src/Paramore.Brighter.ServiceActivator/DispatchBuilder.cs b/src/Paramore.Brighter.ServiceActivator/DispatchBuilder.cs index c420b51c04..43f7b87109 100644 --- a/src/Paramore.Brighter.ServiceActivator/DispatchBuilder.cs +++ b/src/Paramore.Brighter.ServiceActivator/DispatchBuilder.cs @@ -25,6 +25,7 @@ THE SOFTWARE. */ using System; using System.Collections.Generic; using System.Linq; +using Paramore.Brighter.Observability; namespace Paramore.Brighter.ServiceActivator { @@ -34,7 +35,7 @@ namespace Paramore.Brighter.ServiceActivator /// progressive interfaces to manage the requirements for a complete Dispatcher via Intellisense in the IDE. The intent is to make it easier to /// recognize those dependencies that you need to configure /// - public class DispatchBuilder : INeedACommandProcessorFactory, INeedAChannelFactory, INeedAMessageMapper, INeedAListOfSubcriptions, IAmADispatchBuilder + public class DispatchBuilder : INeedACommandProcessorFactory, INeedAChannelFactory, INeedAMessageMapper, INeedAListOfSubcriptions, INeedObservability, IAmADispatchBuilder { private Func _commandProcessorFactory; private IAmAMessageMapperRegistry _messageMapperRegistry; @@ -44,6 +45,8 @@ public class DispatchBuilder : INeedACommandProcessorFactory, INeedAChannelFacto private IAmAMessageTransformerFactory _messageTransformerFactory; private IAmAMessageTransformerFactoryAsync _messageTransformerFactoryAsync; private IAmARequestContextFactory _requestContextFactory; + private IAmABrighterTracer _tracer; + private InstrumentationOptions _instrumentationOptions; private DispatchBuilder() { } @@ -51,7 +54,7 @@ private DispatchBuilder() { } /// Begins the fluent interface /// /// INeedALogger. - public static INeedACommandProcessorFactory With() + public static INeedACommandProcessorFactory StartNew() { return new DispatchBuilder(); } @@ -110,13 +113,33 @@ public INeedAListOfSubcriptions ChannelFactory(IAmAChannelFactory defaultChannel _defaultChannelFactory = defaultChannelFactory; return this; } + + /// + /// Configures OpenTelemetry for the Dispatcher + /// + /// An instance of with which to instrument the Dispatcher + /// An defining how verbose the instrumentation should be + /// INeedAListOfSubcriptions + public IAmADispatchBuilder ConfigureInstrumentation(IAmABrighterTracer tracer, InstrumentationOptions instrumentationOptions = InstrumentationOptions.All) + { + _tracer = tracer; + _instrumentationOptions = instrumentationOptions; + return this; + } + + public IAmADispatchBuilder NoInstrumentation() + { + _tracer = null; + _instrumentationOptions = InstrumentationOptions.None; + return this; + } /// /// A list of subscriptions i.e. mappings of channels to commands or events /// /// The connections. /// IAmADispatchBuilder. - public IAmADispatchBuilder Subscriptions(IEnumerable subscriptions) + public INeedObservability Subscriptions(IEnumerable subscriptions) { _subscriptions = subscriptions; @@ -136,7 +159,7 @@ public Dispatcher Build() { return new Dispatcher(_commandProcessorFactory, _subscriptions, _messageMapperRegistry, _messageMapperRegistryAsync, _messageTransformerFactory, _messageTransformerFactoryAsync, - _requestContextFactory + _requestContextFactory, _tracer, _instrumentationOptions ); } @@ -205,9 +228,32 @@ public interface INeedAListOfSubcriptions /// A list of connections i.e. mappings of channels to commands or events /// /// IAmADispatchBuilder. - IAmADispatchBuilder Subscriptions(IEnumerable subscriptions); + INeedObservability Subscriptions(IEnumerable subscriptions); } + public interface INeedObservability + { + /// + /// Sets the InstrumentationOptions for the Dispatcher + /// + /// The tracer that we should use to create telemetry + /// What depth of instrumentation do we want. + /// InstrumentationOptions.None - no telemetry + /// InstrumentationOptions.RequestInformation - id and type of request + /// InstrumentationOptions.RequestBody - body of the request + /// InstrumentationOptions.RequestContext - what is the context of the request + /// InstrumentationOptions.All - all of the above + /// + /// IAmADispatchBuilder + IAmADispatchBuilder ConfigureInstrumentation(IAmABrighterTracer tracer, InstrumentationOptions instrumentationOptions = InstrumentationOptions.All); + + /// + /// We do not need any instrumentation for the Dispatcher + /// + /// IAmADispatchBuilder + IAmADispatchBuilder NoInstrumentation(); + } + /// /// Interface IAmADispatchBuilder /// diff --git a/src/Paramore.Brighter.ServiceActivator/Dispatcher.cs b/src/Paramore.Brighter.ServiceActivator/Dispatcher.cs index 57baf6c7e0..e4c14ec596 100644 --- a/src/Paramore.Brighter.ServiceActivator/Dispatcher.cs +++ b/src/Paramore.Brighter.ServiceActivator/Dispatcher.cs @@ -31,6 +31,7 @@ THE SOFTWARE. */ using Microsoft.Extensions.Logging; using Paramore.Brighter.Extensions; using Paramore.Brighter.Logging; +using Paramore.Brighter.Observability; using Paramore.Brighter.ServiceActivator.Status; using BindingFlags = System.Reflection.BindingFlags; @@ -52,6 +53,8 @@ public class Dispatcher : IDispatcher private readonly IAmAMessageMapperRegistryAsync _messageMapperRegistryAsync; private readonly IAmAMessageTransformerFactoryAsync _messageTransformerFactoryAsync; private readonly IAmARequestContextFactory _requestContextFactory; + private readonly IAmABrighterTracer _tracer; + private readonly InstrumentationOptions _instrumentationOptions; private readonly ConcurrentDictionary _tasks; private readonly ConcurrentDictionary _consumers; @@ -68,10 +71,9 @@ public class Dispatcher : IDispatcher /// /// Gets the connections. - /// TODO: Rename to Subscriptions in V10 /// /// The connections. - public IEnumerable Connections { get; private set; } + public IEnumerable Subscriptions { get; private set; } /// /// Gets the s @@ -84,7 +86,7 @@ public class Dispatcher : IDispatcher /// Used when communicating with this instance via the Control Bus /// /// The name of the host. - public HostName HostName { get; set; } = new HostName($"Brighter{Guid.NewGuid()}"); + public HostName HostName { get; set; } = new($"Brighter{Guid.NewGuid()}"); /// /// Gets the state of the @@ -102,6 +104,8 @@ public class Dispatcher : IDispatcher /// Creates instances of Transforms /// Creates instances of Transforms async /// The factory used to make a request context + /// What is the we will use for telemetry + /// When creating a span for operations how noisy should the attributes be /// throws You must provide at least one type of message mapper registry public Dispatcher( Func commandProcessorFactory, @@ -110,16 +114,20 @@ public Dispatcher( IAmAMessageMapperRegistryAsync messageMapperRegistryAsync = null, IAmAMessageTransformerFactory messageTransformerFactory = null, IAmAMessageTransformerFactoryAsync messageTransformerFactoryAsync= null, - IAmARequestContextFactory requestContextFactory = null) + IAmARequestContextFactory requestContextFactory = null, + IAmABrighterTracer tracer = null, + InstrumentationOptions instrumentationOptions = InstrumentationOptions.All) { CommandProcessorFactory = commandProcessorFactory; - Connections = subscriptions; + Subscriptions = subscriptions; _messageMapperRegistry = messageMapperRegistry; _messageMapperRegistryAsync = messageMapperRegistryAsync; _messageTransformerFactory = messageTransformerFactory; _messageTransformerFactoryAsync = messageTransformerFactoryAsync; _requestContextFactory = requestContextFactory; + _tracer = tracer; + _instrumentationOptions = instrumentationOptions; if (messageMapperRegistry is null && messageMapperRegistryAsync is null) throw new ConfigurationException("You must provide a message mapper registry or an async message mapper registry"); @@ -142,6 +150,8 @@ public Dispatcher( /// Creates instances of Transforms /// Creates instances of Transforms async /// The factory used to make a request context + /// What is the we will use for telemetry + /// When creating a span for operations how noisy should the attributes be /// throws You must provide at least one type of message mapper registry public Dispatcher( IAmACommandProcessor commandProcessor, @@ -150,11 +160,13 @@ public Dispatcher( IAmAMessageMapperRegistryAsync messageMapperRegistryAsync = null, IAmAMessageTransformerFactory messageTransformerFactory = null, IAmAMessageTransformerFactoryAsync messageTransformerFactoryAsync= null, - IAmARequestContextFactory requestContextFactory = null) + IAmARequestContextFactory requestContextFactory = null, + IAmABrighterTracer tracer = null, + InstrumentationOptions instrumentationOptions = InstrumentationOptions.All) : this(() => new CommandProcessorProvider(commandProcessor), subscriptions, messageMapperRegistry, messageMapperRegistryAsync, messageTransformerFactory, messageTransformerFactoryAsync, - requestContextFactory + requestContextFactory, tracer, instrumentationOptions ) { } @@ -168,7 +180,7 @@ public Task End() if (State == DispatcherState.DS_RUNNING) { s_logger.LogInformation("Dispatcher: Stopping dispatcher"); - Consumers.Each(consumer => consumer.Shut()); + Consumers.Each(consumer => consumer.Shut(consumer.Subscription.RoutingKey)); } return _controlTask; @@ -177,10 +189,10 @@ public Task End() /// /// Opens the specified subscription by name /// - /// The name of the subscription - public void Open(string connectionName) + /// + public void Open(SubscriptionName subscriptionName) { - Open(Connections.SingleOrDefault(c => c.Name == connectionName)); + Open(Subscriptions.SingleOrDefault(c => c.Name == subscriptionName)); } /// @@ -191,7 +203,7 @@ public void Open(Subscription subscription) { s_logger.LogInformation("Dispatcher: Opening subscription {ChannelName}", subscription.Name); - AddConnectionToConnections(subscription); + AddSubscriptionToSubscriptions(subscription); var addedConsumers = CreateConsumers(new[] { subscription }); switch (State) @@ -214,11 +226,11 @@ public void Open(Subscription subscription) } } - private void AddConnectionToConnections(Subscription subscription) + private void AddSubscriptionToSubscriptions(Subscription subscription) { - if (Connections.All(c => c.Name != subscription.Name)) + if (Subscriptions.All(c => c.Name != subscription.Name)) { - Connections = new List(Connections) { subscription }; + Subscriptions = new List(Subscriptions) { subscription }; } } @@ -227,17 +239,17 @@ private void AddConnectionToConnections(Subscription subscription) /// public void Receive() { - CreateConsumers(Connections).Each(consumer => _consumers.TryAdd(consumer.Name, consumer)); + CreateConsumers(Subscriptions).Each(consumer => _consumers.TryAdd(consumer.Name, consumer)); Start(); } /// /// Shuts the specified subscription by name /// - /// The name of the subscription - public void Shut(string connectionName) + /// The name of the subscription + public void Shut(SubscriptionName subscriptionName) { - Shut(Connections.SingleOrDefault(c => c.Name == connectionName)); + Shut(Subscriptions.SingleOrDefault(c => c.Name == subscriptionName)); } /// @@ -249,22 +261,22 @@ public void Shut(Subscription subscription) if (State == DispatcherState.DS_RUNNING) { s_logger.LogInformation("Dispatcher: Stopping subscription {ChannelName}", subscription.Name); - var consumersForConnection = Consumers.Where(consumer => consumer.SubscriptionName == subscription.Name).ToArray(); + var consumersForConnection = Consumers.Where(consumer => consumer.Subscription.Name == subscription.Name).ToArray(); var noOfConsumers = consumersForConnection.Length; for (int i = 0; i < noOfConsumers; ++i) { - consumersForConnection[i].Shut(); + consumersForConnection[i].Shut(subscription.RoutingKey); } } } public DispatcherStateItem[] GetState() { - return Connections.Select(s => new DispatcherStateItem() + return Subscriptions.Select(s => new DispatcherStateItem() { Name = s.Name, ExpectPerformers = s.NoOfPerformers, - Performers = _consumers.Where(c => c.Value.SubscriptionName == s.Name).Select(c => new PerformerInformation() + Performers = _consumers.Where(c => c.Value.Subscription.Name == s.Name).Select(c => new PerformerInformation() { Name = c.Value.Name, State = c.Value.State @@ -274,12 +286,12 @@ public DispatcherStateItem[] GetState() public void SetActivePerformers(string connectionName, int numberOfPerformers) { - var subscription = Connections.SingleOrDefault(c => c.Name == connectionName); - var currentPerformers = subscription.NoOfPerformers; + var subscription = Subscriptions.SingleOrDefault(c => c.Name == connectionName); + var currentPerformers = subscription?.NoOfPerformers; if(currentPerformers == numberOfPerformers) return; - subscription.SetNumberOfPerformers(numberOfPerformers); + subscription?.SetNumberOfPerformers(numberOfPerformers); if (currentPerformers < numberOfPerformers) { for (var i = currentPerformers; i < numberOfPerformers; i++) @@ -292,12 +304,12 @@ public void SetActivePerformers(string connectionName, int numberOfPerformers) } else { - var consumersForConnection = Consumers.Where(consumer => consumer.SubscriptionName == subscription.Name) + var consumersForConnection = Consumers.Where(consumer => subscription != null && consumer.Subscription.Name == subscription.Name) .ToArray(); var consumersToClose = currentPerformers - numberOfPerformers; for (int i = 0; i < consumersToClose; ++i) { - consumersForConnection[i].Shut(); + consumersForConnection[i].Shut(subscription.RoutingKey); } } } @@ -379,15 +391,17 @@ private IEnumerable CreateConsumers(IEnumerable subscrip return list; } - private Consumer CreateConsumer(Subscription subscription, int consumerNumber) + private Consumer CreateConsumer(Subscription subscription, int? consumerNumber) { s_logger.LogInformation("Dispatcher: Creating consumer number {ConsumerNumber} for subscription: {ChannelName}", consumerNumber, subscription.Name); var consumerFactoryType = typeof(ConsumerFactory<>).MakeGenericType(subscription.DataType); if (!subscription.RunAsync) { - var types = new Type[] + var types = new[] { - typeof(IAmACommandProcessorProvider), typeof(Subscription), typeof(IAmAMessageMapperRegistry),typeof(IAmAMessageTransformerFactory), typeof(IAmARequestContextFactory) + typeof(IAmACommandProcessorProvider), typeof(Subscription), typeof(IAmAMessageMapperRegistry), + typeof(IAmAMessageTransformerFactory), typeof(IAmARequestContextFactory), typeof(IAmABrighterTracer), + typeof(InstrumentationOptions) }; var consumerFactoryCtor = consumerFactoryType.GetConstructor( @@ -395,16 +409,23 @@ private Consumer CreateConsumer(Subscription subscription, int consumerNumber) CallingConventions.HasThis, types, null ); - var consumerFactory = (IConsumerFactory)consumerFactoryCtor?.Invoke(new object[] { CommandProcessorFactory.Invoke(), subscription, _messageMapperRegistry, _messageTransformerFactory, _requestContextFactory }); + var consumerFactory = (IConsumerFactory)consumerFactoryCtor?.Invoke(new object[] + { + CommandProcessorFactory.Invoke(), subscription, _messageMapperRegistry, _messageTransformerFactory, + _requestContextFactory, _tracer, _instrumentationOptions + + }); - return consumerFactory.Create(); + return consumerFactory?.Create(); } else { - var types = new Type[] + var types = new[] { - typeof(IAmACommandProcessorProvider),typeof(Subscription), typeof(IAmAMessageMapperRegistryAsync), typeof(IAmAMessageTransformerFactoryAsync), typeof(IAmARequestContextFactory) + typeof(IAmACommandProcessorProvider),typeof(Subscription), typeof(IAmAMessageMapperRegistryAsync), + typeof(IAmAMessageTransformerFactoryAsync), typeof(IAmARequestContextFactory), typeof(IAmABrighterTracer), + typeof(InstrumentationOptions) }; var consumerFactoryCtor = consumerFactoryType.GetConstructor( @@ -412,9 +433,13 @@ private Consumer CreateConsumer(Subscription subscription, int consumerNumber) CallingConventions.HasThis, types, null ); - var consumerFactory = (IConsumerFactory)consumerFactoryCtor?.Invoke(new object[] { CommandProcessorFactory.Invoke(), subscription, _messageMapperRegistryAsync, _messageTransformerFactoryAsync, _requestContextFactory }); + var consumerFactory = (IConsumerFactory)consumerFactoryCtor?.Invoke(new object[] + { + CommandProcessorFactory.Invoke(), subscription, _messageMapperRegistryAsync, _messageTransformerFactoryAsync, + _requestContextFactory, _tracer, _instrumentationOptions + }); - return consumerFactory.Create(); + return consumerFactory?.Create(); } } } diff --git a/src/Paramore.Brighter.ServiceActivator/IAmAConsumer.cs b/src/Paramore.Brighter.ServiceActivator/IAmAConsumer.cs index 143bfb0449..c6bcd52e1c 100644 --- a/src/Paramore.Brighter.ServiceActivator/IAmAConsumer.cs +++ b/src/Paramore.Brighter.ServiceActivator/IAmAConsumer.cs @@ -10,7 +10,11 @@ public interface IAmAConsumer : IDisposable /// /// The name. ConsumerName Name { get; } - SubscriptionName SubscriptionName { get; set; } + + /// + /// What is the subscription that this Consumer is for + /// + Subscription Subscription { get; set; } /// /// Gets the performer. @@ -40,6 +44,7 @@ public interface IAmAConsumer : IDisposable /// /// Shuts the task, which will not receive messages. /// - void Shut(); + /// + void Shut(RoutingKey subscriptionRoutingKey); } -} \ No newline at end of file +} diff --git a/src/Paramore.Brighter.ServiceActivator/IAmAPerformer.cs b/src/Paramore.Brighter.ServiceActivator/IAmAPerformer.cs index 747051396c..2a8ddce240 100644 --- a/src/Paramore.Brighter.ServiceActivator/IAmAPerformer.cs +++ b/src/Paramore.Brighter.ServiceActivator/IAmAPerformer.cs @@ -35,12 +35,14 @@ public interface IAmAPerformer : IDisposable { /// /// Stops this instance. + /// The topic to post the quit message to /// - void Stop(); + void Stop(RoutingKey topic); + /// /// Runs this instance. /// /// Task. Task Run(); } -} \ No newline at end of file +} diff --git a/src/Paramore.Brighter.ServiceActivator/IDispatcher.cs b/src/Paramore.Brighter.ServiceActivator/IDispatcher.cs index 6522923f8f..681af69d20 100644 --- a/src/Paramore.Brighter.ServiceActivator/IDispatcher.cs +++ b/src/Paramore.Brighter.ServiceActivator/IDispatcher.cs @@ -64,8 +64,8 @@ public interface IDispatcher /// /// Opens the specified subscription name. /// - /// Name of the subscription. - void Open(string connectionName); + /// + void Open(SubscriptionName subscriptionName); /// /// Begins listening for messages on channels, and dispatching them to request handlers. @@ -81,8 +81,8 @@ public interface IDispatcher /// /// Shuts the specified subscription name. /// - /// Name of the subscription. - void Shut(string connectionName); + /// Name of the subscription. + void Shut(SubscriptionName subscriptionName); /// /// Get the current running state of the dispatcher diff --git a/src/Paramore.Brighter.ServiceActivator/MessagePump.cs b/src/Paramore.Brighter.ServiceActivator/MessagePump.cs index 81a000e0d5..20ad5be819 100644 --- a/src/Paramore.Brighter.ServiceActivator/MessagePump.cs +++ b/src/Paramore.Brighter.ServiceActivator/MessagePump.cs @@ -23,14 +23,13 @@ THE SOFTWARE. */ #endregion using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Paramore.Brighter.Actions; using Paramore.Brighter.Logging; +using Paramore.Brighter.Observability; using Polly.CircuitBreaker; namespace Paramore.Brighter.ServiceActivator @@ -53,23 +52,11 @@ public abstract class MessagePump : IAmAMessagePump where TRequest : c { internal static readonly ILogger s_logger = ApplicationLogging.CreateLogger>(); - private static readonly ActivitySource s_activitySource; - protected readonly IAmACommandProcessorProvider CommandProcessorProvider; private readonly IAmARequestContextFactory _requestContextFactory; - private int _unacceptableMessageCount = 0; - - /// - /// Used to initialize static members of the message pump - /// - static MessagePump() - { - var name = Assembly.GetAssembly(typeof(MessagePump<>))?.GetName(); - var sourceName = name?.Name; - var sourceVersion = name?.Version?.ToString(); - - s_activitySource = new ActivitySource(sourceName ?? "Paramore.Brighter.ServiceActivator.MessagePump", sourceVersion); - } + private readonly IAmABrighterTracer _tracer; + private readonly InstrumentationOptions _instrumentationOptions; + private int _unacceptableMessageCount; /// /// Constructs a message pump. The message pump is the heart of a consumer. It runs a loop that performs the following: @@ -80,10 +67,18 @@ static MessagePump() /// /// Provides a correctly scoped command processor /// Provides a request context - protected MessagePump(IAmACommandProcessorProvider commandProcessorProvider, IAmARequestContextFactory requestContextFactory) + /// What is the we will use for telemetry + /// When creating a span for operations how noisy should the attributes be + protected MessagePump( + IAmACommandProcessorProvider commandProcessorProvider, + IAmARequestContextFactory requestContextFactory, + IAmABrighterTracer tracer, + InstrumentationOptions instrumentationOptions = InstrumentationOptions.All) { CommandProcessorProvider = commandProcessorProvider; _requestContextFactory = requestContextFactory; + _tracer = tracer; + _instrumentationOptions = instrumentationOptions; } /// @@ -131,6 +126,7 @@ protected MessagePump(IAmACommandProcessorProvider commandProcessorProvider, IAm /// public void Run() { + var pumpSpan = _tracer?.CreateMessagePumpSpan(MessagePumpSpanOperation.Begin, Channel.RoutingKey, MessagingSystem.InternalBus, _instrumentationOptions); do { if (UnacceptableMessageLimitReached()) @@ -139,41 +135,51 @@ public void Run() break; } - s_logger.LogDebug("MessagePump: Receiving messages from channel {ChannelName} on thread # {ManagementThreadId}", Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogDebug("MessagePump: Receiving messages from channel {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); Activity span = null; Message message = null; try { message = Channel.Receive(TimeoutInMilliseconds); - span = CreateSpan(message); + span = _tracer?.CreateSpan(MessagePumpSpanOperation.Receive, message, MessagingSystem.InternalBus, _instrumentationOptions); } catch (ChannelFailureException ex) when (ex.InnerException is BrokenCircuitException) { - s_logger.LogWarning("MessagePump: BrokenCircuitException messages from {ChannelName} on thread # {ManagementThreadId}", Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogWarning("MessagePump: BrokenCircuitException messages from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); + var errorSpan = _tracer?.CreateMessagePumpExceptionSpan(ex, Channel.RoutingKey, MessagePumpSpanOperation.Receive, MessagingSystem.InternalBus, _instrumentationOptions); + _tracer?.EndSpan(errorSpan); Task.Delay(ChannelFailureDelay).Wait(); continue; } - catch (ChannelFailureException) + catch (ChannelFailureException ex) { - s_logger.LogWarning("MessagePump: ChannelFailureException messages from {ChannelName} on thread # {ManagementThreadId}", Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogWarning("MessagePump: ChannelFailureException messages from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); + var errorSpan = _tracer?.CreateMessagePumpExceptionSpan(ex, Channel.RoutingKey, MessagePumpSpanOperation.Receive, MessagingSystem.InternalBus, _instrumentationOptions); + _tracer?.EndSpan(errorSpan ); Task.Delay(ChannelFailureDelay).Wait(); continue; } - catch (Exception exception) + catch (Exception ex) { - s_logger.LogError(exception, "MessagePump: Exception receiving messages from {ChannelName} on thread # {ManagementThreadId}", Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogError(ex, "MessagePump: Exception receiving messages from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); + var errorSpan = _tracer?.CreateMessagePumpExceptionSpan(ex, Channel.RoutingKey, MessagePumpSpanOperation.Receive, MessagingSystem.InternalBus, _instrumentationOptions); + _tracer?.EndSpan(errorSpan ); } if (message == null) { - Channel.Dispose(); - throw new Exception("Could not receive message. Note that should return an MT_NONE from an empty queue on timeout"); + Channel.Dispose(); + span?.SetStatus(ActivityStatusCode.Error, "Could not receive message. Note that should return an MT_NONE from an empty queue on timeout"); + _tracer?.EndSpan(span); + throw new Exception("Could not receive message. Note that should return an MT_NONE from an empty queue on timeout"); } // empty queue if (message.Header.MessageType == MessageType.MT_NONE) { + span?.SetStatus(ActivityStatusCode.Ok); + _tracer?.EndSpan(span); Task.Delay(EmptyChannelDelay).Wait(); continue; } @@ -181,8 +187,9 @@ public void Run() // failed to parse a message from the incoming data if (message.Header.MessageType == MessageType.MT_UNACCEPTABLE) { - s_logger.LogWarning("MessagePump: Failed to parse a message from the incoming message with id {Id} from {ChannelName} on thread # {ManagementThreadId}", message.Id, Channel.Name, Thread.CurrentThread.ManagedThreadId); - + s_logger.LogWarning("MessagePump: Failed to parse a message from the incoming message with id {Id} from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", message.Id, Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); + span?.SetStatus(ActivityStatusCode.Error, $"MessagePump: Failed to parse a message from the incoming message with id {message.Id} from {Channel.Name} on thread # {Environment.CurrentManagedThreadId}"); + _tracer?.EndSpan(span); IncrementUnacceptableMessageLimit(); AcknowledgeMessage(message); @@ -192,7 +199,9 @@ public void Run() // QUIT command if (message.Header.MessageType == MessageType.MT_QUIT) { - s_logger.LogInformation("MessagePump: Quit receiving messages from {ChannelName} on thread # {ManagementThreadId}", Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogInformation("MessagePump: Quit receiving messages from {ChannelName} on thread #{ManagementThreadId}", Channel.Name, Environment.CurrentManagedThreadId); + span?.SetStatus(ActivityStatusCode.Ok); + _tracer?.EndSpan(span); Channel.Dispose(); break; } @@ -219,7 +228,7 @@ public void Run() { if (exception is ConfigurationException configurationException) { - s_logger.LogCritical(configurationException, "MessagePump: Stopping receiving of messages from {ChannelName} on thread # {ManagementThreadId}", Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogCritical(configurationException, "MessagePump: Stopping receiving of messages from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); stop = true; break; } @@ -230,12 +239,12 @@ public void Run() continue; } - s_logger.LogError(exception, "MessagePump: Failed to dispatch message {Id} from {ChannelName} on thread # {ManagementThreadId}", message.Id, Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogError(exception, "MessagePump: Failed to dispatch message {Id} from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", message.Id, Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); } if (defer) { - s_logger.LogDebug("MessagePump: Deferring message {Id} from {ChannelName} on thread # {ManagementThreadId}", message.Id, Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogDebug("MessagePump: Deferring message {Id} from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", message.Id, Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); span?.SetStatus(ActivityStatusCode.Error, $"Deferring message {message.Id} for later action"); if (RequeueMessage(message)) continue; @@ -244,24 +253,24 @@ public void Run() if (stop) { RejectMessage(message); - span?.SetStatus(ActivityStatusCode.Error, $"MessagePump: Stopping receiving of messages from {Channel.Name} on thread # {Thread.CurrentThread.ManagedThreadId}"); + span?.SetStatus(ActivityStatusCode.Error, $"MessagePump: Stopping receiving of messages from {Channel.Name} with {Channel.RoutingKey} on thread # {Environment.CurrentManagedThreadId}"); Channel.Dispose(); break; } - span?.SetStatus(ActivityStatusCode.Error, $"MessagePump: Failed to dispatch message {message.Id} from {Channel.Name} on thread # {Thread.CurrentThread.ManagedThreadId}"); + span?.SetStatus(ActivityStatusCode.Error, $"MessagePump: Failed to dispatch message {message.Id} from {Channel.Name} with {Channel.RoutingKey} on thread # {Environment.CurrentManagedThreadId}"); } catch (ConfigurationException configurationException) { - s_logger.LogCritical(configurationException,"MessagePump: Stopping receiving of messages from {ChannelName} on thread # {ManagementThreadId}", Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogCritical(configurationException,"MessagePump: Stopping receiving of messages from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); RejectMessage(message); - span?.SetStatus(ActivityStatusCode.Error, $"MessagePump: Stopping receiving of messages from {Channel.Name} on thread # {Thread.CurrentThread.ManagedThreadId}"); + span?.SetStatus(ActivityStatusCode.Error, $"MessagePump: Stopping receiving of messages from {Channel.Name} on thread # {Environment.CurrentManagedThreadId}"); Channel.Dispose(); break; } catch (DeferMessageAction) { - s_logger.LogDebug("MessagePump: Deferring message {Id} from {ChannelName} on thread # {ManagementThreadId}", message.Id, Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogDebug("MessagePump: Deferring message {Id} from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", message.Id, Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); span?.SetStatus(ActivityStatusCode.Error, $"Deferring message {message.Id} for later action"); @@ -269,21 +278,23 @@ public void Run() } catch (MessageMappingException messageMappingException) { - s_logger.LogWarning(messageMappingException, "MessagePump: Failed to map message {Id} from {ChannelName} on thread # {ManagementThreadId}", message.Id, Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogWarning(messageMappingException, "MessagePump: Failed to map message {Id} from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", message.Id, Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); IncrementUnacceptableMessageLimit(); - span?.SetStatus(ActivityStatusCode.Error, $"MessagePump: Failed to map message {message.Id} from {Channel.Name} on thread # {Thread.CurrentThread.ManagedThreadId}"); + span?.SetStatus(ActivityStatusCode.Error, $"MessagePump: Failed to map message {message.Id} from {Channel.Name} with {Channel.RoutingKey} on thread # {Thread.CurrentThread.ManagedThreadId}"); } catch (Exception e) { - s_logger.LogError(e, "MessagePump: Failed to dispatch message '{Id}' from {ChannelName} on thread # {ManagementThreadId}", message.Id, Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogError(e, + "MessagePump: Failed to dispatch message '{Id}' from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", + message.Id, Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); - span?.SetStatus(ActivityStatusCode.Error,$"MessagePump: Failed to dispatch message '{message.Id}' from {Channel.Name} on thread # {Thread.CurrentThread.ManagedThreadId}"); + span?.SetStatus(ActivityStatusCode.Error,$"MessagePump: Failed to dispatch message '{message.Id}' from {Channel.Name} with {Channel.RoutingKey} on thread # {Environment.CurrentManagedThreadId}"); } finally { - span?.Dispose(); + _tracer?.EndSpan(span); CommandProcessorProvider.ReleaseScope(); } @@ -292,69 +303,21 @@ public void Run() } while (true); s_logger.LogInformation( - "MessagePump0: Finished running message loop, no longer receiving messages from {ChannelName} on thread # {ManagementThreadId}", - Channel.Name, Thread.CurrentThread.ManagedThreadId); + "MessagePump0: Finished running message loop, no longer receiving messages from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", + Channel.Name, Channel.RoutingKey, Thread.CurrentThread.ManagedThreadId); + _tracer?.EndSpan(pumpSpan); } private void AcknowledgeMessage(Message message) { s_logger.LogDebug( - "MessagePump: Acknowledge message {Id} read from {ChannelName} on thread # {ManagementThreadId}", - message.Id, Channel.Name, Thread.CurrentThread.ManagedThreadId); + "MessagePump: Acknowledge message {Id} read from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", + message.Id, Channel.Name, Channel.RoutingKey, Environment.CurrentManagedThreadId); Channel.Acknowledge(message); } - // For information on the conventions we are using here Cloud Events Spans - private static Activity CreateSpan(Message message) - { - string parentId = message.Header.TraceParent ?? null; - var tags = new Dictionary() - { - //mandatory cloud events tags for tracing - { "cloudevents.event_id", message.Id }, - { "cloudevents.event_source", message.Header.Source }, - { "cloudevents.event_specversion", "1.0" }, - { "cloudevents.event_subject", message.Header.Subject }, - { "cloudevents.event_type", message.Header.Type }, - //optional cloud events tags for tracing - { "cloudevents.event_time", message.Header.TimeStamp }, - { "cloudevents.event_datacontenttype", message.Header.ContentType }, - { "cloudevents.event_dataschema", message.Header.DataSchema }, - //Brighter tags for tracing - { "messageType", message.Header.MessageType }, - { "topic", message.Header.Topic }, - { "timestamp", message.Header.TimeStamp }, - { "replyTo", message.Header.ReplyTo }, - { "handledCount", message.Header.HandledCount }, - }; - - var activity = s_activitySource.CreateActivity( - $"CloudEvents Process {typeof(TRequest)}", - ActivityKind.Consumer, - parentId, - tags, - idFormat: ActivityIdFormat.W3C - ); - - //Put correlation id in the baggage as it should flow across process boundaries - activity?.AddBaggage("correlationId", message.Header.CorrelationId); - - //HACK: Set the current activity to the new activity - this ought to be picked up when we run the command - //processor and look at create/start for an activity on the context. This should re-use this activity - //so that we do not create a new activity for the command processor or message mapper. - //This would allow us to find the tags that have the parent, and set those on any outgoing message. - //Mostly, because we have a reactor model we benefit from being single-threaded and can assume that - //another message will not reset this context before we can initialize those pipelines. - //Our expectation is that in handlers folks should set events not start new spans. - //The risk is that the current activity gets reset before we copy into into the context of a pipeline, and - //thus we lose that context of the incoming message when we come to map the outgoing message. - Activity.Current = activity ?? Activity.Current; - - return activity; - } - private bool DiscardRequeuedMessagesEnabled() { return RequeueCount != -1; @@ -374,14 +337,14 @@ private RequestContext InitRequestContext(Activity span, Message message) var context = _requestContextFactory.Create(); context.Span = span; context.OriginatingMessage = message; - context.Bag.AddOrUpdate("ChannelName", Channel.Name, (s, o) => Channel.Name); - context.Bag.AddOrUpdate("RequestStart", DateTime.UtcNow, (s, o) => DateTime.UtcNow); + context.Bag.AddOrUpdate("ChannelName", Channel.Name, (_, _) => Channel.Name); + context.Bag.AddOrUpdate("RequestStart", DateTime.UtcNow, (_, _) => DateTime.UtcNow); return context; } private void RejectMessage(Message message) { - s_logger.LogWarning("MessagePump: Rejecting message {Id} from {ChannelName} on thread # {ManagementThreadId}", message.Id, Channel.Name, Thread.CurrentThread.ManagedThreadId); + s_logger.LogWarning("MessagePump: Rejecting message {Id} from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", message.Id, Channel.Name, Channel.RoutingKey, Thread.CurrentThread.ManagedThreadId); IncrementUnacceptableMessageLimit(); Channel.Reject(message); @@ -403,13 +366,14 @@ private bool RequeueMessage(Message message) var originalMessageId = message.Header.Bag.TryGetValue(Message.OriginalMessageIdHeaderName, out object value) ? value.ToString() : null; s_logger.LogError( - "MessagePump: Have tried {RequeueCount} times to handle this message {Id}{OriginalMessageId} from {ChannelName} on thread # {ManagementThreadId}, dropping message.", + "MessagePump: Have tried {RequeueCount} times to handle this message {Id}{OriginalMessageId} from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}, dropping message.", RequeueCount, message.Id, string.IsNullOrEmpty(originalMessageId) ? string.Empty : $" (original message id {originalMessageId})", Channel.Name, + Channel.RoutingKey, Thread.CurrentThread.ManagedThreadId); RejectMessage(message); @@ -418,8 +382,8 @@ private bool RequeueMessage(Message message) } s_logger.LogDebug( - "MessagePump: Re-queueing message {Id} from {ManagementThreadId} on thread # {ChannelName}", message.Id, - Channel.Name, Thread.CurrentThread.ManagedThreadId); + "MessagePump: Re-queueing message {Id} from {ManagementThreadId} on thread # {ChannelName} with {RoutingKey}", message.Id, + Channel.Name, Channel.RoutingKey, Thread.CurrentThread.ManagedThreadId); return Channel.Requeue(message, RequeueDelayInMilliseconds); } @@ -433,10 +397,11 @@ private bool UnacceptableMessageLimitReached() if (_unacceptableMessageCount >= UnacceptableMessageLimit) { s_logger.LogCritical( - "MessagePump: Unacceptable message limit of {UnacceptableMessageLimit} reached, stopping reading messages from {ChannelName} on thread # {ManagementThreadId}", + "MessagePump: Unacceptable message limit of {UnacceptableMessageLimit} reached, stopping reading messages from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}", UnacceptableMessageLimit, Channel.Name, - Thread.CurrentThread.ManagedThreadId + Channel.RoutingKey, + Environment.CurrentManagedThreadId ); return true; diff --git a/src/Paramore.Brighter.ServiceActivator/MessagePumpAsync.cs b/src/Paramore.Brighter.ServiceActivator/MessagePumpAsync.cs index 5b43ffb3f7..0d22c755b1 100644 --- a/src/Paramore.Brighter.ServiceActivator/MessagePumpAsync.cs +++ b/src/Paramore.Brighter.ServiceActivator/MessagePumpAsync.cs @@ -27,6 +27,7 @@ THE SOFTWARE. */ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Paramore.Brighter.Observability; namespace Paramore.Brighter.ServiceActivator { @@ -50,12 +51,16 @@ public class MessagePumpAsync : MessagePump where TRequest : /// The registry of mappers /// The factory that lets us create instances of transforms /// A factory to create instances of request context, used to add context to a pipeline + /// What is the tracer we will use for telemetry + /// When creating a span for operations how noisy should the attributes be public MessagePumpAsync( IAmACommandProcessorProvider commandProcessorProvider, IAmAMessageMapperRegistryAsync messageMapperRegistry, IAmAMessageTransformerFactoryAsync messageTransformerFactory, - IAmARequestContextFactory requestContextFactory) - : base(commandProcessorProvider, requestContextFactory) + IAmARequestContextFactory requestContextFactory, + IAmABrighterTracer tracer = null, + InstrumentationOptions instrumentationOptions = InstrumentationOptions.All) + : base(commandProcessorProvider, requestContextFactory, tracer, instrumentationOptions) { var transformPipelineBuilder = new TransformPipelineBuilderAsync(messageMapperRegistry, messageTransformerFactory); _unwrapPipeline = transformPipelineBuilder.BuildUnwrapPipeline(); diff --git a/src/Paramore.Brighter.ServiceActivator/MessagePumpBlocking.cs b/src/Paramore.Brighter.ServiceActivator/MessagePumpBlocking.cs index 30694529e3..f145094b72 100644 --- a/src/Paramore.Brighter.ServiceActivator/MessagePumpBlocking.cs +++ b/src/Paramore.Brighter.ServiceActivator/MessagePumpBlocking.cs @@ -26,6 +26,7 @@ THE SOFTWARE. */ using System.Diagnostics; using System.Threading; using Microsoft.Extensions.Logging; +using Paramore.Brighter.Observability; namespace Paramore.Brighter.ServiceActivator { @@ -47,12 +48,16 @@ public class MessagePumpBlocking : MessagePump where TReques /// The registry of mappers /// The factory that lets us create instances of transforms /// A factory to create instances of request context, used to add context to a pipeline + /// What is the tracer we will use for telemetry + /// When creating a span for operations how noisy should the attributes be public MessagePumpBlocking( IAmACommandProcessorProvider commandProcessorProvider, IAmAMessageMapperRegistry messageMapperRegistry, IAmAMessageTransformerFactory messageTransformerFactory, - IAmARequestContextFactory requestContextFactory) - : base(commandProcessorProvider, requestContextFactory) + IAmARequestContextFactory requestContextFactory, + IAmABrighterTracer tracer = null, + InstrumentationOptions instrumentationOptions = InstrumentationOptions.All) + : base(commandProcessorProvider, requestContextFactory, tracer, instrumentationOptions) { var transformPipelineBuilder = new TransformPipelineBuilder(messageMapperRegistry, messageTransformerFactory); _unwrapPipeline = transformPipelineBuilder.BuildUnwrapPipeline(); diff --git a/src/Paramore.Brighter.ServiceActivator/Performer.cs b/src/Paramore.Brighter.ServiceActivator/Performer.cs index 11ee701cf8..4cd2e548d4 100644 --- a/src/Paramore.Brighter.ServiceActivator/Performer.cs +++ b/src/Paramore.Brighter.ServiceActivator/Performer.cs @@ -33,30 +33,48 @@ public class Performer : IAmAPerformer private readonly IAmAChannel _channel; private readonly IAmAMessagePump _messagePump; - public Performer( - IAmAChannel channel, - IAmAMessagePump messagePump) + /// + /// Constructs a performer, a combination of a message pump and a channel that it reads from + /// A peformer is a single thread, increase the number of performs to increase the number of threads + /// + /// The channel to read messages from + /// The message pump that reads messages + public Performer(IAmAChannel channel, IAmAMessagePump messagePump) { _channel = channel; _messagePump = messagePump; } - public void Stop() + /// + /// Stops a performer, by posting a quit message to the channel + /// + /// The topic to post the quit message too + public void Stop(RoutingKey routingKey) { - _channel.Stop(); + _channel.Stop(routingKey); } + /// + /// Runs this instance. + /// + /// Task. public async Task Run() { await Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); } + /// + /// Shut this performer and clean up its resources + /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + /// + /// Shut this performer and clean up its resources + /// ~Performer() { Dispose(false); diff --git a/src/Paramore.Brighter.ServiceActivator/Ports/Commands/ConfigurationCommand.cs b/src/Paramore.Brighter.ServiceActivator/Ports/Commands/ConfigurationCommand.cs index efdef22a5d..7017ae3766 100644 --- a/src/Paramore.Brighter.ServiceActivator/Ports/Commands/ConfigurationCommand.cs +++ b/src/Paramore.Brighter.ServiceActivator/Ports/Commands/ConfigurationCommand.cs @@ -52,7 +52,10 @@ public class ConfigurationCommand : Command /// The type. public ConfigurationCommandType Type { get; set; } - public string SubscriptionName { get; set; } + /// + /// Gets or sets the subscription name. + /// + public SubscriptionName SubscriptionName { get; set; } /// /// Initializes a new instance of the class. diff --git a/src/Paramore.Brighter.ServiceActivator/Ports/Handlers/ConfigurationCommandHandler.cs b/src/Paramore.Brighter.ServiceActivator/Ports/Handlers/ConfigurationCommandHandler.cs index 83e57a1117..882465b547 100644 --- a/src/Paramore.Brighter.ServiceActivator/Ports/Handlers/ConfigurationCommandHandler.cs +++ b/src/Paramore.Brighter.ServiceActivator/Ports/Handlers/ConfigurationCommandHandler.cs @@ -76,13 +76,13 @@ public override ConfigurationCommand Handle(ConfigurationCommand command) s_logger.LogDebug("--------------------------------------------------------------------------"); s_logger.LogDebug("Configuration Command received and now stopping channel {ChannelName}", command.SubscriptionName); s_logger.LogDebug("--------------------------------------------------------------------------"); - _dispatcher.Shut(command.SubscriptionName); + _dispatcher.Shut(new SubscriptionName(command.SubscriptionName)); break; case ConfigurationCommandType.CM_STARTCHANNEL: s_logger.LogDebug("--------------------------------------------------------------------------"); s_logger.LogDebug("Configuration Command received and now starting channel {ChannelName}", command.SubscriptionName); s_logger.LogDebug("--------------------------------------------------------------------------"); - _dispatcher.Open(command.SubscriptionName); + _dispatcher.Open(new SubscriptionName(command.SubscriptionName)); break; default: throw new ArgumentException("{0} is an unknown Configuration Command", Enum.GetName(typeof(ConfigurationCommandType), command.Type)); diff --git a/src/Paramore.Brighter/Channel.cs b/src/Paramore.Brighter/Channel.cs index 2926ff6ea2..821200659c 100644 --- a/src/Paramore.Brighter/Channel.cs +++ b/src/Paramore.Brighter/Channel.cs @@ -37,21 +37,41 @@ namespace Paramore.Brighter /// public class Channel : IAmAChannel { - private readonly string _channelName; private readonly IAmAMessageConsumer _messageConsumer; - private ConcurrentQueue _queue = new ConcurrentQueue(); + private ConcurrentQueue _queue = new(); private readonly int _maxQueueLength; - private static readonly Message s_NoneMessage = new Message(); + private static readonly Message s_noneMessage = new(); + + /// + /// The name of a channel is its identifier + /// See Topic for the broker routing key + /// May be used for the queue name, if known, on middleware that supports named queues + /// + /// The channel identifier + public ChannelName Name { get; } + + /// + /// The topic that this channel is for (how a broker routes to it) + /// + /// The topic on the broker + public RoutingKey RoutingKey { get; } /// /// Initializes a new instance of the class. /// /// Name of the queue. + /// /// The messageConsumer. /// What is the maximum buffer size we will accept - public Channel(string channelName, IAmAMessageConsumer messageConsumer, int maxQueueLength = 1) + public Channel( + ChannelName channelName, + RoutingKey routingKey, + IAmAMessageConsumer messageConsumer, + int maxQueueLength = 1 + ) { - _channelName = channelName; + Name = channelName; + RoutingKey = routingKey; _messageConsumer = messageConsumer; if (maxQueueLength < 1 || maxQueueLength > 10) @@ -67,7 +87,7 @@ public Channel(string channelName, IAmAMessageConsumer messageConsumer, int maxQ /// Acknowledges the specified message. /// /// The message. - public void Acknowledge(Message message) + public virtual void Acknowledge(Message message) { _messageConsumer.Acknowledge(message); } @@ -79,7 +99,7 @@ public void Acknowledge(Message message) /// after the queue /// /// The messages to insert into the channel - public void Enqueue(params Message[] messages) + public virtual void Enqueue(params Message[] messages) { var currentLength = _queue.Count; var messagesToAdd = messages.Length; @@ -92,17 +112,11 @@ public void Enqueue(params Message[] messages) messages.Each((message) => _queue.Enqueue(message)); } - - /// - /// Gets the name. - /// - /// The name. - public ChannelName Name => new ChannelName(_channelName); - - /// + + /// /// Purges the queue /// - public void Purge() + public virtual void Purge() { _messageConsumer.Purge(); _queue = new ConcurrentQueue(); @@ -113,14 +127,14 @@ public void Purge() /// /// The timeout in milliseconds. /// Message. - public Message Receive(int timeoutInMilliseconds) + public virtual Message Receive(int timeoutInMilliseconds) { if (!_queue.TryDequeue(out Message message)) { Enqueue(_messageConsumer.Receive(timeoutInMilliseconds)); if (!_queue.TryDequeue(out message)) { - message = s_NoneMessage; //Will be MT_NONE + message = s_noneMessage; //Will be MT_NONE } } @@ -131,7 +145,7 @@ public Message Receive(int timeoutInMilliseconds) /// Rejects the specified message. /// /// The message. - public void Reject(Message message) + public virtual void Reject(Message message) { _messageConsumer.Reject(message); } @@ -142,7 +156,7 @@ public void Reject(Message message) /// /// How long should we delay before requeueing /// True if the message was re-queued false otherwise - public bool Requeue(Message message, int delayMilliseconds = 0) + public virtual bool Requeue(Message message, int delayMilliseconds = 0) { return _messageConsumer.Requeue(message, delayMilliseconds); } @@ -150,15 +164,15 @@ public bool Requeue(Message message, int delayMilliseconds = 0) /// /// Stops this instance. /// - public void Stop() + public virtual void Stop(RoutingKey topic) { - _queue.Enqueue(MessageFactory.CreateQuitMessage()); + _queue.Enqueue(MessageFactory.CreateQuitMessage(topic)); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - public void Dispose() + public virtual void Dispose() { Dispose(true); GC.SuppressFinalize(this); diff --git a/src/Paramore.Brighter/ChannelNameConverter.cs b/src/Paramore.Brighter/ChannelNameConverter.cs new file mode 100644 index 0000000000..843c981773 --- /dev/null +++ b/src/Paramore.Brighter/ChannelNameConverter.cs @@ -0,0 +1,55 @@ +#region Licence +/* The MIT License (MIT) +Copyright © 2024 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Paramore.Brighter; + +public class ChannelNameConverter : JsonConverter +{ + public override ChannelName Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + return new ChannelName(reader.GetString()); + default: + throw new JsonException($"Unable to convert Json Type {reader.TokenType} to String, Supported Types are String, Number."); + } + } + + public override void Write(Utf8JsonWriter writer, ChannelName value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStringValue(value.Value.AsSpan()); + } + } +} diff --git a/src/Paramore.Brighter/CommandProcessorBuilder.cs b/src/Paramore.Brighter/CommandProcessorBuilder.cs index 768910c004..e1415216ea 100644 --- a/src/Paramore.Brighter/CommandProcessorBuilder.cs +++ b/src/Paramore.Brighter/CommandProcessorBuilder.cs @@ -62,6 +62,13 @@ namespace Paramore.Brighter /// /// /// + /// A describing how we want to instrument the command processor for Open Telemetry support. We need to provide a to + /// provide telemetry and a to describe how noisy we want the telemetry to be. If you do not want to use Open Telemetry, use the + /// method to indicate your intent. + /// + /// + /// + /// /// Finally we need to provide a to provide context to requests handlers in the pipeline that can be used to pass information without using the message /// that initiated the pipeline. We instantiate this via a user-provided . The default approach is use /// to provide a unless you have a requirement to replace this, such as in testing. @@ -209,7 +216,8 @@ public INeedInstrumentation NoExternalBus() /// InstrumentationOptions.RequestContext - what is the context of the request /// InstrumentationOptions.All - all of the above /// - /// + /// What is the that we will use to instrument the Command Processor + /// A that tells us how detailed the instrumentation should be /// public INeedARequestContext ConfigureInstrumentation(IAmABrighterTracer tracer, InstrumentationOptions instrumentationOptions) { @@ -217,6 +225,16 @@ public INeedARequestContext ConfigureInstrumentation(IAmABrighterTracer tracer, _instrumetationOptions = instrumentationOptions; return this; } + + /// + /// We do not intend to instrument the CommandProcessor + /// + /// + public INeedARequestContext NoInstrumentation() + { + _instrumetationOptions = InstrumentationOptions.None; + return this; + } /// /// The factory for used within the pipeline to pass information between steps. If you do not need to override @@ -361,8 +379,14 @@ public interface INeedInstrumentation /// InstrumentationOptions.RequestContext - what is the context of the request /// InstrumentationOptions.All - all of the above /// - /// + /// INeedARequestContext INeedARequestContext ConfigureInstrumentation(IAmABrighterTracer tracer, InstrumentationOptions instrumentationOptions); + + /// + /// We don't need instrumentation of the CommandProcessor + /// + /// INeedARequestContext + INeedARequestContext NoInstrumentation(); } /// diff --git a/src/Paramore.Brighter/IAmAChannel.cs b/src/Paramore.Brighter/IAmAChannel.cs index c333b20d9b..d77696c0f2 100644 --- a/src/Paramore.Brighter/IAmAChannel.cs +++ b/src/Paramore.Brighter/IAmAChannel.cs @@ -38,8 +38,14 @@ public interface IAmAChannel : IDisposable /// /// The name. ChannelName Name { get; } - - /// + + /// + /// The topic that this channel is for (how a broker routes to it) + /// + /// The topic on the broker + RoutingKey RoutingKey { get; } + + /// /// Acknowledges the specified message. /// /// The message. @@ -65,8 +71,9 @@ public interface IAmAChannel : IDisposable /// /// Stops this instance. + /// The topic to post the MT_QUIT message too /// - void Stop(); + void Stop(RoutingKey topic); /// /// Adds a message to the queue diff --git a/src/Paramore.Brighter/IServiceActivatorOptions.cs b/src/Paramore.Brighter/IServiceActivatorOptions.cs index 62b6a0e6a7..916a1f722b 100644 --- a/src/Paramore.Brighter/IServiceActivatorOptions.cs +++ b/src/Paramore.Brighter/IServiceActivatorOptions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Paramore.Brighter.Observability; namespace Paramore.Brighter { @@ -25,6 +26,10 @@ public interface IServiceActivatorOptions /// Otherwise the CommandProcessor is a singleton. /// bool UseScoped { get; set; } - + + /// + /// How detailed should the instrumentation of the Dispatcher operations be + /// + InstrumentationOptions InstrumentationOptions { get; set; } } } diff --git a/src/Paramore.Brighter/InMemoryChannelFactory.cs b/src/Paramore.Brighter/InMemoryChannelFactory.cs index dd3fe4e408..67a8a728c8 100644 --- a/src/Paramore.Brighter/InMemoryChannelFactory.cs +++ b/src/Paramore.Brighter/InMemoryChannelFactory.cs @@ -8,6 +8,7 @@ public IAmAChannel CreateChannel(Subscription subscription) { return new Channel( subscription.ChannelName, + subscription.RoutingKey, new InMemoryMessageConsumer(subscription.RoutingKey,internalBus, timeProvider, ackTimeoutMs), subscription.BufferSize ); diff --git a/src/Paramore.Brighter/InMemoryMessageConsumer.cs b/src/Paramore.Brighter/InMemoryMessageConsumer.cs index 29e75234db..eb0e225f09 100644 --- a/src/Paramore.Brighter/InMemoryMessageConsumer.cs +++ b/src/Paramore.Brighter/InMemoryMessageConsumer.cs @@ -111,7 +111,14 @@ public void Purge() public Message[] Receive(int timeoutInMilliseconds = 1000) { var messages = new[] {_bus.Dequeue(_topic)}; - _lockedMessages.TryAdd(messages[0].Id, new LockedMessage(messages[0], _timeProvider.GetUtcNow())); + foreach (var message in messages) + { + //don't lock empty messages + if (message.Header.MessageType == MessageType.MT_NONE) + continue; + _lockedMessages.TryAdd(message.Id, new LockedMessage(message, _timeProvider.GetUtcNow())); + } + return messages; } diff --git a/src/Paramore.Brighter/InMemoryProducer.cs b/src/Paramore.Brighter/InMemoryProducer.cs index 9d90b6be64..c5f92ffb4e 100644 --- a/src/Paramore.Brighter/InMemoryProducer.cs +++ b/src/Paramore.Brighter/InMemoryProducer.cs @@ -70,7 +70,7 @@ public void Dispose() { } /// public Task SendAsync(Message message) { - BrighterTracer.WriteProducerEvent(Span, MessagingSystem.InternalBus, message, true); + BrighterTracer.WriteProducerEvent(Span, MessagingSystem.InternalBus, message); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); bus.Enqueue(message); @@ -92,7 +92,7 @@ public async IAsyncEnumerable SendAsync(IEnumerable messages, var msgs = messages as Message[] ?? messages.ToArray(); foreach (var msg in msgs) { - BrighterTracer.WriteProducerEvent(Span, MessagingSystem.InternalBus, msg, true); + BrighterTracer.WriteProducerEvent(Span, MessagingSystem.InternalBus, msg); bus.Enqueue(msg); OnMessagePublished?.Invoke(true, msg.Id); yield return new[] { msg.Id }; @@ -105,7 +105,7 @@ public async IAsyncEnumerable SendAsync(IEnumerable messages, /// The message to send public void Send(Message message) { - BrighterTracer.WriteProducerEvent(Span, MessagingSystem.InternalBus, message, true); + BrighterTracer.WriteProducerEvent(Span, MessagingSystem.InternalBus, message); bus.Enqueue(message); OnMessagePublished?.Invoke(true, message.Id); } diff --git a/src/Paramore.Brighter/InternalBus.cs b/src/Paramore.Brighter/InternalBus.cs index 7a1f4717bf..4f6dffbb88 100644 --- a/src/Paramore.Brighter/InternalBus.cs +++ b/src/Paramore.Brighter/InternalBus.cs @@ -77,10 +77,10 @@ public Message Dequeue(RoutingKey topic, int millisecondsTimeout = -1) var found = _messages.TryGetValue(topic, out var messages); if (!found || !messages.Any()) - return MessageFactory.CreateEmptyMessage(); + return MessageFactory.CreateEmptyMessage(topic); if (!messages.TryTake(out Message message, millisecondsTimeout, CancellationToken.None)) - message = MessageFactory.CreateEmptyMessage(); + message = MessageFactory.CreateEmptyMessage(topic); return message; } diff --git a/src/Paramore.Brighter/JsonSerialisationOptions.cs b/src/Paramore.Brighter/JsonSerialisationOptions.cs index 25f477ad6b..d5ec213868 100644 --- a/src/Paramore.Brighter/JsonSerialisationOptions.cs +++ b/src/Paramore.Brighter/JsonSerialisationOptions.cs @@ -31,6 +31,9 @@ static JsonSerialisationOptions() opts.Converters.Add(new DictionaryStringObjectJsonConverter()); opts.Converters.Add(new ObjectToInferredTypesConverter()); opts.Converters.Add(new JsonStringEnumConverter()); + opts.Converters.Add(new SubscriptionNameConverter()); + opts.Converters.Add(new RoutingKeyConvertor()); + opts.Converters.Add(new ChannelNameConverter()); Options = opts; } diff --git a/src/Paramore.Brighter/MessageFactory.cs b/src/Paramore.Brighter/MessageFactory.cs index c64ebd6c8f..7ac21d746a 100644 --- a/src/Paramore.Brighter/MessageFactory.cs +++ b/src/Paramore.Brighter/MessageFactory.cs @@ -35,18 +35,18 @@ public static class MessageFactory /// Creates the quit message. /// /// Message. - public static Message CreateQuitMessage() + public static Message CreateQuitMessage(RoutingKey routingKey) { - return new Message(new MessageHeader(string.Empty, string.Empty, MessageType.MT_QUIT), new MessageBody(string.Empty)); + return new Message(new MessageHeader(Guid.Empty.ToString(), routingKey, MessageType.MT_QUIT), new MessageBody(string.Empty)); } /// /// Creates an empty message; this sets the message type to MT_NONE /// /// An Empty message - public static Message CreateEmptyMessage() + public static Message CreateEmptyMessage(RoutingKey routingKey) { - return new Message(new MessageHeader(string.Empty, string.Empty, MessageType.MT_NONE), new MessageBody(string.Empty)); + return new Message(new MessageHeader(Guid.Empty.ToString(), routingKey, MessageType.MT_NONE), new MessageBody(string.Empty)); } } } diff --git a/src/Paramore.Brighter/Observability/BrighterSemanticConventions.cs b/src/Paramore.Brighter/Observability/BrighterSemanticConventions.cs index f03ca9028e..fb158fb776 100644 --- a/src/Paramore.Brighter/Observability/BrighterSemanticConventions.cs +++ b/src/Paramore.Brighter/Observability/BrighterSemanticConventions.cs @@ -42,6 +42,7 @@ public static class BrighterSemanticConventions public const string DbTable = "db.table"; public const string DbUser = "db.user"; public const string ErrorType = "error.type"; + public const string HandledCount = "paramore.brighter.handled_count"; public const string HandlerName = "paramore.brighter.handler.name"; public const string HandlerType = "paramore.brighter.handler.type"; public const string IsSink = "paramore.brighter.is_sink"; @@ -51,7 +52,7 @@ public static class BrighterSemanticConventions public const string MessageHeaders = "messaging.message.headers"; public const string MessageId = "messaging.message.id"; public const string MessageType = "messaging.message.type"; - public const string MessagingDestination = "messaging.destination"; + public const string MessagingDestination = "messaging.destination.name"; public const string MessagingDestinationAnonymous = "messaging.destination.anonymous"; public const string MessagingDestinationPartitionId = "messaging.destination.partition.id"; public const string MessagingOperationType = "messaging.operation.type"; @@ -62,6 +63,7 @@ public static class BrighterSemanticConventions public const string Operation = "paramore.brighter.operation"; public const string OutboxSharedTransaction = "paramore.brighter.outbox.shared_transaction"; public const string OutboxType = "paramore.brighter.outbox.type"; + public const string ReplyTo = "paramore.brighter.replyto"; public const string RequestId = "paramore.brighter.request.id"; public const string RequestType = "paramore.brighter.request.type"; public const string RequestBody = "paramore.brighter.request.body"; diff --git a/src/Paramore.Brighter/Observability/BrighterSpanExtensions.cs b/src/Paramore.Brighter/Observability/BrighterSpanExtensions.cs index efac7ee76a..2c858d3cfc 100644 --- a/src/Paramore.Brighter/Observability/BrighterSpanExtensions.cs +++ b/src/Paramore.Brighter/Observability/BrighterSpanExtensions.cs @@ -57,4 +57,12 @@ public static class BrighterSpanExtensions OutboxDbOperation.OutStandingMessages => "retrieve.outstanding_messages", _ => throw new ArgumentOutOfRangeException(nameof(span), span, null) }; + + public static string ToSpanName(this MessagePumpSpanOperation operation) => operation switch + { + MessagePumpSpanOperation.Receive => "receive", + MessagePumpSpanOperation.Process => "process", + MessagePumpSpanOperation.Begin => "begin", + _ => throw new ArgumentOutOfRangeException(nameof(operation), operation, null) + }; } diff --git a/src/Paramore.Brighter/Observability/BrighterTracer.cs b/src/Paramore.Brighter/Observability/BrighterTracer.cs index 7c4b5ad476..2a593e8723 100644 --- a/src/Paramore.Brighter/Observability/BrighterTracer.cs +++ b/src/Paramore.Brighter/Observability/BrighterTracer.cs @@ -23,6 +23,8 @@ THE SOFTWARE. */ #endregion +using OpenTelemetry.Trace; + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -106,7 +108,64 @@ public Activity CreateSpan( return activity; } - + + /// + /// Create a span when we consume a message from a queue or stream + /// + /// How did we obtain the message. InstrumentationOptions.Receive => pull; InstrumentationOptions.Process => push + /// What is the that we received; if they have a traceparentid we will use that as a parent for this trace + /// What is the messaging system that we are receiving a message from + /// The for how deep should the instrumentation go + /// + public Activity CreateSpan( + MessagePumpSpanOperation operation, + Message message, + MessagingSystem messagingSystem, + InstrumentationOptions options = InstrumentationOptions.All + ) + { + var spanName = $"{message.Header.Topic} {operation.ToSpanName()}"; + var kind = ActivityKind.Consumer; + var parentId = message.Header.TraceParent; + var now = _timeProvider.GetUtcNow(); + + var tags = new ActivityTagsCollection() + { + { BrighterSemanticConventions.MessagingOperationType, operation.ToSpanName() }, + { BrighterSemanticConventions.MessagingDestination, message.Header.Topic }, + { BrighterSemanticConventions.MessagingDestinationPartitionId, message.Header.PartitionKey }, + { BrighterSemanticConventions.MessageId, message.Id }, + { BrighterSemanticConventions.MessageType, message.Header.MessageType.ToString() }, + { BrighterSemanticConventions.MessageBodySize, message.Body.Bytes.Length }, + { BrighterSemanticConventions.MessageBody, message.Body.Value }, + { BrighterSemanticConventions.MessageHeaders, JsonSerializer.Serialize(message.Header) }, + { BrighterSemanticConventions.ConversationId, message.Header.CorrelationId }, + { BrighterSemanticConventions.MessagingSystem, messagingSystem.ToMessagingSystemName() }, + { BrighterSemanticConventions.CeMessageId, message.Id }, + { BrighterSemanticConventions.CeSource, message.Header.Source }, + { BrighterSemanticConventions.CeVersion, "1.0"}, + { BrighterSemanticConventions.CeSubject, message.Header.Subject }, + { BrighterSemanticConventions.CeType, message.Header.Type}, + { BrighterSemanticConventions.ReplyTo, message.Header.ReplyTo }, + { BrighterSemanticConventions.HandledCount, message.Header.HandledCount } + + }; + + var activity = ActivitySource.StartActivity( + name: spanName, + kind: kind, + parentId: parentId, + tags: tags, + startTime: now); + + + activity?.AddBaggage("correlationId", message.Header.CorrelationId); + + Activity.Current = activity; + + return activity; + } + /// /// Create a span for a request in CommandProcessor /// @@ -147,6 +206,84 @@ public Activity CreateBatchSpan( return activity; } + /// + /// The parent span for the message pump. This is the entry point for the message pump + /// + /// The . This should be Begin or End + /// The for this span + /// The that we are receiving from + /// The for how deep should the instrumentation go? + /// A span (or dotnet Activity) for the current request named request.name operation.name + public Activity CreateMessagePumpSpan( + MessagePumpSpanOperation operation, + RoutingKey topic, + MessagingSystem messagingSystem, + InstrumentationOptions options = InstrumentationOptions.All) + { + if (operation != MessagePumpSpanOperation.Begin) + throw new ArgumentOutOfRangeException(nameof(operation), "Operation must be Begin or End"); + + var spanName = $"{topic} {operation.ToSpanName()}"; + var kind = ActivityKind.Consumer; + var now = _timeProvider.GetUtcNow(); + + var tags = new ActivityTagsCollection() + { + { BrighterSemanticConventions.MessagingSystem, messagingSystem.ToMessagingSystemName() }, + { BrighterSemanticConventions.MessagingDestination, topic }, + { BrighterSemanticConventions.Operation, operation.ToSpanName() } + }; + + Activity activity = ActivitySource.StartActivity(kind: kind, tags: tags, links: null, startTime: now, name: spanName); + + Activity.Current = activity; + + return activity; + } + + /// + /// When there is a failure during message processing we need to create a span for that message failure + /// as we don't have a message to derive the span details for + /// + /// + /// The for this span + /// The we were trying to perform + /// The that we are receiving from + /// The for how deep should the instrumentation go? + /// A span (or dotnet Activity) for the current message named topic operation.name + public Activity CreateMessagePumpExceptionSpan( + Exception messagePumpException, + RoutingKey topic, + MessagePumpSpanOperation operation, + MessagingSystem messagingSystem, + InstrumentationOptions options = InstrumentationOptions.All) + { + var spanName = $"{topic} {operation.ToSpanName()}"; + var kind = ActivityKind.Consumer; + var now = _timeProvider.GetUtcNow(); + + var tags = new ActivityTagsCollection() + { + { BrighterSemanticConventions.MessagingOperationType, operation.ToSpanName() }, + { BrighterSemanticConventions.MessagingSystem, messagingSystem.ToMessagingSystemName() }, + { BrighterSemanticConventions.MessagingDestination, topic }, + { BrighterSemanticConventions.Operation, operation.ToSpanName() } + }; + + Activity activity; + if (Activity.Current != null) + activity = ActivitySource.StartActivity(name: spanName, kind: kind, parentContext: Activity.Current.Context, tags: tags, links: null, now); + else + activity = ActivitySource.StartActivity(kind: kind, tags: tags, links: null, startTime: now, name: spanName); + + activity?.RecordException(messagePumpException); + activity?.SetStatus(ActivityStatusCode.Error, messagePumpException.Message); + + Activity.Current = activity; + + return activity; + } + /// /// Create a span for a batch of messages to be cleared /// @@ -431,8 +568,7 @@ public static void WriteOutboxEvent( /// The owning to which we will write the event; nothing written if null /// Which is the producer /// The being produced - /// Is the call async - public static void WriteProducerEvent(Activity span, MessagingSystem messagingSystem, Message message, bool isAsync) + public static void WriteProducerEvent(Activity span, MessagingSystem messagingSystem, Message message) { if (span == null) return; @@ -496,7 +632,7 @@ public void LinkSpans(ConcurrentDictionary handlerSpans) var handlerNames = handlerSpans.Keys.ToList(); foreach (var handlerName in handlerNames) { - var handlerSpan = handlerSpans[handlerName]; + //var handlerSpan = handlerSpans[handlerName]; foreach (var hs in handlerSpans) { if (hs.Key != handlerName) diff --git a/src/Paramore.Brighter/Observability/IAmABrighterTracer.cs b/src/Paramore.Brighter/Observability/IAmABrighterTracer.cs index bde56ba649..bf1bc45e2c 100644 --- a/src/Paramore.Brighter/Observability/IAmABrighterTracer.cs +++ b/src/Paramore.Brighter/Observability/IAmABrighterTracer.cs @@ -36,6 +36,21 @@ public interface IAmABrighterTracer : IDisposable /// The ActivitySource for the tracer /// ActivitySource ActivitySource { get; } + + /// + /// Create a span when we consume a message from a queue or stream + /// + /// How did we obtain the message. InstrumentationOptions.Receive => pull; InstrumentationOptions.Process => push + /// What is the that we received; if they have a traceparentid we will use that as a parent for this trace + /// What is the messaging system that we are receiving a message from + /// The for how deep should the instrumentation go + /// + Activity CreateSpan( + MessagePumpSpanOperation operation, + Message message, + MessagingSystem messagingSystem, + InstrumentationOptions options = InstrumentationOptions.All + ); /// /// Create a span for a request in CommandProcessor @@ -69,6 +84,19 @@ Activity CreateBatchSpan( InstrumentationOptions options = InstrumentationOptions.All ) where TRequest : class, IRequest; + /// + /// The for this span + /// The we were trying to perform + /// The that we are receiving from + /// The for how deep should the instrumentation go? + /// A span (or dotnet Activity) for the current request named request.name operation.name + Activity CreateMessagePumpExceptionSpan( + Exception messagePumpException, + RoutingKey topic, + MessagePumpSpanOperation operation, + MessagingSystem messagingSystem, + InstrumentationOptions options = InstrumentationOptions.All); + /// /// Create a span for a batch of messages to be cleared /// @@ -132,4 +160,17 @@ Activity CreateProducerSpan( /// void LinkSpans(ConcurrentDictionary handlerSpans); + /// + /// The parent span for the message pump. This is the entry point for the message pump + /// + /// The . This should be Begin or End + /// The for this span + /// The that we are receiving from + /// The for how deep should the instrumentation go? + /// A span (or dotnet Activity) for the current request named request.name operation.name + Activity CreateMessagePumpSpan( + MessagePumpSpanOperation operation, + RoutingKey topic, + MessagingSystem messagingSystem, + InstrumentationOptions instrumentationOptions = InstrumentationOptions.All); } diff --git a/src/Paramore.Brighter/Observability/MessagePumpSpanOperation.cs b/src/Paramore.Brighter/Observability/MessagePumpSpanOperation.cs new file mode 100644 index 0000000000..438ae20924 --- /dev/null +++ b/src/Paramore.Brighter/Observability/MessagePumpSpanOperation.cs @@ -0,0 +1,11 @@ +namespace Paramore.Brighter.Observability; + +/// +/// What is the operation that the message pump is performing +/// +public enum MessagePumpSpanOperation +{ + Begin = 0, // Begin the message pump + Receive = 1, // Receive a message via a pull + Process = 2, // Process a message from a push +} diff --git a/src/Paramore.Brighter/Paramore.Brighter.csproj b/src/Paramore.Brighter/Paramore.Brighter.csproj index bda42c6ed8..f0b5100c9f 100644 --- a/src/Paramore.Brighter/Paramore.Brighter.csproj +++ b/src/Paramore.Brighter/Paramore.Brighter.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Paramore.Brighter/RoutingKey.cs b/src/Paramore.Brighter/RoutingKey.cs index f72d67aca2..4f6f5a896a 100644 --- a/src/Paramore.Brighter/RoutingKey.cs +++ b/src/Paramore.Brighter/RoutingKey.cs @@ -1,4 +1,28 @@ -namespace Paramore.Brighter +#region Licence +/* The MIT License (MIT) +Copyright © 2024 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +namespace Paramore.Brighter { /// /// The name of a Routing Key used to wrap communication with a Broker diff --git a/src/Paramore.Brighter/RoutingKeyConvertor.cs b/src/Paramore.Brighter/RoutingKeyConvertor.cs new file mode 100644 index 0000000000..7ac1dcfd08 --- /dev/null +++ b/src/Paramore.Brighter/RoutingKeyConvertor.cs @@ -0,0 +1,55 @@ +#region Licence +/* The MIT License (MIT) +Copyright © 2024 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Paramore.Brighter; + +public class RoutingKeyConvertor : JsonConverter +{ + public override RoutingKey Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + return new RoutingKey(reader.GetString()); + default: + throw new JsonException($"Unable to convert Json Type {reader.TokenType} to String, Supported Types are String, Number."); + } + } + + public override void Write(Utf8JsonWriter writer, RoutingKey value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStringValue(value.Value.AsSpan()); + } + } +} diff --git a/src/Paramore.Brighter/SubscriptionName.cs b/src/Paramore.Brighter/SubscriptionName.cs index 7b8ba7f801..c609866941 100644 --- a/src/Paramore.Brighter/SubscriptionName.cs +++ b/src/Paramore.Brighter/SubscriptionName.cs @@ -70,6 +70,16 @@ public static implicit operator string (SubscriptionName rhs) { return rhs.ToString(); } + + /// + /// Performs an implicit conversion from to . + /// + /// The we are converting to a + /// + public static implicit operator SubscriptionName(string rhs) + { + return new SubscriptionName(rhs); + } /// /// Does the subscription name match? @@ -127,4 +137,4 @@ public override int GetHashCode() return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Paramore.Brighter/SubscriptionNameConverter.cs b/src/Paramore.Brighter/SubscriptionNameConverter.cs new file mode 100644 index 0000000000..a483c76a66 --- /dev/null +++ b/src/Paramore.Brighter/SubscriptionNameConverter.cs @@ -0,0 +1,55 @@ +#region Licence +/* The MIT License (MIT) +Copyright © 2024 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Paramore.Brighter; + +public class SubscriptionNameConverter : JsonConverter +{ + public override SubscriptionName Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + return new SubscriptionName(reader.GetString()); + default: + throw new JsonException($"Unable to convert Json Type {reader.TokenType} to String, Supported Types are String, Number."); + } + } + + public override void Write(Utf8JsonWriter writer, SubscriptionName value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStringValue(value.Value.AsSpan()); + } + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config.cs index 7cf14ee5d6..b2aacc00ad 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config.cs @@ -18,24 +18,19 @@ public class CustomisingAwsClientConfigTests : IDisposable private readonly IAmAChannel _channel; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - private readonly string _correlationId; - private readonly string _replyTo; - private readonly string _contentType; - private readonly string _topicName; - private readonly InterceptingDelegatingHandler _publishHttpHandler; - private readonly InterceptingDelegatingHandler _subscribeHttpHandler; + private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); + private readonly InterceptingDelegatingHandler _subscribeHttpHandler = new(); public CustomisingAwsClientConfigTests() { - _myCommand = new MyCommand{Value = "Test"}; - _correlationId = Guid.NewGuid().ToString(); - _replyTo = "http:\\queueUrl"; - _contentType = "text\\plain"; + MyCommand myCommand = new() {Value = "Test"}; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(_topicName); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); SqsSubscription subscription = new( name: new SubscriptionName(channelName), @@ -44,13 +39,11 @@ public CustomisingAwsClientConfigTests() ); _message = new Message( - new MessageHeader(_myCommand.Id, _topicName, MessageType.MT_COMMAND, correlationId: _correlationId, - replyTo: _replyTo, contentType: _contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageHeader(myCommand.Id, topicName, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: replyTo, contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) ); - _publishHttpHandler = new InterceptingDelegatingHandler(); - _subscribeHttpHandler = new InterceptingDelegatingHandler(); (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var subscribeAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => { @@ -65,7 +58,7 @@ public CustomisingAwsClientConfigTests() config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); }); - _messageProducer = new SqsMessageProducer(publishAwsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); + _messageProducer = new SqsMessageProducer(publishAwsConnection, new SnsPublication{Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create}); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive.cs index efae59f004..afcceb1bb4 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive.cs @@ -27,6 +27,7 @@ public class SnsReDrivePolicySDlqTests private readonly IAmAChannel _channel; private readonly SqsMessageProducer _sender; private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; public SnsReDrivePolicySDlqTests() { @@ -39,7 +40,7 @@ public SnsReDrivePolicySDlqTests() var routingKey = new RoutingKey(topicName); //how are we consuming - var subscription = new SqsSubscription( + _subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, @@ -79,7 +80,7 @@ public SnsReDrivePolicySDlqTests() //We need to do this manually in a test - will create the channel from subscriber parameters ChannelFactory channelFactory = new(_awsConnection); - _channel = channelFactory.CreateChannel(subscription); + _channel = channelFactory.CreateChannel(_subscription); //how do we handle a command IHandleRequests handler = new MyDeferredCommandHandler(); @@ -143,7 +144,7 @@ public async Task When_throwing_defer_action_respect_redrive() await Task.Delay(5000); //send a quit message to the pump to terminate it - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); _channel.Enqueue(quitMessage); //wait for the pump to stop once it gets a quit message diff --git a/tests/Paramore.Brighter.Core.Tests/CommandProcessors/TestDoubles/MyEvent.cs b/tests/Paramore.Brighter.Core.Tests/CommandProcessors/TestDoubles/MyEvent.cs index eca891c495..cb74433155 100644 --- a/tests/Paramore.Brighter.Core.Tests/CommandProcessors/TestDoubles/MyEvent.cs +++ b/tests/Paramore.Brighter.Core.Tests/CommandProcessors/TestDoubles/MyEvent.cs @@ -28,7 +28,7 @@ namespace Paramore.Brighter.Core.Tests.CommandProcessors.TestDoubles { internal class MyEvent : Event, IEquatable { - public int Data { get; private set; } + public int Data { get; set; } public MyEvent() : base(Guid.NewGuid()) { diff --git a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_configuring_a_control_bus.cs b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_configuring_a_control_bus.cs index d823f70165..3c596ba06d 100644 --- a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_configuring_a_control_bus.cs +++ b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_configuring_a_control_bus.cs @@ -59,8 +59,8 @@ public void When_configuring_a_control_bus() { _controlBus = _busReceiverBuilder.Build(_hostName); - _controlBus.Connections.Should().Contain(cn => cn.Name == $"{_hostName}.{ControlBusReceiverBuilder.CONFIGURATION}"); - _controlBus.Connections.Should().Contain(cn => cn.Name == $"{_hostName}.{ControlBusReceiverBuilder.HEARTBEAT}"); + _controlBus.Subscriptions.Should().Contain(cn => cn.Name == $"{_hostName}.{ControlBusReceiverBuilder.CONFIGURATION}"); + _controlBus.Subscriptions.Should().Contain(cn => cn.Name == $"{_hostName}.{ControlBusReceiverBuilder.HEARTBEAT}"); _controlBus.CommandProcessor.Should().NotBeNull(); } diff --git a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_mapping_from_a_configuration_command_from_a_message.cs b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_mapping_from_a_configuration_command_from_a_message.cs index 24c524de81..8c3c57c91f 100644 --- a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_mapping_from_a_configuration_command_from_a_message.cs +++ b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_mapping_from_a_configuration_command_from_a_message.cs @@ -40,10 +40,8 @@ public ConfigurationCommandMessageMapperTests() { _mapper = new ConfigurationCommandMessageMapper(); - _message = new Message( - new MessageHeader(Guid.NewGuid().ToString(), "myTopic", MessageType.MT_COMMAND), - new MessageBody(string.Format("{{\"Type\":1,\"SubscriptionName\":\"getallthethings\",\"Id\":\"{0}\"}}", Guid.NewGuid())) - ); + var command = new ConfigurationCommand(ConfigurationCommandType.CM_STARTALL){SubscriptionName = "getallthethings"}; + _message = _mapper.MapToMessage(command, new Publication(){Topic = new RoutingKey("myTopic")}); } [Fact] @@ -54,7 +52,7 @@ public void When_mapping_from_a_configuration_command_from_a_message() //_should_rehydrate_the_command_type _command.Type.Should().Be(ConfigurationCommandType.CM_STARTALL); // _should_rehydrate_the_connection_name - _command.SubscriptionName.Should().Be("getallthethings"); + _command.SubscriptionName.Should().Be(new SubscriptionName("getallthethings")); } } } diff --git a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_mapping_to_a_wire_message_from_a_configuration_command.cs b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_mapping_to_a_wire_message_from_a_configuration_command.cs index 2cccad4d19..afc8985d64 100644 --- a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_mapping_to_a_wire_message_from_a_configuration_command.cs +++ b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_mapping_to_a_wire_message_from_a_configuration_command.cs @@ -40,8 +40,7 @@ public ConfigurationCommandToMessageMapperTests() { _mapper = new ConfigurationCommandMessageMapper(); - //"{\"Type\":1,\"SubscriptionName\":\"getallthethings\",\"Id\":\"XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\"}" - _command = new ConfigurationCommand(ConfigurationCommandType.CM_STARTALL) {SubscriptionName = "getallthethings"}; + _command = new ConfigurationCommand(ConfigurationCommandType.CM_STARTALL) {SubscriptionName = new SubscriptionName("getallthethings")}; _publication = new Publication { Topic = new RoutingKey("ConfigurationCommand") }; } diff --git a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_receiving_a_start_message_for_a_connection.cs b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_receiving_a_start_message_for_a_connection.cs index d6b44aea55..5bec1f0c47 100644 --- a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_receiving_a_start_message_for_a_connection.cs +++ b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_receiving_a_start_message_for_a_connection.cs @@ -32,7 +32,7 @@ namespace Paramore.Brighter.Core.Tests.ControlBus { public class ConfigurationCommandStartTests { - private const string CONNECTION_NAME = "Test"; + private const string SubscriptionName = "Test"; private readonly ConfigurationCommandHandler _configurationCommandHandler; private readonly ConfigurationCommand _configurationCommand; private readonly IDispatcher _dispatcher; @@ -41,7 +41,7 @@ public ConfigurationCommandStartTests() { _dispatcher = A.Fake(); _configurationCommandHandler = new ConfigurationCommandHandler(_dispatcher); - _configurationCommand = new ConfigurationCommand(ConfigurationCommandType.CM_STARTCHANNEL) {SubscriptionName = CONNECTION_NAME}; + _configurationCommand = new ConfigurationCommand(ConfigurationCommandType.CM_STARTCHANNEL) {SubscriptionName = new SubscriptionName(SubscriptionName)}; } [Fact] @@ -50,7 +50,7 @@ public void When_receiving_a_start_message_for_a_connection() _configurationCommandHandler.Handle(_configurationCommand); //_should_call_stop_for_the_given_connection - A.CallTo(() => _dispatcher.Shut(CONNECTION_NAME)); + A.CallTo(() => _dispatcher.Shut(new SubscriptionName(SubscriptionName))); } } } diff --git a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_receiving_a_stop_message_for_a_connection.cs b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_receiving_a_stop_message_for_a_connection.cs index 24b695021d..2a242344fd 100644 --- a/tests/Paramore.Brighter.Core.Tests/ControlBus/When_receiving_a_stop_message_for_a_connection.cs +++ b/tests/Paramore.Brighter.Core.Tests/ControlBus/When_receiving_a_stop_message_for_a_connection.cs @@ -8,7 +8,7 @@ namespace Paramore.Brighter.Core.Tests.ControlBus { public class ConfigurationCommandStopTests { - const string CONNECTION_NAME = "Test"; + const string SUBSCRIPTION_NAME = "Test"; private readonly ConfigurationCommandHandler _configurationCommandHandler; private readonly ConfigurationCommand _configurationCommand; private readonly IDispatcher _dispatcher; @@ -17,7 +17,7 @@ public ConfigurationCommandStopTests() { _dispatcher = A.Fake(); _configurationCommandHandler = new ConfigurationCommandHandler(_dispatcher); - _configurationCommand = new ConfigurationCommand(ConfigurationCommandType.CM_STOPCHANNEL) {SubscriptionName = CONNECTION_NAME}; + _configurationCommand = new ConfigurationCommand(ConfigurationCommandType.CM_STOPCHANNEL) {SubscriptionName = new SubscriptionName(SUBSCRIPTION_NAME)}; } [Fact] @@ -26,7 +26,7 @@ public void When_receiving_a_stop_message_for_a_connection() _configurationCommandHandler.Handle(_configurationCommand); //_should_call_stop_for_the_given_connection - A.CallTo(() => _dispatcher.Shut(CONNECTION_NAME)); + A.CallTo(() => _dispatcher.Shut( new SubscriptionName(SUBSCRIPTION_NAME))); } } } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/TestDoubles/FailingChannel.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/TestDoubles/FailingChannel.cs index d304d2adc6..17fbf95d06 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/TestDoubles/FailingChannel.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/TestDoubles/FailingChannel.cs @@ -23,21 +23,29 @@ THE SOFTWARE. */ #endregion using System; +using Polly.CircuitBreaker; namespace Paramore.Brighter.Core.Tests.MessageDispatch.TestDoubles { - internal class FailingChannel(ChannelName channelName, IAmAMessageConsumer messageConsumer, int maxQueueLength= 1) - : Channel(channelName, messageConsumer, maxQueueLength) + internal class FailingChannel(ChannelName channelName, RoutingKey topic, IAmAMessageConsumer messageConsumer, int maxQueueLength= 1, bool brokenCircuit = false) + : Channel(channelName, topic, messageConsumer, maxQueueLength) { public int NumberOfRetries { get; set; } = 0; private int _attempts = 0; - public new Message Receive(int timeoutinMilliseconds) + public override Message Receive(int timeoutinMilliseconds) { if (_attempts <= NumberOfRetries) { _attempts++; - throw new ChannelFailureException("Test general failure", new Exception("inner test exception")); + var channelFailureException = new ChannelFailureException("Test general failure", new Exception("inner test exception")); + if (brokenCircuit) + { + var brokenCircuitException = new BrokenCircuitException("An inner broken circuit exception"); + channelFailureException = new ChannelFailureException("Test broken circuit failure", brokenCircuitException); + } + + throw channelFailureException; } return base.Receive(timeoutinMilliseconds); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_channel_failure_exception_is_thrown_for_command_should_retry_until_connection_re_established.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_channel_failure_exception_is_thrown_for_command_should_retry_until_connection_re_established.cs index fcd05cac7a..c31710f727 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_channel_failure_exception_is_thrown_for_command_should_retry_until_connection_re_established.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_channel_failure_exception_is_thrown_for_command_should_retry_until_connection_re_established.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpRetryCommandOnConnectionFailureTests { private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -47,7 +48,7 @@ public MessagePumpRetryCommandOnConnectionFailureTests() { _commandProcessor = new SpyCommandProcessor(); var provider = new CommandProcessorProvider(_commandProcessor); - var channel = new FailingChannel(new ChannelName(Topic), new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2) + var channel = new FailingChannel(new ChannelName(ChannelName), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2) { NumberOfRetries = 1 }; @@ -75,7 +76,7 @@ public MessagePumpRetryCommandOnConnectionFailureTests() channel.Enqueue(message2); //end the pump - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); channel.Enqueue(quitMessage); } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_channel_failure_exception_is_thrown_for_event_should_retry_until_connection_re_established.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_channel_failure_exception_is_thrown_for_event_should_retry_until_connection_re_established.cs index c83af75152..83b00083ff 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_channel_failure_exception_is_thrown_for_event_should_retry_until_connection_re_established.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_channel_failure_exception_is_thrown_for_event_should_retry_until_connection_re_established.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpRetryEventConnectionFailureTests { private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -47,7 +48,7 @@ public MessagePumpRetryEventConnectionFailureTests() { _commandProcessor = new SpyCommandProcessor(); var provider = new CommandProcessorProvider(_commandProcessor); - var channel = new FailingChannel(new ChannelName(Topic), new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2) + var channel = new FailingChannel(new ChannelName(ChannelName), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2) { NumberOfRetries = 1 }; @@ -77,7 +78,7 @@ public MessagePumpRetryEventConnectionFailureTests() channel.Enqueue(message2); //Quit the message pump - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); channel.Enqueue(quitMessage); } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_a_defer_message_Then_message_is_requeued_until_rejected.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_a_defer_message_Then_message_is_requeued_until_rejected.cs index b424d48cc7..b9c9be0ab3 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_a_defer_message_Then_message_is_requeued_until_rejected.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_a_defer_message_Then_message_is_requeued_until_rejected.cs @@ -35,6 +35,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpCommandProcessingDeferMessageActionTests { private const string Topic = "MyCommand"; + private const string ChannelName = "myChannel"; private readonly IAmAMessagePump _messagePump; private readonly Channel _channel; private readonly int _requeueCount = 5; @@ -47,7 +48,7 @@ public MessagePumpCommandProcessingDeferMessageActionTests() SpyRequeueCommandProcessor commandProcessor = new(); var provider = new CommandProcessorProvider(commandProcessor); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new(ChannelName), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyCommandMessageMapper()), @@ -74,7 +75,7 @@ public async Task When_a_command_handler_throws_a_defer_message_Then_message_is_ _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(_routingKey); _channel.Enqueue(quitMessage); await Task.WhenAll(task); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_a_defer_message_Then_message_is_requeued_until_rejectedAsync.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_a_defer_message_Then_message_is_requeued_until_rejectedAsync.cs index 8cd5738eec..1c76f268ef 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_a_defer_message_Then_message_is_requeued_until_rejectedAsync.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_a_defer_message_Then_message_is_requeued_until_rejectedAsync.cs @@ -35,6 +35,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpCommandProcessingDeferMessageActionTestsAsync { private const string Topic = "MyCommand"; + private const string ChannelName = "myChannel"; private readonly IAmAMessagePump _messagePump; private readonly Channel _channel; private readonly int _requeueCount = 5; @@ -47,7 +48,7 @@ public MessagePumpCommandProcessingDeferMessageActionTestsAsync() SpyRequeueCommandProcessor commandProcessor = new(); var commandProcessorProvider = new CommandProcessorProvider(commandProcessor); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new(ChannelName),_routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( null, @@ -74,7 +75,7 @@ public async Task When_a_command_handler_throws_a_defer_message_Then_message_is_ _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(_routingKey); _channel.Enqueue(quitMessage); await Task.WhenAll(task); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_unhandled_exception_Then_message_is_acked.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_unhandled_exception_Then_message_is_acked.cs index 38a6cb64fa..0c2517a271 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_unhandled_exception_Then_message_is_acked.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_unhandled_exception_Then_message_is_acked.cs @@ -38,6 +38,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpCommandProcessingExceptionTests { private const string Topic = "MyCommand"; + private const string ChannelName = "myChannel"; private readonly IAmAMessagePump _messagePump; private readonly Channel _channel; private readonly int _requeueCount = 5; @@ -50,7 +51,7 @@ public MessagePumpCommandProcessingExceptionTests() var provider = new CommandProcessorProvider(commandProcessor); InternalBus bus = new(); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, bus, _timeProvider, 1000)); + _channel = new Channel(new(ChannelName),_routingKey, new InMemoryMessageConsumer(_routingKey, bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyCommandMessageMapper()), @@ -88,7 +89,7 @@ public async Task When_a_command_handler_throws_unhandled_exception_Then_message TestCorrelator.GetLogEventsFromCurrentContext() .Should().Contain(x => x.Level == LogEventLevel.Error) .Which.MessageTemplate.Text - .Should().Be("MessagePump: Failed to dispatch message '{Id}' from {ChannelName} on thread # {ManagementThreadId}"); + .Should().Be("MessagePump: Failed to dispatch message '{Id}' from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}"); } } } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_unhandled_exception_Then_message_is_acked_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_unhandled_exception_Then_message_is_acked_async.cs index 9c4e74f74f..2770c4b1b9 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_unhandled_exception_Then_message_is_acked_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_command_handler_throws_unhandled_exception_Then_message_is_acked_async.cs @@ -38,6 +38,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpCommandProcessingExceptionTestsAsync { private const string Topic = "MyCommand"; + private const string ChannelName = "myChannel"; private readonly IAmAMessagePump _messagePump; private readonly Channel _channel; private readonly int _requeueCount = 5; @@ -51,7 +52,7 @@ public MessagePumpCommandProcessingExceptionTestsAsync() InternalBus bus = new(); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, bus, _timeProvider, 1000)); + _channel = new Channel(new(ChannelName),_routingKey, new InMemoryMessageConsumer(_routingKey, bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( null, @@ -92,7 +93,7 @@ public async Task When_a_command_handler_throws_unhandled_exception_Then_message TestCorrelator.GetLogEventsFromCurrentContext() .Should().Contain(x => x.Level == LogEventLevel.Error) .Which.MessageTemplate.Text - .Should().Be("MessagePump: Failed to dispatch message '{Id}' from {ChannelName} on thread # {ManagementThreadId}"); + .Should().Be("MessagePump: Failed to dispatch message '{Id}' from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}"); } } } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_Is_asked_to_connect_a_channel_and_handler_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_Is_asked_to_connect_a_channel_and_handler_async.cs index 49bebaa3cd..4da29e33b0 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_Is_asked_to_connect_a_channel_and_handler_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_Is_asked_to_connect_a_channel_and_handler_async.cs @@ -32,7 +32,7 @@ public MessageDispatcherRoutingAsyncTests() ); messageMapperRegistry.RegisterAsync(); - var connection = new Subscription( + var subscription = new Subscription( new SubscriptionName("test"), noOfPerformers: 1, timeoutInMilliseconds: 1000, @@ -44,14 +44,14 @@ public MessageDispatcherRoutingAsyncTests() _dispatcher = new Dispatcher( _commandProcessor, - new List { connection }, + new List { subscription }, null, messageMapperRegistry, requestContextFactory: new InMemoryRequestContextFactory() ); - var @event = new MyEvent(); - var message = new MyEventMessageMapperAsync().MapToMessageAsync(@event, new() { Topic = connection.RoutingKey }).Result; + var @event = new MyEvent {Data = 4}; + var message = new MyEventMessageMapperAsync().MapToMessageAsync(@event, new() { Topic = _routingKey }).Result; _bus.Enqueue(message); @@ -61,17 +61,18 @@ public MessageDispatcherRoutingAsyncTests() #pragma warning disable xUnit1031 [Fact] - public void When_a_message_dispatcher_is_asked_to_connect_a_channel_and_handler_async() + public async Task When_a_message_dispatcher_is_asked_to_connect_a_channel_and_handler_async() { - Task.Delay(5000).Wait(); + await Task.Delay(5000); + _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - _dispatcher.End().Wait(); - - Assert.Empty(_bus.Stream(_routingKey)); + await _dispatcher.End(); + _dispatcher.State.Should().Be(DispatcherState.DS_STOPPED); _commandProcessor.Observe().Should().NotBeNull(); _commandProcessor.Commands.Should().Contain(ctype => ctype == CommandType.PublishAsync); + Assert.Empty(_bus.Stream(_routingKey)); } public void Dispose() diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_has_a_new_connection_added_while_running.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_has_a_new_connection_added_while_running.cs index bbd8512fc5..c276c4e451 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_has_a_new_connection_added_while_running.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_has_a_new_connection_added_while_running.cs @@ -91,7 +91,7 @@ public async Task When_A_Message_Dispatcher_Has_A_New_Connection_Added_While_Run _bus.Stream(new RoutingKey(TopicName)).Count().Should().Be(0); _dispatcher.State.Should().Be(DispatcherState.DS_RUNNING); _dispatcher.Consumers.Should().HaveCount(2); - _dispatcher.Connections.Should().HaveCount(2); + _dispatcher.Subscriptions.Should().HaveCount(2); } public void Dispose() diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_is_asked_to_connect_a_channel_and_handler.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_is_asked_to_connect_a_channel_and_handler.cs index 87daf78989..1c8601a0cc 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_is_asked_to_connect_a_channel_and_handler.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_is_asked_to_connect_a_channel_and_handler.cs @@ -53,7 +53,7 @@ public MessageDispatcherRoutingTests() null); messageMapperRegistry.Register(); - var connection = new Subscription( + var subscription = new Subscription( new SubscriptionName("test"), noOfPerformers: 1, timeoutInMilliseconds: 1000, @@ -64,7 +64,7 @@ public MessageDispatcherRoutingTests() _dispatcher = new Dispatcher( _commandProcessor, - new List { connection }, + new List { subscription }, messageMapperRegistry, requestContextFactory: new InMemoryRequestContextFactory() ); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_restarts_a_connection_after_all_connections_have_stopped.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_restarts_a_connection_after_all_connections_have_stopped.cs index f6ed42c5dd..46d8609ca2 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_restarts_a_connection_after_all_connections_have_stopped.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_restarts_a_connection_after_all_connections_have_stopped.cs @@ -88,8 +88,8 @@ public DispatcherRestartConnectionTests() _dispatcher.State.Should().Be(DispatcherState.DS_AWAITING); _dispatcher.Receive(); Task.Delay(250).Wait(); - _dispatcher.Shut("test"); - _dispatcher.Shut("newTest"); + _dispatcher.Shut(subscription.Name); + _dispatcher.Shut(newSubscription.Name); Task.Delay(1000).Wait(); _dispatcher.Consumers.Should().HaveCount(0); } @@ -97,7 +97,7 @@ public DispatcherRestartConnectionTests() [Fact] public async Task When_A_Message_Dispatcher_Restarts_A_Connection_After_All_Connections_Have_Stopped() { - _dispatcher.Open("newTest"); + _dispatcher.Open(new SubscriptionName("newTest")); var @event = new MyEvent(); var message = new MyEventMessageMapper().MapToMessage(@event, _publication); _bus.Enqueue(message); @@ -108,7 +108,7 @@ public async Task When_A_Message_Dispatcher_Restarts_A_Connection_After_All_Conn Assert.Empty(_bus.Stream(_routingKey)); _dispatcher.State.Should().Be(DispatcherState.DS_RUNNING); _dispatcher.Consumers.Should().HaveCount(1); - _dispatcher.Connections.Should().HaveCount(2); + _dispatcher.Subscriptions.Should().HaveCount(2); } public void Dispose() diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_starts_multiple_performers.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_starts_multiple_performers.cs index 1ea305c292..a9135a6320 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_starts_multiple_performers.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_dispatcher_starts_multiple_performers.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessageDispatcherMultiplePerformerTests { private const string Topic = "myTopic"; + private const string ChannelName = "myChannel"; private readonly Dispatcher _dispatcher; private readonly InternalBus _bus; @@ -46,7 +47,7 @@ public MessageDispatcherMultiplePerformerTests() _bus = new InternalBus(); var consumer = new InMemoryMessageConsumer(routingKey, _bus, TimeProvider.System, 1000); - IAmAChannel channel = new Channel(Topic, consumer, 6); + IAmAChannel channel = new Channel(new (ChannelName), new(Topic), consumer, 6); IAmACommandProcessor commandProcessor = new SpyCommandProcessor(); var messageMapperRegistry = new MessageMapperRegistry( diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request.cs index ac7e83d1f0..d135cc2999 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request.cs @@ -11,6 +11,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpFailingMessageTranslationTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new RoutingKey(Topic); private readonly FakeTimeProvider _timeProvider = new(); private readonly IAmAMessagePump _messagePump; @@ -21,7 +22,7 @@ public MessagePumpFailingMessageTranslationTests() { SpyRequeueCommandProcessor commandProcessor = new(); var provider = new CommandProcessorProvider(commandProcessor); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new FailingEventMessageMapper()), null); @@ -49,7 +50,7 @@ public async Task When_A_Message_Fails_To_Be_Mapped_To_A_Request () _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - _channel.Stop(); + _channel.Stop(new RoutingKey(Topic)); await Task.WhenAll(task); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_and_the_unacceptable_message_limit_is_reached.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_and_the_unacceptable_message_limit_is_reached.cs index 9c752cd283..2e9519d889 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_and_the_unacceptable_message_limit_is_reached.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_and_the_unacceptable_message_limit_is_reached.cs @@ -34,10 +34,10 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpUnacceptableMessageLimitTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly IAmAMessagePump _messagePump; - private readonly Channel _channel; private readonly FakeTimeProvider _timeProvider; public MessagePumpUnacceptableMessageLimitTests() @@ -45,7 +45,7 @@ public MessagePumpUnacceptableMessageLimitTests() SpyRequeueCommandProcessor commandProcessor = new(); var provider = new CommandProcessorProvider(commandProcessor); _timeProvider = new FakeTimeProvider(); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + Channel channel = new(new (Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new FailingEventMessageMapper()), null); @@ -53,7 +53,7 @@ public MessagePumpUnacceptableMessageLimitTests() _messagePump = new MessagePumpBlocking(provider, messageMapperRegistry, null, new InMemoryRequestContextFactory()) { - Channel = _channel, TimeoutInMilliseconds = 5000, RequeueCount = 3, UnacceptableMessageLimit = 3 + Channel = channel, TimeoutInMilliseconds = 5000, RequeueCount = 3, UnacceptableMessageLimit = 3 }; var unmappableMessage = new Message( diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_and_the_unacceptable_message_limit_is_reached_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_and_the_unacceptable_message_limit_is_reached_async.cs index 218268bb0c..34978a61bb 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_and_the_unacceptable_message_limit_is_reached_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_and_the_unacceptable_message_limit_is_reached_async.cs @@ -34,17 +34,17 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpUnacceptableMessageLimitTestsAsync { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly IAmAMessagePump _messagePump; - private readonly Channel _channel; private readonly FakeTimeProvider _timeProvider = new(); public MessagePumpUnacceptableMessageLimitTestsAsync() { SpyRequeueCommandProcessor commandProcessor = new(); var provider = new CommandProcessorProvider(commandProcessor); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2); + Channel channel = new(new (Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2); var messageMapperRegistry = new MessageMapperRegistry( null, new SimpleMessageMapperFactoryAsync(_ => new FailingEventMessageMapperAsync())); @@ -52,14 +52,14 @@ public MessagePumpUnacceptableMessageLimitTestsAsync() _messagePump = new MessagePumpAsync(provider, messageMapperRegistry, null, new InMemoryRequestContextFactory()) { - Channel = _channel, TimeoutInMilliseconds = 5000, RequeueCount = 3, UnacceptableMessageLimit = 3 + Channel = channel, TimeoutInMilliseconds = 5000, RequeueCount = 3, UnacceptableMessageLimit = 3 }; var unmappableMessage = new Message(new MessageHeader(Guid.NewGuid().ToString(), Topic, MessageType.MT_EVENT), new MessageBody("{ \"Id\" : \"48213ADB-A085-4AFF-A42C-CF8209350CF7\" }")); - _channel.Enqueue(unmappableMessage); - _channel.Enqueue(unmappableMessage); - _channel.Enqueue(unmappableMessage); + channel.Enqueue(unmappableMessage); + channel.Enqueue(unmappableMessage); + channel.Enqueue(unmappableMessage); } [Fact] diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_async.cs index 00d8332bb6..d5c7ed53b8 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_fails_to_be_mapped_to_a_request_async.cs @@ -11,6 +11,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpFailingMessageTranslationTestsAsync { private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -21,7 +22,7 @@ public MessagePumpFailingMessageTranslationTestsAsync() { SpyRequeueCommandProcessor commandProcessor = new(); var provider = new CommandProcessorProvider(commandProcessor); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new(ChannelName), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( null, new SimpleMessageMapperFactoryAsync(_ => new FailingEventMessageMapperAsync())); @@ -44,7 +45,7 @@ public async Task When_A_Message_Fails_To_Be_Mapped_To_A_Request_Should_Ack() _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - _channel.Stop(); + _channel.Stop(new RoutingKey(Topic)); await Task.WhenAll(task); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_is_dispatched_it_should_reach_a_handler.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_is_dispatched_it_should_reach_a_handler.cs index ef0f17a7ec..07e6bb6d2c 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_is_dispatched_it_should_reach_a_handler.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_is_dispatched_it_should_reach_a_handler.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpDispatchTests { private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -61,7 +62,7 @@ public MessagePumpDispatchTests() PipelineBuilder.ClearPipelineCache(); - var channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + var channel = new Channel(new(ChannelName), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory( _ => new MyEventMessageMapper()), @@ -79,7 +80,7 @@ public MessagePumpDispatchTests() ); channel.Enqueue(message); - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); channel.Enqueue(quitMessage); } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_is_dispatched_it_should_reach_a_handler_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_is_dispatched_it_should_reach_a_handler_async.cs index 8060b7c0dd..7a74d8f006 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_is_dispatched_it_should_reach_a_handler_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_message_is_dispatched_it_should_reach_a_handler_async.cs @@ -12,6 +12,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpDispatchAsyncTests { private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -34,7 +35,7 @@ public MessagePumpDispatchAsyncTests() PipelineBuilder.ClearPipelineCache(); - var channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + var channel = new Channel(new(ChannelName), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( null, new SimpleMessageMapperFactoryAsync(_ => new MyEventMessageMapperAsync())); @@ -45,7 +46,7 @@ public MessagePumpDispatchAsyncTests() var message = new Message(new MessageHeader(Guid.NewGuid().ToString(), Topic, MessageType.MT_EVENT), new MessageBody(JsonSerializer.Serialize(_myEvent))); channel.Enqueue(message); - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); channel.Enqueue(quitMessage); } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_count_threshold_for_commands_has_been_reached.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_count_threshold_for_commands_has_been_reached.cs index 7bfcfc06a7..0538ea579c 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_count_threshold_for_commands_has_been_reached.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_count_threshold_for_commands_has_been_reached.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpCommandRequeueCountThresholdTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -48,7 +49,7 @@ public MessagePumpCommandRequeueCountThresholdTests() { _commandProcessor = new SpyRequeueCommandProcessor(); var provider = new CommandProcessorProvider(_commandProcessor); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new(Channel) ,_routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyCommandMessageMapper()), null); @@ -74,7 +75,7 @@ public async Task When_A_Requeue_Count_Threshold_For_Commands_Has_Been_Reached() _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); _channel.Enqueue(quitMessage); await Task.WhenAll(task); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_count_threshold_for_events_has_been_reached.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_count_threshold_for_events_has_been_reached.cs index e0ba491f4e..f23c471d19 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_count_threshold_for_events_has_been_reached.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_count_threshold_for_events_has_been_reached.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpEventRequeueCountThresholdTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -48,7 +49,7 @@ public MessagePumpEventRequeueCountThresholdTests() { _commandProcessor = new SpyRequeueCommandProcessor(); var provider = new CommandProcessorProvider(_commandProcessor); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyEventMessageMapper()), null); @@ -77,7 +78,7 @@ public async Task When_A_Requeue_Count_Threshold_For_Events_Has_Been_Reached() _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); _channel.Enqueue(quitMessage); await Task.WhenAll(task); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_of_command_exception_is_thrown.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_of_command_exception_is_thrown.cs index 8a90dfee4c..b87c50a86a 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_of_command_exception_is_thrown.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_of_command_exception_is_thrown.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpCommandRequeueTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -48,7 +49,7 @@ public MessagePumpCommandRequeueTests() { _commandProcessor = new SpyRequeueCommandProcessor(); var provider = new CommandProcessorProvider(_commandProcessor); - Channel channel = new(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2); + Channel channel = new(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyCommandMessageMapper()), null); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_of_event_exception_is_thrown.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_of_event_exception_is_thrown.cs index 03bc85c839..9e47b052ca 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_of_event_exception_is_thrown.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_a_requeue_of_event_exception_is_thrown.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpEventRequeueTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -47,7 +48,7 @@ public MessagePumpEventRequeueTests() { _commandProcessor = new SpyRequeueCommandProcessor(); var provider = new CommandProcessorProvider(_commandProcessor); - Channel channel = new(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2); + Channel channel = new(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 2); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyEventMessageMapper()), null); @@ -67,7 +68,7 @@ public MessagePumpEventRequeueTests() channel.Enqueue(message1); channel.Enqueue(message2); - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); channel.Enqueue(quitMessage); } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_a_defer_message_Then_message_is_requeued_until_rejected.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_a_defer_message_Then_message_is_requeued_until_rejected.cs index 3c3e761877..2ff6c605e2 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_a_defer_message_Then_message_is_requeued_until_rejected.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_a_defer_message_Then_message_is_requeued_until_rejected.cs @@ -35,6 +35,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpEventProcessingDeferMessageActionTests { private const string Topic = "MyEvent"; + private const string Channel = "MyChannel"; private readonly IAmAMessagePump _messagePump; private readonly int _requeueCount = 5; private readonly InternalBus _bus; @@ -51,7 +52,7 @@ public MessagePumpEventProcessingDeferMessageActionTests() _bus = new InternalBus(); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyEventMessageMapper()), @@ -81,7 +82,7 @@ public async Task When_an_event_handler_throws_a_defer_message_Then_message_is_r _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); _channel.Enqueue(quitMessage); await Task.WhenAll(task); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_a_defer_message_Then_message_is_requeued_until_rejectedAsync.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_a_defer_message_Then_message_is_requeued_until_rejectedAsync.cs index da683652bb..4f521de950 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_a_defer_message_Then_message_is_requeued_until_rejectedAsync.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_a_defer_message_Then_message_is_requeued_until_rejectedAsync.cs @@ -35,6 +35,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpEventProcessingDeferMessageActionTestsAsync { private const string Topic = "MyEvent"; + private const string Channel = "MyChannel"; private readonly IAmAMessagePump _messagePump; private readonly int _requeueCount = 5; private readonly RoutingKey _routingKey = new(Topic); @@ -48,7 +49,7 @@ public MessagePumpEventProcessingDeferMessageActionTestsAsync() var commandProcessorProvider = new CommandProcessorProvider(commandProcessor); _bus = new InternalBus(); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new (Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( null, @@ -74,7 +75,7 @@ public async Task When_an_event_handler_throws_a_defer_message_Then_message_is_r _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); _channel.Enqueue(quitMessage); await Task.WhenAll(task); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_unhandled_exception_Then_message_is_acked.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_unhandled_exception_Then_message_is_acked.cs index 4ceff4c88e..5442aadc66 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_unhandled_exception_Then_message_is_acked.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_unhandled_exception_Then_message_is_acked.cs @@ -38,6 +38,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpEventProcessingExceptionTests { private const string Topic = "MyEvent"; + private const string Channel = "MyChannel"; private readonly IAmAMessagePump _messagePump; private readonly int _requeueCount = 5; private readonly RoutingKey _routingKey = new(Topic); @@ -52,7 +53,7 @@ public MessagePumpEventProcessingExceptionTests() _bus = new InternalBus(); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new (Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyEventMessageMapper()), null); @@ -94,7 +95,7 @@ public async Task When_an_event_handler_throws_unhandled_exception_Then_message_ .Should().Contain(x => x.Level == LogEventLevel.Error) .Which.MessageTemplate.Text .Should().Be( - "MessagePump: Failed to dispatch message {Id} from {ChannelName} on thread # {ManagementThreadId}"); + "MessagePump: Failed to dispatch message {Id} from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}"); } } } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_unhandled_exception_Then_message_is_acked_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_unhandled_exception_Then_message_is_acked_async.cs index b010398a0f..ad55823186 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_unhandled_exception_Then_message_is_acked_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_event_handler_throws_unhandled_exception_Then_message_is_acked_async.cs @@ -38,6 +38,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpEventProcessingExceptionTestsAsync { private const string Topic = "MyEvent"; + private const string Channel = "MyChannel"; private readonly IAmAMessagePump _messagePump; private readonly Channel _channel; private readonly SpyExceptionCommandProcessor _commandProcessor; @@ -53,7 +54,7 @@ public MessagePumpEventProcessingExceptionTestsAsync() _bus = new InternalBus(); - _channel = new Channel(_routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new (Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( null, @@ -92,7 +93,7 @@ public async Task When_an_event_handler_throws_unhandled_exception_Then_message_ .Should().Contain(x => x.Level == LogEventLevel.Error) .Which.MessageTemplate.Text .Should().Be( - "MessagePump: Failed to dispatch message {Id} from {ChannelName} on thread # {ManagementThreadId}"); + "MessagePump: Failed to dispatch message {Id} from {ChannelName} with {RoutingKey} on thread # {ManagementThreadId}"); } } } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_is_recieved.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_is_recieved.cs index 24456d6000..921a63c2dc 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_is_recieved.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_is_recieved.cs @@ -36,11 +36,12 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpUnacceptableMessageTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly IAmAMessagePump _messagePump; private readonly Channel _channel; private readonly InternalBus _bus; private readonly RoutingKey _routingKey = new RoutingKey(Topic); - private FakeTimeProvider _timeProvider = new FakeTimeProvider(); + private readonly FakeTimeProvider _timeProvider = new FakeTimeProvider(); public MessagePumpUnacceptableMessageTests() { @@ -49,7 +50,7 @@ public MessagePumpUnacceptableMessageTests() _bus = new InternalBus(); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new (Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyEventMessageMapper()), @@ -78,7 +79,7 @@ public async Task When_An_Unacceptable_Message_Is_Recieved() _timeProvider.Advance(TimeSpan.FromSeconds(2)); //This will trigger requeue of not acked/rejected messages - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); _channel.Enqueue(quitMessage); await Task.WhenAll(new[] { task }); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_is_recieved_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_is_recieved_async.cs index 7919da3a54..b971f2b93e 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_is_recieved_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_is_recieved_async.cs @@ -36,6 +36,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class AsyncMessagePumpUnacceptableMessageTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly IAmAMessagePump _messagePump; private readonly Channel _channel; private readonly InternalBus _bus; @@ -49,7 +50,7 @@ public AsyncMessagePumpUnacceptableMessageTests() _bus = new InternalBus(); - _channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + _channel = new Channel(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( null, diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_limit_is_reached.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_limit_is_reached.cs index 1d3acc5be6..6f18f43496 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_limit_is_reached.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_limit_is_reached.cs @@ -36,10 +36,11 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpUnacceptableMessageLimitBreachedTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly IAmAMessagePump _messagePump; private readonly InternalBus _bus; private readonly FakeTimeProvider _timeProvider = new(); - private readonly RoutingKey _routingKey = new RoutingKey(Topic); + private readonly RoutingKey _routingKey = new(Topic); public MessagePumpUnacceptableMessageLimitBreachedTests() { @@ -48,7 +49,7 @@ public MessagePumpUnacceptableMessageLimitBreachedTests() _bus = new InternalBus(); - var channel = new Channel(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 3); + var channel = new Channel(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 3); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyEventMessageMapper()), null); diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_limit_is_reached_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_limit_is_reached_async.cs index d2ba32f7b4..4225989e06 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_limit_is_reached_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_an_unacceptable_message_limit_is_reached_async.cs @@ -35,6 +35,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpUnacceptableMessageLimitBreachedAsyncTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly IAmAMessagePump _messagePump; private readonly InternalBus _bus = new(); private readonly RoutingKey _routingKey = new(Topic); @@ -45,7 +46,7 @@ public MessagePumpUnacceptableMessageLimitBreachedAsyncTests() SpyRequeueCommandProcessor commandProcessor = new(); var provider = new CommandProcessorProvider(commandProcessor); - Channel channel = new(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 3); + Channel channel = new(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), 3); var messageMapperRegistry = new MessageMapperRegistry( null, diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_reading_a_message_from_a_channel_pump_out_to_command_processor.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_reading_a_message_from_a_channel_pump_out_to_command_processor.cs index a9130dd3ba..6d85722e64 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_reading_a_message_from_a_channel_pump_out_to_command_processor.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_reading_a_message_from_a_channel_pump_out_to_command_processor.cs @@ -36,6 +36,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpToCommandProcessorTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new (); private readonly FakeTimeProvider _timeProvider = new(); @@ -47,7 +48,7 @@ public MessagePumpToCommandProcessorTests() { _commandProcessor = new SpyCommandProcessor(); var provider = new CommandProcessorProvider(_commandProcessor); - Channel channel = new(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + Channel channel = new(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messagerMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyEventMessageMapper()), null); @@ -60,7 +61,7 @@ public MessagePumpToCommandProcessorTests() var message = new Message(new MessageHeader(Guid.NewGuid().ToString(), Topic, MessageType.MT_EVENT), new MessageBody(JsonSerializer.Serialize(_event, JsonSerialisationOptions.Options))); channel.Enqueue(message); - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(_routingKey); channel.Enqueue(quitMessage); } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_reading_a_message_from_a_channel_pump_out_to_command_processor_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_reading_a_message_from_a_channel_pump_out_to_command_processor_async.cs index c294b00e6f..8d38da1917 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_reading_a_message_from_a_channel_pump_out_to_command_processor_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_reading_a_message_from_a_channel_pump_out_to_command_processor_async.cs @@ -36,6 +36,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class MessagePumpToCommandProcessorTestsAsync { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -47,7 +48,7 @@ public MessagePumpToCommandProcessorTestsAsync() { _commandProcessor = new SpyCommandProcessor(); var provider = new CommandProcessorProvider(_commandProcessor); - Channel channel = new(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + Channel channel = new(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messagerMapperRegistry = new MessageMapperRegistry( null, new SimpleMessageMapperFactoryAsync(_ => new MyEventMessageMapperAsync())); @@ -60,7 +61,7 @@ public MessagePumpToCommandProcessorTestsAsync() var message = new Message(new MessageHeader(Guid.NewGuid().ToString(), Topic, MessageType.MT_EVENT), new MessageBody(JsonSerializer.Serialize(_event, JsonSerialisationOptions.Options))); channel.Enqueue(message); - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(_routingKey); channel.Enqueue(quitMessage); } diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_running_a_message_pump_on_a_thread_should_be_able_to_stop.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_running_a_message_pump_on_a_thread_should_be_able_to_stop.cs index 9ea11a4a59..e87f2ef9e1 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_running_a_message_pump_on_a_thread_should_be_able_to_stop.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_running_a_message_pump_on_a_thread_should_be_able_to_stop.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class PerformerCanStopTests { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -46,7 +47,7 @@ public PerformerCanStopTests() { SpyCommandProcessor commandProcessor = new(); var provider = new CommandProcessorProvider(commandProcessor); - Channel channel = new(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + Channel channel = new(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyEventMessageMapper()), null); @@ -65,7 +66,7 @@ public PerformerCanStopTests() Performer performer = new(channel, messagePump); _performerTask = performer.Run(); - performer.Stop(); + performer.Stop(new RoutingKey(Topic)); } #pragma warning disable xUnit1031 diff --git a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_running_a_message_pump_on_a_thread_should_be_able_to_stop_async.cs b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_running_a_message_pump_on_a_thread_should_be_able_to_stop_async.cs index 1c86415d7d..a37e74d086 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_running_a_message_pump_on_a_thread_should_be_able_to_stop_async.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessageDispatch/When_running_a_message_pump_on_a_thread_should_be_able_to_stop_async.cs @@ -37,6 +37,7 @@ namespace Paramore.Brighter.Core.Tests.MessageDispatch public class PerformerCanStopTestsAsync { private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly RoutingKey _routingKey = new(Topic); private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); @@ -46,7 +47,7 @@ public PerformerCanStopTestsAsync() { SpyCommandProcessor commandProcessor = new(); var provider = new CommandProcessorProvider(commandProcessor); - Channel channel = new(Topic, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + Channel channel = new(new(Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); var messageMapperRegistry = new MessageMapperRegistry( null, new SimpleMessageMapperFactoryAsync(_ => new MyEventMessageMapperAsync())); @@ -65,7 +66,7 @@ public PerformerCanStopTestsAsync() Performer performer = new(channel, messagePump); _performerTask = performer.Run(); - performer.Stop(); + performer.Stop(new RoutingKey(Topic)); } #pragma warning disable xUnit1031 diff --git a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_A_Stop_Message_Is_Added_To_A_Channel.cs b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_A_Stop_Message_Is_Added_To_A_Channel.cs index 063cf08043..f872895e7e 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_A_Stop_Message_Is_Added_To_A_Channel.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_A_Stop_Message_Is_Added_To_A_Channel.cs @@ -30,6 +30,7 @@ namespace Paramore.Brighter.Core.Tests.MessagingGateway public class ChannelStopTests { private const string Topic = "myTopic"; + private const string ChannelName = "myChannel"; private readonly IAmAChannel _channel; private readonly InternalBus _bus; @@ -38,7 +39,7 @@ public ChannelStopTests() _bus = new InternalBus(); IAmAMessageConsumer gateway = new InMemoryMessageConsumer(new RoutingKey(Topic), _bus, TimeProvider.System, 1000); - _channel = new Channel(Topic, gateway); + _channel = new Channel(new(ChannelName), new(Topic), gateway); Message sentMessage = new( new MessageHeader(Guid.NewGuid().ToString(), Topic, MessageType.MT_EVENT), @@ -46,7 +47,7 @@ public ChannelStopTests() _bus.Enqueue(sentMessage); - _channel.Stop(); + _channel.Stop(new RoutingKey(Topic)); } diff --git a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Acknowledge_Is_Called_On_A_Channel.cs b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Acknowledge_Is_Called_On_A_Channel.cs index db600069e9..ef17d427e7 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Acknowledge_Is_Called_On_A_Channel.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Acknowledge_Is_Called_On_A_Channel.cs @@ -34,13 +34,14 @@ public class ChannelAcknowledgeTests private readonly InternalBus _bus = new InternalBus(); private readonly FakeTimeProvider _fakeTimeProvider = new FakeTimeProvider(); private const string Topic = "myTopic"; + private const string ChannelName = "myChannel"; public ChannelAcknowledgeTests() { const int ackTimeoutMs = 1000; IAmAMessageConsumer gateway = new InMemoryMessageConsumer(new RoutingKey(Topic), _bus, _fakeTimeProvider, ackTimeoutMs); - _channel = new Channel(Topic, gateway); + _channel = new Channel(new (ChannelName), new(Topic), gateway); var sentMessage = new Message( new MessageHeader(Guid.NewGuid().ToString(), Topic, MessageType.MT_EVENT), diff --git a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Listening_To_Messages_On_A_Channel.cs b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Listening_To_Messages_On_A_Channel.cs index e6d09d4c9b..59198f57e5 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Listening_To_Messages_On_A_Channel.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Listening_To_Messages_On_A_Channel.cs @@ -33,6 +33,7 @@ public class ChannelMessageReceiveTests { private readonly IAmAChannel _channel; private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _fakeTimeProvider = new(); private readonly Message _sentMessage; @@ -41,7 +42,7 @@ public ChannelMessageReceiveTests() { IAmAMessageConsumer gateway = new InMemoryMessageConsumer(new RoutingKey(Topic), _bus, _fakeTimeProvider, 1000); - _channel = new Channel(Topic, gateway); + _channel = new Channel(new(ChannelName),new(Topic), gateway); _sentMessage = new Message( new MessageHeader(Guid.NewGuid().ToString(), Topic, MessageType.MT_EVENT), diff --git a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_No_Acknowledge_Is_Called_On_A_Channel.cs b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_No_Acknowledge_Is_Called_On_A_Channel.cs index bf9c45e74f..64c5333162 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_No_Acknowledge_Is_Called_On_A_Channel.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_No_Acknowledge_Is_Called_On_A_Channel.cs @@ -34,12 +34,13 @@ public class ChannelNackTests private readonly InternalBus _bus = new(); private readonly FakeTimeProvider _timeProvider = new(); private const string Topic = "myTopic"; + private const string ChannelName = "myChannel"; public ChannelNackTests() { IAmAMessageConsumer gateway = new InMemoryMessageConsumer(new RoutingKey(Topic), _bus, _timeProvider, 1000); - _channel = new Channel(Topic, gateway); + _channel = new Channel(new(ChannelName), new(Topic), gateway); var sentMessage = new Message( new MessageHeader(Guid.NewGuid().ToString(), Topic, MessageType.MT_EVENT), diff --git a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Requeuing_A_Message_With_No_Delay.cs b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Requeuing_A_Message_With_No_Delay.cs index 35560a736e..3f2e89312e 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Requeuing_A_Message_With_No_Delay.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_Requeuing_A_Message_With_No_Delay.cs @@ -33,13 +33,14 @@ public class ChannelRequeueWithoutDelayTest { private readonly IAmAChannel _channel; private const string Topic = "myTopic"; + private const string ChannelName = "myChannel"; private readonly InternalBus _bus = new(); public ChannelRequeueWithoutDelayTest() { var consumer = new InMemoryMessageConsumer(new RoutingKey(Topic), _bus, new FakeTimeProvider(), 1000); - _channel = new Channel(Topic, consumer); + _channel = new Channel(new(ChannelName),new (Topic), consumer); var sentMessage = new Message( new MessageHeader(Guid.NewGuid().ToString(), Topic, MessageType.MT_EVENT), diff --git a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_The_Buffer_Is_Not_Empty_Read_From_That_Before_Receiving.cs b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_The_Buffer_Is_Not_Empty_Read_From_That_Before_Receiving.cs index b2078a0623..ee9b249839 100644 --- a/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_The_Buffer_Is_Not_Empty_Read_From_That_Before_Receiving.cs +++ b/tests/Paramore.Brighter.Core.Tests/MessagingGateway/When_The_Buffer_Is_Not_Empty_Read_From_That_Before_Receiving.cs @@ -11,12 +11,13 @@ public class BufferedChannelTests private readonly IAmAMessageConsumer _gateway; private const int BufferLimit = 2; private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; private readonly InternalBus _bus = new(); public BufferedChannelTests() { _gateway = new InMemoryMessageConsumer(new RoutingKey(Topic), _bus,new FakeTimeProvider(), 1000); - _channel = new Channel(Topic, _gateway, BufferLimit); + _channel = new Channel(new (Channel), new (Topic), _gateway, BufferLimit); } [Fact] @@ -79,13 +80,13 @@ public void When_the_buffer_is_replenished_allow_up_to_the_maximum_number_of_new [Fact] public void When_we_try_to_create_with_too_small_a_buffer() { - Assert.Throws(() => new Channel("test", _gateway, 0)); + Assert.Throws(() => new Channel(new(Channel), new (Topic), _gateway, 0)); } [Fact] public void When_we_try_to_create_with_too_large_a_buffer() { - Assert.Throws(() => new Channel("test", _gateway, 11)); + Assert.Throws(() => new Channel(new(Channel), new (Topic), _gateway, 11)); } } } diff --git a/tests/Paramore.Brighter.Core.Tests/Observability/CommandProcessor/Clear/When_Clearing_A_Message_A_Span_Is_Exported.cs b/tests/Paramore.Brighter.Core.Tests/Observability/CommandProcessor/Clear/When_Clearing_A_Message_A_Span_Is_Exported.cs index 57440ae97c..3b31cf3a35 100644 --- a/tests/Paramore.Brighter.Core.Tests/Observability/CommandProcessor/Clear/When_Clearing_A_Message_A_Span_Is_Exported.cs +++ b/tests/Paramore.Brighter.Core.Tests/Observability/CommandProcessor/Clear/When_Clearing_A_Message_A_Span_Is_Exported.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Linq; using System.Text.Json; -using System.Threading.Tasks; using System.Transactions; using FluentAssertions; using Microsoft.Extensions.Time.Testing; @@ -25,10 +24,9 @@ public class CommandProcessorClearObservabilityTests private readonly List _exportedActivities; private readonly TracerProvider _traceProvider; private readonly Brighter.CommandProcessor _commandProcessor; - private readonly InMemoryOutbox _outbox; private readonly string _topic; private readonly InMemoryProducer _producer; - private InternalBus _internalBus = new InternalBus(); + private readonly InternalBus _internalBus = new(); public CommandProcessorClearObservabilityTests() { @@ -57,7 +55,7 @@ public CommandProcessorClearObservabilityTests() var timeProvider = new FakeTimeProvider(); var tracer = new BrighterTracer(timeProvider); - _outbox = new InMemoryOutbox(timeProvider){Tracer = tracer}; + InMemoryOutbox outbox = new(timeProvider){Tracer = tracer}; var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory((_) => new MyEventMessageMapper()), @@ -87,7 +85,7 @@ public CommandProcessorClearObservabilityTests() new EmptyMessageTransformerFactory(), new EmptyMessageTransformerFactoryAsync(), tracer, - _outbox, + outbox, maxOutStandingMessages: -1 ); @@ -150,7 +148,7 @@ public void When_Clearing_A_Message_A_Span_Is_Exported() depositEvent.Tags.Any(a => a.Key == BrighterSemanticConventions.MessageId && (string)a.Value == message.Id ).Should().BeTrue(); depositEvent.Tags.Any(a => a.Key == BrighterSemanticConventions.MessagingDestination && (string)a.Value == message.Header.Topic).Should().BeTrue(); depositEvent.Tags.Any(a => a is { Value: not null, Key: BrighterSemanticConventions.MessageBodySize } && (int)a.Value == message.Body.Bytes.Length).Should().BeTrue(); - depositEvent.Tags.Any(a => a.Key == BrighterSemanticConventions.MessageBody && (string)a.Value == message.Body.Value.ToString()).Should().BeTrue(); + depositEvent.Tags.Any(a => a.Key == BrighterSemanticConventions.MessageBody && (string)a.Value == message.Body.Value).Should().BeTrue(); depositEvent.Tags.Any(a => a.Key == BrighterSemanticConventions.MessageType && (string)a.Value == message.Header.MessageType.ToString()).Should().BeTrue(); depositEvent.Tags.Any(a => a.Key == BrighterSemanticConventions.MessagingDestinationPartitionId && (string)a.Value == message.Header.PartitionKey).Should().BeTrue(); depositEvent.Tags.Any(a => a.Key == BrighterSemanticConventions.MessageHeaders && (string)a.Value == JsonSerializer.Serialize(message.Header)).Should().BeTrue(); @@ -193,7 +191,7 @@ public void When_Clearing_A_Message_A_Span_Is_Exported() produceEvent.Tags.Any(t => t.Key == BrighterSemanticConventions.MessageType && (string)t.Value == message.Header.MessageType.ToString()).Should().BeTrue(); produceEvent.Tags.Any(t => t.Key == BrighterSemanticConventions.MessageHeaders && (string)t.Value == JsonSerializer.Serialize(message.Header)).Should().BeTrue(); produceEvent.Tags.Any(t => t is { Value: not null, Key: BrighterSemanticConventions.MessageBodySize } && (int)t.Value == message.Body.Bytes.Length).Should().BeTrue(); - produceEvent.Tags.Any(t => t.Key == BrighterSemanticConventions.MessageBody && (string)t.Value == message.Body.Value.ToString()).Should().BeTrue(); + produceEvent.Tags.Any(t => t.Key == BrighterSemanticConventions.MessageBody && (string)t.Value == message.Body.Value).Should().BeTrue(); produceEvent.Tags.Any(t => t.Key == BrighterSemanticConventions.ConversationId && (string)t.Value == message.Header.CorrelationId).Should().BeTrue(); produceEvent.Tags.Any(t => t.Key == BrighterSemanticConventions.CeMessageId && (string)t.Value == message.Id).Should().BeTrue(); diff --git a/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_A_Message_Is_Dispatched_It_Should_Begin_A_Span.cs b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_A_Message_Is_Dispatched_It_Should_Begin_A_Span.cs new file mode 100644 index 0000000000..037ace4e2b --- /dev/null +++ b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_A_Message_Is_Dispatched_It_Should_Begin_A_Span.cs @@ -0,0 +1,160 @@ +#region Licence +/* The MIT License (MIT) +Copyright © 2014 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; +using FluentAssertions; +using Microsoft.Extensions.Time.Testing; +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Paramore.Brighter.Core.Tests.CommandProcessors.TestDoubles; +using Paramore.Brighter.Observability; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.Core.Tests.Observability.MessageDispatch +{ + public class MessagePumpDispatchObservabilityTests + { + private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; + private readonly RoutingKey _routingKey = new(Topic); + private readonly InternalBus _bus = new(); + private readonly FakeTimeProvider _timeProvider = new(); + private readonly IAmAMessagePump _messagePump; + private readonly MyEvent _myEvent = new(); + private readonly IDictionary _receivedMessages = new Dictionary(); + private readonly List _exportedActivities; + private readonly TracerProvider _traceProvider; + private readonly Message _message; + + public MessagePumpDispatchObservabilityTests() + { + var builder = Sdk.CreateTracerProviderBuilder(); + _exportedActivities = new List(); + + _traceProvider = builder + .AddSource("Paramore.Brighter.Tests", "Paramore.Brighter") + .ConfigureResource(r => r.AddService("in-memory-tracer")) + .AddInMemoryExporter(_exportedActivities) + .Build(); + + Brighter.CommandProcessor.ClearServiceBus(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + var handlerFactory = new SimpleHandlerFactorySync(_ => new MyEventHandler(_receivedMessages)); + + var timeProvider = new FakeTimeProvider(); + var tracer = new BrighterTracer(timeProvider); + var instrumentationOptions = InstrumentationOptions.All; + + var commandProcessor = new Brighter.CommandProcessor( + subscriberRegistry, + handlerFactory, + new InMemoryRequestContextFactory(), + new PolicyRegistry(), + tracer: tracer, + instrumentationOptions: instrumentationOptions); + + var provider = new CommandProcessorProvider(commandProcessor); + + PipelineBuilder.ClearPipelineCache(); + + var channel = new Channel(new(ChannelName),_routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory( + _ => new MyEventMessageMapper()), + null); + messageMapperRegistry.Register(); + + _messagePump = new MessagePumpBlocking(provider, messageMapperRegistry, null, + new InMemoryRequestContextFactory(), tracer, instrumentationOptions) + { + Channel = channel, TimeoutInMilliseconds = 5000 + }; + + var externalActivity = new ActivitySource("Paramore.Brighter.Tests").StartActivity("MessagePumpSpanTests"); + + var header = new MessageHeader(_myEvent.Id, Topic, MessageType.MT_EVENT) + { + TraceParent = externalActivity?.Id, TraceState = externalActivity?.TraceStateString + }; + + externalActivity?.Stop(); + + _message = new Message( + header, + new MessageBody(JsonSerializer.Serialize(_myEvent, JsonSerialisationOptions.Options)) + ); + + channel.Enqueue(_message); + var quitMessage = MessageFactory.CreateQuitMessage(new RoutingKey(Topic)); + channel.Enqueue(quitMessage); + } + + [Fact] + public void When_a_message_is_dispatched_it_should_begin_a_span() + { + _messagePump.Run(); + + _traceProvider.ForceFlush(); + + _exportedActivities.Count.Should().Be(6); + _exportedActivities.Any(a => a.Source.Name == "Paramore.Brighter").Should().BeTrue(); + + //there should be a span for each message received by a pump + var createActivity = _exportedActivities.FirstOrDefault(a => + a.DisplayName == $"{_message.Header.Topic} {MessagePumpSpanOperation.Receive.ToSpanName()}" + && a.TagObjects.Any(to => to is { Value: not null, Key: BrighterSemanticConventions.MessageType } && Enum.Parse(to.Value.ToString() ?? string.Empty) == MessageType.MT_EVENT) + ); + createActivity.Should().NotBeNull(); + createActivity!.ParentId.Should().Be(_message.Header.TraceParent); + createActivity.Tags.Any(t => t is { Key: BrighterSemanticConventions.MessagingOperationType, Value: "receive" }).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.MessagingDestination && t.Value == _message.Header.Topic).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.MessagingDestinationPartitionId && t.Value == _message.Header.PartitionKey).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.MessageId && t.Value == _message.Id).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.MessageType && t.Value == _message.Header.MessageType.ToString()).Should().BeTrue(); + createActivity.TagObjects.Any(t => t.Value != null && t.Key == BrighterSemanticConventions.MessageBodySize && Convert.ToInt32(t.Value) == _message.Body.Bytes.Length).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.MessageBody && t.Value == _message.Body.Value).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.MessageHeaders && t.Value == JsonSerializer.Serialize(_message.Header)).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.ConversationId && t.Value == _message.Header.CorrelationId).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.MessagingSystem && t.Value == MessagingSystem.InternalBus.ToMessagingSystemName()).Should().BeTrue(); + createActivity.TagObjects.Any(t => t.Value != null && t.Key == BrighterSemanticConventions.CeSource && ((Uri)(t.Value)) == _message.Header.Source).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.CeSubject && t.Value == _message.Header.Subject).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.CeVersion && t.Value == "1.0").Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.CeType && t.Value == _message.Header.Type).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.CeMessageId && t.Value == _message.Id).Should().BeTrue(); + createActivity.TagObjects.Any(t => t.Key == BrighterSemanticConventions.HandledCount && Convert.ToInt32(t.Value) == _message.Header.HandledCount).Should().BeTrue(); + createActivity.Tags.Any(t => t.Key == BrighterSemanticConventions.ReplyTo && t.Value == _message.Header.ReplyTo).Should().BeTrue(); + + } + } +} diff --git a/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Are_No_Messages_Close_The_Span.cs b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Are_No_Messages_Close_The_Span.cs new file mode 100644 index 0000000000..b37b8ad49c --- /dev/null +++ b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Are_No_Messages_Close_The_Span.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using FluentAssertions; +using Microsoft.Extensions.Time.Testing; +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Paramore.Brighter.Core.Tests.CommandProcessors.TestDoubles; +using Paramore.Brighter.Observability; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.Core.Tests.Observability.MessageDispatch; + +public class MessagePumpEmptyQueueOberservabilityTests +{ + private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; + private readonly RoutingKey _routingKey = new(Topic); + private readonly InternalBus _bus = new(); + private readonly FakeTimeProvider _timeProvider = new(); + private readonly IAmAMessagePump _messagePump; + private readonly IDictionary _receivedMessages = new Dictionary(); + private readonly List _exportedActivities; + private readonly TracerProvider _traceProvider; + private readonly Message _message; + + public MessagePumpEmptyQueueOberservabilityTests() + { + var builder = Sdk.CreateTracerProviderBuilder(); + _exportedActivities = new List(); + + _traceProvider = builder + .AddSource("Paramore.Brighter.Tests", "Paramore.Brighter") + .ConfigureResource(r => r.AddService("in-memory-tracer")) + .AddInMemoryExporter(_exportedActivities) + .Build(); + + Brighter.CommandProcessor.ClearServiceBus(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + var handlerFactory = new SimpleHandlerFactorySync(_ => new MyEventHandler(_receivedMessages)); + + var timeProvider = new FakeTimeProvider(); + var tracer = new BrighterTracer(timeProvider); + var instrumentationOptions = InstrumentationOptions.All; + + var commandProcessor = new Brighter.CommandProcessor( + subscriberRegistry, + handlerFactory, + new InMemoryRequestContextFactory(), + new PolicyRegistry(), + tracer: tracer, + instrumentationOptions: instrumentationOptions); + + var provider = new CommandProcessorProvider(commandProcessor); + + PipelineBuilder.ClearPipelineCache(); + + Channel channel = new(new(ChannelName),_routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory( + _ => new MyEventMessageMapper()), + null); + messageMapperRegistry.Register(); + + _messagePump = new MessagePumpBlocking(provider, messageMapperRegistry, null, + new InMemoryRequestContextFactory(), tracer, instrumentationOptions) + { + Channel = channel, TimeoutInMilliseconds = 5000, EmptyChannelDelay = 1000 + }; + + //in theory the message pump should see this from the consumer when the queue is empty + //we are just forcing this to run exactly once, before we quit. + _message = MessageFactory.CreateEmptyMessage(_routingKey); + + channel.Enqueue(_message); + + var quitMessage = MessageFactory.CreateQuitMessage(_routingKey); + channel.Enqueue(quitMessage); + } + + [Fact] + public void When_There_Are_No_Messages_Close_The_Span() + { + _messagePump.Run(); + + _traceProvider.ForceFlush(); + + _exportedActivities.Count.Should().Be(3); + _exportedActivities.Any(a => a.Source.Name == "Paramore.Brighter").Should().BeTrue(); + + var emptyMessageActivity = _exportedActivities.FirstOrDefault(a => + a.DisplayName == $"{_message.Header.Topic} {MessagePumpSpanOperation.Receive.ToSpanName()}" + && a.TagObjects.Any(t => + t is { Value: not null, Key: BrighterSemanticConventions.MessageType } + && Enum.Parse(t.Value.ToString()) == MessageType.MT_NONE + ) + ); + + emptyMessageActivity.Should().NotBeNull(); + emptyMessageActivity!.Status.Should().Be(ActivityStatusCode.Ok); + + } +} diff --git a/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_A_BrokenCircuit_Channel_Failure_Close_The_Span.cs b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_A_BrokenCircuit_Channel_Failure_Close_The_Span.cs new file mode 100644 index 0000000000..4444ece7bf --- /dev/null +++ b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_A_BrokenCircuit_Channel_Failure_Close_The_Span.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; +using FluentAssertions; +using Microsoft.Extensions.Time.Testing; +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Paramore.Brighter.Core.Tests.CommandProcessors.TestDoubles; +using Paramore.Brighter.Core.Tests.MessageDispatch.TestDoubles; +using Paramore.Brighter.Observability; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.Core.Tests.Observability.MessageDispatch; + +public class MessagePumpBrokenCircuitChannelFailureOberservabilityTests +{ + private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; + private readonly RoutingKey _routingKey = new(Topic); + private readonly InternalBus _bus = new(); + private readonly FakeTimeProvider _timeProvider = new(); + private readonly IAmAMessagePump _messagePump; + private readonly IDictionary _receivedMessages = new Dictionary(); + private readonly List _exportedActivities; + private readonly TracerProvider _traceProvider; + private readonly MyEvent _myEvent = new(); + private readonly Message _message; + + public MessagePumpBrokenCircuitChannelFailureOberservabilityTests() + { + var builder = Sdk.CreateTracerProviderBuilder(); + _exportedActivities = new List(); + + _traceProvider = builder + .AddSource("Paramore.Brighter.Tests", "Paramore.Brighter") + .ConfigureResource(r => r.AddService("in-memory-tracer")) + .AddInMemoryExporter(_exportedActivities) + .Build(); + + Brighter.CommandProcessor.ClearServiceBus(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + var handlerFactory = new SimpleHandlerFactorySync(_ => new MyEventHandler(_receivedMessages)); + + var timeProvider = new FakeTimeProvider(); + var tracer = new BrighterTracer(timeProvider); + var instrumentationOptions = InstrumentationOptions.All; + + var commandProcessor = new Brighter.CommandProcessor( + subscriberRegistry, + handlerFactory, + new InMemoryRequestContextFactory(), + new PolicyRegistry(), + tracer: tracer, + instrumentationOptions: instrumentationOptions); + + var provider = new CommandProcessorProvider(commandProcessor); + + PipelineBuilder.ClearPipelineCache(); + + FailingChannel channel = new( + new (ChannelName), + _routingKey, + new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), + brokenCircuit: true); + + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory( + _ => new MyEventMessageMapper()), + null); + messageMapperRegistry.Register(); + + _messagePump = new MessagePumpBlocking(provider, messageMapperRegistry, null, + new InMemoryRequestContextFactory(), tracer, instrumentationOptions) + { + Channel = channel, TimeoutInMilliseconds = 5000, EmptyChannelDelay = 1000 + }; + + var externalActivity = new ActivitySource("Paramore.Brighter.Tests").StartActivity("MessagePumpSpanTests"); + + var header = new MessageHeader(_myEvent.Id, Topic, MessageType.MT_EVENT) + { + TraceParent = externalActivity?.Id, TraceState = externalActivity?.TraceStateString + }; + + externalActivity?.Stop(); + + _message = new Message( + header, + new MessageBody(JsonSerializer.Serialize(_myEvent, JsonSerialisationOptions.Options)) + ); + + channel.Enqueue(_message); + + var quitMessage = MessageFactory.CreateQuitMessage(_routingKey); + channel.Enqueue(quitMessage); + } + + [Fact] + public void When_There_Is_A_BrokenCircuit_Channel_Failure_Close_The_Span() + { + _messagePump.Run(); + + _traceProvider.ForceFlush(); + + _exportedActivities.Count.Should().Be(7); + _exportedActivities.Any(a => a.Source.Name == "Paramore.Brighter").Should().BeTrue(); + + var errorMessageActivity = _exportedActivities.FirstOrDefault(a => + a.DisplayName == $"{_message.Header.Topic} {MessagePumpSpanOperation.Receive.ToSpanName()}" + && a.Status == ActivityStatusCode.Error + ); + + errorMessageActivity.Should().NotBeNull(); + errorMessageActivity?.Status.Should().Be(ActivityStatusCode.Error); + } +} diff --git a/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_A_Channel_Failure_Close_The_Span.cs b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_A_Channel_Failure_Close_The_Span.cs new file mode 100644 index 0000000000..1fba5d6aaf --- /dev/null +++ b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_A_Channel_Failure_Close_The_Span.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; +using FluentAssertions; +using Microsoft.Extensions.Time.Testing; +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Paramore.Brighter.Core.Tests.CommandProcessors.TestDoubles; +using Paramore.Brighter.Core.Tests.MessageDispatch.TestDoubles; +using Paramore.Brighter.Observability; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.Core.Tests.Observability.MessageDispatch; + +public class MessagePumpChannelFailureOberservabilityTests +{ + private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; + private readonly RoutingKey _routingKey = new(Topic); + private readonly InternalBus _bus = new(); + private readonly FakeTimeProvider _timeProvider = new(); + private readonly IAmAMessagePump _messagePump; + private readonly IDictionary _receivedMessages = new Dictionary(); + private readonly List _exportedActivities; + private readonly TracerProvider _traceProvider; + private readonly MyEvent _myEvent = new(); + private readonly Message _message; + + public MessagePumpChannelFailureOberservabilityTests() + { + var builder = Sdk.CreateTracerProviderBuilder(); + _exportedActivities = new List(); + + _traceProvider = builder + .AddSource("Paramore.Brighter.Tests", "Paramore.Brighter") + .ConfigureResource(r => r.AddService("in-memory-tracer")) + .AddInMemoryExporter(_exportedActivities) + .Build(); + + Brighter.CommandProcessor.ClearServiceBus(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + var handlerFactory = new SimpleHandlerFactorySync(_ => new MyEventHandler(_receivedMessages)); + + var timeProvider = new FakeTimeProvider(); + var tracer = new BrighterTracer(timeProvider); + var instrumentationOptions = InstrumentationOptions.All; + + var commandProcessor = new Brighter.CommandProcessor( + subscriberRegistry, + handlerFactory, + new InMemoryRequestContextFactory(), + new PolicyRegistry(), + tracer: tracer, + instrumentationOptions: instrumentationOptions); + + var provider = new CommandProcessorProvider(commandProcessor); + + PipelineBuilder.ClearPipelineCache(); + + FailingChannel channel = new( + new (ChannelName), + _routingKey, + new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000), + brokenCircuit: false); + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory( + _ => new MyEventMessageMapper()), + null); + messageMapperRegistry.Register(); + + _messagePump = new MessagePumpBlocking(provider, messageMapperRegistry, null, + new InMemoryRequestContextFactory(), tracer, instrumentationOptions) + { + Channel = channel, TimeoutInMilliseconds = 5000, EmptyChannelDelay = 1000 + }; + + var externalActivity = new ActivitySource("Paramore.Brighter.Tests").StartActivity("MessagePumpSpanTests"); + + var header = new MessageHeader(_myEvent.Id, Topic, MessageType.MT_EVENT) + { + TraceParent = externalActivity?.Id, TraceState = externalActivity?.TraceStateString + }; + + externalActivity?.Stop(); + + _message = new Message( + header, + new MessageBody(JsonSerializer.Serialize(_myEvent, JsonSerialisationOptions.Options)) + ); + + channel.Enqueue(_message); + + var quitMessage = MessageFactory.CreateQuitMessage(_routingKey); + channel.Enqueue(quitMessage); + } + + [Fact] + public void When_There_Is_A_Channel_Failure_Close_The_Span() + { + _messagePump.Run(); + + _traceProvider.ForceFlush(); + + _exportedActivities.Count.Should().Be(7); + _exportedActivities.Any(a => a.Source.Name == "Paramore.Brighter").Should().BeTrue(); + + var errorMessageActivity = _exportedActivities.FirstOrDefault(a => + a.DisplayName == $"{_message.Header.Topic} {MessagePumpSpanOperation.Receive.ToSpanName()}" + && a.Status == ActivityStatusCode.Error + ); + + errorMessageActivity.Should().NotBeNull(); + errorMessageActivity!.Status.Should().Be(ActivityStatusCode.Error); + } +} diff --git a/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_A_Quit_Message_Close_The_Span.cs b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_A_Quit_Message_Close_The_Span.cs new file mode 100644 index 0000000000..5cf74bea00 --- /dev/null +++ b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_A_Quit_Message_Close_The_Span.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using FluentAssertions; +using Microsoft.Extensions.Time.Testing; +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Paramore.Brighter.Core.Tests.CommandProcessors.TestDoubles; +using Paramore.Brighter.Observability; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.Core.Tests.Observability.MessageDispatch; + +public class MessagePumpQuitOberservabilityTests +{ + private const string Topic = "MyTopic"; + private const string Channel = "MyChannel"; + private readonly RoutingKey _routingKey = new(Topic); + private readonly InternalBus _bus = new(); + private readonly FakeTimeProvider _timeProvider = new(); + private readonly IAmAMessagePump _messagePump; + private readonly IDictionary _receivedMessages = new Dictionary(); + private readonly List _exportedActivities; + private readonly TracerProvider _traceProvider; + + public MessagePumpQuitOberservabilityTests() + { + var builder = Sdk.CreateTracerProviderBuilder(); + _exportedActivities = new List(); + + _traceProvider = builder + .AddSource("Paramore.Brighter.Tests", "Paramore.Brighter") + .ConfigureResource(r => r.AddService("in-memory-tracer")) + .AddInMemoryExporter(_exportedActivities) + .Build(); + + Brighter.CommandProcessor.ClearServiceBus(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + var handlerFactory = new SimpleHandlerFactorySync(_ => new MyEventHandler(_receivedMessages)); + + var timeProvider = new FakeTimeProvider(); + var tracer = new BrighterTracer(timeProvider); + var instrumentationOptions = InstrumentationOptions.All; + + var commandProcessor = new Brighter.CommandProcessor( + subscriberRegistry, + handlerFactory, + new InMemoryRequestContextFactory(), + new PolicyRegistry(), + tracer: tracer, + instrumentationOptions: instrumentationOptions); + + var provider = new CommandProcessorProvider(commandProcessor); + + PipelineBuilder.ClearPipelineCache(); + + Channel channel = new(new (Channel), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory( + _ => new MyEventMessageMapper()), + null); + messageMapperRegistry.Register(); + + _messagePump = new MessagePumpBlocking(provider, messageMapperRegistry, null, + new InMemoryRequestContextFactory(), tracer, instrumentationOptions) + { + Channel = channel, TimeoutInMilliseconds = 5000, EmptyChannelDelay = 1000 + }; + + var quitMessage = MessageFactory.CreateQuitMessage(_routingKey); + channel.Enqueue(quitMessage); + } + + [Fact] + public void When_There_Is_A_Quit_Message_Close_The_Span() + { + _messagePump.Run(); + + _traceProvider.ForceFlush(); + + _exportedActivities.Count.Should().Be(2); + _exportedActivities.Any(a => a.Source.Name == "Paramore.Brighter").Should().BeTrue(); + + var emptyMessageActivity = _exportedActivities.FirstOrDefault(a => + a.DisplayName == $"{_routingKey} {MessagePumpSpanOperation.Receive.ToSpanName()}" + && a.TagObjects.Any(t => + t is { Value: not null, Key: BrighterSemanticConventions.MessageType } + && Enum.Parse(t.Value.ToString()) == MessageType.MT_QUIT + ) + ); + + emptyMessageActivity.Should().NotBeNull(); + emptyMessageActivity!.Status.Should().Be(ActivityStatusCode.Ok); + + } +} diff --git a/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_An_Unacceptable_Messages_Close_The_Span.cs b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_An_Unacceptable_Messages_Close_The_Span.cs new file mode 100644 index 0000000000..43c2037ff7 --- /dev/null +++ b/tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_There_Is_An_Unacceptable_Messages_Close_The_Span.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using FluentAssertions; +using Microsoft.Extensions.Time.Testing; +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Paramore.Brighter.Core.Tests.CommandProcessors.TestDoubles; +using Paramore.Brighter.Observability; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.Core.Tests.Observability.MessageDispatch; + +public class MessagePumpUnacceptableMessageOberservabilityTests +{ + private const string Topic = "MyTopic"; + private const string ChannelName = "myChannel"; + private readonly RoutingKey _routingKey = new(Topic); + private readonly InternalBus _bus = new(); + private readonly FakeTimeProvider _timeProvider = new(); + private readonly IAmAMessagePump _messagePump; + private readonly IDictionary _receivedMessages = new Dictionary(); + private readonly List _exportedActivities; + private readonly TracerProvider _traceProvider; + private readonly Message _message; + private readonly Channel _channel; + + public MessagePumpUnacceptableMessageOberservabilityTests() + { + var builder = Sdk.CreateTracerProviderBuilder(); + _exportedActivities = new List(); + + _traceProvider = builder + .AddSource("Paramore.Brighter.Tests", "Paramore.Brighter") + .ConfigureResource(r => r.AddService("in-memory-tracer")) + .AddInMemoryExporter(_exportedActivities) + .Build(); + + Brighter.CommandProcessor.ClearServiceBus(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + var handlerFactory = new SimpleHandlerFactorySync(_ => new MyEventHandler(_receivedMessages)); + + var timeProvider = new FakeTimeProvider(); + var tracer = new BrighterTracer(timeProvider); + var instrumentationOptions = InstrumentationOptions.All; + + var commandProcessor = new Brighter.CommandProcessor( + subscriberRegistry, + handlerFactory, + new InMemoryRequestContextFactory(), + new PolicyRegistry(), + tracer: tracer, + instrumentationOptions: instrumentationOptions); + + var provider = new CommandProcessorProvider(commandProcessor); + + PipelineBuilder.ClearPipelineCache(); + + _channel = new Channel(new(ChannelName), _routingKey, new InMemoryMessageConsumer(_routingKey, _bus, _timeProvider, 1000)); + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory( + _ => new MyEventMessageMapper()), + null); + messageMapperRegistry.Register(); + + _messagePump = new MessagePumpBlocking(provider, messageMapperRegistry, null, + new InMemoryRequestContextFactory(), tracer, instrumentationOptions) + { + Channel = _channel, TimeoutInMilliseconds = 5000, EmptyChannelDelay = 1000 + }; + + //in theory the message pump should see this from the consumer when the queue is empty + //we are just forcing this to run exactly once, before we quit. + _message = new Message( + new MessageHeader(Guid.Empty.ToString(), _routingKey, MessageType.MT_UNACCEPTABLE), + new MessageBody(string.Empty) + ); + + _channel.Enqueue(_message); + + var quitMessage = MessageFactory.CreateQuitMessage(_routingKey); + _channel.Enqueue(quitMessage); + } + + [Fact] + public void When_There_Are_No_Messages_Close_The_Span() + { + _messagePump.Run(); + + _traceProvider.ForceFlush(); + + _exportedActivities.Count.Should().Be(3); + _exportedActivities.Any(a => a.Source.Name == "Paramore.Brighter").Should().BeTrue(); + + var emptyMessageActivity = _exportedActivities.FirstOrDefault(a => + a.DisplayName == $"{_message.Header.Topic} {MessagePumpSpanOperation.Receive.ToSpanName()}" + && a.TagObjects.Any(t => + t is { Value: not null, Key: BrighterSemanticConventions.MessageType } + && Enum.Parse(t.Value.ToString()) == MessageType.MT_UNACCEPTABLE + ) + ); + + emptyMessageActivity.Should().NotBeNull(); + emptyMessageActivity!.Status.Should().Be(ActivityStatusCode.Error); + emptyMessageActivity.StatusDescription.Should().Contain( + $"MessagePump: Failed to parse a message from the incoming message with id {_message.Id} from {_channel.Name}"); + } +} diff --git a/tests/Paramore.Brighter.Core.Tests/Paramore.Brighter.Core.Tests.csproj b/tests/Paramore.Brighter.Core.Tests/Paramore.Brighter.Core.Tests.csproj index 20b53dbbdb..4e7e7214f8 100644 --- a/tests/Paramore.Brighter.Core.Tests/Paramore.Brighter.Core.Tests.csproj +++ b/tests/Paramore.Brighter.Core.Tests/Paramore.Brighter.Core.Tests.csproj @@ -37,6 +37,5 @@ - diff --git a/tests/Paramore.Brighter.RMQ.Tests/MessageDispatch/When_building_a_dispatcher.cs b/tests/Paramore.Brighter.RMQ.Tests/MessageDispatch/When_building_a_dispatcher.cs index 7dd6828a5f..c180ec03b8 100644 --- a/tests/Paramore.Brighter.RMQ.Tests/MessageDispatch/When_building_a_dispatcher.cs +++ b/tests/Paramore.Brighter.RMQ.Tests/MessageDispatch/When_building_a_dispatcher.cs @@ -72,6 +72,9 @@ public DispatchBuilderTests() var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection); var container = new ServiceCollection(); + var tracer = new BrighterTracer(TimeProvider.System); + var instrumentationOptions = InstrumentationOptions.All; + var commandProcessor = CommandProcessorBuilder.StartNew() .Handlers(new HandlerConfiguration(new SubscriberRegistry(), new ServiceProviderHandlerFactory(container.BuildServiceProvider()))) .Policies(new PolicyRegistry @@ -80,11 +83,11 @@ public DispatchBuilderTests() { CommandProcessor.CIRCUITBREAKER, circuitBreakerPolicy } }) .NoExternalBus() - .ConfigureInstrumentation(new BrighterTracer(TimeProvider.System), InstrumentationOptions.All) + .ConfigureInstrumentation(tracer, instrumentationOptions) .RequestContextFactory(new InMemoryRequestContextFactory()) .Build(); - _builder = DispatchBuilder.With() + _builder = DispatchBuilder.StartNew() .CommandProcessorFactory(() => new CommandProcessorProvider(commandProcessor), new InMemoryRequestContextFactory() @@ -103,7 +106,8 @@ public DispatchBuilderTests() new ChannelName("alice"), new RoutingKey("simon"), timeoutInMilliseconds: 200) - }); + }) + .ConfigureInstrumentation(tracer, instrumentationOptions); } [Fact] @@ -128,7 +132,7 @@ public void Dispose() private Subscription GetConnection(string name) { - return Enumerable.SingleOrDefault(_dispatcher.Connections, conn => conn.Name == name); + return Enumerable.SingleOrDefault(_dispatcher.Subscriptions, conn => conn.Name == name); } } } diff --git a/tests/Paramore.Brighter.RMQ.Tests/MessageDispatch/When_building_a_dispatcher_with_named_gateway.cs b/tests/Paramore.Brighter.RMQ.Tests/MessageDispatch/When_building_a_dispatcher_with_named_gateway.cs index 9cfae2f570..d108bf3e69 100644 --- a/tests/Paramore.Brighter.RMQ.Tests/MessageDispatch/When_building_a_dispatcher_with_named_gateway.cs +++ b/tests/Paramore.Brighter.RMQ.Tests/MessageDispatch/When_building_a_dispatcher_with_named_gateway.cs @@ -72,15 +72,18 @@ public DispatchBuilderWithNamedGateway() var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(connection); var container = new ServiceCollection(); + var tracer = new BrighterTracer(TimeProvider.System); + var instrumentationOptions = InstrumentationOptions.All; + var commandProcessor = CommandProcessorBuilder.StartNew() .Handlers(new HandlerConfiguration(new SubscriberRegistry(), new ServiceProviderHandlerFactory(container.BuildServiceProvider()))) .Policies(policyRegistry) .NoExternalBus() - .ConfigureInstrumentation(new BrighterTracer(TimeProvider.System), InstrumentationOptions.All) + .ConfigureInstrumentation(tracer, instrumentationOptions) .RequestContextFactory(new InMemoryRequestContextFactory()) .Build(); - _builder = DispatchBuilder.With() + _builder = DispatchBuilder.StartNew() .CommandProcessorFactory(() => new CommandProcessorProvider(commandProcessor), new InMemoryRequestContextFactory() @@ -99,7 +102,8 @@ public DispatchBuilderWithNamedGateway() new ChannelName("alice"), new RoutingKey("simon"), timeoutInMilliseconds: 200) - }); + }) + .ConfigureInstrumentation(tracer, instrumentationOptions); } [Fact] diff --git a/tests/Paramore.Brighter.RMQ.Tests/MessagingGateway/When_retry_limits_force_a_message_onto_the_DLQ.cs b/tests/Paramore.Brighter.RMQ.Tests/MessagingGateway/When_retry_limits_force_a_message_onto_the_DLQ.cs index 7dff1ee85b..1499b77c79 100644 --- a/tests/Paramore.Brighter.RMQ.Tests/MessagingGateway/When_retry_limits_force_a_message_onto_the_DLQ.cs +++ b/tests/Paramore.Brighter.RMQ.Tests/MessagingGateway/When_retry_limits_force_a_message_onto_the_DLQ.cs @@ -23,6 +23,7 @@ public class RMQMessageConsumerRetryDLQTests : IDisposable private readonly string _topicName; private readonly IAmACommandProcessor _commandProcessor; private readonly RmqMessageConsumer _deadLetterConsumer; + private readonly RmqSubscription _subscription; public RMQMessageConsumerRetryDLQTests() @@ -45,7 +46,7 @@ public RMQMessageConsumerRetryDLQTests() var deadLetterQueueName = $"{_message.Header.Topic}.DLQ"; var deadLetterRoutingKey = $"{_message.Header.Topic}.DLQ"; - var subscription = new RmqSubscription( + _subscription = new RmqSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, @@ -74,7 +75,7 @@ public RMQMessageConsumerRetryDLQTests() //set up our receiver _channelFactory = new ChannelFactory(new RmqMessageConsumerFactory(rmqConnection)); - _channel = _channelFactory.CreateChannel(subscription); + _channel = _channelFactory.CreateChannel(_subscription); //how do we handle a command IHandleRequests handler = new MyDeferredCommandHandler(); @@ -130,7 +131,7 @@ public async Task When_retry_limits_force_a_message_onto_the_dlq() await Task.Delay(20000); //send a quit message to the pump to terminate it - var quitMessage = new Message(new MessageHeader(string.Empty, "", MessageType.MT_QUIT), new MessageBody("")); + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); _channel.Enqueue(quitMessage); //wait for the pump to stop once it gets a quit message