Skip to content

Commit

Permalink
Merge branch 'main' into feature
Browse files Browse the repository at this point in the history
  • Loading branch information
utpilla authored Feb 17, 2024
2 parents 3b1db0b + dd877fe commit f4d044c
Show file tree
Hide file tree
Showing 65 changed files with 795 additions and 545 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="[8.0.0,)" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="[8.0.0,)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[8.0.0,)" />
<PackageVersion Include="Microsoft.Extensions.Telemetry.Abstractions" Version="[8.1.0,)" />
<PackageVersion Include="Microsoft.Extensions.Telemetry.Abstractions" Version="[8.2.0,)" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="[1.0.3,2.0)" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="[17.8.0,18.0.0)" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[1.1.1,2.0)" />
Expand Down
7 changes: 7 additions & 0 deletions OpenTelemetry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "resources", "resources", "{
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "extending-the-sdk", "docs\resources\extending-the-sdk\extending-the-sdk.csproj", "{7BE494FC-4B0D-4340-A62A-9C9F3E7389FE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dedicated-pipeline", "docs\logs\dedicated-pipeline\dedicated-pipeline.csproj", "{19545B37-8518-4BDD-AD49-00C031FB3C2A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -626,6 +628,10 @@ Global
{7BE494FC-4B0D-4340-A62A-9C9F3E7389FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BE494FC-4B0D-4340-A62A-9C9F3E7389FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BE494FC-4B0D-4340-A62A-9C9F3E7389FE}.Release|Any CPU.Build.0 = Release|Any CPU
{19545B37-8518-4BDD-AD49-00C031FB3C2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19545B37-8518-4BDD-AD49-00C031FB3C2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19545B37-8518-4BDD-AD49-00C031FB3C2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19545B37-8518-4BDD-AD49-00C031FB3C2A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -680,6 +686,7 @@ Global
{17A22B0E-6EC3-4A39-B955-0A486AD06699} = {52AF6D7D-9E66-4234-9A2C-5D16C6F22B40}
{A115CE4C-71A8-4B95-96A5-C1DF46FD94C2} = {7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}
{7BE494FC-4B0D-4340-A62A-9C9F3E7389FE} = {A115CE4C-71A8-4B95-96A5-C1DF46FD94C2}
{19545B37-8518-4BDD-AD49-00C031FB3C2A} = {3862190B-E2C5-418E-AFDC-DB281FB5C705}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521}
Expand Down
38 changes: 28 additions & 10 deletions docs/diagnostics/experimental-apis/OTEL1003.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,37 @@ Experimental APIs may be changed or removed in the future.

## Details

The OpenTelemetry Specification defines the
[cardinality limit](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits)
of a metric can be set by the matching view.

From the specification:

> The cardinality limit for an aggregation is defined in one of three ways:
> A view with criteria matching the instrument an aggregation is created for has
> an aggregation_cardinality_limit value defined for the stream, that value
> SHOULD be used. If there is no matching view, but the MetricReader defines a
> default cardinality limit value based on the instrument an aggregation is
> created for, that value SHOULD be used. If none of the previous values are
> defined, the default value of 2000 SHOULD be used.
>
> 1. A view with criteria matching the instrument an aggregation is created for
> has an `aggregation_cardinality_limit` value defined for the stream, that
> value SHOULD be used.
> 2. If there is no matching view, but the `MetricReader` defines a default
> cardinality limit value based on the instrument an aggregation is created
> for, that value SHOULD be used.
> 3. If none of the previous values are defined, the default value of 2000
> SHOULD be used.
We are exposing these APIs experimentally until the specification declares them
stable.

### Setting cardinality limit for a specific Metric via the View API

The OpenTelemetry Specification defines the [cardinality
limit](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits)
of a metric can be set by the matching view.

```csharp
using var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddView(
instrumentName: "MyFruitCounter",
new MetricStreamConfiguration { CardinalityLimit = 10 })
.Build();
```

### Setting cardinality limit for a specific MetricReader

[This is not currently supported by OpenTelemetry
.NET.](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5331)
95 changes: 58 additions & 37 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 All @@ -31,7 +32,7 @@ OpenTelemetry .NET:
:heavy_check_mark: You should use structured logging.

* Structured logging is more efficient than unstructured logging.
* Filtering and redaction can happen on invidual key-value pairs instead of
* Filtering and redaction can happen on individual key-value pairs instead of
the entire log message.
* Storage and indexing are more efficient.
* Structured logging makes it easier to manage and consume logs.
Expand Down Expand Up @@ -62,8 +63,8 @@ from the latest stable version of
[Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging/)
package, regardless of the .NET runtime version being used:

* If you're using the latest stable version of [OpenTelemetry .NET
SDK](../../src/OpenTelemetry/README.md), you don't have to worry about the
* If you are using the latest stable version of [OpenTelemetry .NET
SDK](../../src/OpenTelemetry/README.md), you do not have to worry about the
version of `Microsoft.Extensions.Logging` package because it is already taken
care of for you via [package dependency](../../Directory.Packages.props).
* Starting from version `3.1.0`, the .NET runtime team is holding a high bar for
Expand All @@ -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
2 changes: 1 addition & 1 deletion docs/logs/complex-objects/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
// This will flush the remaining logs and shutdown the logging pipeline.
loggerFactory.Dispose();

public static partial class ApplicationLogs
internal static partial class LoggerExtensions
{
[LoggerMessage(LogLevel.Critical)]
public static partial void FoodRecallNotice(
Expand Down
4 changes: 3 additions & 1 deletion docs/logs/complex-objects/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ that the following code is added which uses the `LogPropertiesAttribute` to log
the `FoodRecallNotice` object:

```csharp
public static partial class ApplicationLogs
internal static partial class LoggerExtensions
{
[LoggerMessage(LogLevel.Critical)]
public static partial void FoodRecallNotice(
Expand Down Expand Up @@ -89,6 +89,8 @@ LogRecord.Attributes (Key:Value):
ProductType: Food & Beverages
ProductDescription: Salads
BrandName: Contoso
LogRecord.EventId: 252550133
LogRecord.EventName: FoodRecallNotice
```

> [!NOTE]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenTelemetry.Logs;

namespace DedicatedLogging;

public static class DedicatedLoggingServiceCollectionExtensions
{
public static IServiceCollection AddDedicatedLogging(
this IServiceCollection services,
IConfiguration configuration,
Action<OpenTelemetryLoggerOptions> configureOpenTelemetry)
{
ArgumentNullException.ThrowIfNull(configureOpenTelemetry);

services.TryAddSingleton(sp =>
{
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConfiguration(configuration);

builder.AddOpenTelemetry(configureOpenTelemetry);
});

return new DedicatedLoggerFactory(loggerFactory);
});

services.TryAdd(ServiceDescriptor.Singleton(typeof(IDedicatedLogger<>), typeof(DedicatedLogger<>)));

return services;
}

private sealed class DedicatedLogger<T> : IDedicatedLogger<T>
{
private readonly ILogger innerLogger;

public DedicatedLogger(DedicatedLoggerFactory loggerFactory)
{
this.innerLogger = loggerFactory.CreateLogger(typeof(T).FullName!);
}

public IDisposable? BeginScope<TState>(TState state)
where TState : notnull
=> this.innerLogger.BeginScope(state);

public bool IsEnabled(LogLevel logLevel)
=> this.innerLogger.IsEnabled(logLevel);

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
=> this.innerLogger.Log(logLevel, eventId, state, exception, formatter);
}

private sealed class DedicatedLoggerFactory : ILoggerFactory
{
private readonly ILoggerFactory innerLoggerFactory;

public DedicatedLoggerFactory(ILoggerFactory loggerFactory)
{
this.innerLoggerFactory = loggerFactory;
}

public void AddProvider(ILoggerProvider provider)
=> this.innerLoggerFactory.AddProvider(provider);

public ILogger CreateLogger(string categoryName)
=> this.innerLoggerFactory.CreateLogger(categoryName);

public void Dispose()
=> this.innerLoggerFactory.Dispose();
}
}
12 changes: 12 additions & 0 deletions docs/logs/dedicated-pipeline/DedicatedLogging/IDedicatedLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

namespace DedicatedLogging;

public interface IDedicatedLogger : ILogger
{
}

public interface IDedicatedLogger<out TCategoryName> : IDedicatedLogger
{
}
47 changes: 47 additions & 0 deletions docs/logs/dedicated-pipeline/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using DedicatedLogging;
using OpenTelemetry.Logs;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.ClearProviders();

builder.Logging.AddOpenTelemetry(options =>
{
// Set up primary pipeline for common app logs
options.AddConsoleExporter();
});

builder.Services.AddDedicatedLogging(
builder.Configuration.GetSection("DedicatedLogging"), // Bind configuration for dedicated logging pipeline
options =>
{
// Set up secondary pipeline for dedicated logs
options.AddConsoleExporter();
});

var app = builder.Build();

app.MapGet("/", (HttpContext context, ILogger<Program> logger, IDedicatedLogger<Program> dedicatedLogger) =>
{
// Standard log written
logger.FoodPriceChanged("artichoke", 9.99);

// Dedicated log written
dedicatedLogger.RequestInitiated(context.Connection.RemoteIpAddress?.ToString() ?? "unknown");

return "Hello from OpenTelemetry Logs!";
});

app.Run();

internal static partial class LoggerExtensions
{
[LoggerMessage(LogLevel.Information, "Food `{name}` price changed to `{price}`.")]
public static partial void FoodPriceChanged(this ILogger logger, string name, double price);

[LoggerMessage(LogLevel.Information, "Request initiated from `{ipAddress}`.")]
public static partial void RequestInitiated(this IDedicatedLogger logger, string ipAddress);
}
4 changes: 4 additions & 0 deletions docs/logs/dedicated-pipeline/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Dedicated pipeline

This example shows how to create a dedicated logging pipeline for specific logs
which will be sent to a different location than normal application logs.
Loading

0 comments on commit f4d044c

Please sign in to comment.