diff --git a/examples/AspNetCore/Controllers/WeatherForecastController.cs b/examples/AspNetCore/Controllers/WeatherForecastController.cs index 54f19e63b4d..fab6acf5a73 100644 --- a/examples/AspNetCore/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore/Controllers/WeatherForecastController.cs @@ -16,6 +16,9 @@ namespace Examples.AspNetCore.Controllers; +using System.Diagnostics; +using System.Diagnostics.Metrics; +using Examples.AspNetCore; using Microsoft.AspNetCore.Mvc; [ApiController] @@ -24,16 +27,22 @@ public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", }; private static readonly HttpClient HttpClient = new(); private readonly ILogger logger; + private readonly ActivitySource activitySource; + private readonly Counter freezingDaysCounter; - public WeatherForecastController(ILogger logger) + public WeatherForecastController(ILogger logger, Instrumentation instrumentation) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + ArgumentNullException.ThrowIfNull(instrumentation); + this.activitySource = instrumentation.ActivitySource; + this.freezingDaysCounter = instrumentation.FreezingDaysCounter; } [HttpGet] @@ -45,6 +54,17 @@ public IEnumerable Get() // how dependency calls will be captured and treated // automatically as child of incoming request. var res = HttpClient.GetStringAsync("http://google.com").Result; + + // Optional: Manually create an activity. This will become a child of + // the activity created from the instrumentation library for AspNetCore. + // Manually created activities are useful when there is a desire to track + // a specific subset of the request. In this example one could imagine + // that calculating the forecast is an expensive operation and therefore + // something to be distinguished from the overall request. + // Note: Tags can be added to the current activity without the need for + // a manual activity using Acitivty.Current?.SetTag() + using var activity = this.activitySource.StartActivity("calculate forecast"); + var rng = new Random(); var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast { @@ -54,6 +74,9 @@ public IEnumerable Get() }) .ToArray(); + // Optional: Count the freezing days + this.freezingDaysCounter.Add(forecast.Count(f => f.TemperatureC < 0)); + this.logger.LogInformation( "WeatherForecasts generated {count}: {forecasts}", forecast.Length, diff --git a/examples/AspNetCore/Instrumentation.cs b/examples/AspNetCore/Instrumentation.cs new file mode 100644 index 00000000000..e4583887e07 --- /dev/null +++ b/examples/AspNetCore/Instrumentation.cs @@ -0,0 +1,50 @@ +// +// 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. +// + +namespace Examples.AspNetCore; + +using System.Diagnostics; +using System.Diagnostics.Metrics; + +/// +/// It is recommended to use a custom type to hold references for +/// ActivitySource and Instruments. This avoids possible type collisions +/// with other components in the DI container. +/// +public class Instrumentation : IDisposable +{ + internal const string ActivitySourceName = "Examples.AspNetCore"; + internal const string MeterName = "Examples.AspNetCore"; + private readonly Meter meter; + + public Instrumentation() + { + string? version = typeof(Instrumentation).Assembly.GetName().Version?.ToString(); + this.ActivitySource = new ActivitySource(ActivitySourceName, version); + this.meter = new Meter(MeterName, version); + this.FreezingDaysCounter = this.meter.CreateCounter("weather.days.freezing", "The number of days where the temperature is below freezing"); + } + + public ActivitySource ActivitySource { get; } + + public Counter FreezingDaysCounter { get; } + + public void Dispose() + { + this.ActivitySource.Dispose(); + this.meter.Dispose(); + } +} diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index 5bbd283582a..53245406d57 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -14,7 +14,7 @@ // limitations under the License. // -using System.Reflection; +using Examples.AspNetCore; using OpenTelemetry; using OpenTelemetry.Exporter; using OpenTelemetry.Instrumentation.AspNetCore; @@ -37,9 +37,13 @@ // Build a resource configuration action to set service information. Action configureResource = r => r.AddService( serviceName: appBuilder.Configuration.GetValue("ServiceName"), - serviceVersion: Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown", + serviceVersion: typeof(Program).Assembly.GetName().Version?.ToString() ?? "unknown", serviceInstanceId: Environment.MachineName); +// Create a service to expose ActivitySource, and Metric Instruments +// for manual instrumentation +appBuilder.Services.AddSingleton(); + // Configure OpenTelemetry tracing & metrics with auto-start using the // StartWithHost extension from OpenTelemetry.Extensions.Hosting. appBuilder.Services.AddOpenTelemetry() @@ -48,7 +52,9 @@ { // Tracing + // Ensure the TracerProvider subscribes to any custom ActivitySources. builder + .AddSource(Instrumentation.ActivitySourceName) .SetSampler(new AlwaysOnSampler()) .AddHttpClientInstrumentation() .AddAspNetCoreInstrumentation(); @@ -98,7 +104,9 @@ { // Metrics + // Ensure the MeterProvider subscribes to any custom Meters. builder + .AddMeter(Instrumentation.MeterName) .AddRuntimeInstrumentation() .AddHttpClientInstrumentation() .AddAspNetCoreInstrumentation();