Skip to content

Commit

Permalink
Clean up guidance docs (#5329)
Browse files Browse the repository at this point in the history
  • Loading branch information
reyang authored Feb 8, 2024
1 parent 05700f6 commit 5ac06dd
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 52 deletions.
89 changes: 55 additions & 34 deletions docs/logs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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

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.
## LoggerFactory

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
Expand Down
41 changes: 32 additions & 9 deletions docs/metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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`
Expand All @@ -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`
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/trace/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 21 additions & 8 deletions test/Benchmarks/Logs/LogBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ .NET SDK 8.0.101
| 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 |
Expand All @@ -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]
Expand All @@ -84,6 +90,13 @@ public void NoListener()
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()
{
Expand Down

0 comments on commit 5ac06dd

Please sign in to comment.