Skip to content

Commit

Permalink
[automated] Merge branch 'main' => 'dev' (#4806)
Browse files Browse the repository at this point in the history
* Address issue with legacy logger's calling ToString on the TState arguments. (#4798)

* Fix #4795

And while I was in the neighborhood, also addresses #4637

---------

Co-authored-by: Martin Taillefer <mataille@microsoft.com>
Co-authored-by: Igor Velikorossov <RussKie@users.noreply.github.com>

* Add missing [GeneratedCode] attribute (#4802)

The logging code generator emits a static lambda. The lambda's containing
function is annotated with the [GeneratedCode] attribute, but the lambda
itself is not. The result is that code generators can get confused,
considering the lambda as being user code.

This PR adds the [GeneratedCode] attribute to the lambda.

Co-authored-by: Martin Taillefer <mataille@microsoft.com>

* Add more information and sample to Http.Diagnostics Readme (#4808)

* Add more information and sample to Http.Diagnostics Readme

* Simplify first statement and adding using statements.

* Also add extra docs to xml docs

* Improve code coverage for M.E.Diagnostics.Testing (#4820)

---------

Co-authored-by: Martin Taillefer <geeknoid@users.noreply.github.com>
Co-authored-by: Martin Taillefer <mataille@microsoft.com>
Co-authored-by: Igor Velikorossov <RussKie@users.noreply.github.com>
Co-authored-by: Jose Perez Rodriguez <joperezr@microsoft.com>
Co-authored-by: Sébastien Ros <sebastienros@gmail.com>
  • Loading branch information
6 people authored Dec 18, 2023
1 parent e8fc0f4 commit 4e2934e
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ private void GenLogMethod(LoggingMethod lm)

var lambdaStateName = PickUniqueName("s", lm.TemplateToParameterName.Select(kvp => kvp.Key));

OutLn($"static ({lambdaStateName}, {exceptionLambdaName}) =>");
OutLn($"[{GeneratorUtilities.GeneratedCodeAttribute}] static string ({lambdaStateName}, {exceptionLambdaName}) =>");
OutOpenBrace();

if (GenVariableAssignments(lm, lambdaStateName, numReservedUnclassifiedTags, numReservedClassifiedTags))
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/Generators/Microsoft.Gen.Logging/Parsing/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@
<value>Tag provider method not found</value>
</data>
<data name="TagProviderMethodInvalidSignatureTitle" xml:space="preserve">
<value>Property provider method has an invalid signature</value>
<value>Tag provider method has an invalid signature</value>
</data>
<data name="LoggingMethodParameterRefKindMessage" xml:space="preserve">
<value>Logging method "{0}" has parameter "{1}" with either "ref" or "out" modifier</value>
Expand Down Expand Up @@ -295,7 +295,7 @@
<value>Logging method "{0}" doesn't have anything to be logged</value>
</data>
<data name="TagProviderMethodInvalidSignatureMessage" xml:space="preserve">
<value>Property provider method "{0}" in type "{1}" doesn't have a signature compatible with "{2}"</value>
<value>Tag provider method "{0}" in type "{1}" doesn't have a signature compatible with "{2}"</value>
</data>
<data name="TemplateStartsWithAtSymbolMessage" xml:space="preserve">
<value>Logging method "{0}" has template(s) that start with "@": {1}</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.Logging.Testing;
Expand Down Expand Up @@ -67,6 +69,31 @@ public FakeLogRecord(LogLevel level, EventId id, object? state, Exception? excep
/// <exception cref="InvalidCastException">The state object is not compatible with supported logging model and is not a read-only list.</exception>
public IReadOnlyList<KeyValuePair<string, string?>>? StructuredState => (IReadOnlyList<KeyValuePair<string, string?>>?)State;

/// <summary>
/// Gets the value of a particular key value pair in the record's structured state.
/// </summary>
/// <param name="key">The key to search for in the record's structured state.</param>
/// <returns>
/// The value associated with the key, or <see langword="null"/> if the key was not found. If the structured
/// state contains multiple entries with the same key, the value associated with the first matching key encountered is returned.
/// </returns>
[Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)]
public string? GetStructuredStateValue(string key)
{
if (StructuredState is not null)
{
foreach (var kvp in StructuredState)
{
if (kvp.Key == key)
{
return kvp.Value;
}
}
}

return null;
}

/// <summary>
/// Gets an optional exception associated with the log record.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public static class HttpClientLoggingHttpClientBuilderExtensions
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <remarks>
/// All other loggers are removed - including the default one, registered via <see cref="HttpClientBuilderExtensions.AddDefaultLogger(IHttpClientBuilder)"/>.
/// A lot of the information logged by this method (like bodies, methods, host, path, and duration) will be added as enrichment tags to the structured log. Make sure
/// you have a way of viewing structured logs in order to view this extra information.
/// </remarks>
/// <exception cref="ArgumentNullException">Argument <paramref name="builder"/> is <see langword="null"/>.</exception>
public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder)
Expand All @@ -43,6 +45,8 @@ public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBu
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <remarks>
/// All other loggers are removed - including the default one, registered via <see cref="HttpClientBuilderExtensions.AddDefaultLogger(IHttpClientBuilder)"/>.
/// A lot of the information logged by this method (like bodies, methods, host, path, and duration) will be added as enrichment tags to the structured log. Make sure
/// you have a way of viewing structured logs in order to view this extra information.
/// </remarks>
/// <exception cref="ArgumentNullException">Any of the arguments is <see langword="null"/>.</exception>
public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, IConfigurationSection section)
Expand All @@ -61,6 +65,8 @@ public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBu
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <remarks>
/// All other loggers are removed - including the default one, registered via <see cref="HttpClientBuilderExtensions.AddDefaultLogger(IHttpClientBuilder)"/>.
/// A lot of the information logged by this method (like bodies, methods, host, path, and duration) will be added as enrichment tags to the structured log. Make sure
/// you have a way of viewing structured logs in order to view this extra information.
/// </remarks>
/// <exception cref="ArgumentNullException">Any of the arguments is <see langword="null"/>.</exception>
public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, Action<LoggingOptions> configure)
Expand Down
47 changes: 44 additions & 3 deletions src/Libraries/Microsoft.Extensions.Http.Diagnostics/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Microsoft.Extensions.Http.Diagnostics

Telemetry support for `HttpClient` that allows tracking latency and enriching and redacting log output.
Telemetry support for `HttpClient` that allows tracking latency and enriching and redacting log output for structured logs.

## Install the package

Expand All @@ -24,9 +24,9 @@ Or directly in the C# project file:

These components enable enriching and redacting `HttpClient` request logs. They remove built-it HTTP Client logging.

In order to use the redaction feature, you need to reference the `Microsoft.Extensions.Compliance.Redaction` package.
When using this package, some of the log properties are redacted by default (like full routes), which means that you will need to make sure that a redactor provider is registered in the Dependency Injection container. You can do this by making sure that you call `builder.Services.AddRedaction()` which requires a reference to the `Microsoft.Extensions.Compliance.Redaction` package.

The services can be registered using the following methods:
The http client logging services can be registered using the following methods:

```csharp
public static IServiceCollection AddExtendedHttpClientLogging(this IServiceCollection services)
Expand Down Expand Up @@ -55,6 +55,47 @@ builder.Services.AddHttpClientLogEnricher<MyHttpClientLogEnricher>();
var host = builder.Build();
```

It is important to note that the `AddExtendedHttpClientLogging` method will add information to the logs using *enrichment*. This means that the information will be added as tags to the structured logs, but will not be visible in the log message that is printed by default in the console. To view the information, you will need to use a logging provider that supports structured logs. One quick and built-in way to do this, is to call `AddJsonConsole()` to your logging builder, which will print out the full structured logs to the console. Here is a quick sample that uses the `ExtendedHttpClientLogging()` method to automatically log all `HttpClient` request and response bodies, and then prints the full structured logs to the console:

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

var services = new ServiceCollection();

services.AddLogging(o => o.SetMinimumLevel(LogLevel.Trace).AddJsonConsole()); // <-- Enable structured logging to the console
// Adding default redactor provider to the DI container. This is required when using the AddExtendedHttpClientLogging() method.
services.AddRedaction();

services.AddHttpClient("foo")
.AddExtendedHttpClientLogging(o =>
{
// Enable logging of request and response bodies:
o.LogBody = true;

// We also need to specify the content types that we want to log:
o.ResponseBodyContentTypes.Add("application/json");
});

var sp = services.BuildServiceProvider();

var client = sp.GetRequiredService<IHttpClientFactory>().CreateClient("foo");

var response = await client.GetAsync(new Uri("https://httpbin.org/json")).ConfigureAwait(false);
```

By default, request and response routes are redacted for privacy reasons. You can change this behavior by making use of the `RequestPathParameterRedactionMode` option like:

```csharp
.AddExtendedHttpClientLogging(o =>
{
//.. Other options
o.RequestPathParameterRedactionMode = HttpRouteParameterRedactionMode.None; // <-- Disable redaction of request/response routes
});
```

You can also use the following extension methods to apply the logging to the specific `IHttpClientBuilder`:

```csharp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,7 @@ public void SetIncomingTags(IReadOnlyList<KeyValuePair<string, object?>> value)
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public override string? ToString() => State?.ToString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ public void State()
Assert.Null(ss["K2"]);
Assert.Equal("[\"0\",\"1\",\"2\"]", ss["K3"]);

Assert.Equal("V0", logger.LatestRecord.GetStructuredStateValue("K0"));
Assert.Equal("V1", logger.LatestRecord.GetStructuredStateValue("K1"));
Assert.Equal("[\"0\",\"1\",\"2\"]", logger.LatestRecord.GetStructuredStateValue("K3"));

logger = new FakeLogger();
logger.Log<object?>(LogLevel.Error, new EventId(0), null, null, (_, _) => "MESSAGE");
Assert.Null(logger.LatestRecord.State);
Expand All @@ -102,6 +106,15 @@ public void State()
Assert.Equal("Hello {name}", ss["{OriginalFormat}"]);
}

[Fact]
public void NullState()
{
var logger = new FakeLogger();
logger.Log<object?>(LogLevel.Error, new EventId(0), null, null, (_, _) => "MESSAGE");

Assert.Null(logger.LatestRecord.GetStructuredStateValue("K0"));
}

[Fact]
public void StateInvariant()
{
Expand Down
Loading

0 comments on commit 4e2934e

Please sign in to comment.