2323using System . Linq ;
2424using System . Net . Http ;
2525using System . Net . Http . Headers ;
26+ using System . Security . Cryptography ;
27+ using System . Text ;
2628using System . Threading ;
2729using System . Threading . Tasks ;
2830using Microsoft . Extensions . Logging ;
@@ -51,53 +53,71 @@ public HttpLoggingHandler(ILogger<HttpLoggingHandler> logger, bool logBody = fal
5153 _logBody = logBody ;
5254 }
5355
54- protected override async Task < HttpResponseMessage > SendAsync ( HttpRequestMessage request , CancellationToken cancellationToken )
56+ protected override Task < HttpResponseMessage > SendAsync ( HttpRequestMessage request , CancellationToken cancellationToken )
5557 {
56- // Log request with better formatting
57- var requestId = Guid . NewGuid ( ) . ToString ( "N" ) . Substring ( 0 , 8 ) ; // Short ID for correlation
58+ if ( ! _logger . IsEnabled ( LogLevel . Information ) )
59+ {
60+ return base . SendAsync ( request , cancellationToken ) ;
61+ }
5862
59- _logger . LogDebug ( "🚀 HTTP Request [{RequestId}]" , requestId ) ;
60- _logger . LogDebug ( " Method: {Method}" , request . Method ) ;
61- _logger . LogDebug ( " URL: {RequestUri}" , request . RequestUri ) ;
63+ return SendAsyncWithLogging ( request , cancellationToken ) ;
64+ }
6265
63- // Log headers in a more readable format
64- LogHeaders ( " Request Headers:" , request . Headers , SensitiveHeaders ) ;
66+ private async Task < HttpResponseMessage > SendAsyncWithLogging ( HttpRequestMessage request , CancellationToken cancellationToken )
67+ {
68+ var requestId = RandomNumberGenerator . GetHexString ( 8 ) ;
6569
66- if ( request . Content != null )
70+ var logDebugEnabled = _logger . IsEnabled ( LogLevel . Debug ) ;
71+
72+ if ( logDebugEnabled )
6773 {
68- LogHeaders ( " Content Headers:" , request . Content . Headers , null ) ;
74+ // Log request with better formatting.
75+ _logger . LogDebug ( "🚀 HTTP Request [{RequestId}]" , requestId ) ;
76+ _logger . LogDebug ( " Method: {Method}" , request . Method ) ;
77+ _logger . LogDebug ( " URL: {RequestUri}" , request . RequestUri ) ;
78+
79+ // Log headers in a more readable format.
80+ LogHeaders ( " Request Headers:" , request . Headers , SensitiveHeaders ) ;
6981
70- if ( _logBody )
82+ if ( request . Content != null )
7183 {
72- var requestBody = await request . Content . ReadAsStringAsync ( ) ;
73- if ( ! string . IsNullOrEmpty ( requestBody ) )
84+ LogHeaders ( " Content Headers:" , request . Content . Headers , null ) ;
85+
86+ if ( _logBody )
7487 {
75- _logger . LogDebug ( " Request Body:\n {RequestBody}" , FormatBody ( requestBody ) ) ;
88+ var requestBody = await request . Content . ReadAsStringAsync ( ) ;
89+ if ( ! string . IsNullOrEmpty ( requestBody ) )
90+ {
91+ _logger . LogDebug ( " Request Body:\n {RequestBody}" , FormatBody ( requestBody ) ) ;
92+ }
7693 }
7794 }
7895 }
7996
80- var stopwatch = System . Diagnostics . Stopwatch . StartNew ( ) ;
97+ var startTimestamp = System . Diagnostics . Stopwatch . GetTimestamp ( ) ;
8198 HttpResponseMessage response = await base . SendAsync ( request , cancellationToken ) ;
82- stopwatch . Stop ( ) ;
99+ var elapsedMilliseconds = System . Diagnostics . Stopwatch . GetElapsedTime ( startTimestamp ) ;
83100
84- // Log response with better formatting
101+ // Log response with better formatting.
85102 var statusIcon = response . IsSuccessStatusCode ? "✅" : "❌" ;
86103 _logger . LogInformation ( "{StatusIcon} HTTP Response [{RequestId}] - {StatusCode} {ReasonPhrase} ({ElapsedMs}ms)" ,
87- statusIcon , requestId , ( int ) response . StatusCode , response . ReasonPhrase , stopwatch . ElapsedMilliseconds ) ;
104+ statusIcon , requestId , ( int ) response . StatusCode , response . ReasonPhrase , elapsedMilliseconds ) ;
88105
89- LogHeaders ( " Response Headers:" , response . Headers , null ) ;
90-
91- if ( response . Content != null )
106+ if ( logDebugEnabled )
92107 {
93- LogHeaders ( " Content Headers:" , response . Content . Headers , SensitiveHeaders ) ;
108+ LogHeaders ( " Response Headers:" , response . Headers , null ) ;
94109
95- if ( _logBody )
110+ if ( response . Content != null )
96111 {
97- var responseBody = await response . Content . ReadAsStringAsync ( ) ;
98- if ( ! string . IsNullOrEmpty ( responseBody ) )
112+ LogHeaders ( " Content Headers:" , response . Content . Headers , SensitiveHeaders ) ;
113+
114+ if ( _logBody )
99115 {
100- _logger . LogDebug ( " Response Body:\n {ResponseBody}" , FormatBody ( responseBody ) ) ;
116+ var responseBody = await response . Content . ReadAsStringAsync ( ) ;
117+ if ( ! string . IsNullOrEmpty ( responseBody ) )
118+ {
119+ _logger . LogDebug ( " Response Body:\n {ResponseBody}" , FormatBody ( responseBody ) ) ;
120+ }
101121 }
102122 }
103123 }
@@ -135,7 +155,8 @@ private static string FormatBody(string body)
135155 // System.Text.Json that is not present for net462/netstandard2.0. TinyJson
136156 // returns null on parse failure rather than throwing, so an invalid body simply
137157 // falls through to the raw output below.
138- if ( body . TrimStart ( ) . StartsWith ( "{" ) || body . TrimStart ( ) . StartsWith ( "[" ) )
158+ var trimmedBody = body . AsSpan ( ) . TrimStart ( ) ;
159+ if ( ! trimmedBody . IsEmpty && trimmedBody [ 0 ] is '{' or '[' )
139160 {
140161 var parsed = body . FromJson < object > ( ) ;
141162 if ( parsed != null )
@@ -144,7 +165,20 @@ private static string FormatBody(string body)
144165 }
145166 }
146167
168+ const int maxLoggedBodyChars = 500 ;
147169 // For non-JSON or if JSON parsing failed, add some basic formatting
148- return body . Length > 500 ? $ "{ body . Substring ( 0 , 500 ) } ... (truncated, { body . Length } total chars)" : body ;
170+ if ( body . Length <= maxLoggedBodyChars )
171+ {
172+ return body ;
173+ }
174+
175+ var truncatedBody = new StringBuilder ( maxLoggedBodyChars + 64 ) ;
176+ truncatedBody
177+ . Append ( body , 0 , maxLoggedBodyChars )
178+ . Append ( "... (truncated, " )
179+ . Append ( body . Length )
180+ . Append ( " total chars)" ) ;
181+
182+ return truncatedBody . ToString ( ) ;
149183 }
150184}
0 commit comments