Skip to content

Commit 205d8b9

Browse files
feat(middleware): logging detailed + per request config (#11)
* feat(middleware): logging detailed + per request config * update comment * add more tests for merged logging options + minor refactoring when getting options * update changelogs
1 parent b3a2a94 commit 205d8b9

File tree

9 files changed

+335
-20
lines changed

9 files changed

+335
-20
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# Fluently Http Changelog
22

3-
[*vNext*](https://github.com/sketch7/FluentlyHttpClient/compare/1.3.0...1.3.1) (201X-X-X)
3+
[*vNext*](https://github.com/sketch7/FluentlyHttpClient/compare/1.4.0...1.4.1) (201X-X-X)
4+
5+
## [1.4.0](https://github.com/sketch7/FluentlyHttpClient/compare/1.3.0...1.4.0) (2018-03-03)
6+
7+
### Features
8+
- **logger middleware:** add `LoggerHttpMiddlewareOptions` to configure `ShouldLogDetailedRequest` and `ShouldLogDetailedResponse`.
9+
- **logger middleware:** add `WithLoggingOptions` on request builder, to specify options per request.
10+
- **timer middleware:** add `WithTimerWarnThreshold` on request builder, to specify options per request.
11+
412

513
## [1.3.0](https://github.com/sketch7/FluentlyHttpClient/compare/1.2.1...1.3.0) (2018-02-24)
614

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ namespace FluentlyHttpClient
294294
// Response extension methods - useful to extend FluentHttpResponse
295295
public static class TimerFluentResponseExtensions
296296
{
297-
private const string TimeTakenKey = "TIME_TAKEN";
297+
private const string TimeTakenKey = "TIMER_TIME_TAKEN";
298298

299299
public static void SetTimeTaken(this FluentHttpResponse response, TimeSpan value)
300300
=> response.Items.Add(TimeTakenKey, value);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sketch7/fluently-http-client",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"scripts": {
55
"pack": "bash ./tools/pack.sh",
66
"prepublish:dev": "npm run pack",

src/FluentlyHttpClient/Middleware/LoggerHttpMiddleware.cs

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,68 @@
44

55
namespace FluentlyHttpClient.Middleware
66
{
7+
/// <summary>
8+
/// Logger HTTP middleware options.
9+
/// </summary>
10+
public class LoggerHttpMiddlewareOptions
11+
{
12+
/// <summary>
13+
/// Gets or sets whether the request log should be detailed e.g. include body. Note: This should only be enabled for development or as needed,
14+
/// as it will reduce performance.
15+
/// </summary>
16+
public bool? ShouldLogDetailedRequest { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets whether the response log should be detailed e.g. include body. Note: This should only be enabled for development or as needed,
20+
/// as it will reduce performance.
21+
/// </summary>
22+
public bool? ShouldLogDetailedResponse { get; set; }
23+
}
24+
725
/// <summary>
826
/// Logging middleware for HTTP client.
927
/// </summary>
1028
public class LoggerHttpMiddleware : IFluentHttpMiddleware
1129
{
1230
private readonly FluentHttpRequestDelegate _next;
31+
private readonly LoggerHttpMiddlewareOptions _options;
1332
private readonly ILogger _logger;
1433

1534
/// <summary>
1635
/// Initializes a new instance.
1736
/// </summary>
18-
public LoggerHttpMiddleware(FluentHttpRequestDelegate next, ILogger<LoggerHttpMiddleware> logger)
37+
public LoggerHttpMiddleware(FluentHttpRequestDelegate next, LoggerHttpMiddlewareOptions options, ILogger<LoggerHttpMiddleware> logger)
1938
{
2039
_next = next;
40+
_options = options;
2141
_logger = logger;
2242
}
2343

24-
/// <summary>
25-
/// Function to invoke.
26-
/// </summary>
44+
/// <inheritdoc />
2745
public async Task<FluentHttpResponse> Invoke(FluentHttpRequest request)
2846
{
29-
if (_logger.IsEnabled(LogLevel.Information))
47+
if (!_logger.IsEnabled(LogLevel.Information))
48+
return await _next(request);
49+
50+
var options = request.GetLoggingOptions(_options);
51+
if (request.Message.Content == null || !options.ShouldLogDetailedRequest.GetValueOrDefault(false))
3052
_logger.LogInformation("Pre-request... {request}", request);
53+
else
54+
{
55+
var requestContent = await request.Message.Content.ReadAsStringAsync();
56+
_logger.LogInformation("Pre-request... {request}\nContent: {requestContent}", request, requestContent);
57+
}
3158

3259
var response = await _next(request);
3360

34-
if (_logger.IsEnabled(LogLevel.Information))
61+
if (response.Content == null || !options.ShouldLogDetailedResponse.GetValueOrDefault(false))
62+
{
3563
_logger.LogInformation("Post-request... {response}", response);
64+
return response;
65+
}
66+
67+
var responseContent = await response.Content.ReadAsStringAsync();
68+
_logger.LogInformation("Post-request... {response}\nContent: {responseContent}", response, responseContent);
3669
return response;
3770
}
3871
}
@@ -45,11 +78,44 @@ namespace FluentlyHttpClient
4578
/// </summary>
4679
public static class LoggerHttpMiddlwareExtensions
4780
{
81+
private const string LoggingOptionsKey = "LOGGING_OPTIONS";
82+
83+
#region Request Extensions
84+
/// <summary>
85+
/// Set logging options for the request.
86+
/// </summary>
87+
/// <param name="requestBuilder">Request builder instance.</param>
88+
/// <param name="options">Logging options to set.</param>
89+
public static FluentHttpRequestBuilder WithLoggingOptions(this FluentHttpRequestBuilder requestBuilder, LoggerHttpMiddlewareOptions options)
90+
{
91+
requestBuilder.Items[LoggingOptionsKey] = options;
92+
return requestBuilder;
93+
}
94+
95+
/// <summary>
96+
/// Get logging option for the request.
97+
/// </summary>
98+
/// <param name="request">Request to get options from.</param>
99+
/// <param name="defaultOptions"></param>
100+
/// <returns>Returns merged logging options.</returns>
101+
public static LoggerHttpMiddlewareOptions GetLoggingOptions(this FluentHttpRequest request, LoggerHttpMiddlewareOptions defaultOptions = null)
102+
{
103+
if (!request.Items.TryGetValue(LoggingOptionsKey, out var result)) return defaultOptions;
104+
var options = (LoggerHttpMiddlewareOptions)result;
105+
if (defaultOptions == null)
106+
return options;
107+
options.ShouldLogDetailedRequest = options.ShouldLogDetailedRequest ?? defaultOptions.ShouldLogDetailedRequest;
108+
options.ShouldLogDetailedResponse = options.ShouldLogDetailedResponse ?? defaultOptions.ShouldLogDetailedResponse;
109+
return options;
110+
}
111+
#endregion
112+
48113
/// <summary>
49114
/// Use logger middleware which logs out going requests and incoming responses.
50115
/// </summary>
51116
/// <param name="builder">Builder instance</param>
52-
public static FluentHttpClientBuilder UseLogging(this FluentHttpClientBuilder builder)
53-
=> builder.UseMiddleware<LoggerHttpMiddleware>();
117+
/// <param name="options"></param>
118+
public static FluentHttpClientBuilder UseLogging(this FluentHttpClientBuilder builder, LoggerHttpMiddlewareOptions options = null)
119+
=> builder.UseMiddleware<LoggerHttpMiddleware>(options ?? new LoggerHttpMiddlewareOptions());
54120
}
55121
}

src/FluentlyHttpClient/Middleware/TimerHttpMiddleware.cs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class TimerHttpMiddlewareOptions
2222
/// </summary>
2323
public class TimerHttpMiddleware : IFluentHttpMiddleware
2424
{
25+
private const string TimeTakenMessage = "Executed request {request} in {timeTakenMillis}ms";
2526
private readonly FluentHttpRequestDelegate _next;
2627
private readonly TimerHttpMiddlewareOptions _options;
2728
private readonly ILogger _logger;
@@ -35,23 +36,23 @@ public TimerHttpMiddleware(FluentHttpRequestDelegate next, TimerHttpMiddlewareOp
3536
_options = options;
3637
_logger = logger;
3738

38-
if(_options.WarnThreshold <= TimeSpan.Zero)
39+
if (_options.WarnThreshold <= TimeSpan.Zero)
3940
throw new ArgumentException($"{nameof(_options.WarnThreshold)} must be greater than Zero.");
4041
}
4142

42-
/// <summary>
43-
/// Function to invoke.
44-
/// </summary>
43+
/// <inheritdoc />
4544
public async Task<FluentHttpResponse> Invoke(FluentHttpRequest request)
4645
{
4746
var watch = Stopwatch.StartNew();
4847
var response = await _next(request);
4948
var elapsed = watch.Elapsed;
50-
51-
if (_logger.IsEnabled(LogLevel.Warning) && elapsed > _options.WarnThreshold)
52-
_logger.LogWarning("Executed request {request} in {timeTakenMillis}ms", request, elapsed.TotalMilliseconds);
49+
var threshold = request.GetTimerWarnThreshold()
50+
.GetValueOrDefault(_options.WarnThreshold);
51+
52+
if (_logger.IsEnabled(LogLevel.Warning) && elapsed > threshold)
53+
_logger.LogWarning(TimeTakenMessage, request, elapsed.TotalMilliseconds);
5354
else if (_logger.IsEnabled(LogLevel.Information))
54-
_logger.LogInformation("Executed request {request} in {timeTakenMillis}ms", request, elapsed.TotalMilliseconds);
55+
_logger.LogInformation(TimeTakenMessage, request, elapsed.TotalMilliseconds);
5556

5657
response.SetTimeTaken(elapsed);
5758
return response;
@@ -66,8 +67,35 @@ namespace FluentlyHttpClient
6667
/// </summary>
6768
public static class TimerHttpMiddlwareExtensions
6869
{
69-
private const string TimeTakenKey = "TIME_TAKEN";
70+
private const string TimeTakenKey = "TIMER_TIME_TAKEN";
71+
private const string WarnThresholdOptionKey = "TIMER_OPTION_WARN_THRESHOLD";
72+
73+
#region Request Extensions
74+
/// <summary>
75+
/// Set timer warn threshold for request.
76+
/// </summary>
77+
/// <param name="requestBuilder">Request builder instance.</param>
78+
/// <param name="value">Timespan value.</param>
79+
public static FluentHttpRequestBuilder WithTimerWarnThreshold(this FluentHttpRequestBuilder requestBuilder, TimeSpan value)
80+
{
81+
requestBuilder.Items[WarnThresholdOptionKey] = value;
82+
return requestBuilder;
83+
}
84+
85+
/// <summary>
86+
/// Get timer warn threshold option for the request.
87+
/// </summary>
88+
/// <param name="request">Request to get time from.</param>
89+
/// <returns>Returns timespan for the time taken.</returns>
90+
public static TimeSpan? GetTimerWarnThreshold(this FluentHttpRequest request)
91+
{
92+
if (request.Items.TryGetValue(WarnThresholdOptionKey, out var result))
93+
return (TimeSpan)result;
94+
return null;
95+
}
96+
#endregion
7097

98+
#region Response Extensions
7199
/// <summary>
72100
/// Set time taken.
73101
/// </summary>
@@ -83,6 +111,7 @@ public static void SetTimeTaken(this FluentHttpResponse response, TimeSpan value
83111
/// <returns>Returns timespan for the time taken.</returns>
84112
public static TimeSpan GetTimeTaken(this FluentHttpResponse response)
85113
=> (TimeSpan)response.Items[TimeTakenKey];
114+
#endregion
86115

87116
/// <summary>
88117
/// Use timer middleware which measures how long the request takes.

test/FluentlyHttpClient.Test.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
1111
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
1212
<PackageReference Include="RichardSzalay.MockHttp" Version="3.2.1" />
13+
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
14+
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
15+
<PackageReference Include="Serilog.Sinks.Debug" Version="1.0.0" />
1316
<PackageReference Include="xunit" Version="2.3.1" />
1417
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
1518
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Net.Http;
3+
using FluentlyHttpClient.Middleware;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using RichardSzalay.MockHttp;
6+
using Serilog;
7+
using Xunit;
8+
9+
namespace FluentlyHttpClient.Test.Integration
10+
{
11+
public class SerilogIntegrationTest
12+
{
13+
private IServiceProvider BuildContainer()
14+
{
15+
Log.Logger = new LoggerConfiguration()
16+
.WriteTo.Console()
17+
.WriteTo.Debug()
18+
.CreateLogger();
19+
var container = new ServiceCollection()
20+
.AddFluentlyHttpClient()
21+
.AddLogging(x => x.AddSerilog());
22+
return container.BuildServiceProvider();
23+
}
24+
25+
[Fact]
26+
public async void ShouldLogAll()
27+
{
28+
var mockHttp = new MockHttpMessageHandler();
29+
mockHttp.When(HttpMethod.Post, "https://sketch7.com/api/heroes")
30+
.Respond("application/json", "{ 'name': 'Azmodan', 'title': 'Lord of Sin' }");
31+
32+
var fluentHttpClientFactory = BuildContainer()
33+
.GetRequiredService<IFluentHttpClientFactory>();
34+
35+
var clientBuilder = fluentHttpClientFactory.CreateBuilder("sketch7")
36+
.WithBaseUrl("https://sketch7.com")
37+
.UseLogging(new LoggerHttpMiddlewareOptions
38+
{
39+
ShouldLogDetailedResponse = false,
40+
ShouldLogDetailedRequest = false
41+
})
42+
.WithMessageHandler(mockHttp);
43+
44+
var httpClient = fluentHttpClientFactory.Add(clientBuilder);
45+
var hero = await httpClient.CreateRequest("/api/heroes")
46+
.AsPost()
47+
.WithBody(new Hero
48+
{
49+
Key = "valeera",
50+
Name = "Valeera",
51+
Title = "Shadow of the Ucrowned"
52+
})
53+
.WithLoggingOptions(new LoggerHttpMiddlewareOptions
54+
{
55+
ShouldLogDetailedRequest = false,
56+
ShouldLogDetailedResponse = true
57+
})
58+
.Return<Hero>();
59+
60+
Assert.NotNull(hero);
61+
Assert.Equal("Azmodan", hero.Name);
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)