-
Notifications
You must be signed in to change notification settings - Fork 3
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 |
flowchart TD
AppHost["SampleApp.AppHost<br/>.NET Aspire Orchestrator"]
subgraph running["Running Services"]
Web["SampleApp.Web<br/>Blazor Server Frontend"]
API["SampleApp.APIService<br/>Weather REST API"]
Scalar["scalar<br/>API Docs"]
end
subgraph libs["Shared Libraries"]
Defaults["SampleApp.ServiceDefaults<br/>OpenTelemetry · Health · Resilience"]
SharedLib["SampleApp.Shared<br/>WeatherForecast DTO"]
end
AppHost --> Web
AppHost --> API
AppHost --> Scalar
Web -->|HTTP + service discovery| API
Scalar -.->|API reference| API
Web --> Defaults
API --> Defaults
Web --> SharedLib
API --> SharedLib
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 — once in SampleApp.Web as the HTTP client call is made, and once in SampleApp.APIService as the request is handled.
sequenceDiagram
actor User
participant Web as SampleApp.Web
participant CT as IWeatherAPIClientTelemetry
participant API as SampleApp.APIService
participant ST as IWeatherServiceTelemetry
User->>Web: Load Weather (count=10)
activate Web
Web->>CT: GetWeatherForecasts(count)
Note over CT: Activity started (Client)<br/>Info logged · Counter++
Web->>+API: GET /weatherforecast/10
API->>ST: GettingWeatherForecast(guid, count)
Note over ST: Activity started (Client)<br/>Trace logged
loop for each forecast
API->>ST: HistogramOfTemperature(tempC)
end
API->>ST: ForecastReceived(activity, min, max)
Note over ST: ActivityEvent added
alt min < -10°C
API->>ST: ItsTooCold(activity, min, coldCount)
Note over ST: ActivityEvent · Warning logged · Counter++
else temperatures within range
API->>ST: TemperaturesWithinRange(temps[])
Note over ST: Info logged (array expanded)
end
API->>ST: TemperaturesReceived(activity, elapsed)
Note over ST: ActivityEvent (Ok status)
API-->>-Web: 200 OK WeatherForecast[10]
Web->>CT: RequestComplete(activity, 200, true)
Note over CT: ActivityEvent added
Web->>CT: RequestSuccess()
Note over CT: Counter++
Web->>CT: ForecastsRecieved(activity, 10, forecasts)
Note over CT: ActivityEvent (Ok) · Debug logged
Web-->>User: display forecasts
deactivate Web
sequenceDiagram
actor User
participant Web as SampleApp.Web
participant CT as IWeatherAPIClientTelemetry
participant API as SampleApp.APIService
participant ST as IWeatherServiceTelemetry
Note over User,ST: Validation failure — count out of range (e.g. count=1 or count=25)
User->>Web: Load Weather (count=1)
activate Web
Web->>CT: GetWeatherForecasts(count)
Note over CT: Activity started · Info logged · Counter++
Web->>+API: GET /weatherforecast/1
API->>ST: RequestedCountIsOutOfRange(1)
Note over ST: Error logged · Counter++
API-->>-Web: 422 Validation Error
Web->>CT: RequestComplete(activity, 422, false)
Web->>CT: FailedToGetForecast(activity, ex, count)
Note over CT: ActivityEvent · Error logged · Counter++
Web-->>User: display error message
deactivate Web
Note over User,ST: Simulated upstream failure (random ~10% of valid requests)
User->>Web: Load Weather (count=10)
activate Web
Web->>CT: GetWeatherForecasts(count)
Web->>+API: GET /weatherforecast/10
API->>ST: GettingWeatherForecast(guid, count)
Note over ST: Activity started · Trace logged
API->>ST: FailedToRetrieveForecast(activity, ex)
Note over ST: ActivityEvent (Error status) · Critical logged
API-->>-Web: 502 Problem Details
Web->>CT: RequestComplete(activity, 502, false)
Web->>CT: FailedToGetForecast(activity, ex, count)
Note over CT: ActivityEvent · Error logged · Counter++
Web-->>User: display error message
deactivate Web
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
| Step | Image |
|---|---|
Open the solution and run SampleApp.AppHost. The Aspire dashboard shows all three running resources — api-service, web, and scalar — with status indicators and endpoint links. |
![]() |
Navigate to the web endpoint to open the Blazor frontend. Go to the Weather page. Use the count input and Load Weather button, or use the error scenario buttons to trigger failure paths. |
![]() |
Alternatively, click the scalar endpoint to open the Scalar API docs. Expand the Weather APIs group and click the endpoint you want to test. |
![]() |
| Press the Send (▶ Play) button several times to generate telemetry. Use an out-of-range count (e.g. 1 or 25) to trigger validation failures. | ![]() |
After generating some traffic, explore each tab in the Aspire dashboard:
| Dashboard view | Image |
|---|---|
Resources — failed requests surface immediately as error badges on api-service and/or web. |
![]() |
| Console Logs — raw stdout output per service, including startup messages and per-request lines. | ![]() |
Structured Logs — log entries with Level, Message, and all structured properties (e.g. RequestedCount, MinTempInC). |
![]() |
Traces — distributed traces spanning both web and api-service hops, with full end-to-end duration. |
![]() |
Trace detail — Activity span hierarchy with ActivityEvents (ForecastReceived, TemperaturesReceived), baggage, and tag properties. |
![]() |
Metrics — generated counters and histograms from both services: getting-weather-forecast, its-too-cold, histogram-of-temperature, get-weather-forecasts, etc. |
![]() |
Histogram detail — histogram-of-temperature bucket distribution and percentiles (P50, P95, P99). |
![]() |
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










