-
-
Save CarnaViire/08346da1634d357f6bcb8adefa01da67 to your computer and use it in GitHub Desktop.
HttpLoggingHandler implemented via IHttpClientLogger
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal class HttpClientLogger : IHttpClientLogger | |
{ | |
internal TimeProvider TimeProvider = TimeProvider.System; | |
private readonly IHttpClientLogEnricher[] _enrichers; | |
private readonly IHttpRequestReader _httpRequestReader; | |
private readonly ObjectPool<List<KeyValuePair<string, string>>> _headersPool = | |
PoolFactory.CreateListPool<KeyValuePair<string, string>>(); | |
private readonly ObjectPool<LogRecord> _logRecordPool = | |
PoolFactory.CreatePool(new LogRecordPooledObjectPolicy()); | |
private readonly bool _logRequestStart; | |
private readonly bool _logRequestHeaders; | |
private readonly bool _logResponseHeaders; | |
public ILogger Logger { get; private set; } | |
public HttpClientLogger( | |
ILogger<HttpLoggingHandler> logger, | |
IHttpRequestReader httpRequestReader, | |
IEnumerable<IHttpClientLogEnricher> enrichers, | |
IOptions<LoggingOptions> options) | |
{ | |
_httpRequestReader = httpRequestReader; | |
_enrichers = enrichers.ToArray(); | |
_ = Throw.IfMemberNull(options, options.Value); | |
Logger = logger; | |
_logRequestStart = options.Value.LogRequestStart; | |
_logResponseHeaders = options.Value.ResponseHeadersDataClasses.Count > 0; | |
_logRequestHeaders = options.Value.RequestHeadersDataClasses.Count > 0; | |
} | |
public async ValueTask<object?> LogRequestStartAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) | |
{ | |
var timestamp = TimeProvider.GetTimestamp(); | |
var logRecord = _logRecordPool.Get(); | |
List<KeyValuePair<string, string>>? requestHeadersBuffer = null; | |
if (_logRequestHeaders) | |
{ | |
requestHeadersBuffer = _headersPool.Get(); | |
} | |
await _httpRequestReader.ReadRequestAsync(logRecord, request, requestHeadersBuffer, cancellationToken).ConfigureAwait(false); | |
if (_logRequestStart) | |
{ | |
Log.OutgoingRequest(Logger, LogLevel.Information, logRecord); | |
} | |
return logRecord; | |
} | |
public async ValueTask LogRequestStopAsync( | |
object? context, | |
HttpRequestMessage request, | |
HttpResponseMessage response, | |
TimeSpan elapsed, | |
CancellationToken cancellationToken = default) | |
{ | |
var logRecord = (LogRecord?)context; | |
_ = Throw.IfNull(logRecord); | |
List<KeyValuePair<string, string>>? responseHeadersBuffer = null; | |
if (_logResponseHeaders) | |
{ | |
responseHeadersBuffer = _headersPool.Get(); | |
} | |
await _httpRequestReader.ReadResponseAsync(logRecord, response, responseHeadersBuffer, cancellationToken).ConfigureAwait(false); | |
var propertyBag = LogMethodHelper.GetHelper(); | |
FillLogRecord(logRecord, propertyBag, elapsed, request, response); | |
Log.OutgoingRequest(Logger, GetLogLevel(logRecord), logRecord); | |
ReturnToPool(logRecord, propertyBag); | |
} | |
public ValueTask LogRequestFailedAsync(object? context, HttpRequestMessage request, HttpResponseMessage? response, Exception exception, TimeSpan elapsed, CancellationToken cancellationToken = default) | |
{ | |
var logRecord = (LogRecord?)context; | |
_ = Throw.IfNull(logRecord); | |
var propertyBag = LogMethodHelper.GetHelper(); | |
FillLogRecord(logRecord, propertyBag, elapsed, request, response); | |
Log.OutgoingRequestError(Logger, logRecord, exception); | |
ReturnToPool(logRecord, propertyBag); | |
return ValueTask.CompletedTask; | |
} | |
private void ReturnToPool(LogRecord logRecord, LogMethodHelper propertyBag) | |
{ | |
LogMethodHelper.ReturnHelper(propertyBag); | |
if (logRecord.RequestHeaders is not null) | |
{ | |
_headersPool.Return(logRecord.RequestHeaders); | |
} | |
if (logRecord.ResponseHeaders is not null) | |
{ | |
_headersPool.Return(logRecord.ResponseHeaders); | |
} | |
_logRecordPool.Return(logRecord); | |
} | |
private void FillLogRecord( | |
LogRecord logRecord, LogMethodHelper propertyBag, TimeSpan elapsed, | |
HttpRequestMessage request, HttpResponseMessage? response) | |
{ | |
foreach (var enricher in _enrichers) | |
{ | |
try | |
{ | |
enricher.Enrich(propertyBag, request, response); | |
} | |
catch (Exception e) | |
{ | |
Log.EnrichmentError(Logger, e); | |
} | |
} | |
logRecord.EnrichmentProperties = propertyBag; | |
logRecord.Duration = (long)elapsed.TotalMilliseconds; | |
} | |
private static LogLevel GetLogLevel(LogRecord logRecord) | |
{ | |
const int HttpErrorsRangeStart = 400; | |
const int HttpErrorsRangeEnd = 599; | |
int statusCode = logRecord.StatusCode!.Value; | |
if (statusCode >= HttpErrorsRangeStart && statusCode <= HttpErrorsRangeEnd) | |
{ | |
return LogLevel.Error; | |
} | |
return LogLevel.Information; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal sealed class HttpLoggingHandler : DelegatingHandler | |
{ | |
internal TimeProvider TimeProvider = TimeProvider.System; | |
private IHttpClientLogger _httpClientLogger; | |
public HttpLoggingHandler( | |
IHttpClientLogger httpClientLogger) | |
{ | |
_httpClientLogger = httpClientLogger; | |
} | |
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |
{ | |
_ = Throw.IfNull(request); | |
var timestamp = TimeProvider.GetTimestamp(); | |
HttpResponseMessage? response = null; | |
object? state = await _httpClientLogger.LogRequestStartAsync(request, cancellationToken).ConfigureAwait(false); | |
try | |
{ | |
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); | |
TimeSpan elapsed = TimeProvider.GetElapsedTime(timestamp, TimeProvider.GetTimestamp()); | |
await _httpClientLogger.LogRequestStopAsync(state, request, response, elapsed, cancellationToken).ConfigureAwait(false); | |
return response; | |
} | |
catch (Exception exception) | |
{ | |
TimeSpan elapsed = TimeProvider.GetElapsedTime(timestamp, TimeProvider.GetTimestamp()); | |
await _httpClientLogger.LogRequestFailedAsync(state, request, response, exception, elapsed); | |
throw; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IHttpClientLogger | |
{ | |
ValueTask<object?> LogRequestStartAsync( | |
HttpRequestMessage request, | |
CancellationToken cancellationToken = default); | |
ValueTask LogRequestStopAsync( | |
object? context, | |
HttpRequestMessage request, | |
HttpResponseMessage response, | |
TimeSpan elapsed, | |
CancellationToken cancellationToken = default); | |
ValueTask LogRequestFailedAsync( | |
object? context, | |
HttpRequestMessage request, | |
HttpResponseMessage? response, | |
Exception exception, | |
TimeSpan elapsed, | |
CancellationToken cancellationToken = default); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment