OpenTelemetry support library for Visual Studio 2022+ extensions. Add distributed tracing, metrics, logging, and exception tracking to your VSIX with minimal configuration.
- Distributed Tracing - Track operations across your extension with spans and activities
- Metrics - Counters, histograms, and gauges for performance monitoring
- Structured Logging - OpenTelemetry-integrated logging via
ILogger - Exception Tracking - Automatic and manual exception capture with full context
- Multiple Exporters - OTLP (gRPC/HTTP) for production, Console for debugging
- VS-Specific Helpers - Pre-configured spans for commands, tool windows, and documents
Install-Package CodingWithCalvin.Otel4Vsixdotnet add package CodingWithCalvin.Otel4Vsix<PackageReference Include="CodingWithCalvin.Otel4Vsix" Version="1.0.0" />In your Visual Studio extension's InitializeAsync method:
using Otel4Vsix;
protected override async Task InitializeAsync(
CancellationToken cancellationToken,
IProgress<ServiceProgressData> progress)
{
await base.InitializeAsync(cancellationToken, progress);
VsixTelemetry.Initialize(new TelemetryConfiguration
{
ServiceName = "MyAwesomeExtension",
ServiceVersion = "1.0.0",
OtlpEndpoint = "http://localhost:4317",
EnableConsoleExporter = true // Useful during development
});
}protected override void Dispose(bool disposing)
{
if (disposing)
{
VsixTelemetry.Shutdown();
}
base.Dispose(disposing);
}Create spans to track operations and their duration:
// Simple span
using var activity = VsixTelemetry.Tracer.StartActivity("ProcessFile");
activity?.SetTag("file.path", filePath);
activity?.SetTag("file.size", fileSize);
// VS command span (with pre-configured attributes)
using var commandSpan = VsixTelemetry.StartCommandActivity("MyExtension.DoSomething");
// Nested spans for detailed tracing
using var outer = VsixTelemetry.Tracer.StartActivity("LoadProject");
{
using var inner = VsixTelemetry.Tracer.StartActivity("ParseProjectFile");
// ... parse logic
}using var activity = VsixTelemetry.StartActivity("RiskyOperation");
try
{
// Your code here
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
throw;
}Record counters, histograms, and gauges:
// Counter - track occurrences
var commandCounter = VsixTelemetry.GetOrCreateCounter<long>(
"extension.commands.executed",
"{command}",
"Number of commands executed");
commandCounter?.Add(1,
new KeyValuePair<string, object>("command.name", "FormatDocument"));
// Histogram - track distributions (e.g., durations)
var durationHistogram = VsixTelemetry.GetOrCreateHistogram<double>(
"extension.operation.duration",
"ms",
"Duration of operations in milliseconds");
var stopwatch = Stopwatch.StartNew();
// ... do work ...
stopwatch.Stop();
durationHistogram?.Record(stopwatch.ElapsedMilliseconds,
new KeyValuePair<string, object>("operation.name", "BuildSolution"));Structured logging with OpenTelemetry integration:
// Use the default logger
VsixTelemetry.Logger.LogInformation("Processing file: {FilePath}", filePath);
VsixTelemetry.Logger.LogWarning("File not found, using default: {DefaultPath}", defaultPath);
// Create a typed logger for your class
public class MyToolWindow
{
private readonly ILogger<MyToolWindow> _logger = VsixTelemetry.CreateLogger<MyToolWindow>();
public void DoWork()
{
_logger.LogDebug("Starting work...");
// ...
_logger.LogInformation("Work completed successfully");
}
}
// Log errors with exceptions
try
{
// risky operation
}
catch (Exception ex)
{
VsixTelemetry.Logger.LogError(ex, "Failed to process {FileName}", fileName);
}Track exceptions with full context:
// Manual exception tracking
try
{
// risky operation
}
catch (Exception ex)
{
VsixTelemetry.TrackException(ex);
// Handle or rethrow
}
// With additional context
catch (Exception ex)
{
VsixTelemetry.TrackException(ex, new Dictionary<string, object>
{
{ "operation.name", "LoadProject" },
{ "project.path", projectPath },
{ "user.action", "OpenSolution" }
});
throw;
}Note: Global unhandled exceptions are automatically captured when
EnableGlobalExceptionHandleristrue(default).
| Property | Type | Default | Description |
|---|---|---|---|
ServiceName |
string |
"VsixExtension" |
Service name for telemetry identification |
ServiceVersion |
string |
"1.0.0" |
Service version |
OtlpEndpoint |
string |
null |
OTLP collector endpoint (e.g., http://localhost:4317) |
UseOtlpHttp |
bool |
false |
Use HTTP/protobuf instead of gRPC |
OtlpHeaders |
IDictionary<string, string> |
empty | Custom headers for OTLP requests (auth, API keys) |
EnableConsoleExporter |
bool |
false |
Output telemetry to console (for debugging) |
EnableTracing |
bool |
true |
Enable distributed tracing |
EnableMetrics |
bool |
true |
Enable metrics collection |
EnableLogging |
bool |
true |
Enable structured logging |
EnableGlobalExceptionHandler |
bool |
true |
Capture unhandled exceptions automatically |
TraceSamplingRatio |
double |
1.0 |
Trace sampling ratio (0.0 - 1.0) |
IncludeVisualStudioContext |
bool |
true |
Add VS context to telemetry |
ExceptionFilter |
Func<Exception, bool> |
null |
Filter which exceptions to track |
ResourceAttributes |
IDictionary<string, object> |
empty | Custom resource attributes |
ExportTimeoutMilliseconds |
int |
30000 |
Export timeout |
BatchExportScheduledDelayMilliseconds |
int |
5000 |
Batch export delay |
VsixTelemetry.Initialize(new TelemetryConfiguration
{
ServiceName = "MyExtension",
ServiceVersion = typeof(MyPackage).Assembly.GetName().Version.ToString(),
OtlpEndpoint = "https://otel-collector.mycompany.com:4317",
TraceSamplingRatio = 0.1, // Sample 10% of traces
EnableConsoleExporter = false,
ResourceAttributes =
{
{ "deployment.environment", "production" },
{ "service.namespace", "visualstudio-extensions" }
},
ExceptionFilter = ex => !(ex is OperationCanceledException) // Ignore cancellations
});var config = new TelemetryConfiguration
{
ServiceName = "MyExtension",
OtlpEndpoint = "https://api.honeycomb.io:443",
UseOtlpHttp = true
};
// Add authentication headers
config.OtlpHeaders["x-honeycomb-team"] = "your-api-key";
config.OtlpHeaders["x-honeycomb-dataset"] = "your-dataset";
VsixTelemetry.Initialize(config);Otel4Vsix exports telemetry via OTLP, which is supported by:
- Jaeger
- Zipkin
- Azure Monitor / Application Insights
- Honeycomb
- Datadog
- Grafana Tempo
- AWS X-Ray
- Google Cloud Trace
- Any OTLP-compatible collector
- .NET Framework 4.8
- Visual Studio 2022 or later
- OpenTelemetry (>= 1.7.0)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.7.0)
- OpenTelemetry.Exporter.Console (>= 1.7.0)
- Microsoft.Extensions.Logging (>= 8.0.0)
- Microsoft.VisualStudio.SDK (>= 17.0)
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built on top of OpenTelemetry .NET
- Inspired by the need for better observability in Visual Studio extensions