Clean up guidance docs (#5329)
This commit is contained in:
parent
05700f640a
commit
5ac06dd20a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue