๐ Add OpenTelemetry observability to your Visual Studio extensions in minutes!
Otel4Vsix is a powerful yet simple library that brings distributed tracing, metrics, logging, and exception tracking to your VSIX extensions with minimal configuration. See exactly what's happening inside your extension! ๐
| Feature | Description |
|---|---|
| ๐ 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 Export Modes | OTLP (gRPC/HTTP) for production, Debug output for development |
| ๐ฏ VS-Specific Helpers | Pre-configured spans for commands, tool windows, and documents |
| ๐๏ธ Fluent Builder API | Clean, chainable configuration |
| ๐ง Auto-Detection | Automatically captures VS version, edition, OS, and architecture |
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 CodingWithCalvin.Otel4Vsix;
protected override async Task InitializeAsync(
CancellationToken cancellationToken,
IProgress<ServiceProgressData> progress)
{
await JoinableTaskFactory.SwitchToMainThreadAsync();
VsixTelemetry.Configure()
.WithServiceName("MyAwesomeExtension")
.WithServiceVersion("1.0.0")
.WithVisualStudioAttributes(this) // ๐ช Auto-captures VS version & edition!
.WithEnvironmentAttributes() // ๐ฅ๏ธ Auto-captures OS & architecture!
.WithOtlpHttp("https://api.honeycomb.io")
.WithHeader("x-honeycomb-team", "your-api-key")
.Initialize();
}protected override void Dispose(bool disposing)
{
if (disposing)
{
VsixTelemetry.Shutdown();
}
base.Dispose(disposing);
}๐ That's it! Your extension is now observable!
Otel4Vsix supports multiple telemetry modes to fit your workflow:
| Mode | Description |
|---|---|
Auto |
๐ค Default - Uses OTLP if endpoint configured, otherwise Debug output |
Debug |
๐ Outputs to VS Output window (visible when debugging) |
Otlp |
๐ก Exports via OTLP protocol to your collector |
Disabled |
๐ No telemetry collection |
var builder = VsixTelemetry.Configure()
.WithServiceName(Vsix.Name)
.WithServiceVersion(Vsix.Version)
.WithVisualStudioAttributes(this)
.WithEnvironmentAttributes();
#if !DEBUG
// ๐ก Only send to collector in Release builds
builder
.WithOtlpHttp("https://api.honeycomb.io")
.WithHeader("x-honeycomb-team", apiKey);
#endif
builder.Initialize();In Debug builds, telemetry automatically outputs to the VS Output window! ๐
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:
// ๐ข Quick logging methods
VsixTelemetry.LogInformation("Processing file: {FilePath}", filePath);
VsixTelemetry.LogWarning("File not found, using default: {DefaultPath}", defaultPath);
VsixTelemetry.LogError(ex, "Failed to process {FileName}", fileName);
// ๐ท๏ธ 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! ๐");
}
}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).
| Method | Description |
|---|---|
WithServiceName(name) |
Set the service name for identification |
WithServiceVersion(version) |
Set the service version |
WithVisualStudioAttributes(serviceProvider) |
๐ช Auto-capture VS version & edition |
WithVisualStudioAttributes(version, edition) |
Manually set VS attributes |
WithEnvironmentAttributes() |
๐ฅ๏ธ Auto-capture OS version & architecture |
WithResourceAttribute(key, value) |
Add custom resource attributes |
WithOtlpHttp(endpoint) |
Configure OTLP HTTP export |
WithOtlpGrpc(endpoint) |
Configure OTLP gRPC export |
WithHeader(key, value) |
Add headers for OTLP requests |
WithMode(mode) |
Set telemetry mode (Auto/Debug/Otlp/Disabled) |
WithTracing(enabled) |
Enable/disable tracing |
WithMetrics(enabled) |
Enable/disable metrics |
WithLogging(enabled) |
Enable/disable logging |
WithTraceSamplingRatio(ratio) |
Set trace sampling (0.0 - 1.0) |
WithGlobalExceptionHandler(enabled) |
Enable/disable auto exception capture |
WithExceptionFilter(filter) |
Filter which exceptions to track |
WithExportTimeout(ms) |
Set export timeout in milliseconds |
Initialize() |
๐ Initialize telemetry |
When using the helper methods, these attributes are automatically captured:
| Attribute | Source | Example |
|---|---|---|
vs.version |
WithVisualStudioAttributes() |
"17.12.35521.163" |
vs.edition |
WithVisualStudioAttributes() |
"Enterprise" |
os.version |
WithEnvironmentAttributes() |
"10.0.22631.0" |
host.arch |
WithEnvironmentAttributes() |
"X64" or "Arm64" |
Otel4Vsix exports telemetry via OTLP, which is supported by:
| Backend | Link |
|---|---|
| ๐ Honeycomb | honeycomb.io |
| ๐ต Azure Monitor | Application Insights |
| ๐ Datadog | datadoghq.com |
| ๐ก Jaeger | jaegertracing.io |
| ๐ด Grafana Tempo | grafana.com/oss/tempo |
| ๐ฎ Zipkin | zipkin.io |
| โ๏ธ AWS X-Ray | aws.amazon.com/xray |
| ๐ Google Cloud Trace | cloud.google.com/trace |
| ๐ง Any OTLP-compatible collector | โ |
using CodingWithCalvin.Otel4Vsix;
public sealed class MyExtensionPackage : AsyncPackage
{
protected override async Task InitializeAsync(
CancellationToken cancellationToken,
IProgress<ServiceProgressData> progress)
{
await JoinableTaskFactory.SwitchToMainThreadAsync();
var builder = VsixTelemetry.Configure()
.WithServiceName("MyExtension")
.WithServiceVersion(Vsix.Version)
.WithVisualStudioAttributes(this)
.WithEnvironmentAttributes()
.WithResourceAttribute("deployment.environment", "production")
.WithTraceSamplingRatio(0.1) // Sample 10% of traces
.WithExceptionFilter(ex => ex is not OperationCanceledException);
#if !DEBUG
builder
.WithOtlpHttp("https://api.honeycomb.io")
.WithHeader("x-honeycomb-team", Config.HoneycombApiKey);
#endif
builder.Initialize();
// ... rest of initialization
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
VsixTelemetry.Shutdown();
}
base.Dispose(disposing);
}
}| Requirement | Version |
|---|---|
| .NET Framework | 4.8 |
| Visual Studio | 2022 or later |
Contributions are welcome! ๐
- ๐ด 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 ๐ก
- Made with โค๏ธ by Coding with Calvin