diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/EventSourceTests.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/EventSourceTests.cs index ae13131b0465..aff84d9e3a5c 100644 --- a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/EventSourceTests.cs +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/EventSourceTests.cs @@ -18,7 +18,7 @@ namespace Azure.Base.Tests { // Avoid running these tests in parallel with anything else that's sharing the event source [NonParallelizable] - public class EventSourceTests + public class EventSourceTests: PipelineTestBase { private readonly TestEventListener _listener = new TestEventListener(); @@ -43,33 +43,86 @@ public void MatchesNameAndGuid() [Test] public async Task SendingRequestProducesEvents() { - var options = new HttpPipelineOptions(new HttpClientTransport(new HttpClient(new MockHttpMessageHandler()))); - options.LoggingPolicy = LoggingPolicy.Shared; + var handler = new MockHttpClientHandler(httpRequestMessage => { + var response = new HttpResponseMessage((HttpStatusCode)500); + response.Content = new ByteArrayContent(new byte[] {6, 7, 8, 9, 0}); + response.Headers.Add("Custom-Response-Header", "Improved value"); + return Task.FromResult(response); + }); + var transport = new HttpClientTransport(new HttpClient(handler)); + var options = new HttpPipelineOptions(transport) + { + LoggingPolicy = LoggingPolicy.Shared + }; var pipeline = options.Build("test", "1.0.0"); + string requestId; using (var request = pipeline.CreateRequest()) { request.SetRequestLine(HttpVerb.Get, new Uri("https://contoso.a.io")); + request.AddHeader("Date", "3/26/2019"); + request.AddHeader("Custom-Header", "Value"); + request.Content = HttpPipelineRequestContent.Create(new byte[] {1, 2, 3, 4, 5}); + requestId = request.RequestId; + var response = await pipeline.SendRequestAsync(request, CancellationToken.None); Assert.AreEqual(500, response.Status); } Assert.True(_listener.EventData.Any(e => - e.EventId == 3 && - e.EventName == "ProcessingRequest" && - GetStringProperty(e, "request").Contains("https://contoso.a.io"))); + e.EventId == 1 && + e.Level == EventLevel.Informational && + e.EventName == "Request" && + GetStringProperty(e, "requestId").Equals(requestId) && + GetStringProperty(e, "uri").Equals("https://contoso.a.io/") && + GetStringProperty(e, "method").Equals("GET") && + GetStringProperty(e, "headers").Contains($"Date:3/26/2019{Environment.NewLine}") && + GetStringProperty(e, "headers").Contains($"Custom-Header:Value{Environment.NewLine}") + )); + + Assert.True(_listener.EventData.Any(e => + e.EventId == 2 && + e.Level == EventLevel.Verbose && + e.EventName == "RequestContent" && + GetStringProperty(e, "requestId").Equals(requestId) && + ((byte[])GetProperty(e, "content")).SequenceEqual(new byte[] {1, 2 , 3, 4, 5})) + ); Assert.True(_listener.EventData.Any(e => - e.EventId == 4 && - e.EventName == "ProcessingResponse" && - GetStringProperty(e, "response").Contains("500"))); + e.EventId == 5 && + e.Level == EventLevel.Informational && + e.EventName == "Response" && + GetStringProperty(e, "requestId").Equals(requestId) && + (int)GetProperty(e, "status") == 500 && + GetStringProperty(e, "headers").Contains($"Custom-Response-Header:Improved value{Environment.NewLine}") + )); Assert.True(_listener.EventData.Any(e => e.EventId == 6 && + e.Level == EventLevel.Verbose && + e.EventName == "ResponseContent" && + GetStringProperty(e, "requestId").Equals(requestId) && + ((byte[])GetProperty(e, "content")).SequenceEqual(new byte[] {6, 7, 8, 9, 0})) + ); + + Assert.True(_listener.EventData.Any(e => + e.EventId == 8 && + e.Level == EventLevel.Error && e.EventName == "ErrorResponse" && - (int)GetProperty(e, "status") == 500)); + GetStringProperty(e, "requestId").Equals(requestId) && + (int)GetProperty(e, "status") == 500 && + GetStringProperty(e, "headers").Contains($"Custom-Response-Header:Improved value{Environment.NewLine}") + )); + + Assert.True(_listener.EventData.Any(e => + e.EventId == 9 && + e.Level == EventLevel.Informational && + e.EventName == "ErrorResponseContent" && + GetStringProperty(e, "requestId").Equals(requestId) && + ((byte[])GetProperty(e, "content")).SequenceEqual(new byte[] {6, 7, 8, 9, 0})) + ); } private object GetProperty(EventWrittenEventArgs data, string propName) @@ -77,13 +130,5 @@ private object GetProperty(EventWrittenEventArgs data, string propName) private string GetStringProperty(EventWrittenEventArgs data, string propName) => data.Payload[data.PayloadNames.IndexOf(propName)] as string; - - private class MockHttpMessageHandler: HttpMessageHandler - { - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - return Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)); - } - } } } \ No newline at end of file diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/HttpClientTransportTests.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/HttpClientTransportTests.cs index 9ec838beac8c..40163772bf58 100644 --- a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/HttpClientTransportTests.cs +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/HttpClientTransportTests.cs @@ -8,7 +8,6 @@ using System.Net; using System.Net.Http; using System.Text; -using System.Threading; using System.Threading.Tasks; using Azure.Base.Http; using Azure.Base.Http.Pipeline; @@ -16,7 +15,7 @@ namespace Azure.Base.Tests { - public class HttpClientTransportTests + public class HttpClientTransportTests: PipelineTestBase { public static object[] ContentWithLength => new object[] @@ -277,41 +276,34 @@ public async Task SettingContentHeaderDoesNotSetContent(string headerName, strin Assert.Null(httpMessageContent); } - private static async Task ExecuteRequest(HttpPipelineRequest request, HttpClientTransport transport) + [Test] + public async Task RequestAndResponseHasRequestId() { - using (var message = new HttpPipelineMessage(CancellationToken.None) - { - Request = request - }) - { - await transport.ProcessAsync(message); - return message.Response; - } + var mockHandler = new MockHttpClientHandler(httpRequestMessage => { }); + + var transport = new HttpClientTransport(new HttpClient(mockHandler)); + var request = transport.CreateRequest(null); + Assert.IsNotEmpty(request.RequestId); + Assert.True(Guid.TryParse(request.RequestId, out _)); + request.SetRequestLine(HttpVerb.Get, new Uri("http://example.com:340")); + + var response = await ExecuteRequest(request, transport); + Assert.AreEqual(request.RequestId, response.RequestId); } - private class MockHttpClientHandler : HttpMessageHandler + [Test] + public async Task RequestIdCanBeOverriden() { - private readonly Func> _onSend; - - public MockHttpClientHandler(Action onSend) - { - _onSend = req => { - onSend(req); - return Task.FromResult(null); - }; - } + var mockHandler = new MockHttpClientHandler(httpRequestMessage => { }); - public MockHttpClientHandler(Func> onSend) - { - _onSend = onSend; - } + var transport = new HttpClientTransport(new HttpClient(mockHandler)); + var request = transport.CreateRequest(null); - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var response = await _onSend(request); + request.RequestId = "123"; + request.SetRequestLine(HttpVerb.Get, new Uri("http://example.com:340")); - return response ?? new HttpResponseMessage((HttpStatusCode)200); - } + var response = await ExecuteRequest(request, transport); + Assert.AreEqual(request.RequestId, response.RequestId); } } } diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/MockHttpClientHandler.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/MockHttpClientHandler.cs new file mode 100644 index 000000000000..29953af76cb7 --- /dev/null +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/MockHttpClientHandler.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Base.Tests +{ + internal class MockHttpClientHandler : HttpMessageHandler + { + private readonly Func> _onSend; + + public MockHttpClientHandler(Action onSend) + { + _onSend = req => { + onSend(req); + return Task.FromResult(null); + }; + } + + public MockHttpClientHandler(Func> onSend) + { + _onSend = onSend; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = await _onSend(request); + + return response ?? new HttpResponseMessage((HttpStatusCode)200); + } + } +} \ No newline at end of file diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/PipelineTestBase.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/PipelineTestBase.cs new file mode 100644 index 000000000000..a8da87147056 --- /dev/null +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/PipelineTestBase.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Azure.Base.Http; +using Azure.Base.Http.Pipeline; + +namespace Azure.Base.Tests +{ + public class PipelineTestBase + { + protected static async Task ExecuteRequest(HttpPipelineRequest request, HttpClientTransport transport) + { + using (var message = new HttpPipelineMessage(CancellationToken.None) + { + Request = request + }) + { + await transport.ProcessAsync(message); + return message.Response; + } + } + } +} \ No newline at end of file diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/PipelineTests.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/PipelineTests.cs index 3091ca0b77f8..031d18cf07ac 100644 --- a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/PipelineTests.cs +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/PipelineTests.cs @@ -69,6 +69,8 @@ public override IEnumerable Headers } } + public override string RequestId { get; set; } + public override void Dispose() { } diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/Testing/Mocks.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/Testing/Mocks.cs index 4a490dad7a44..30e750cfe593 100644 --- a/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/Testing/Mocks.cs +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base.Tests/Testing/Mocks.cs @@ -80,6 +80,8 @@ public override IEnumerable Headers } } + public override string RequestId { get; set; } + public override string ToString() => $"{Method} {Uri}"; public override void Dispose() @@ -105,6 +107,8 @@ public PipelineResponse(HttpVerb method, Uri uri) public override Stream ResponseContentStream => throw new NotImplementedException(); + public override string RequestId { get; set; } + public void SetStatus(int status) => _status = status; public override string ToString() => $"{_method} {_uri}"; diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base/Diagnostics/HttpPipelineEventSource.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base/Diagnostics/HttpPipelineEventSource.cs index d2f22b5ccadd..b7e5fe104a1b 100644 --- a/src/SDKs/Azure.Base/data-plane/Azure.Base/Diagnostics/HttpPipelineEventSource.cs +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base/Diagnostics/HttpPipelineEventSource.cs @@ -3,77 +3,191 @@ using Azure.Base.Http; using System; +using System.Collections.Generic; using System.Diagnostics.Tracing; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; // TODO (pri 2): we should log correction/activity // TODO (pri 2): we should log exceptions namespace Azure.Base.Diagnostics { // TODO (pri 2): make the type internal - [EventSource(Name = SOURCE_NAME)] + [EventSource(Name = EventSourceName)] internal sealed class HttpPipelineEventSource : EventSource { // TODO (pri 3): do we want the same source name for all SDk components? - const string SOURCE_NAME = "AzureSDK"; + private const string EventSourceName = "AzureSDK"; - const int LOG_REQUEST = 3; - const int LOG_RESPONSE = 4; - const int LOG_DELAY = 5; - const int LOG_ERROR_RESPONSE = 6; + private const int MaxEventPayloadSize = 10 * 1024; - private HttpPipelineEventSource() : base(SOURCE_NAME) { } + private const int RequestEvent = 1; + private const int RequestContentEvent = 2; + private const int ResponseEvent = 5; + private const int ResponseContentEvent = 6; + private const int RequestDelayEvent = 7; + private const int ErrorResponseEvent = 8; + private const int ErrorResponseContentEvent = 9; + + private HttpPipelineEventSource() : base(EventSourceName) { } internal static readonly HttpPipelineEventSource Singleton = new HttpPipelineEventSource(); // TODO (pri 2): this logs just the URI. We need more [NonEvent] - public void ProcessingRequest(HttpPipelineRequest request) - => ProcessingRequest(request.ToString()); + public void Request(HttpPipelineRequest request) + { + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + Request(request.RequestId, request.Method.ToString().ToUpperInvariant(), request.Uri.ToString(), FormatHeaders(request.Headers)); + } + } + + [NonEvent] + public async Task RequestContentAsync(HttpPipelineRequest request, CancellationToken cancellationToken) + { + if (IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + RequestContent(request.RequestId, await FormatContentAsync(request.Content, cancellationToken)); + } + } + + [NonEvent] + public void Response(HttpPipelineResponse response) + { + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + Response(response.RequestId, response.Status, FormatHeaders(response.Headers)); + } + } [NonEvent] - public void ProcessingResponse(HttpPipelineResponse response) - => ProcessingResponse(response.ToString()); + public async Task ResponseContentAsync(HttpPipelineResponse response, CancellationToken cancellationToken) + { + if (IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + ResponseContent(response.RequestId, await FormatContentAsync(response.ResponseContentStream)); + } + } [NonEvent] public void ErrorResponse(HttpPipelineResponse response) - => ErrorResponse(response.Status); + { + if (IsEnabled(EventLevel.Error, EventKeywords.None)) + { + ErrorResponse(response.RequestId, response.Status, FormatHeaders(response.Headers)); + } + } + + [NonEvent] + public async Task ErrorResponseContentAsync(HttpPipelineResponse response, CancellationToken cancellationToken) + { + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + ErrorResponseContent(response.RequestId, await FormatContentAsync(response.ResponseContentStream).ConfigureAwait(false)); + } + } [NonEvent] public void ResponseDelay(HttpPipelineResponse response, long delayMilliseconds) - => ResponseDelayCore(delayMilliseconds); + { + ResponseDelayCore(delayMilliseconds); + } // TODO (pri 2): there are more attribute properties we might want to set - [Event(LOG_REQUEST, Level = EventLevel.Informational)] - void ProcessingRequest(string request) + [Event(RequestEvent, Level = EventLevel.Informational)] + private void Request(string requestId, string method, string uri, string headers) { - // TODO (pri 2): is EventKeywords.None ok? - if (IsEnabled(EventLevel.Informational, EventKeywords.None)) { - WriteEvent(LOG_REQUEST, request); + WriteEvent(RequestEvent, requestId, method, uri, headers); + } + + [Event(RequestContentEvent, Level = EventLevel.Verbose)] + private void RequestContent(string requestId, byte[] content) + { + WriteEvent(RequestContentEvent, requestId, content); + } + + [Event(ResponseEvent, Level = EventLevel.Informational)] + private void Response(string requestId, int status, string headers) + { + WriteEvent(ResponseEvent, requestId, status, headers); + } + + + [Event(ResponseContentEvent, Level = EventLevel.Verbose)] + private void ResponseContent(string requestId, byte[] content) + { + WriteEvent(ResponseContentEvent, requestId, content); + } + + [Event(ErrorResponseEvent, Level = EventLevel.Error)] + public void ErrorResponse(string requestId, int status, string headers) + { + WriteEvent(ErrorResponseEvent, requestId, status, headers); + } + + [Event(ErrorResponseContentEvent, Level = EventLevel.Informational)] + private void ErrorResponseContent(string requestId, byte[] content) + { + WriteEvent(ErrorResponseContentEvent, requestId, content); + } + + [Event(RequestDelayEvent, Level = EventLevel.Warning)] + private void ResponseDelayCore(long delayMilliseconds) + { + if (IsEnabled(EventLevel.Warning, EventKeywords.None)) + { + WriteEvent(RequestDelayEvent, delayMilliseconds); } } - [Event(LOG_RESPONSE, Level = EventLevel.Informational)] - void ProcessingResponse(string response) + private static async Task FormatContentAsync(Stream responseContent) { - if (IsEnabled(EventLevel.Informational, EventKeywords.None)) { - WriteEvent(LOG_RESPONSE, response); + using (var memoryStream = new MemoryStream()) + { + await responseContent.CopyToAsync(memoryStream); + + // Rewind the stream + responseContent.Position = 0; + + return FormatContent(memoryStream.ToArray()); } } - [Event(LOG_DELAY, Level = EventLevel.Warning)] - void ResponseDelayCore(long delayMilliseconds) + private static async Task FormatContentAsync(HttpPipelineRequestContent requestContent, CancellationToken cancellationToken) { - if (IsEnabled(EventLevel.Warning, EventKeywords.None)) { - WriteEvent(LOG_DELAY, delayMilliseconds); + using (var memoryStream = new MemoryStream()) + { + await requestContent.WriteTo(memoryStream, cancellation: cancellationToken).ConfigureAwait(false); + + return FormatContent(memoryStream.ToArray()); } } - [Event(LOG_ERROR_RESPONSE, Level = EventLevel.Error)] - void ErrorResponse(int status) + private static byte[] FormatContent(byte[] buffer) + { + int count = Math.Min(buffer.Length, MaxEventPayloadSize); + + byte[] slice = buffer; + if (count != buffer.Length) + { + slice = new byte[count]; + Buffer.BlockCopy(buffer, 0, slice, 0, count); + } + + return slice; + } + + private static string FormatHeaders(IEnumerable headers) { - if (IsEnabled(EventLevel.Error, EventKeywords.None)) { - WriteEvent(LOG_ERROR_RESPONSE, status); + var stringBuilder = new StringBuilder(); + foreach (var header in headers) + { + stringBuilder.AppendLine(header.ToString()); } + return stringBuilder.ToString(); } } } diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/HttpPipelineRequest.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/HttpPipelineRequest.cs index 9e9435ff0a8e..c06261f2fe63 100644 --- a/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/HttpPipelineRequest.cs +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/HttpPipelineRequest.cs @@ -29,6 +29,8 @@ public virtual void AddHeader(string name, string value) public abstract IEnumerable Headers { get; } + public abstract string RequestId { get; set; } + public abstract void Dispose(); } } \ No newline at end of file diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/HttpPipelineResponse.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/HttpPipelineResponse.cs index 5b0dc3bfd099..fc518ee6f7f9 100644 --- a/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/HttpPipelineResponse.cs +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/HttpPipelineResponse.cs @@ -15,6 +15,8 @@ public abstract class HttpPipelineResponse: IDisposable public abstract Stream ResponseContentStream { get; } + public abstract string RequestId { get; set; } + public abstract IEnumerable Headers { get; } public abstract void Dispose(); diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/Pipeline/HttpClientTransport.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/Pipeline/HttpClientTransport.cs index d00779509de4..c0b55796875b 100644 --- a/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/Pipeline/HttpClientTransport.cs +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/Pipeline/HttpClientTransport.cs @@ -35,7 +35,7 @@ public sealed override async Task ProcessAsync(HttpPipelineMessage message) using (HttpRequestMessage httpRequest = pipelineRequest.BuildRequestMessage(message.Cancellation)) { HttpResponseMessage responseMessage = await ProcessCoreAsync(message.Cancellation, httpRequest).ConfigureAwait(false); - message.Response = new PipelineResponse(responseMessage); + message.Response = new PipelineResponse(message.Request.RequestId, responseMessage); } } @@ -97,6 +97,7 @@ sealed class PipelineRequest : HttpPipelineRequest public PipelineRequest() { _requestMessage = new HttpRequestMessage(); + RequestId = Guid.NewGuid().ToString(); } public override Uri Uri @@ -121,6 +122,8 @@ public override HttpPipelineRequestContent Content } } + public override string RequestId { get; set; } + public override void AddHeader(HttpHeader header) { AddHeader(header.Name, header.Value); @@ -226,8 +229,9 @@ sealed class PipelineResponse : HttpPipelineResponse { readonly HttpResponseMessage _responseMessage; - public PipelineResponse(HttpResponseMessage responseMessage) + public PipelineResponse(string requestId, HttpResponseMessage responseMessage) { + RequestId = requestId; _responseMessage = responseMessage; } @@ -237,6 +241,8 @@ public PipelineResponse(HttpResponseMessage responseMessage) public override Stream ResponseContentStream => _responseMessage?.Content?.ReadAsStreamAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + public override string RequestId { get; set; } + public override bool TryGetHeader(string name, out string value) => HttpClientTransport.TryGetHeader(_responseMessage.Headers, _responseMessage.Content, name, out value); public override IEnumerable Headers => HttpClientTransport.GetHeaders(_responseMessage.Headers, _responseMessage.Content); diff --git a/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/Pipeline/LoggingPolicy.cs b/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/Pipeline/LoggingPolicy.cs index 22d4945c4029..e9a55ef1a1a0 100644 --- a/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/Pipeline/LoggingPolicy.cs +++ b/src/SDKs/Azure.Base/data-plane/Azure.Base/Http/Pipeline/LoggingPolicy.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; +using System.Diagnostics.Tracing; using System.Threading.Tasks; using Azure.Base.Diagnostics; @@ -11,13 +12,13 @@ namespace Azure.Base.Http.Pipeline { public class LoggingPolicy : HttpPipelinePolicy { - static readonly long s_delayWarningThreshold = 3000; // 3000ms - static readonly long s_frequency = Stopwatch.Frequency; + private static readonly long s_delayWarningThreshold = 3000; // 3000ms + private static readonly long s_frequency = Stopwatch.Frequency; private static readonly HttpPipelineEventSource s_eventSource = HttpPipelineEventSource.Singleton; - int[] _excludeErrors = Array.Empty(); + private int[] _excludeErrors = Array.Empty(); - public readonly static LoggingPolicy Shared = new LoggingPolicy(); + public static readonly LoggingPolicy Shared = new LoggingPolicy(); public LoggingPolicy(params int[] excludeErrors) => _excludeErrors = excludeErrors; @@ -25,7 +26,12 @@ public LoggingPolicy(params int[] excludeErrors) // TODO (pri 1): we should remove sensitive information, e.g. keys public override async Task ProcessAsync(HttpPipelineMessage message, ReadOnlyMemory pipeline) { - s_eventSource.ProcessingRequest(message.Request); + s_eventSource.Request(message.Request); + + if (message.Request.Content != null) + { + await s_eventSource.RequestContentAsync(message.Request, message.Cancellation); + } var before = Stopwatch.GetTimestamp(); await ProcessNextAsync(pipeline, message).ConfigureAwait(false); @@ -33,11 +39,22 @@ public override async Task ProcessAsync(HttpPipelineMessage message, ReadOnlyMem var status = message.Response.Status; // if error status - if (status >= 400 && status <= 599 && (Array.IndexOf(_excludeErrors, status) == -1)) { + if (status >= 400 && status <= 599 && (Array.IndexOf(_excludeErrors, status) == -1)) + { s_eventSource.ErrorResponse(message.Response); + + if (message.Response.ResponseContentStream != null) + { + await s_eventSource.ErrorResponseContentAsync(message.Response, message.Cancellation).ConfigureAwait(false); + } } - s_eventSource.ProcessingResponse(message.Response); + s_eventSource.Response(message.Response); + + if (message.Response.ResponseContentStream != null) + { + await s_eventSource.ResponseContentAsync(message.Response, message.Cancellation).ConfigureAwait(false); + } var elapsedMilliseconds = (after - before) * 1000 / s_frequency; if (elapsedMilliseconds > s_delayWarningThreshold) {