-
Notifications
You must be signed in to change notification settings - Fork 2
Sample Application
The sample app is a complete weather forecast application demonstrating the Purview Telemetry Source Generator in a real-world .NET Aspire setup. It covers all three telemetry targets — Activities, Logging, and Metrics — including multi-target methods that generate multiple telemetry types from a single method call.
The solution lives in samples/SampleApp/.
| Project | Description |
|---|---|
SampleApp.AppHost |
.NET Aspire orchestrator — wires up all resources and the Aspire dashboard |
SampleApp.APIService |
RESTful weather API backend; contains the primary telemetry interfaces |
SampleApp.Web |
Blazor Server frontend that calls the API with its own HTTP client telemetry |
SampleApp.Shared |
Shared WeatherForecast DTO used by both API and frontend |
SampleApp.ServiceDefaults |
Common Aspire service defaults (OpenTelemetry, health checks, resilience) |
SampleApp.APIService.UnitTests |
Unit tests demonstrating how to mock and assert generated telemetry interfaces |
There are three generated telemetry interfaces across the solution:
IEntityStoreTelemetry (SampleApp.APIService, global namespace) — a standalone demo interface matching the Quick Start guide. It is included in the project as a reference example and is not wired into the app's HTTP endpoints. See Generated Output for the code it produces.
[ActivitySource]
[Logger]
[Meter]
interface IEntityStoreTelemetry
{
[Activity]
[Info]
[AutoCounter]
Activity? GettingEntityFromStore(int entityId, [Baggage] string serviceUrl);
[Event]
[Trace]
void GetDuration(Activity? activity, int durationInMS);
[Context]
void RetrievedEntity(Activity? activity, float totalValue, int lastUpdatedByUserId);
[Warning]
void EntityNotFound(int entityId);
[Histogram]
void RecordEntitySize(int sizeInBytes);
}IWeatherServiceTelemetry (SampleApp.APIService.Services) — the primary telemetry for the weather service backend. Registered via builder.Services.AddWeatherServiceTelemetry() and injected into WeatherService. Demonstrates the full range of multi-target patterns:
[ActivitySource]
[Logger]
[Meter]
public interface IWeatherServiceTelemetry
{
// MULTI-TARGET: starts Activity + logs Trace
[Activity(ActivityKind.Client)]
[Trace]
Activity? GettingWeatherForecast([Baggage] string someRandomBaggageInfo, int requestedCount);
// SINGLE-TARGET: adds ActivityEvent
[Event]
void ForecastReceived(Activity? activity, int minTempInC, int maxTempInC);
// SINGLE-TARGET: adds ActivityEvent with Error status
[Event(ActivityStatusCode.Error)]
void FailedToRetrieveForecast(Activity? activity, Exception ex);
// SINGLE-TARGET: adds ActivityEvent with Ok status
[Event(ActivityStatusCode.Ok)]
void TemperaturesReceived(Activity? activity, TimeSpan elapsed);
// MULTI-TARGET: increments counter + logs Warning + adds ActivityEvent
[AutoCounter]
[Warning]
[Event]
void ItsTooCold(Activity? activity, int minTempInC, int tooColdCount);
// SINGLE-TARGET: histogram per temperature reading
[Histogram]
void HistogramOfTemperature(int temperature);
// MULTI-TARGET: logs Error + increments counter (no Activity parameter)
[Error]
[AutoCounter]
void RequestedCountIsOutOfRange(int requestCount);
// SINGLE-TARGET: Info log with enumerable expansion (up to 100 items)
[Info]
void TemperaturesWithinRange([ExpandEnumerable(maximumValueCount: 100)] int[] temperaturesInC);
}IWeatherAPIClientTelemetry (SampleApp.Web.Clients) — telemetry for the Blazor frontend's typed HttpClient. Registered via builder.Services.AddWeatherAPIClientTelemetry() and injected into WeatherAPIClient. Uses [ExcludeTargets] to prevent parameters appearing in specific telemetry targets, and [ExpandEnumerable] to log response items individually:
[ActivitySource]
[Logger]
[Meter(InstrumentPrefix = "weather")]
public interface IWeatherAPIClientTelemetry
{
// MULTI-TARGET: starts Activity (Client kind) + logs Info + increments counter
[Activity(ActivityKind.Client)]
[Info]
[AutoCounter]
Activity? GetWeatherForecasts(int? count);
// MULTI-TARGET: adds ActivityEvent + logs Error + increments counter
// count excluded from Activities (Exception already carries the context)
[Event]
[Error]
[AutoCounter]
void FailedToGetForecast(Activity? activity, Exception ex,
[ExcludeTargets(Targets.Activities)] int? count);
// SINGLE-TARGET: adds ActivityEvent with HTTP status details
[Event]
void RequestComplete(Activity? activity, HttpStatusCode statusCode, bool isSuccessStatusCode);
// SINGLE-TARGET: increments success counter
[AutoCounter]
void RequestSuccess();
// MULTI-TARGET: adds ActivityEvent + logs Warning
[Event]
[Warning]
void NoForecastsRecieved(Activity? activity);
// MULTI-TARGET: adds ActivityEvent with Ok status + logs Debug
// forecasts excluded from Activities (large enumerable, not suitable for tags)
[Event(ActivityStatusCode.Ok)]
[Debug]
void ForecastsRecieved(Activity? activity, int forecastCount,
[ExpandEnumerable(100), ExcludeTargets(Targets.Activities)] WeatherForecast[] weatherForecasts);
}When the user clicks Load Weather in the Blazor frontend, telemetry is generated at two hops:
Hop 1 — SampleApp.Web (Blazor frontend → HTTP request)
WeatherAPIClient.GetWeatherForecastsAsync(count)
telemetry.GetWeatherForecasts(count) → starts Activity + logs Info + increments counter
GET /weatherforecast/{count} ──────────────────────────────────────────► SampleApp.APIService
telemetry.RequestComplete(activity, statusCode, isSuccess) ← on response
telemetry.RequestSuccess() → increments counter (on success)
telemetry.ForecastsRecieved(activity, ...) → adds ActivityEvent (Ok) + logs Debug
— on error:
telemetry.FailedToGetForecast(activity, ex, count) → adds event + logs Error + increments counter
Hop 2 — SampleApp.APIService (API request handling)
WeatherService.GetWeatherForecastsAsync(requestCount)
— validation failure path (count < 5 or > 20):
telemetry.RequestedCountIsOutOfRange(requestCount) → logs Error + increments counter
— success path:
telemetry.GettingWeatherForecast(guid, requestCount) → starts Activity + logs Trace
foreach forecast: telemetry.HistogramOfTemperature(temp) → records histogram
telemetry.ForecastReceived(activity, min, max) → adds ActivityEvent
if min < -10: telemetry.ItsTooCold(activity, min, count) → adds event + logs Warning + counter
else: telemetry.TemperaturesWithinRange(temps[]) → logs Info (expanded array)
telemetry.TemperaturesReceived(activity, elapsed) → adds ActivityEvent (Ok)
— simulated failure path (random ~10% chance):
telemetry.FailedToRetrieveForecast(activity, ex) → adds ActivityEvent (Error) + logs Critical
The SampleApp.APIService.UnitTests project demonstrates how to test code that depends on generated telemetry. Using TUnit + NSubstitute, you mock the interface and verify calls:
// Create a mock with NSubstitute
static IWeatherServiceTelemetry CreateTelemetry() =>
Substitute.For<IWeatherServiceTelemetry>();
// Configure a method return value
telemetry.GettingWeatherForecast(Arg.Any<string>(), requestCount)
.Returns(activity);
// Verify a call was made exactly once
telemetry.Received(1).FailedToRetrieveForecast(Arg.Is(activity), Arg.Any<Exception>());Tests are split across:
-
WeatherServiceTests.Success.cs— happy path (valid count, results returned) -
WeatherServiceTests.Failure.cs— simulated upstream exception -
WeatherServiceTests.Validation.cs— invalidrequestCountrange
- Open the solution (
samples/SampleApp/SampleApp.slnx) and run theSampleApp.AppHostproject. - The .NET Aspire dashboard opens automatically. It lists three running resources:
api-service,web, andscalar. - To generate telemetry, use either approach:
-
Web Frontend — click the
webresource endpoint, navigate to the Weather page, and click Load Weather (or use the error scenario buttons). -
Scalar API Docs — click the
scalarresource endpoint to open the Scalar UI and call the API directly.
-
Web Frontend — click the
📸 [Screenshot needed —
AppHostEndpointView] The Aspire dashboard Resources list showingapi-service,web, andscalarwith running status, health indicators, and endpoint links in the Endpoint column. Replace./assets/AppHostEndpointView.png.
Navigate to the web endpoint to open the Blazor frontend, or click the scalar endpoint for direct API access.
📸 [Screenshot needed —
WebFrontendWeatherPage] The Blazor frontend Weather page showing the "Load Weather" button, the forecast count input, and the error scenario buttons ("Trigger Validation Error", "Trigger Large Count Error"). This is a new screenshot — save as./assets/WebFrontendWeatherPage.png.
In the Scalar UI, expand the Weather APIs group and click the endpoint you want to test.
📸 [Screenshot needed —
ScalarEndpointView] The Scalar API reference showing the/weatherforecast/{requestCount}endpoint expanded with the Test Request panel visible. Replace./assets/ScalarEndpointView.png.
Click the Send (▶ Play) button several times to generate telemetry data. Use the Blazor error scenario buttons or Scalar with an out-of-range count (e.g. 1 or 25) to see failure paths.
📸 [Screenshot needed —
ScalarPlayButton] The Scalar Send/Play button highlighted, ready to fire a request. Replace./assets/ScalarPlayButton.pngand./assets/ScalarPlayButtonScreenShot.png(the small inline icon).
After generating some traffic, explore each tab in the Aspire dashboard:
📸 [Screenshot needed —
AspireProjectListFailure] The Aspire dashboard Resources list after triggering failures — red error badge onapi-serviceand/orweb, showing that errors are surfaced immediately at the resource level. Replace./assets/AspireProjectListFailure.png.
📸 [Screenshot needed —
AspireConsoleLogsView] The Console Logs view forapi-service, showing raw stdout output including startup messages and per-request log lines. Replace./assets/AspireConsoleLogsView.png.
📸 [Screenshot needed —
AspireStructuredLogsView] The Structured Logs view forapi-service, showing a table of log entries with Level, Message, and property columns (e.g.RequestedCount,MinTempInC). Replace./assets/AspireStructuredLogsView.png.
📸 [Screenshot needed —
AspireTracesView] The Traces view showing a list of distributed traces. Each row spans bothwebandapi-servicehops, showing the full end-to-end duration. Replace./assets/AspireTracesView.png.
📸 [Screenshot needed —
AspireTracesWithEvents] A single trace expanded to show the Activity span hierarchy: theGetWeatherForecastsclient span fromwebwrapping theGettingWeatherForecastspan fromapi-service, withActivityEvents (ForecastReceived, TemperaturesReceived) and baggage/tag properties visible. Replace./assets/AspireTracesWithEvents.png.
📸 [Screenshot needed —
ApsireMetricsView] The Metrics view showing the generated counters and histograms:getting-weather-forecast,its-too-cold,requested-count-is-out-of-range, andhistogram-of-temperaturefromSampleApp.APIService.Services, plusget-weather-forecasts,failed-to-get-forecast,request-successfromSampleApp.Web.Clients. Replace./assets/ApsireMetricsView.png.
📸 [Screenshot needed —
AspireMetricsHistogramView] Thehistogram-of-temperaturehistogram expanded to show bucket distribution and percentiles (P50, P95, P99). Replace./assets/AspireMetricsHistogramView.png.
Both projects have <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> enabled, so generated files appear in your IDE.
SampleApp.APIService generates files for both interfaces:
SampleApp.APIService/obj/Release/net10.0/generated/
Purview.Telemetry.SourceGenerator/
Purview.Telemetry.SourceGenerator.TelemetrySourceGenerator/
EntityStoreTelemetryCore.Activity.g.cs
EntityStoreTelemetryCore.Logging.g.cs
EntityStoreTelemetryCore.Metric.g.cs
EntityStoreTelemetryCoreDIExtension.DependencyInjection.g.cs
SampleApp.APIService.Services.WeatherServiceTelemetryCore.Activity.g.cs
SampleApp.APIService.Services.WeatherServiceTelemetryCore.Logging.g.cs
SampleApp.APIService.Services.WeatherServiceTelemetryCore.Metric.g.cs
SampleApp.APIService.Services.WeatherServiceTelemetryCoreDIExtension.DependencyInjection.g.cs
SampleApp.APIService.TelemetryNames.g.cs
SampleApp.Web generates files for IWeatherAPIClientTelemetry:
SampleApp.Web/obj/Release/net10.0/generated/
Purview.Telemetry.SourceGenerator/
Purview.Telemetry.SourceGenerator.TelemetrySourceGenerator/
SampleApp.Web.Clients.WeatherAPIClientTelemetryCore.Activity.g.cs
SampleApp.Web.Clients.WeatherAPIClientTelemetryCore.Logging.g.cs
SampleApp.Web.Clients.WeatherAPIClientTelemetryCore.Metric.g.cs
SampleApp.Web.Clients.WeatherAPIClientTelemetryCoreDIExtension.DependencyInjection.g.cs
SampleApp.Web.TelemetryNames.g.cs
The TelemetryNames.g.cs files expose static arrays of all meter and activity source names, used in Program.cs to register them with OpenTelemetry:
builder.AddServiceDefaults(TelemetryNames.MeterNames, TelemetryNames.ActivitySourceNames);See Generated Output for full annotated examples of the generated code.
Important
Consider helping children around the world affected by conflict. You can donate any amount to War Child here - any amount can help save a life.
Purview Telemetry Source Generator v4.0.0-prerelease.1 | Home | Getting Started | FAQ | Breaking Changes | GitHub