Zum Inhalt springen

How to use the Aspire Dashboard with a legacy WinForms application

Introduction

If you have a legacy Windows Forms (WinForms) application and want to add modern observability, this post shows how to wire up OpenTelemetry tracing in .NET Framework and stream it to the .NET Aspire Dashboard — using only a lightweight Docker container and a few lines of code.

But why use Aspire Dashboard for legacy apps? Modern observability platforms can feel out of reach for older desktop applications. The .NET Aspire Dashboard gives you a clean UI to inspect traces locally, and OpenTelemetry provides a vendor-neutral way to capture telemetry from your code.

The main challanges are:

  • configure OpenTelemetry and the Aspire Dashboard to work with the .Net Framework 4.7.2
  • manually add traces because auto instrumentation is not available for desktop applications

I’ve created a proof of concept that shows how to solve both issues. It is available at https://github.com/VolkmarR/OTEL-Traces-Winforms-Aspire

Aspire Dashboard configuration

The repo contains a docker compose file with the necessary configuration for the Aspire Dashboard.

Since the GRPC Protocol is not supported by the .Net Framework 4.7.2, it is very important to add the „4318:18890“ port mapping, used by the HttpProtobuf Protocol.

Application configuration

The OpenTelemetry.Exporter.OpenTelemetryProtocol nuget package must be added to the WinForms application.

This function needs to be added to the project and called in the program.cs. Make sure that the setup method is only executed for local development and not for production.

// OTEL_Traces/OpenTelemetry/Tracing.cs
using System;
using System.Diagnostics;
using System.Windows.Forms;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

public static class Tracing
{
    public static ActivitySource TelemetrySource;

    // Initializes OpenTelemetry and sends data to Aspire.
    public static TracerProvider Setup(string sourceName)
    {
        TelemetrySource = new ActivitySource(sourceName);

        return Sdk.CreateTracerProviderBuilder()
            .AddSource(sourceName)
            .AddOtlpExporter(o =>
            {
                o.Protocol = OtlpExportProtocol.HttpProtobuf;
                o.Endpoint = new Uri("http://localhost:4318/v1/traces");
            })
            .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
                Application.ProductName.Replace(' ', '-'),
                serviceVersion: "1.0",
                autoGenerateServiceInstanceId: false))
            .Build();
    }
}

Instrumentation

Since there is no automatic instrumentation, the spans for the traces must be added manually to the code.

There are different ways to approach this. My recommendation is to find methods in your base classes that perform noteworthy tasks that take a certain amount of time. Executing SQL statements or making HTTP calls are good examples.

Here is the example from the repo, simulating an SQL execution

public void Execute(string sql)
{
    Activity activity = null;
    try
    {
        // Only start a child activity when there is a parent.
        if (Activity.Current != null)
            activity = Tracing.TelemetrySource.StartActivity();

        // add custom information to the span
        activity?.SetTag("custom.sql", sql);

        // ...

    }
    finally
    {
        activity?.Dispose();
    }
}

This way, spans are only created when a root activity was created before.

These root activities can be started in methods that are triggered by the user (button clicks, …).

Here’s the example for that.

private void LoadButton_Click(object sender, EventArgs e)
{
    // ...

    using (var _ = Tracing.TelemetrySource.StartActivity())
    {
        for (var i = 0; i < userCount; i++)
            query.Execute($"Select * from Users where Id = {i}");
    }

    // ...
}

Summary

This article contains the key steps to use OpenTelemetry and the Aspire Dashboard in a legacy Winforms application. The github repo mentioned in the article contains a working prototype to show how this works in practice.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert