Skip to content

Muhannad/visibility errors - #28

Merged
samerzughul merged 2 commits into
mainfrom
muhannad/visibility-errors
Jan 8, 2026
Merged

Muhannad/visibility errors#28
samerzughul merged 2 commits into
mainfrom
muhannad/visibility-errors

Conversation

@mmalkhatib

@mmalkhatib mmalkhatib commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features
    • Added error queue inspection and debugging capabilities to review retry and bad message queues
    • Expanded configuration options for monitoring cache intervals, heartbeat timeouts, RabbitMQ management credentials, queue prefetch, and retry behavior
    • Added tracking for last consumer statistics update times

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 8, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR introduces error queue reading capabilities for RabbitMQ, extends bus configuration options significantly, refactors consumer statistics tracking with caching and timestamp awareness, and updates service registration to wire HTTP clients for the new readers.

Changes

Cohort / File(s) Summary
Consumer Statistics API
SW.Bus.RabbitMqExtensions/IConsumerReader.cs
Added LastUpdated property of type Task<DateTime> to expose last cache refresh timestamp.
Error Queue Reading Interface & Types
SW.Bus.RabbitMqExtensions/IErrorQueueReader.cs
Introduced IErrorQueueReader interface with three overloads of Peek() methods; added ErrorMessage record with properties for headers, correlation ID, and exception history; added ErrorQueueType enum distinguishing Retry and Bad queues.
Bus Configuration
SW.Bus/BusOptions.cs
Added 15+ configuration properties (MonitoringCacheSeconds, HeartBeatTimeOut, ManagementUrl, ManagementUsername, ManagementPassword, VirtualHost, Token, ApplicationName, DefaultQueuePrefetch, DefaultRetryCount, DefaultRetryAfter, DefaultMaxPriority, NodeId, ListenRetryCount, ListenRetryAfter); added AddQueueOption() method for per-queue customization.
Consumer Statistics Implementation
SW.Bus/ConsumerReader.cs
Refactored to accept injected HttpClient, added internal caching with IMemoryCache, tracking lastUpdatedUtc timestamp, exposed LastUpdated property, and implemented bulk queue fetching with parallel retrieval for main/retry/bad queues.
Error Queue Reading Implementation
SW.Bus/ErrorQueueReader.cs
Implemented ErrorQueueReader class with three public Peek() methods to retrieve messages from retry/bad queues, internal queue name resolution via ConsumerDiscovery, and validation with exception handling.
Service Registration
SW.Bus/IServiceCollectionExtensions.cs
Enhanced AddBus() with BusOptions binding, RabbitMQ connection validation, and HTTP client registration; updated AddBusPublish() to establish connections; updated XML documentation for AddBusConsume() and AddBusListen().
Project Configuration
SW.Bus.RabbitMqExtensions/SW.Bus.RabbitMqExtensions.csproj, SW.Bus/SW.Bus.csproj
Enabled XML documentation generation (GenerateDocumentationFile=true) and suppressed missing documentation warnings (NoWarn=1591); added new package references (EasyNetQ.Management.Client, RabbitMQ.Client, Scrutor, SimplyWorks.HttpExtensions) and project reference to SW.Bus.RabbitMqExtensions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ConsumerReader
    participant MemoryCache
    participant ManagementAPI
    
    Client->>ConsumerReader: GetConsumerCounts(consumer, message)
    activate ConsumerReader
    ConsumerReader->>MemoryCache: Check cache for queue
    alt Cache Hit
        MemoryCache-->>ConsumerReader: Cached queue data
    else Cache Miss
        ConsumerReader->>ManagementAPI: Fetch queue data
        ManagementAPI-->>ConsumerReader: Queue info
        ConsumerReader->>MemoryCache: Store with expiration
        ConsumerReader->>ConsumerReader: Update lastUpdatedUtc
    end
    ConsumerReader-->>Client: Return consumer counts
    deactivate ConsumerReader
    
    Client->>ConsumerReader: GetLastUpdated()
    ConsumerReader-->>Client: lastUpdatedUtc as Task<DateTime>
Loading
sequenceDiagram
    participant Client
    participant ErrorQueueReader
    participant ConsumerDiscovery
    participant ManagementAPI
    
    Client->>ErrorQueueReader: Peek<TConsumer, TMessage>(ErrorQueueType, count)
    activate ErrorQueueReader
    ErrorQueueReader->>ConsumerDiscovery: Resolve consumer definition
    activate ConsumerDiscovery
    ConsumerDiscovery-->>ErrorQueueReader: Consumer metadata
    deactivate ConsumerDiscovery
    
    ErrorQueueReader->>ErrorQueueReader: Compute queue name (RetryQueueName or BadQueueName)
    ErrorQueueReader->>ManagementAPI: Fetch messages from queue
    activate ManagementAPI
    ManagementAPI-->>ErrorQueueReader: Raw messages
    deactivate ManagementAPI
    
    ErrorQueueReader->>ErrorQueueReader: Map to ErrorMessage (extract headers, correlationId, exceptions)
    ErrorQueueReader-->>Client: Return IEnumerable<ErrorMessage>
    deactivate ErrorQueueReader
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A hop, a cache, a queue so deep,
Error messages we're set to keep,
With LastUpdated tracking time,
Configuration options all align—
RabbitMQ reading feels divine!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Muhannad/visibility errors' is vague and does not clearly convey the actual changes made in the pull request, which include adding error queue reader functionality, consumer reader enhancements, and configuration properties. Consider using a more descriptive title such as 'Add error queue reader and enhance consumer monitoring' or 'Add RabbitMQ error queue management and consumer statistics APIs' that better reflects the primary changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 96.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@samerzughul
samerzughul merged commit 0446338 into main Jan 8, 2026
1 of 2 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
SW.Bus/ConsumerReader.cs (1)

228-231: Silent exception swallowing may hide issues.

The empty catch block silently swallows all exceptions, including connection failures, authentication errors, and timeouts. Consider logging at debug/warning level to aid troubleshooting when queue lookups fail unexpectedly.

🔧 Suggested improvement
+using Microsoft.Extensions.Logging;

// In constructor, add ILogger<ConsumerReader> logger parameter

 catch
 {
+    // Optionally log the exception for debugging
+    // logger?.LogDebug(ex, "Failed to get queue info for {QueueName}", queueName);
     return null;
 }
SW.Bus/IServiceCollectionExtensions.cs (2)

74-74: Conflicting service registrations for IConsumerReader.

Line 74 registers IConsumerReader as a Singleton, but lines 89-95 use AddHttpClient<IConsumerReader, ConsumerReader>() which registers it as Transient by default. The AddHttpClient registration will override the Singleton, potentially causing unexpected behavior where each request gets a fresh instance (losing cached state like lastUpdatedUtc).

Remove the Singleton registration on line 74 since AddHttpClient handles the registration, or explicitly specify the lifetime on AddHttpClient.

🐛 Remove duplicate registration
-            services.AddSingleton<IConsumerReader, ConsumerReader>();
             services.AddSingleton<ConsumerDiscovery>();
             services.AddMemoryCache();

If Singleton behavior is needed, consider:

services.AddHttpClient<IConsumerReader, ConsumerReader>(...)
    .ConfigureHttpClient(...)
    .AddTypedClient<IConsumerReader>((client, sp) => 
        sp.GetRequiredService<ConsumerReader>());
// Plus additional wiring for singleton lifetime

Also applies to: 89-95


127-128: RabbitMQ connection and model are never disposed.

The conn and model created here are assigned but never disposed, leading to resource leaks. These should be registered as singletons and disposed during application shutdown.

🐛 Suggested fix - register as singletons
-            var conn = factory.CreateConnection();
-            var model = conn.CreateModel();
+            services.AddSingleton<IConnection>(sp =>
+            {
+                var rabbitUrl = sp.GetRequiredService<IConfiguration>().GetConnectionString("RabbitMQ");
+                var busOpts = sp.GetRequiredService<BusOptions>();
+                var factory = new ConnectionFactory
+                {
+                    Uri = new Uri(rabbitUrl),
+                    ClientProvidedName = $"{Assembly.GetCallingAssembly().GetName().Name} Publisher"
+                };
+                return factory.CreateConnection();
+            });
+            
+            services.AddSingleton<IModel>(sp => sp.GetRequiredService<IConnection>().CreateModel());

-            return services.AddScoped(serviceProvider => new BasicPublisher(
-                model,
+            return services.AddScoped(serviceProvider => new BasicPublisher(
+                serviceProvider.GetRequiredService<IModel>(),
                 serviceProvider.GetRequiredService<BusOptions>(),
                 serviceProvider.GetRequiredService<RequestContext>()))
🤖 Fix all issues with AI agents
In @SW.Bus/ErrorQueueReader.cs:
- Around line 69-70: The call to consumerDiscovery.Load(true) inside the peek
flow causes expensive reflection on every call; cache the Load() result (e.g.,
store the returned definitions collection in a private field or a MemoryCache
inside ErrorQueueReader) and use the cached definitions for subsequent lookups
of definition = definitions.FirstOrDefault(...) in the peek method; add a
clear/invalidate method or hook to refresh the cached value when consumers
register/unregister (or on a configurable TTL) so you only call
consumerDiscovery.Load(true) when the cache is empty or invalidated.

In @SW.Bus/SW.Bus.csproj:
- Around line 29-32: Update the RabbitMQ.Client PackageReference in
SW.Bus.csproj from Version="6.8.1" to Version="7.2.0" so the project targets the
native net8.0 feed; locate the <PackageReference Include="RabbitMQ.Client".../>
entry and change its Version attribute to "7.2.0", then run dotnet restore and
rebuild/run tests to ensure no API breaks (adjust any code using RabbitMQ.Client
APIs if compiler errors appear).
🧹 Nitpick comments (5)
SW.Bus.RabbitMqExtensions/IConsumerReader.cs (1)

40-43: Consider using a non-async property signature.

The Task<DateTime> return type suggests asynchronous computation, but the implementation in ConsumerReader.cs simply returns Task.FromResult(lastUpdatedUtc). A plain DateTime LastUpdated { get; } would be simpler and more accurately represent the synchronous nature of this property.

♻️ Suggested simplification
-    public Task<DateTime> LastUpdated { get; }
+    public DateTime LastUpdated { get; }
SW.Bus.RabbitMqExtensions/IErrorQueueReader.cs (2)

43-46: Exception key matching may be too broad.

The StartsWith("exception") filter could match unintended header keys like "exceptionType", "exceptionHandler", etc. If the expected pattern is exception0, exception1, etc., consider a more precise regex or suffix check.

♻️ More precise matching
 public IEnumerable<string?> ExceptionHistory => Headers
-    .Where(h => h.Key.StartsWith("exception", StringComparison.OrdinalIgnoreCase))
+    .Where(h => h.Key.StartsWith("exception", StringComparison.OrdinalIgnoreCase) 
+             && h.Key.Length > 9 
+             && char.IsDigit(h.Key[9]))
     .OrderBy(h => GetExceptionIndex(h.Key))
     .Select(h => h.Value?.ToString());

54-55: Index parsing fallback may cause incorrect ordering.

When GetExceptionIndex fails to parse the numeric suffix, it returns 0. This could cause non-exception keys that slip through the filter to appear first, or cause genuinely malformed keys to sort unexpectedly.

♻️ Consider returning -1 for invalid keys
 private static int GetExceptionIndex(string key) =>
-    int.TryParse(key.Replace("exception", "", StringComparison.OrdinalIgnoreCase), out var index) ? index : 0;
+    int.TryParse(key.Replace("exception", "", StringComparison.OrdinalIgnoreCase), out var index) ? index : int.MaxValue;

This would push malformed keys to the end of the sorted list rather than the beginning.

SW.Bus/ConsumerReader.cs (1)

31-31: Consider thread-safety for lastUpdatedUtc.

The lastUpdatedUtc field can be updated concurrently from multiple cache-miss callbacks. While DateTime assignment is atomic on 64-bit systems, consider using volatile or Interlocked for explicit thread-safety guarantees if precision matters.

Also applies to: 61-61

SW.Bus/IServiceCollectionExtensions.cs (1)

28-32: BuildServiceProvider() during configuration is an anti-pattern.

Calling BuildServiceProvider() creates an orphaned service provider that won't be disposed properly and may cause issues with singleton instances. Consider using the IServiceProvider from IHostApplicationBuilder or deferring configuration access.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c792810 and a509645.

📒 Files selected for processing (8)
  • SW.Bus.RabbitMqExtensions/IConsumerReader.cs
  • SW.Bus.RabbitMqExtensions/IErrorQueueReader.cs
  • SW.Bus.RabbitMqExtensions/SW.Bus.RabbitMqExtensions.csproj
  • SW.Bus/BusOptions.cs
  • SW.Bus/ConsumerReader.cs
  • SW.Bus/ErrorQueueReader.cs
  • SW.Bus/IServiceCollectionExtensions.cs
  • SW.Bus/SW.Bus.csproj
🧰 Additional context used
🧬 Code graph analysis (4)
SW.Bus/ConsumerReader.cs (3)
SW.Bus/BusOptions.cs (2)
  • BusOptions (11-246)
  • BusOptions (23-37)
SW.Bus/ConsumerDiscovery.cs (1)
  • ConsumerDiscovery (12-118)
SW.Bus/ConsumerDefinition.cs (3)
  • ConsumerDefinition (9-77)
  • ConsumerDefinition (15-21)
  • ConsumerDefinition (23-32)
SW.Bus/ErrorQueueReader.cs (2)
SW.Bus/ConsumerDiscovery.cs (1)
  • ConsumerDiscovery (12-118)
SW.Bus.RabbitMqExtensions/IErrorQueueReader.cs (3)
  • Task (99-101)
  • Task (120-121)
  • Task (137-137)
SW.Bus/IServiceCollectionExtensions.cs (4)
SW.Bus/ConsumerReader.cs (2)
  • ConsumerReader (19-234)
  • ConsumerReader (40-58)
SW.Bus/BusOptions.cs (2)
  • BusOptions (11-246)
  • BusOptions (23-37)
SW.Bus/ErrorQueueReader.cs (2)
  • ErrorQueueReader (17-119)
  • ErrorQueueReader (29-34)
SW.Bus/MockPublisher.cs (2)
  • MockPublisher (7-33)
  • MockPublisher (11-14)
SW.Bus.RabbitMqExtensions/IConsumerReader.cs (1)
SW.Bus/ConsumerReader.cs (7)
  • Task (64-76)
  • Task (79-84)
  • Task (87-101)
  • Task (104-108)
  • Task (117-153)
  • Task (179-205)
  • Task (214-233)
🔇 Additional comments (9)
SW.Bus.RabbitMqExtensions/SW.Bus.RabbitMqExtensions.csproj (1)

5-6: LGTM!

Enabling XML documentation generation with suppression of warning 1591 is a reasonable approach for gradual documentation adoption.

SW.Bus.RabbitMqExtensions/IErrorQueueReader.cs (1)

77-138: LGTM!

The IErrorQueueReader interface is well-designed with comprehensive XML documentation. The peek methods provide good flexibility with typed and untyped access patterns.

SW.Bus/BusOptions.cs (3)

88-94: Consider credential handling practices.

ManagementPassword is stored as a plain string. While this is often unavoidable for runtime use, ensure this value is sourced from secure configuration (e.g., Azure Key Vault, environment variables) and not logged or serialized inadvertently.


39-64: LGTM!

The MonitoringCacheSeconds property with value clamping between 3-60 seconds is a good defensive pattern that prevents misconfiguration.


215-244: LGTM!

The AddQueueOption method provides a clean API for per-queue configuration overrides with comprehensive documentation.

SW.Bus/ConsumerReader.cs (1)

181-205: LGTM!

Parallel fetching of main, retry, and bad queue statistics with Task.WhenAll is efficient and well-structured.

SW.Bus/ErrorQueueReader.cs (3)

17-34: LGTM!

The constructor follows proper dependency injection patterns. The HttpClient is injected and managed externally by IHttpClientFactory, so the ManagementClient doesn't need to handle disposal.


36-42: LGTM!

Clean implementation that properly resolves the message type name and delegates to the internal method.


88-118: LGTM!

Well-structured implementation with:

  • Proper input validation including sensible upper bound on count
  • Correct use of AckRequeueTrue for non-destructive message inspection
  • Appropriate exception wrapping with helpful context for network-level failures

The decision to only catch HttpRequestException is reasonable, as other exceptions (e.g., auth failures, JSON parsing) will bubble up with their original context.

Comment on lines +69 to +70
var definitions = await consumerDiscovery.Load(true);
var definition = definitions.FirstOrDefault(d => d.ServiceType == consumerType && d.MessageTypeName == messageName);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find ConsumerDiscovery.cs file
find . -name "ConsumerDiscovery.cs" -type f

Repository: simplify9/SW-Bus

Length of output: 89


🏁 Script executed:

# Once we locate the file, examine the Load method implementation
git ls-files | grep -i discoverer

Repository: simplify9/SW-Bus

Length of output: 42


🏁 Script executed:

# Read ConsumerDiscovery.cs to examine the Load() method
cat ./SW.Bus/ConsumerDiscovery.cs

Repository: simplify9/SW-Bus

Length of output: 5134


Cache consumerDiscovery.Load() results to avoid repeated reflection-based discovery.

consumerDiscovery.Load(true) is called on every peek operation, but the Load() method performs expensive reflection-based discovery with no caching—creating service scopes, scanning implemented interfaces, and invoking methods via reflection each time. Consider caching the discovery results and invalidating only when necessary (e.g., on consumer registration changes).

🤖 Prompt for AI Agents
In @SW.Bus/ErrorQueueReader.cs around lines 69 - 70, The call to
consumerDiscovery.Load(true) inside the peek flow causes expensive reflection on
every call; cache the Load() result (e.g., store the returned definitions
collection in a private field or a MemoryCache inside ErrorQueueReader) and use
the cached definitions for subsequent lookups of definition =
definitions.FirstOrDefault(...) in the peek method; add a clear/invalidate
method or hook to refresh the cached value when consumers register/unregister
(or on a configurable TTL) so you only call consumerDiscovery.Load(true) when
the cache is empty or invalidated.

Comment thread SW.Bus/SW.Bus.csproj
Comment on lines +29 to +32
<PackageReference Include="EasyNetQ.Management.Client" Version="3.0.1"/>
<PackageReference Include="RabbitMQ.Client" Version="6.8.1"/>
<PackageReference Include="Scrutor" Version="4.2.2"/>
<PackageReference Include="SimplyWorks.HttpExtensions" Version="5.0.0"/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

RabbitMQ.Client NuGet latest version 2025 2026

💡 Result:

The latest RabbitMQ.Client NuGet version as of January 8, 2026 is 7.2.0. [NuGet listing]. [1]

Sources:

  • NuGet: RabbitMQ.Client 7.2.0. [1]
  • RabbitMQ .NET client info (distribution via NuGet). [2]

References:
[1] NuGet Gallery — RabbitMQ.Client 7.2.0.
[2] RabbitMQ — .NET/C# client library (NuGet distribution).


🌐 Web query:

EasyNetQ.Management.Client NuGet latest version 2025 2026

💡 Result:

Latest EasyNetQ.Management.Client NuGet version: 3.0.1 — published October 17, 2025. [1][2]

Sources:
[1] NuGet profile for EasyNetQ (package list showing EasyNetQ.Management.Client 3.0.1, last updated 10/17/2025).
[2] NuGetMustHaves package page for EasyNetQ.Management.Client (version 3.0.1, last update 10/17/2025).


🌐 Web query:

RabbitMQ.Client 6.8.1 .NET 8 compatibility

💡 Result:

Short answer: Yes — RabbitMQ.Client 6.8.1 (targets .NET Standard 2.0) runs on .NET 8, but it does not include a native net8.0 target. For native net8.0 builds use the 7.x series (which includes a net8.0 target).

Sources:

  • RabbitMQ.Client 6.8.1 NuGet (targets .NETStandard2.0 — compatible with .NET 8). [1]
  • RabbitMQ .NET client release-series info (6.x is a .NET Standard release series; 7.x has newer framework targeting). [2]
  • RabbitMQ.Client 7.2.0 NuGet (explicit net8.0 target in 7.x). [3]

If you want, I can list potential runtime caveats (dependency version mismatches) and steps to test/upgrade.


Upgrade RabbitMQ.Client to 7.2.0 for proper .NET 8 support.

RabbitMQ.Client 6.8.1 is outdated; version 7.2.0 is available. While 6.8.1 runs on .NET 8 via .NET Standard 2.0 compatibility, it lacks a native net8.0 target. Upgrade to 7.2.0 for explicit .NET 8 framework support. EasyNetQ.Management.Client 3.0.1 is already at the latest version.

🤖 Prompt for AI Agents
In @SW.Bus/SW.Bus.csproj around lines 29 - 32, Update the RabbitMQ.Client
PackageReference in SW.Bus.csproj from Version="6.8.1" to Version="7.2.0" so the
project targets the native net8.0 feed; locate the <PackageReference
Include="RabbitMQ.Client".../> entry and change its Version attribute to
"7.2.0", then run dotnet restore and rebuild/run tests to ensure no API breaks
(adjust any code using RabbitMQ.Client APIs if compiler errors appear).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants