@@ -229,14 +229,13 @@ private async Task<List<DiagnosticResult>> DiagnoseMessagesAsync(
229229 DiagnoseDlqOptions options ,
230230 CancellationToken cancellationToken )
231231 {
232- var results = new List < DiagnosticResult > ( ) ;
233- var diagnosed = 0 ;
232+ // Extract operation IDs and build mapping to messages
233+ var messagesByOperationId = new Dictionary < string , ServiceBusReceivedMessage > ( ) ;
234+ var operations = new List < ( string OperationId , DateTimeOffset EnqueuedTime ) > ( ) ;
234235 var skipped = 0 ;
235236
236237 foreach ( var message in messages )
237238 {
238- cancellationToken . ThrowIfCancellationRequested ( ) ;
239-
240239 var operationId = ExtractOperationId ( message ) ;
241240 if ( string . IsNullOrEmpty ( operationId ) )
242241 {
@@ -245,33 +244,43 @@ private async Task<List<DiagnosticResult>> DiagnoseMessagesAsync(
245244 continue ;
246245 }
247246
248- try
247+ // Handle duplicate operation IDs by keeping the first one
248+ if ( ! messagesByOperationId . ContainsKey ( operationId ) )
249249 {
250- var result = await appInsightsService . DiagnoseMessageAsync ( operationId ,
251- message . EnqueuedTime ,
252- cancellationToken ) ;
250+ messagesByOperationId [ operationId ] = message ;
251+ operations . Add ( ( operationId , message . EnqueuedTime ) ) ;
252+ }
253+ }
254+
255+ if ( operations . Count == 0 )
256+ {
257+ Output . Warning ( $ "No messages with valid operation IDs found (skipped { skipped } )") ;
258+ return [ ] ;
259+ }
260+
261+ Output . Info ( $ "Querying Application Insights for { operations . Count } messages (skipped { skipped } without operation ID)...") ;
262+
263+ // Batch query Application Insights
264+ var diagnosticResults = await appInsightsService . DiagnoseBatchAsync ( operations ,
265+ ( current , total ) => Output . Progress ( $ "Querying App Insights batch { current } /{ total } ...") ,
266+ cancellationToken ) ;
267+ Console . WriteLine ( ) ;
253268
254- // Enrich with message info
269+ // Enrich results with message info
270+ var results = new List < DiagnosticResult > ( ) ;
271+ foreach ( var ( operationId , result ) in diagnosticResults )
272+ {
273+ if ( messagesByOperationId . TryGetValue ( operationId , out var message ) )
274+ {
255275 result . MessageId = message . MessageId ;
256276 result . Subject = message . Subject ;
257277 result . DeadLetterReason = message . DeadLetterReason ;
258278 result . Body = TryDecodeBody ( message ) ;
259-
260279 results . Add ( result ) ;
261- diagnosed ++ ;
262-
263- Output . Progress ( $ "Diagnosed { diagnosed } /{ messages . Count } messages (skipped { skipped } )...") ;
264- }
265- catch ( Exception ex )
266- {
267- Output . Verbose ( $ "Error diagnosing message { message . MessageId } : { ex . Message } ", options . Verbose ) ;
268- skipped ++ ;
269280 }
270281 }
271282
272- Console . WriteLine ( ) ;
273- Output . Info ( $ "Diagnosed { diagnosed } messages, skipped { skipped } (no operation ID or query error)") ;
274-
283+ Output . Info ( $ "Diagnosed { results . Count } messages") ;
275284 return results ;
276285 }
277286
@@ -338,7 +347,7 @@ private async Task OutputResultsAsync(
338347
339348 Output . Success ( $ "Found telemetry for { resultsWithTelemetry . Count } of { results . Count } messages") ;
340349
341- // Print summary to console
350+ // Print summary to console - grouped by Subject
342351 PrintDiagnosticSummary ( resultsWithTelemetry ) ;
343352
344353 // Write to file if specified
@@ -353,65 +362,99 @@ private async Task OutputResultsAsync(
353362 private void PrintDiagnosticSummary ( List < DiagnosticResult > results )
354363 {
355364 Output . Info ( "" ) ;
356- Output . Info ( "Diagnostic Summary:" ) ;
357- Output . Info ( "===================" ) ;
358-
359- // Group exceptions by type/message
360- var exceptionGroups = results
361- . SelectMany ( r => r . Exceptions )
362- . GroupBy ( e => new
363- {
364- e . ExceptionType ,
365- e . InnermostMessage
366- } )
367- . OrderByDescending ( g => g . Count ( ) )
368- . Take ( 10 ) ;
369-
370- if ( exceptionGroups . Any ( ) )
365+ Output . Info ( "Diagnostic Summary by Message Type:" ) ;
366+ Output . Info ( "====================================" ) ;
367+
368+ // Group by Subject (message type)
369+ var groupedBySubject = results
370+ . GroupBy ( r => r . Subject ?? "(none)" )
371+ . OrderByDescending ( g => g . Count ( ) ) ;
372+
373+ foreach ( var subjectGroup in groupedBySubject )
371374 {
375+ var messageCount = subjectGroup . Count ( ) ;
376+ var totalExceptions = subjectGroup . Sum ( r => r . Exceptions . Count ) ;
377+
372378 Output . Info ( "" ) ;
373- Output . Info ( "Top Exceptions:" ) ;
374- var headers = new [ ]
379+ Output . Info ( $ "[{ subjectGroup . Key } ] - { messageCount } messages, { totalExceptions } exceptions") ;
380+ Output . Info ( new string ( '-' , 60 ) ) ;
381+
382+ // Get exceptions for this subject, grouped by type only
383+ var exceptionGroups = subjectGroup
384+ . SelectMany ( r => r . Exceptions )
385+ . GroupBy ( e => e . ExceptionType ?? "(unknown)" )
386+ . OrderByDescending ( g => g . Count ( ) )
387+ . Take ( 5 )
388+ . ToList ( ) ;
389+
390+ if ( exceptionGroups . Count > 0 )
375391 {
376- "Count" ,
377- "Type" ,
378- "Message"
379- } ;
380- var rows = exceptionGroups . Select ( g => new [ ]
392+ var headers = new [ ]
393+ {
394+ "Count" ,
395+ "Exception Type" ,
396+ "Sample Message"
397+ } ;
398+ var rows = exceptionGroups . Select ( g => new [ ]
399+ {
400+ g . Count ( ) . ToString ( ) ,
401+ g . Key ,
402+ GetExceptionMessage ( g . First ( ) )
403+ } ) ;
404+ Output . Table ( headers , rows ) ;
405+ }
406+ else
381407 {
382- g . Count ( ) . ToString ( ) ,
383- TruncateString ( g . Key . ExceptionType ?? "(unknown)" , 40 ) ,
384- TruncateString ( g . Key . InnermostMessage ?? "(no message)" , 60 )
385- } ) ;
386- Output . Table ( headers , rows ) ;
408+ Output . Info ( " No exceptions found (check traces/dependencies in output file)" ) ;
409+ }
410+
411+ // Show failed dependencies if any
412+ var dependencyGroups = subjectGroup
413+ . SelectMany ( r => r . FailedDependencies )
414+ . GroupBy ( d => new
415+ {
416+ d . Type ,
417+ d . Target
418+ } )
419+ . OrderByDescending ( g => g . Count ( ) )
420+ . Take ( 3 ) ;
421+
422+ if ( dependencyGroups . Any ( ) )
423+ {
424+ Output . Info ( "" ) ;
425+ Output . Info ( " Failed Dependencies:" ) ;
426+ foreach ( var dep in dependencyGroups )
427+ {
428+ Output . Info ( $ " - [{ dep . Count ( ) } x] { dep . Key . Type } : { TruncateString ( dep . Key . Target ?? "" , 40 ) } ") ;
429+ }
430+ }
387431 }
388432
389- // Group failed dependencies by target
390- var dependencyGroups = results
391- . SelectMany ( r => r . FailedDependencies )
392- . GroupBy ( d => new
393- {
394- d . Type ,
395- d . Target
396- } )
397- . OrderByDescending ( g => g . Count ( ) )
398- . Take ( 5 ) ;
433+ // Overall summary
434+ Output . Info ( "" ) ;
435+ Output . Info ( "Overall Top Exceptions:" ) ;
436+ Output . Info ( "=======================" ) ;
399437
400- if ( dependencyGroups . Any ( ) )
438+ var allExceptions = results
439+ . SelectMany ( r => r . Exceptions )
440+ . GroupBy ( e => e . ExceptionType ?? "(unknown)" )
441+ . OrderByDescending ( g => g . Count ( ) )
442+ . Take ( 10 )
443+ . ToList ( ) ;
444+
445+ if ( allExceptions . Count > 0 )
401446 {
402- Output . Info ( "" ) ;
403- Output . Info ( "Failed Dependencies:" ) ;
404447 var headers = new [ ]
405448 {
406449 "Count" ,
407450 "Type" ,
408- "Target "
451+ "Sample Message "
409452 } ;
410- var rows = dependencyGroups . Select ( g => new [ ]
453+ var rows = allExceptions . Select ( g => new [ ]
411454 {
412455 g . Count ( ) . ToString ( ) ,
413- g . Key . Type ?? "(unknown)" ,
414- TruncateString ( g . Key . Target ?? "(unknown)" , 50 )
456+ g . Key ,
457+ GetExceptionMessage ( g . First ( ) )
415458 } ) ;
416459 Output . Table ( headers , rows ) ;
417460 }
@@ -554,4 +597,20 @@ private static string TruncateString(string value, int maxLength)
554597
555598 return value . Length <= maxLength ? value : value [ ..( maxLength - 3 ) ] + "..." ;
556599 }
600+
601+ private static string GetExceptionMessage ( ExceptionInfo ex )
602+ {
603+ // Prefer innermostMessage, fall back to outerMessage
604+ if ( ! string . IsNullOrWhiteSpace ( ex . InnermostMessage ) )
605+ {
606+ return ex . InnermostMessage ;
607+ }
608+
609+ if ( ! string . IsNullOrWhiteSpace ( ex . OuterMessage ) )
610+ {
611+ return ex . OuterMessage ;
612+ }
613+
614+ return "(no message)" ;
615+ }
557616}
0 commit comments