Skip to content

Update HttpLoggingAttribute API #48214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 53 additions & 12 deletions src/Middleware/HttpLogging/src/HttpLoggingAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,73 @@ public sealed class HttpLoggingAttribute : Attribute
/// Initializes an instance of the <see cref="HttpLoggingAttribute"/> class.
/// </summary>
/// <param name="loggingFields">Specifies what fields to log for the endpoint.</param>
/// <param name="requestBodyLogLimit">Specifies the maximum number of bytes to be logged for the request body. A value of <c>-1</c> means use the default setting in <see cref="HttpLoggingOptions.RequestBodyLogLimit"/>.</param>
/// <param name="responseBodyLogLimit">Specifies the maximum number of bytes to be logged for the response body. A value of <c>-1</c> means use the default setting in <see cref="HttpLoggingOptions.ResponseBodyLogLimit"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="requestBodyLogLimit"/> or <paramref name="responseBodyLogLimit"/> is less than <c>-1</c>.</exception>
public HttpLoggingAttribute(HttpLoggingFields loggingFields, int requestBodyLogLimit = -1, int responseBodyLogLimit = -1)
public HttpLoggingAttribute(HttpLoggingFields loggingFields)
{
LoggingFields = loggingFields;

ArgumentOutOfRangeException.ThrowIfLessThan(requestBodyLogLimit, -1);
ArgumentOutOfRangeException.ThrowIfLessThan(responseBodyLogLimit, -1);

RequestBodyLogLimit = requestBodyLogLimit;
ResponseBodyLogLimit = responseBodyLogLimit;
}

private int _responseBodyLogLimit;
private int _requestBodyLogLimit;

/// <summary>
/// Specifies what fields to log.
/// </summary>
public HttpLoggingFields LoggingFields { get; }

/// <summary>
/// Indicates whether <see cref="RequestBodyLogLimit"/> has been set.
/// </summary>
public bool IsRequestBodyLogLimitSet { get; private set; }

/// <summary>
/// Specifies the maximum number of bytes to be logged for the request body.
/// </summary>
public int RequestBodyLogLimit { get; }
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="RequestBodyLogLimit"/> set to a value less than <c>0</c>.</exception>
/// <exception cref="InvalidOperationException">Thrown when getting <see cref="RequestBodyLogLimit"/> if it hasn't been set to a value. Check <see cref="IsRequestBodyLogLimitSet"/> first.</exception>
public int RequestBodyLogLimit
{
get
{
if (IsRequestBodyLogLimitSet)
{
return _requestBodyLogLimit;
}

throw new InvalidOperationException($"{nameof(RequestBodyLogLimit)} was not set. Check {nameof(IsRequestBodyLogLimitSet)} before accessing this property.");
}
set
{
ArgumentOutOfRangeException.ThrowIfLessThan(value, 0, nameof(RequestBodyLogLimit));
_requestBodyLogLimit = value;
IsRequestBodyLogLimitSet = true;
}
}

/// <summary>
/// Indicates whether <see cref="ResponseBodyLogLimit"/> has been set.
/// </summary>
public bool IsResponseBodyLogLimitSet { get; private set; }

/// <summary>
/// Specifies the maximum number of bytes to be logged for the response body.
/// </summary>
public int ResponseBodyLogLimit { get; }
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="ResponseBodyLogLimit"/> set to a value less than <c>0</c>.</exception>
/// <exception cref="InvalidOperationException">Thrown when getting <see cref="ResponseBodyLogLimit"/> if it hasn't been set to a value. Check <see cref="IsResponseBodyLogLimitSet"/> first.</exception>
public int ResponseBodyLogLimit
{
get
{
if (IsResponseBodyLogLimitSet)
{
return _responseBodyLogLimit;
}
throw new InvalidOperationException($"{nameof(ResponseBodyLogLimit)} was not set. Check {nameof(IsResponseBodyLogLimitSet)} before accessing this property.");
}
set
{
ArgumentOutOfRangeException.ThrowIfLessThan(value, 0, nameof(ResponseBodyLogLimit));
_responseBodyLogLimit = value;
IsResponseBodyLogLimitSet = true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,22 @@ public static class HttpLoggingEndpointConventionBuilderExtensions
/// <param name="requestBodyLogLimit">Sets the <see cref="HttpLoggingOptions.RequestBodyLogLimit"/> for this endpoint. A value of <c>-1</c> means use the default setting in <see cref="HttpLoggingOptions.RequestBodyLogLimit"/>.</param>
/// <param name="responseBodyLogLimit">Sets the <see cref="HttpLoggingOptions.ResponseBodyLogLimit"/> for this endpoint. A value of <c>-1</c> means use the default setting in <see cref="HttpLoggingOptions.ResponseBodyLogLimit"/>.</param>
/// <returns>The original convention builder parameter.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="requestBodyLogLimit"/> or <paramref name="responseBodyLogLimit"/> is less than <c>-1</c>.</exception>
public static TBuilder WithHttpLogging<TBuilder>(this TBuilder builder, HttpLoggingFields loggingFields, int requestBodyLogLimit = -1, int responseBodyLogLimit = -1) where TBuilder : IEndpointConventionBuilder
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="requestBodyLogLimit"/> or <paramref name="responseBodyLogLimit"/> is less than <c>0</c>.</exception>
public static TBuilder WithHttpLogging<TBuilder>(this TBuilder builder, HttpLoggingFields loggingFields, int? requestBodyLogLimit = null, int? responseBodyLogLimit = null) where TBuilder : IEndpointConventionBuilder
{
// Construct outside build.Add lambda to allow exceptions to be thrown immediately
var metadata = new HttpLoggingAttribute(loggingFields, requestBodyLogLimit, responseBodyLogLimit);
var metadata = new HttpLoggingAttribute(loggingFields);

if (requestBodyLogLimit is not null)
{
ArgumentOutOfRangeException.ThrowIfLessThan(requestBodyLogLimit.Value, 0, nameof(requestBodyLogLimit));
metadata.RequestBodyLogLimit = requestBodyLogLimit.Value;
}
if (responseBodyLogLimit is not null)
{
ArgumentOutOfRangeException.ThrowIfLessThan(responseBodyLogLimit.Value, 0, nameof(responseBodyLogLimit));
metadata.ResponseBodyLogLimit = responseBodyLogLimit.Value;
}

builder.Add(endpointBuilder =>
{
Expand Down
4 changes: 2 additions & 2 deletions src/Middleware/HttpLogging/src/HttpLoggingMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ private async Task InvokeInternal(HttpContext context)
out var encoding))
{
var requestBodyLogLimit = options.RequestBodyLogLimit;
if (loggingAttribute?.RequestBodyLogLimit is int)
if (loggingAttribute?.IsRequestBodyLogLimitSet is true)
{
requestBodyLogLimit = loggingAttribute.RequestBodyLogLimit;
}
Expand Down Expand Up @@ -161,7 +161,7 @@ private async Task InvokeInternal(HttpContext context)
originalBodyFeature = context.Features.Get<IHttpResponseBodyFeature>()!;

var responseBodyLogLimit = options.ResponseBodyLogLimit;
if (loggingAttribute?.ResponseBodyLogLimit is int)
if (loggingAttribute?.IsRequestBodyLogLimitSet is true)
{
responseBodyLogLimit = loggingAttribute.ResponseBodyLogLimit;
}
Expand Down
8 changes: 6 additions & 2 deletions src/Middleware/HttpLogging/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#nullable enable
Microsoft.AspNetCore.Builder.HttpLoggingEndpointConventionBuilderExtensions
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute.HttpLoggingAttribute(Microsoft.AspNetCore.HttpLogging.HttpLoggingFields loggingFields, int requestBodyLogLimit = -1, int responseBodyLogLimit = -1) -> void
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute.HttpLoggingAttribute(Microsoft.AspNetCore.HttpLogging.HttpLoggingFields loggingFields) -> void
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute.IsRequestBodyLogLimitSet.get -> bool
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute.IsResponseBodyLogLimitSet.get -> bool
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute.LoggingFields.get -> Microsoft.AspNetCore.HttpLogging.HttpLoggingFields
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute.RequestBodyLogLimit.get -> int
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute.RequestBodyLogLimit.set -> void
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute.ResponseBodyLogLimit.get -> int
static Microsoft.AspNetCore.Builder.HttpLoggingEndpointConventionBuilderExtensions.WithHttpLogging<TBuilder>(this TBuilder builder, Microsoft.AspNetCore.HttpLogging.HttpLoggingFields loggingFields, int requestBodyLogLimit = -1, int responseBodyLogLimit = -1) -> TBuilder
Microsoft.AspNetCore.HttpLogging.HttpLoggingAttribute.ResponseBodyLogLimit.set -> void
static Microsoft.AspNetCore.Builder.HttpLoggingEndpointConventionBuilderExtensions.WithHttpLogging<TBuilder>(this TBuilder builder, Microsoft.AspNetCore.HttpLogging.HttpLoggingFields loggingFields, int? requestBodyLogLimit = null, int? responseBodyLogLimit = null) -> TBuilder
26 changes: 26 additions & 0 deletions src/Middleware/HttpLogging/test/HttpLoggingAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.HttpLogging.Tests;

public class HttpLoggingAttributeTests
{
[Fact]
public void ThrowsForInvalidOptions()
{
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new HttpLoggingAttribute(HttpLoggingFields.None) { RequestBodyLogLimit = -1 });
Assert.Equal(nameof(HttpLoggingAttribute.RequestBodyLogLimit), ex.ParamName);

ex = Assert.Throws<ArgumentOutOfRangeException>(() => new HttpLoggingAttribute(HttpLoggingFields.None) { ResponseBodyLogLimit = -1 });
Assert.Equal(nameof(HttpLoggingAttribute.ResponseBodyLogLimit), ex.ParamName);
}

[Fact]
public void ThrowsWhenAccessingFieldsThatAreNotSet()
{
var attribute = new HttpLoggingAttribute(HttpLoggingFields.None);

Assert.Throws<InvalidOperationException>(() => attribute.RequestBodyLogLimit);
Assert.Throws<InvalidOperationException>(() => attribute.ResponseBodyLogLimit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ public void WithHttpLogging_ThrowsForInvalidLimits()

// Act & Assert
var ex = Assert.Throws<ArgumentOutOfRangeException>(() =>
testConventionBuilder.WithHttpLogging(HttpLoggingFields.None, requestBodyLogLimit: -2));
testConventionBuilder.WithHttpLogging(HttpLoggingFields.None, requestBodyLogLimit: -1));
Assert.Equal("requestBodyLogLimit", ex.ParamName);

ex = Assert.Throws<ArgumentOutOfRangeException>(() =>
testConventionBuilder.WithHttpLogging(HttpLoggingFields.None, responseBodyLogLimit: -2));
testConventionBuilder.WithHttpLogging(HttpLoggingFields.None, responseBodyLogLimit: -1));
Assert.Equal("responseBodyLogLimit", ex.ParamName);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1337,7 +1337,7 @@ private IHost CreateApp(HttpLoggingFields defaultFields = HttpLoggingFields.All)
return "testing";
}).WithHttpLogging((HttpLoggingFields.Request & ~HttpLoggingFields.RequestScheme) | (HttpLoggingFields.Response & ~HttpLoggingFields.ResponseStatusCode));

endpoint.MapGet("/attr_restrictedsize", [HttpLogging(HttpLoggingFields.Request | HttpLoggingFields.Response, requestBodyLogLimit: 3, responseBodyLogLimit: 6)] async (HttpContext c) =>
endpoint.MapGet("/attr_restrictedsize", [HttpLogging(HttpLoggingFields.Request | HttpLoggingFields.Response, RequestBodyLogLimit = 3, ResponseBodyLogLimit = 6)] async (HttpContext c) =>
{
await c.Request.Body.ReadAsync(new byte[100]);
return "testing";
Expand Down