Skip to content

Commit 7a52993

Browse files
Added HttpLogging enrichment and redaction sample (#148)
* Added HttpLogging enrichment and redaction sample * Renamed project folder * Edit a comment * Use types from Shared folder * Use default route to map to home/index using optional route values * Added README * Fix README * Add filtering for Microsoft.Hosting.Lifetime logs * PR comments * PR comments
1 parent 4109003 commit 7a52993

File tree

9 files changed

+282
-0
lines changed

9 files changed

+282
-0
lines changed

Samples.sln

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpClientLogging", "HttpCl
4040
EndProject
4141
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpClientLogging", "src\Telemetry\Logging\HttpClientLogging\HttpClientLogging.csproj", "{A8922F1D-0A30-45DA-ADD2-927455CB6549}"
4242
EndProject
43+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpLogging", "HttpLogging", "{175F98E5-AFE8-4978-A512-EB84AD660208}"
44+
EndProject
45+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpLogging", "src\Telemetry\Logging\HttpLogging\HttpLogging.csproj", "{931A6585-1085-4185-AE12-78BBA87F2A73}"
46+
EndProject
4347
Global
4448
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4549
Debug|Any CPU = Debug|Any CPU
@@ -66,6 +70,10 @@ Global
6670
{A8922F1D-0A30-45DA-ADD2-927455CB6549}.Debug|Any CPU.Build.0 = Debug|Any CPU
6771
{A8922F1D-0A30-45DA-ADD2-927455CB6549}.Release|Any CPU.ActiveCfg = Release|Any CPU
6872
{A8922F1D-0A30-45DA-ADD2-927455CB6549}.Release|Any CPU.Build.0 = Release|Any CPU
73+
{931A6585-1085-4185-AE12-78BBA87F2A73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
74+
{931A6585-1085-4185-AE12-78BBA87F2A73}.Debug|Any CPU.Build.0 = Debug|Any CPU
75+
{931A6585-1085-4185-AE12-78BBA87F2A73}.Release|Any CPU.ActiveCfg = Release|Any CPU
76+
{931A6585-1085-4185-AE12-78BBA87F2A73}.Release|Any CPU.Build.0 = Release|Any CPU
6977
EndGlobalSection
7078
GlobalSection(SolutionProperties) = preSolution
7179
HideSolutionNode = FALSE
@@ -83,6 +91,8 @@ Global
8391
{75034993-150B-4940-B633-4FEF73E30401} = {2B4A9009-DB4D-497A-BA1A-23A18EBC32E8}
8492
{01EA865B-B3B0-4A2F-8361-8E59C004653B} = {248CB37C-2412-4231-96C0-092413C10D4B}
8593
{A8922F1D-0A30-45DA-ADD2-927455CB6549} = {01EA865B-B3B0-4A2F-8361-8E59C004653B}
94+
{175F98E5-AFE8-4978-A512-EB84AD660208} = {248CB37C-2412-4231-96C0-092413C10D4B}
95+
{931A6585-1085-4185-AE12-78BBA87F2A73} = {175F98E5-AFE8-4978-A512-EB84AD660208}
8696
EndGlobalSection
8797
GlobalSection(ExtensibilityGlobals) = postSolution
8898
SolutionGuid = {6083D400-BF26-4ACE-86A8-778D26A8FA6A}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Shared.Compliance;
7+
8+
namespace HttpLogging.Controllers;
9+
10+
[ApiController]
11+
[Route("api/chats")]
12+
public class ChatController : ControllerBase
13+
{
14+
// Data classification for "chatId" parameter will be taken from
15+
// LoggingRedactionOptions.RouteParameterDataClasses configured in Startup.cs.
16+
// "chatId" is private data, thus it's value will be redacted.
17+
[HttpGet("{chatId}")]
18+
public string GetChat(string chatId)
19+
{
20+
AddSensitiveHeader(Response);
21+
return $"Getting chat info...";
22+
}
23+
24+
// You can use attributes to specify data classification for parameters.
25+
// "memberId" is private data, thus it's value will be redacted.
26+
[HttpGet("{chatId}/members/{memberId}")]
27+
public string GetChatMember(string chatId, [PrivateData] string memberId)
28+
{
29+
AddSensitiveHeader(Response);
30+
return "Getting chat member info...";
31+
}
32+
33+
// Data classification for "messageId" is configured in Startup.cs.
34+
// "messageId" is public data, thus it's value will be logged as-is.
35+
[HttpGet("{chatId}/messages/{messageId}")]
36+
public string GetChatMessage(string chatId, string messageId)
37+
{
38+
AddSensitiveHeader(Response);
39+
return "Getting chat message info...";
40+
}
41+
42+
// Data classification for "attachmentId" is not specified neither in Startup.cs
43+
// nor using data classification attributes.
44+
// If a route parameter doesn't have data classification, then the property
45+
// LoggingRedactionOptions.RequestPathParameterRedactionMode defines how
46+
// it will be redacted.
47+
[HttpGet("{chatId}/attachments/{attachmentId}")]
48+
public string GetChatAttachment(string chatId, string attachmentId)
49+
{
50+
AddSensitiveHeader(Response);
51+
return "Getting chat attachment info...";
52+
}
53+
54+
// Here we add a sensitive header to the response to demonstrate how it will be redacted.
55+
private static void AddSensitiveHeader(HttpResponse response)
56+
{
57+
response.Headers.Append("SensitiveHeader", "Sensitive header value");
58+
}
59+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Mvc;
5+
6+
namespace HttpLogging.Controllers;
7+
8+
[ApiController]
9+
[Route("[controller]/[action]")]
10+
public class HomeController : ControllerBase
11+
{
12+
// HTTP requests to /home/index will not be logged, because it is excluded from logging
13+
// using LoggingRedactionOptions.ExcludePathStartsWith property. See Startup.cs.
14+
public string Index()
15+
{
16+
return "Learn about dotnet/extensions: https://github.com/dotnet/extensions";
17+
}
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Diagnostics.Logging;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.Extensions.Diagnostics.Enrichment;
7+
8+
namespace HttpLogging.Enrichment;
9+
10+
#pragma warning disable EXTEXP0013 // Type is for evaluation purposes only and is subject to change or removal in future updates
11+
// HTTP log enrichers are used to add additional information to the HTTP logs.
12+
internal sealed class HttpLogEnricher : IHttpLogEnricher
13+
#pragma warning restore EXTEXP0013 // Type is for evaluation purposes only and is subject to change or removal in future updates
14+
{
15+
public void Enrich(IEnrichmentTagCollector collector, HttpContext httpContext)
16+
{
17+
// Add additional information to the HTTP logs so that they will have more value.
18+
collector.Add("Response.Headers.Count", httpContext.Response.Headers.Count);
19+
}
20+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<Description>Demonstrates how to use HTTP logging enrichment and redaction.</Description>
5+
<OutputType>Exe</OutputType>
6+
<TargetFrameworks>$(LatestTargetFramework)</TargetFrameworks>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.Middleware" Version="$(MicrosoftAspNetCoreDiagnosticsMiddlewareVersion)" />
11+
<PackageReference Include="Microsoft.Extensions.Compliance.Redaction" Version="$(MicrosoftExtensionsComplianceRedactionVersion)" />
12+
</ItemGroup>
13+
14+
</Project>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.Json;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.Extensions.Hosting;
7+
using Microsoft.Extensions.Logging;
8+
9+
namespace HttpLogging;
10+
11+
internal static class Program
12+
{
13+
public static void Main(string[] args)
14+
{
15+
CreateHostBuilder(args).Build().Run();
16+
}
17+
18+
public static IHostBuilder CreateHostBuilder(string[] args)
19+
{
20+
return Host.CreateDefaultBuilder(args)
21+
.ConfigureLogging(builder =>
22+
{
23+
_ = builder.ClearProviders();
24+
25+
_ = builder
26+
.AddJsonConsole(options =>
27+
{
28+
options.JsonWriterOptions = new JsonWriterOptions
29+
{
30+
Indented = true
31+
};
32+
});
33+
})
34+
.ConfigureWebHostDefaults(builder =>
35+
{
36+
_ = builder.UseStartup<Startup>();
37+
});
38+
}
39+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# HTTP Logging enrichment and redaction
2+
3+
This sample shows how to extend [ASP.NET Core HTTP logging](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-logging)
4+
with [enrichment and redaction capabilities](https://github.com/dotnet/extensions/blob/main/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/README.md#http-request-logs-enrichment-and-redaction).
5+
Enrichment allows to add additional information to the log messages, while redaction
6+
allows to remove sensitive information from the log messages.
7+
8+
The sample exposes a couple of HTTP URLs that demonstrate how extended HTTP logging functionality works:
9+
10+
* HTTP routes
11+
12+
* `/api/chats/{chatId}`
13+
* `/api/chats/{chatId}/members/{memberId}`
14+
* `/api/chats/{chatId}/messages/{messageId}`
15+
* `/api/chats/{chatId}/attachments/{attachmentId}`
16+
17+
demonstrate how to redact values of sensitive HTTP route parameters in a compliant manner. Also, when you
18+
navigate to each of these HTTP endpoints, the log message information is enriched with additional data.
19+
20+
* HTTP URL `/home/index` demonstrates how to exclude certain HTTP paths from being logged.
21+
22+
Navigate to each of these HTTP URLs and see how extended HTTP logging functionality works.
23+
24+
For more information, see the source code of the sample.
25+
26+
Useful links:
27+
28+
* [ASP.NET Core HTTP logging](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-logging)
29+
* [Extended HTTP logging functionality](https://github.com/dotnet/extensions/blob/main/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/README.md#http-request-logs-enrichment-and-redaction)
30+
* [Abstractions to classify compliance sensitive data](https://github.com/dotnet/extensions/blob/main/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/README.md)
31+
* [Implementation of a couple of data redaction algorithms](https://github.com/dotnet/extensions/blob/main/src/Libraries/Microsoft.Extensions.Compliance.Redaction/README.md)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using HttpLogging.Enrichment;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Diagnostics.Logging;
7+
using Microsoft.Extensions.Compliance.Classification;
8+
using Microsoft.Extensions.Compliance.Redaction;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Http.Diagnostics;
11+
using Microsoft.Net.Http.Headers;
12+
using Shared.Compliance;
13+
14+
namespace HttpLogging;
15+
16+
internal sealed class Startup
17+
{
18+
public static void ConfigureServices(IServiceCollection services)
19+
{
20+
_ = services
21+
.AddRouting()
22+
.AddControllers();
23+
24+
// Here we register ASP.NET Core HTTP logging. You can configure HTTP logging by
25+
// providing a delegate that configures HttpLoggingOptions.
26+
_ = services.AddHttpLogging(_ => { });
27+
28+
// Here we register HttpLogEnricher to enrich HTTP logs with additional information.
29+
_ = services.AddHttpLogEnricher<HttpLogEnricher>();
30+
31+
// Here we register HTTP log redaction. Redaction is applied to HTTP route parameters,
32+
// request and response headers.
33+
_ = services.AddHttpLoggingRedaction(options =>
34+
{
35+
// Here we specify a strategy for logging HTTP request paths. "Formatted" means that
36+
// the path will contain actual values of route parameters.
37+
options.RequestPathLoggingMode = IncomingPathLoggingMode.Formatted;
38+
39+
// Here we specify how to treat HTTP route parameters.
40+
// "Strict" means that all route parameters are considered as sensitive.
41+
options.RequestPathParameterRedactionMode = HttpRouteParameterRedactionMode.Strict;
42+
43+
// Here we specify which HTTP paths we want to exclude from logging.
44+
// We can exclude, for example, health check endpoints, "favicon.ico", etc.
45+
options.ExcludePathStartsWith.Add("/home");
46+
47+
// Here we specify data classification for HTTP route parameters:
48+
options.RouteParameterDataClasses.Add("chatId", DataTaxonomy.PrivateData);
49+
options.RouteParameterDataClasses.Add("messageId", DataTaxonomy.PublicData);
50+
51+
// Data classification for request headers:
52+
options.RequestHeadersDataClasses.Add(HeaderNames.UserAgent, DataTaxonomy.PrivateData);
53+
options.RequestHeadersDataClasses.Add(HeaderNames.Accept, DataTaxonomy.PublicData);
54+
55+
// Data classification for response headers:
56+
options.ResponseHeadersDataClasses.Add("SensitiveHeader", DataTaxonomy.PrivateData);
57+
options.ResponseHeadersDataClasses.Add(HeaderNames.ContentType, DataTaxonomy.PublicData);
58+
});
59+
60+
// We need to register a redactor that will handle all sensitive data.
61+
// Here for the sake of showing redaction functionality we use "StarRedactor" and "NullRedactor" redactors.
62+
// In a real world scenario you would use the one that suits your needs (e.g. HMAC redactor).
63+
_ = services.AddRedaction(builder =>
64+
{
65+
_ = builder.SetRedactor<StarRedactor>(new DataClassificationSet(DataTaxonomy.PrivateData));
66+
_ = builder.SetRedactor<NullRedactor>(new DataClassificationSet(DataTaxonomy.PublicData));
67+
});
68+
}
69+
70+
public static void Configure(IApplicationBuilder app)
71+
{
72+
_ = app.UseRouting();
73+
74+
// Be aware that http request logging should be low in stack,
75+
// as different middlewares may feed HTTP request with data that you want to log.
76+
_ = app.UseHttpLogging();
77+
78+
_ = app.UseEndpoints(endpoints => endpoints.MapControllers());
79+
}
80+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Microsoft.AspNetCore": "Warning",
5+
"Microsoft.Hosting": "Warning",
6+
"Microsoft.Hosting.Lifetime": "Information",
7+
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
8+
}
9+
},
10+
"AllowedHosts": "*"
11+
}

0 commit comments

Comments
 (0)