Clean up guidance docs (#5329)

This commit is contained in:
Reiley Yang 2024-02-08 11:43:28 -08:00 committed by GitHub
parent 05700f640a
commit 5ac06dd20a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 52 deletions

View File

@ -7,7 +7,8 @@
* [Best Practices](#best-practices)
* [Package Version](#package-version)
* [Logging API](#logging-api)
* [Logger Management](#logger-management)
* [ILogger](#ilogger)
* [LoggerFactory](#loggerfactory)
* [Log Correlation](#log-correlation)
* [Log Enrichment](#log-enrichment)
* [Log Filtering](#log-filtering)
@ -72,21 +73,62 @@ package, regardless of the .NET runtime version being used:
## Logging API
### ILogger
.NET supports high performance, structured logging via the
[`Microsoft.Extensions.Logging.ILogger`](https://docs.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger)
interface (including
[`ILogger<TCategoryName>`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger-1))
to help monitor application behavior and diagnose issues.
#### Get Logger
In order to use the `ILogger` interface, you need to first get a logger. How to
get a logger depends on two things:
* The type of application you are building.
* The place where you want to log.
Here is the rule of thumb:
* If you are building an application with [dependency injection
(DI)](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection)
(e.g. [ASP.NET Core](https://learn.microsoft.com/aspnet/core) and [.NET
Worker](https://learn.microsoft.com/dotnet/core/extensions/workers)), in most
cases you should use the logger provided by DI, there are special cases when
you want log before DI logging pipeline is available or after DI logging
pipeline is disposed. Refer to the [.NET official
document](https://learn.microsoft.com/dotnet/core/extensions/logging#integration-with-hosts-and-dependency-injection)
and [Getting Started with OpenTelemetry .NET Logs in 5 Minutes - ASP.NET Core
Application](./getting-started-aspnetcore/README.md) tutorial to learn more.
* If you are building an application without DI, create a
[LoggerFactory](#loggerfactory) instance and configure OpenTelemetry to work
with it. Refer to the [Getting Started with OpenTelemetry .NET Logs in 5
Minutes - Console Application](./getting-started-console/README.md) tutorial
to learn more.
:stop_sign: You should avoid creating loggers too frequently. Although loggers
are not super expensive, they still come with CPU and memory cost, and are meant
to be reused throughout the application. Refer to the [logging performance
benchmark](../../test/Benchmarks/Logs/LogBenchmarks.cs) for more details.
#### Use Logger
:heavy_check_mark: You should use [compile-time logging source
generation](https://docs.microsoft.com/dotnet/core/extensions/logger-message-generator)
pattern to achieve the best performance.
```csharp
public static partial class Food
{
[LoggerMessage(Level = LogLevel.Information, Message = "Hello from {food} {price}.")]
public static partial void SayHello(ILogger logger, string food, double price);
}
var food = "tomato";
var price = 2.99;
Food.SayHello(logger, food, price);
logger.SayHello(food, price);
internal static partial class LoggerExtensions
{
[LoggerMessage(Level = LogLevel.Information, Message = "Hello from {food} {price}.")]
public static partial void SayHello(this ILogger logger, string food, double price);
}
```
> [!NOTE]
@ -122,33 +164,12 @@ logger.LogInformation("Hello from {food} {price}.", food, price);
Refer to the [logging performance
benchmark](../../test/Benchmarks/Logs/LogBenchmarks.cs) for more details.
## Logger Management
## LoggerFactory
In order to use
[`ILogger`](https://docs.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger)
interface (including
[`ILogger<TCategoryName>`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger-1)),
you need to first get a logger. How to get a logger depends on two things:
* The type of application you are building.
* The place where you want to log.
Here is the rule of thumb:
* If you are building an application with [dependency injection
(DI)](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection)
(e.g. [ASP.NET Core](https://learn.microsoft.com/aspnet/core) and [.NET
Worker](https://learn.microsoft.com/dotnet/core/extensions/workers)), in most
cases you should use the logger provided by DI, there are special cases when
you want log before DI logging pipeline is available or after DI logging
pipeline is disposed. Refer to the [.NET official
document](https://learn.microsoft.com/dotnet/core/extensions/logging#integration-with-hosts-and-dependency-injection)
and [Getting Started with OpenTelemetry .NET Logs in 5 Minutes - ASP.NET Core
Application](./getting-started-aspnetcore/README.md) tutorial to learn more.
* If you are building an application without DI, create a `LoggerFactory`
instance and configure OpenTelemetry to work with it. Refer to the [Getting
Started with OpenTelemetry .NET Logs in 5 Minutes - Console
Application](./getting-started-console/README.md) tutorial to learn more.
In many cases, you can use [ILogger](#ilogger) without having to interact with
[Microsoft.Extensions.Logging.LoggerFactory](https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.loggerfactory)
directly. This section is intended for users who need to create and manage
`LoggerFactory` explicitly.
:stop_sign: You should avoid creating `LoggerFactory` instances too frequently,
`LoggerFactory` is fairly expensive and meant to be reused throughout the

View File

@ -7,6 +7,8 @@
* [Best Practices](#best-practices)
* [Package Version](#package-version)
* [Metrics API](#metrics-api)
* [Meter](#meter)
* [Instruments](#instruments)
* [MeterProvider Management](#meterprovider-management)
* [Memory Management](#memory-management)
* [Pre-Aggregation](#pre-aggregation)
@ -46,6 +48,27 @@ package, regardless of the .NET runtime version being used:
## Metrics API
### Meter
:stop_sign: You should avoid creating
[`System.Diagnostics.Metrics.Meter`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.meter)
too frequently. `Meter` is fairly expensive and meant to be reused throughout
the application. For most applications, it can be modeled as static readonly
field (e.g. [Program.cs](./getting-started-console/Program.cs)) or singleton via
dependency injection (e.g.
[Instrumentation.cs](../../examples/AspNetCore/Instrumentation.cs)).
:heavy_check_mark: You should use dot-separated
[UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case) as the
[`Meter.Name`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.meter.name).
In many cases, using the fully qualified class name might be a good option.
```csharp
static readonly Meter MyMeter = new("MyCompany.MyProduct.MyLibrary", "1.0");
```
### Instruments
:heavy_check_mark: You should understand and pick the right instrument type.
> [!NOTE]
@ -233,7 +256,7 @@ Let's take the following example:
* value = 2, name = `lemon`, color = `yellow`
* During the time range (T1, T2]:
* no fruit has been received
* During the time range (T2, T3]
* During the time range (T2, T3]:
* value = 5, name = `apple`, color = `red`
* value = 2, name = `apple`, color = `green`
* value = 4, name = `lemon`, color = `yellow`
@ -242,7 +265,7 @@ Let's take the following example:
* value = 3, name = `lemon`, color = `yellow`
If we aggregate and export the metrics using [Cumulative Aggregation
Temporality](https://github.com/open-telemetry/opentelemetry-specification/blob/main/pecification/metrics/data-model.md#temporality):
Temporality](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#temporality):
* (T0, T1]
* attributes: {name = `apple`, color = `red`}, count: `1`
@ -331,12 +354,12 @@ table to summarize the total number of fruits based on the name and color.
| Name | Color | Count |
| ----- | ------ | ----- |
| apple | red | ? |
| apple | yellow | ? |
| apple | green | ? |
| lemon | red | ? |
| lemon | yellow | ? |
| lemon | green | ? |
| apple | red | 6 |
| apple | yellow | 0 |
| apple | green | 2 |
| lemon | red | 0 |
| lemon | yellow | 12 |
| lemon | green | 0 |
In other words, we know how much storage and network are needed to collect and
transmit these metrics, regardless of the traffic pattern.
@ -414,7 +437,7 @@ Check the [Exemplars](./exemplars/README.md) tutorial to learn more.
## Metrics Enrichment
When the metrics are being collected, they normally get stored in a [time series
When metrics are being collected, they normally get stored in a [time series
database](https://en.wikipedia.org/wiki/Time_series_database). From storage and
consumption perspective, metrics can be multi-dimensional. Taking the [fruit
example](#example), there are two dimensions - "name" and "color". For basic

View File

@ -46,7 +46,7 @@ package, regardless of the .NET runtime version being used:
### ActivitySource
:stop_sign: You should avoid creating
[`ActivitySource`](https://learn.microsoft.com/dotnet/api/system.diagnostics.activitysource)
[`System.Diagnostics.ActivitySource`](https://learn.microsoft.com/dotnet/api/system.diagnostics.activitysource)
too frequently. `ActivitySource` is fairly expensive and meant to be reused
throughout the application. For most applications, it can be modeled as static
readonly field (e.g. [Program.cs](./getting-started-console/Program.cs)) or

View File

@ -19,6 +19,7 @@ BenchmarkDotNet v0.13.10, Windows 11 (10.0.22621.3007/22H2/2022Update/SunValley2
| NoListenerStringInterpolation | 124.458 ns | 2.5188 ns | 2.2329 ns | 0.0114 | 72 B |
| NoListenerExtensionMethod | 36.326 ns | 0.2916 ns | 0.2435 ns | 0.0102 | 64 B |
| NoListener | 1.375 ns | 0.0586 ns | 0.0896 ns | - | - |
| CreateLoggerRepeatedly | 48.295 ns | 0.5951 ns | 0.4970 ns | 0.0038 | 24 B |
| OneProcessor | 98.133 ns | 1.8805 ns | 1.5703 ns | 0.0063 | 40 B |
| TwoProcessors | 105.414 ns | 0.4610 ns | 0.3850 ns | 0.0063 | 40 B |
| ThreeProcessors | 102.023 ns | 1.4187 ns | 1.1847 ns | 0.0063 | 40 B |
@ -36,34 +37,39 @@ public class LogBenchmarks
private readonly ILogger loggerWithTwoProcessors;
private readonly ILogger loggerWithThreeProcessors;
private readonly ILoggerFactory loggerFactoryWithNoListener;
private readonly ILoggerFactory loggerFactoryWithOneProcessor;
private readonly ILoggerFactory loggerFactoryWithTwoProcessor;
private readonly ILoggerFactory loggerFactoryWithThreeProcessor;
public LogBenchmarks()
{
using var loggerFactoryWithNoListener = LoggerFactory.Create(builder => { });
this.loggerWithNoListener = loggerFactoryWithNoListener.CreateLogger<LogBenchmarks>();
this.loggerFactoryWithNoListener = LoggerFactory.Create(builder => { });
this.loggerWithNoListener = this.loggerFactoryWithNoListener.CreateLogger<LogBenchmarks>();
using var loggerFactoryWithOneProcessor = LoggerFactory.Create(builder =>
this.loggerFactoryWithOneProcessor = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options => options
.AddProcessor(new DummyLogProcessor()));
});
this.loggerWithOneProcessor = loggerFactoryWithOneProcessor.CreateLogger<LogBenchmarks>();
this.loggerWithOneProcessor = this.loggerFactoryWithOneProcessor.CreateLogger<LogBenchmarks>();
using var loggerFactoryWithTwoProcessor = LoggerFactory.Create(builder =>
this.loggerFactoryWithTwoProcessor = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options => options
.AddProcessor(new DummyLogProcessor())
.AddProcessor(new DummyLogProcessor()));
});
this.loggerWithTwoProcessors = loggerFactoryWithTwoProcessor.CreateLogger<LogBenchmarks>();
this.loggerWithTwoProcessors = this.loggerFactoryWithTwoProcessor.CreateLogger<LogBenchmarks>();
using var loggerFactoryWithThreeProcessor = LoggerFactory.Create(builder =>
this.loggerFactoryWithThreeProcessor = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options => options
.AddProcessor(new DummyLogProcessor())
.AddProcessor(new DummyLogProcessor())
.AddProcessor(new DummyLogProcessor()));
});
this.loggerWithThreeProcessors = loggerFactoryWithThreeProcessor.CreateLogger<LogBenchmarks>();
this.loggerWithThreeProcessors = this.loggerFactoryWithThreeProcessor.CreateLogger<LogBenchmarks>();
}
[Benchmark]
@ -84,6 +90,13 @@ public class LogBenchmarks
Food.SayHello(this.loggerWithNoListener, FoodName, FoodPrice);
}
[Benchmark]
public void CreateLoggerRepeatedly()
{
var logger = this.loggerFactoryWithNoListener.CreateLogger<LogBenchmarks>();
Food.SayHello(logger, FoodName, FoodPrice);
}
[Benchmark]
public void OneProcessor()
{