From 34f89ebed5b1a32c166f9d199d2964c199391db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Wed, 22 Apr 2026 13:35:34 +0200 Subject: [PATCH 1/2] Make endpoint parsing code recillient to missing host information --- src/Directory.Packages.props | 8 ++++---- .../Operations/EndpointDetailsParser.cs | 12 +++++++----- .../ExceptionTypeAndStackTraceFailureClassifier.cs | 6 ++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 2d7e064b9a..fffb6ab650 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -34,7 +34,7 @@ - + @@ -47,12 +47,12 @@ - + - + @@ -94,4 +94,4 @@ - + \ No newline at end of file diff --git a/src/ServiceControl/Operations/EndpointDetailsParser.cs b/src/ServiceControl/Operations/EndpointDetailsParser.cs index c2edcef186..ab34edda08 100644 --- a/src/ServiceControl/Operations/EndpointDetailsParser.cs +++ b/src/ServiceControl/Operations/EndpointDetailsParser.cs @@ -75,14 +75,16 @@ public static EndpointDetails ReceivingEndpoint(IReadOnlyDictionary new StackFrame @@ -54,10 +55,7 @@ public string ClassifyFailure(ClassifiableMessageDetails failure) return GetNonStandardClassification(exception.ExceptionType); } - static string GetNonStandardClassification(string exceptionType) - { - return exceptionType + ": 0"; - } + static string GetNonStandardClassification(string exceptionType) => exceptionType + ": No stacktrace available"; public const string Id = "Exception Type and Stack Trace"; From 9abe226d96edf64db7f6477a4f191872e3dd88b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Thu, 23 Apr 2026 14:20:16 +0200 Subject: [PATCH 2/2] Add unit tests --- .../Operations/EndpointDetailsParserTests.cs | 49 +++++++++++++++++++ ...nTypeAndStackTraceFailureClassifierTest.cs | 16 +++--- .../Operations/EndpointDetailsParser.cs | 5 +- 3 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 src/ServiceControl.UnitTests/Operations/EndpointDetailsParserTests.cs diff --git a/src/ServiceControl.UnitTests/Operations/EndpointDetailsParserTests.cs b/src/ServiceControl.UnitTests/Operations/EndpointDetailsParserTests.cs new file mode 100644 index 0000000000..ff56217174 --- /dev/null +++ b/src/ServiceControl.UnitTests/Operations/EndpointDetailsParserTests.cs @@ -0,0 +1,49 @@ +namespace ServiceControl.UnitTests.Operations; + +using System.Collections.Generic; +using NServiceBus.Faults; +using NUnit.Framework; +using ServiceControl.Contracts.Operations; +using ServiceControl.Infrastructure; + +[TestFixture] +public class EndpointDetailsParserTests +{ + [Test] + public void Receiving_endpoint_should_use_failed_queue_machine_when_host_is_missing() + { + var headers = new Dictionary + { + { FaultsHeaderKeys.FailedQ, "Sales@backend-01" } + }; + + var endpoint = EndpointDetailsParser.ReceivingEndpoint(headers); + + Assert.Multiple(() => + { + Assert.That(endpoint, Is.Not.Null); + Assert.That(endpoint.Name, Is.EqualTo("Sales")); + Assert.That(endpoint.Host, Is.EqualTo("backend-01")); + Assert.That(endpoint.HostId, Is.EqualTo(DeterministicGuid.MakeId("Sales", "backend-01"))); + }); + } + + [Test] + public void Receiving_endpoint_should_use_unknown_host_when_failed_queue_is_used_to_infer_endpoint_name() + { + var headers = new Dictionary + { + { FaultsHeaderKeys.FailedQ, "Billing" } + }; + + var endpoint = EndpointDetailsParser.ReceivingEndpoint(headers); + + Assert.Multiple(() => + { + Assert.That(endpoint, Is.Not.Null); + Assert.That(endpoint.Name, Is.EqualTo("Billing")); + Assert.That(endpoint.Host, Is.EqualTo("unknown")); + Assert.That(endpoint.HostId, Is.EqualTo(DeterministicGuid.MakeId("Billing", "unknown"))); + }); + } +} \ No newline at end of file diff --git a/src/ServiceControl.UnitTests/Recoverability/ExceptionTypeAndStackTraceFailureClassifierTest.cs b/src/ServiceControl.UnitTests/Recoverability/ExceptionTypeAndStackTraceFailureClassifierTest.cs index d669039557..93b182ff41 100644 --- a/src/ServiceControl.UnitTests/Recoverability/ExceptionTypeAndStackTraceFailureClassifierTest.cs +++ b/src/ServiceControl.UnitTests/Recoverability/ExceptionTypeAndStackTraceFailureClassifierTest.cs @@ -7,6 +7,8 @@ [TestFixture] public class ExceptionTypeAndStackTraceFailureClassifierTest { + const string noStackTraceClassification = "exceptionType: No stacktrace available"; + [Test] public void Failure_Without_ExceptionDetails_should_not_group() { @@ -23,7 +25,7 @@ public void Empty_stack_trace_should_group_by_exception_type() var failureWithEmptyStackTrace = CreateFailureDetailsWithStackTrace(string.Empty); var classification = classifier.ClassifyFailure(failureWithEmptyStackTrace); - Assert.That(classification, Is.EqualTo("exceptionType: 0")); + Assert.That(classification, Is.EqualTo(noStackTraceClassification)); } [Test] @@ -33,7 +35,7 @@ public void Null_stack_trace_should_group_by_exception_type() var failureWithNullStackTrace = CreateFailureDetailsWithStackTrace(null); var classification = classifier.ClassifyFailure(failureWithNullStackTrace); - Assert.That(classification, Is.EqualTo("exceptionType: 0")); + Assert.That(classification, Is.EqualTo(noStackTraceClassification)); } [Test] @@ -43,7 +45,7 @@ public void Non_standard_stack_trace_format_should_group_by_exception_type() var failureWithNonStandardStackTrace = CreateFailureDetailsWithStackTrace("something other than a normal stack trace"); var classification = classifier.ClassifyFailure(failureWithNonStandardStackTrace); - Assert.That(classification, Is.EqualTo("exceptionType: 0")); + Assert.That(classification, Is.EqualTo(noStackTraceClassification)); } [Test] @@ -53,7 +55,7 @@ public void Null_message_should_group_by_exception_type() var failureWithNullMessage = CreateFailureDetailsWithMessage(null); var classification = classifier.ClassifyFailure(failureWithNullMessage); - Assert.That(classification, Is.EqualTo("exceptionType: 0")); + Assert.That(classification, Is.EqualTo(noStackTraceClassification)); } [Test] @@ -63,7 +65,7 @@ public void Empty_message_should_group_by_exception_type() var failureWithEmptyMessage = CreateFailureDetailsWithMessage(string.Empty); var classification = classifier.ClassifyFailure(failureWithEmptyMessage); - Assert.That(classification, Is.EqualTo("exceptionType: 0")); + Assert.That(classification, Is.EqualTo(noStackTraceClassification)); } [Test] @@ -73,7 +75,7 @@ public void Whitespace_message_should_group_by_exception_type() var failureWithWhitespaceMessage = CreateFailureDetailsWithMessage(" "); var classification = classifier.ClassifyFailure(failureWithWhitespaceMessage); - Assert.That(classification, Is.EqualTo("exceptionType: 0")); + Assert.That(classification, Is.EqualTo(noStackTraceClassification)); } [Test] @@ -154,4 +156,4 @@ static ClassifiableMessageDetails CreateFailureDetailsWithMessage(string message return new ClassifiableMessageDetails(null, failure, null); } } -} \ No newline at end of file +} diff --git a/src/ServiceControl/Operations/EndpointDetailsParser.cs b/src/ServiceControl/Operations/EndpointDetailsParser.cs index ab34edda08..7801a0fe1e 100644 --- a/src/ServiceControl/Operations/EndpointDetailsParser.cs +++ b/src/ServiceControl/Operations/EndpointDetailsParser.cs @@ -75,7 +75,10 @@ public static EndpointDetails ReceivingEndpoint(IReadOnlyDictionary