From e9d0e2b0351c1ea1c65173e44da738a193b0e8e5 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Thu, 14 Sep 2023 17:44:29 -0700 Subject: [PATCH] Add ASP.NET Core logging example (#4821) --- README.md | 2 +- .../getting-started-aspnetcore/Program.cs | 63 +++++++++ .../logs/getting-started-aspnetcore/README.md | 133 ++++++++++++++++++ .../appsettings.Development.json | 9 ++ .../appsettings.json | 9 ++ .../getting-started-aspnetcore.csproj | 14 ++ 6 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 docs/logs/getting-started-aspnetcore/Program.cs create mode 100644 docs/logs/getting-started-aspnetcore/README.md create mode 100644 docs/logs/getting-started-aspnetcore/appsettings.Development.json create mode 100644 docs/logs/getting-started-aspnetcore/appsettings.json create mode 100644 docs/logs/getting-started-aspnetcore/getting-started-aspnetcore.csproj diff --git a/README.md b/README.md index b4fd74a11a..e0bcc7c7cc 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ repo. If you are new here, please read the getting started docs: -* Logs: [Console](./docs/logs/getting-started-console/README.md) +* Logs: [ASP.NET Core](./docs/logs/getting-started-aspnetcore/README.md) | [Console](./docs/logs/getting-started-console/README.md) * Metrics: [ASP.NET Core](./docs/metrics/getting-started-aspnetcore/README.md) | [Console](./docs/metrics/getting-started-console/README.md) * Traces: [ASP.NET Core](./docs/trace/getting-started-aspnetcore/README.md) | diff --git a/docs/logs/getting-started-aspnetcore/Program.cs b/docs/logs/getting-started-aspnetcore/Program.cs new file mode 100644 index 0000000000..82da252259 --- /dev/null +++ b/docs/logs/getting-started-aspnetcore/Program.cs @@ -0,0 +1,63 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using OpenTelemetry.Logs; +using OpenTelemetry.Resources; + +var builder = WebApplication.CreateBuilder(args); + +// Remove default providers and add OpenTelemetry logging provider +// For instructional purposes only, disable the default .NET console logging provider to +// use the verbose OpenTelemetry console exporter instead. For most development +// and production scenarios the default console provider works well and there is no need to +// clear these providers. +builder.Logging.ClearProviders(); + +builder.Logging.AddOpenTelemetry(logging => +{ + logging.IncludeScopes = true; + + var resourceBuilder = ResourceBuilder + .CreateDefault() + .AddService(builder.Environment.ApplicationName); + + logging.SetResourceBuilder(resourceBuilder) + // ConsoleExporter is used for demo purpose only. + // In production environment, ConsoleExporter should be replaced with other exporters (e.g. OTLP Exporter). + .AddConsoleExporter(); +}); + +var app = builder.Build(); + +app.MapGet("/", (ILogger logger) => +{ + logger.FoodPriceChanged("artichoke", 9.99); + + return "Hello from OpenTelemetry Logs!"; +}); + +app.Logger.StartingApp(); + +app.Run(); + +public static partial class ApplicationLogs +{ + [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Starting the app...")] + public static partial void StartingApp(this ILogger logger); + + [LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "Food `{name}` price changed to `{price}`.")] + public static partial void FoodPriceChanged(this ILogger logger, string name, double price); +} diff --git a/docs/logs/getting-started-aspnetcore/README.md b/docs/logs/getting-started-aspnetcore/README.md new file mode 100644 index 0000000000..81b5407eee --- /dev/null +++ b/docs/logs/getting-started-aspnetcore/README.md @@ -0,0 +1,133 @@ +# Getting Started with OpenTelemetry .NET Logs in 5 Minutes - ASP.NET Core Application + +First, download and install the [.NET +SDK](https://dotnet.microsoft.com/download) on your computer. + +Create a new web application: + +```sh +dotnet new web -o aspnetcoreapp +cd aspnetcoreapp +``` + +Install the +[OpenTelemetry.Exporter.Console](../../../src/OpenTelemetry.Exporter.Console/README.md) +and +[OpenTelemetry.Extensions.Hosting](../../../src/OpenTelemetry.Extensions.Hosting/README.md) +packages: + +```sh +dotnet add package OpenTelemetry.Exporter.Console +dotnet add package OpenTelemetry.Extensions.Hosting +``` + +Update the `Program.cs` file with the code from [Program.cs](./Program.cs). + +Run the application (using `dotnet run`) and then browse to the URL shown in the +console for your application (e.g. `http://localhost:5000`). You should see the +logs output from the console: + +```text +LogRecord.Timestamp: 2023-09-06T22:59:17.9787564Z +LogRecord.CategoryName: getting-started-aspnetcore +LogRecord.Severity: Info +LogRecord.SeverityText: Information +LogRecord.Body: Starting the app... +LogRecord.Attributes (Key:Value): + OriginalFormat (a.k.a Body): Starting the app... +LogRecord.EventId: 1 +LogRecord.EventName: StartingApp + +... + +LogRecord.Timestamp: 2023-09-06T22:59:18.0644378Z +LogRecord.CategoryName: Microsoft.Hosting.Lifetime +LogRecord.Severity: Info +LogRecord.SeverityText: Information +LogRecord.Body: Now listening on: {address} +LogRecord.Attributes (Key:Value): + address: http://localhost:5000 + OriginalFormat (a.k.a Body): Now listening on: {address} +LogRecord.EventId: 14 +LogRecord.EventName: ListeningOnAddress + +... + +LogRecord.Timestamp: 2023-09-06T23:00:46.1639248Z +LogRecord.TraceId: 3507087d60ae4b1d2f10e68f4e40784a +LogRecord.SpanId: c51be9f19c598b69 +LogRecord.TraceFlags: None +LogRecord.CategoryName: Program +LogRecord.Severity: Info +LogRecord.SeverityText: Information +LogRecord.Body: Food `{name}` price changed to `{price}`. +LogRecord.Attributes (Key:Value): + name: artichoke + price: 9.99 + OriginalFormat (a.k.a Body): Food `{name}` price changed to `{price}`. +LogRecord.EventId: 2 +LogRecord.EventName: FoodPriceChanged + +... +``` + +Congratulations! You are now collecting logs using OpenTelemetry. + +What does the above program do? + +The program has cleared the default [logging +providers](https://learn.microsoft.com/dotnet/core/extensions/logging-providers) +then added OpenTelemetry as a logging provider to the ASP.NET Core logging +pipeline. OpenTelemetry SDK is then configured with a +[ConsoleExporter](../../../src/OpenTelemetry.Exporter.Console/README.md) to +export the logs to the console for demonstration purpose (note: ConsoleExporter +is not intended for production usage, other exporters such as [OTLP +Exporter](../../../src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md) +should be used instead). In addition, `OpenTelemetryLoggerOptions.IncludeScopes` +is enabled so the logs will include the [log +scopes](https://learn.microsoft.com/dotnet/core/extensions/logging#log-scopes). +From the console output we can see the log scopes that are coming from the +ASP.NET Core framework, and we can see logs from both our logger and the ASP.NET +Core framework loggers, as indicated by the `LogRecord.CategoryName`. + +The example has demonstrated the best practice from ASP.NET Core by injecting +generic `ILogger`: + +```csharp +app.MapGet("/", (ILogger logger) => +{ + logger.FoodPriceChanged("artichoke", 9.99); + + return "Hello from OpenTelemetry Logs!"; +}); +``` + +Following the .NET logging best practice, [compile-time logging source +generation](https://docs.microsoft.com/dotnet/core/extensions/logger-message-generator) +has been used across the example, which delivers high performance, structured +logging, and type-checked parameters: + +```csharp +public static partial class ApplicationLogs +{ + [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Starting the app...")] + public static partial void StartingApp(this ILogger logger); + + [LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "Food `{name}` price changed to `{price}`.")] + public static partial void FoodPriceChanged(this ILogger logger, string name, double price); +} +``` + +For logs that occur between `builder.Build()` and `app.Run()` when injecting a +generic `ILogger` is not an option, `app.Logger` is used instead: + +```csharp +app.Logger.StartingApp(); +``` + +## Learn more + +* [Compile-time logging source + generation](https://docs.microsoft.com/dotnet/core/extensions/logger-message-generator) +* [Customizing the OpenTelemetry .NET SDK](../customizing-the-sdk/README.md) +* [Extending the OpenTelemetry .NET SDK](../extending-the-sdk/README.md) diff --git a/docs/logs/getting-started-aspnetcore/appsettings.Development.json b/docs/logs/getting-started-aspnetcore/appsettings.Development.json new file mode 100644 index 0000000000..770d3e9314 --- /dev/null +++ b/docs/logs/getting-started-aspnetcore/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/docs/logs/getting-started-aspnetcore/appsettings.json b/docs/logs/getting-started-aspnetcore/appsettings.json new file mode 100644 index 0000000000..10f68b8c8b --- /dev/null +++ b/docs/logs/getting-started-aspnetcore/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/docs/logs/getting-started-aspnetcore/getting-started-aspnetcore.csproj b/docs/logs/getting-started-aspnetcore/getting-started-aspnetcore.csproj new file mode 100644 index 0000000000..85b466b4f2 --- /dev/null +++ b/docs/logs/getting-started-aspnetcore/getting-started-aspnetcore.csproj @@ -0,0 +1,14 @@ + + + + net6.0;net7.0 + enable + enable + + + + + + + +