A real-world .NET Aspire weather forecast application demonstrating the Purview Telemetry Source Generator with all three telemetry targets — Activities, Logging, and Metrics — across two services, including multi-target methods that emit multiple telemetry types from a single call.
| Project | Description |
|---|---|
SampleApp.AppHost |
.NET Aspire orchestrator — wires up all resources and the Aspire dashboard |
SampleApp.APIService |
Weather REST 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 |
A self-contained reference example matching the Quick Start guide. Not wired into the API endpoints — its purpose is to show a clean before/after comparison and demonstrate that generated code is inspectable with EmitCompilerGeneratedFiles.
[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);
}The primary backend telemetry. Injected into WeatherService and registered via builder.Services.AddWeatherServiceTelemetry(). Demonstrates the full range of multi-target patterns:
[ActivitySource]
[Logger]
[Meter]
public interface IWeatherServiceTelemetry
{
// MULTI-TARGET: starts Activity (Client kind) + 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
[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);
}Frontend HTTP client telemetry. Registered via builder.Services.AddWeatherAPIClientTelemetry() and injected into WeatherAPIClient. Demonstrates [ExcludeTargets] and [ExpandEnumerable]:
[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
[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
[Event(ActivityStatusCode.Ok)]
[Debug]
void ForecastsRecieved(Activity? activity, int forecastCount,
[ExpandEnumerable(100), ExcludeTargets(Targets.Activities)] WeatherForecast[] weatherForecasts);
}- Multi-target generation — single method call emits Activity + Log + Metric simultaneously
- Activity lifecycle — start, events, status codes (Ok / Error), baggage propagation
- Structured logging — all log levels (Trace → Critical), structured properties, enumerable expansion
- Metrics — auto-counters, histograms,
InstrumentPrefixcustomisation [ExcludeTargets]— exclude a parameter from specific telemetry types in multi-target methods[ExpandEnumerable]— log individual array/IEnumerable elements as separate properties- DI registration — generated
Add*Telemetry()extension methods TelemetryNames— generated static class with all meter and activity source names for OTel registration- Unit testing — mock telemetry interfaces with NSubstitute; see
SampleApp.APIService.UnitTests
SampleApp/
├── SampleApp.AppHost/
│ └── Program.cs # Aspire orchestration
├── SampleApp.APIService/
│ ├── Endpoints/
│ │ └── WeatherEndpoints.cs # Minimal API endpoints
│ ├── Properties/
│ │ └── AssemblyInfo.cs # [assembly: ActivitySourceGeneration]
│ ├── Services/
│ │ ├── IEntityStoreTelemetry.cs # Reference telemetry interface (Quick Start)
│ │ ├── IWeatherService.cs # Business logic interface
│ │ ├── IWeatherServiceTelemetry.cs # Primary telemetry interface
│ │ └── WeatherService.cs # Business logic implementation
│ └── Program.cs
├── SampleApp.Web/
│ ├── Clients/
│ │ ├── IWeatherAPIClientTelemetry.cs # Frontend HTTP client telemetry
│ │ └── WeatherAPIClient.cs
│ ├── Components/ # Blazor components
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ └── Program.cs
├── SampleApp.ServiceDefaults/ # OpenTelemetry, health checks, resilience
├── SampleApp.Shared/ # WeatherForecast DTO
└── SampleApp.APIService.UnitTests/ # Unit tests with telemetry mocking
- .NET 10.0 SDK or later
- .NET Aspire workload
dotnet workload install aspirecd samples/SampleApp
dotnet run --project SampleApp.AppHostThe .NET Aspire dashboard opens automatically. It lists three running resources: api-service, web, and scalar.
To generate telemetry:
- Web Frontend — click the
webresource endpoint, go to the Weather page, and click Load Weather - Scalar API Docs — click the
scalarendpoint, expand the Weather API, and use the Send button
cd samples/SampleApp
dotnet testTests validate business logic while mocking telemetry calls using NSubstitute. See SampleApp.APIService.UnitTests for examples of mocking and asserting generated telemetry interfaces.
Both SampleApp.APIService and SampleApp.Web have <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> enabled, so generated files appear in your IDE and on disk.
SampleApp.APIService generates:
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:
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 used in Program.cs to register all sources with OpenTelemetry:
builder.AddServiceDefaults(TelemetryNames.MeterNames, TelemetryNames.ActivitySourceNames);After generating some traffic, explore each tab:
| Tab | What you see |
|---|---|
| Traces | Distributed traces spanning both web and api-service hops with full end-to-end duration, Activity span hierarchy, ActivityEvents, baggage, and tag properties |
| Structured Logs | Log entries with Level, Message, and all structured properties (e.g. RequestedCount, MinTempInC) |
| Metrics | Generated counters and histograms from both services: getting-weather-forecast, its-too-cold, histogram-of-temperature, get-weather-forecasts, etc. |
| Console Logs | Raw stdout output per service |
| Resources | Failed requests surface as error badges on api-service and/or web |
Monitor metrics from the running API service in real time:
# Find the process ID
dotnet-counters ps
# Monitor all meters
dotnet-counters monitor --process-id <PID>
# Monitor a specific meter
dotnet-counters monitor --process-id <PID> --counters SampleApp.APIService.Services.IWeatherServiceTelemetry
# Watch specific instruments
dotnet-counters monitor --process-id <PID> --counters SampleApp.APIService.Services.IWeatherServiceTelemetry[its-too-cold]- Purview Telemetry Source Generator docs
- Wiki: Sample Application — annotated walkthroughs, sequence diagrams, dashboard screenshots
- Wiki: Generated Output — full annotated examples of generated code
- .NET Aspire