From a38de683362adc9ccb5a5c16d1ae7cc66685ba02 Mon Sep 17 00:00:00 2001 From: "Calvin A. Allen" Date: Mon, 29 Dec 2025 20:57:39 -0500 Subject: [PATCH] chore: update README and add contributors --- .github/workflows/contributors.yml | 42 ++++ README.md | 351 +++++++++++++++++------------ 2 files changed, 253 insertions(+), 140 deletions(-) create mode 100644 .github/workflows/contributors.yml diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml new file mode 100644 index 0000000..a99ad0b --- /dev/null +++ b/.github/workflows/contributors.yml @@ -0,0 +1,42 @@ +name: Update Contributors + +on: + schedule: + - cron: '0 0 * * *' # Run daily at midnight UTC + workflow_dispatch: # Allow manual trigger + +jobs: + contributors: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.CONTRIBUTORS_TOKEN }} + + - name: Update contributors + env: + GH_TOKEN: ${{ secrets.CONTRIBUTORS_TOKEN }} + run: | + # Fetch contributors from GitHub API (exclude bots) + contributors=$(gh api repos/CodingWithCalvin/Otel4Vsix/contributors --paginate --jq '.[] | select(.type != "Bot") | select(.login | test("\\[bot\\]$") | not) | "\"\(.login)\"/"') + + # Build the contributors section + contrib_section=" +

+ $contributors +

+ " + + # Update README between the markers + awk -v contrib="$contrib_section" ' + //{found=1; print contrib; next} + //{found=0; next} + !found{print} + ' README.md > README.tmp && mv README.tmp README.md + + - name: Commit and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add README.md + git diff --staged --quiet || (git commit -m "docs: update contributors [skip ci]" && git push) diff --git a/README.md b/README.md index 75060be..3fd816a 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,40 @@ -# Otel4Vsix +

+ Otel4Vsix Logo +

-[![Build](https://github.com/CodingWithCalvin/Otel4Vsix/actions/workflows/build.yml/badge.svg)](https://github.com/CodingWithCalvin/Otel4Vsix/actions/workflows/build.yml) -[![NuGet](https://img.shields.io/nuget/v/CodingWithCalvin.Otel4Vsix.svg)](https://www.nuget.org/packages/CodingWithCalvin.Otel4Vsix/) -[![NuGet Downloads](https://img.shields.io/nuget/dt/CodingWithCalvin.Otel4Vsix.svg)](https://www.nuget.org/packages/CodingWithCalvin.Otel4Vsix/) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +

🔭 Otel4Vsix

-OpenTelemetry support library for Visual Studio 2022+ extensions. Add distributed tracing, metrics, logging, and exception tracking to your VSIX with minimal configuration. +

+ Build + NuGet + NuGet Downloads + License: MIT +

+ +

+ 🚀 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! 👀 --- -## Features +## ✨ Features -- **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 +| 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 | --- -## Installation +## 📦 Installation ### Package Manager ```powershell @@ -39,32 +53,33 @@ dotnet add package CodingWithCalvin.Otel4Vsix --- -## Quick Start +## 🚀 Quick Start -### 1. Initialize Telemetry +### 1️⃣ Initialize Telemetry In your Visual Studio extension's `InitializeAsync` method: ```csharp -using Otel4Vsix; +using CodingWithCalvin.Otel4Vsix; protected override async Task InitializeAsync( CancellationToken cancellationToken, IProgress 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 - }); + 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(); } ``` -### 2. Shutdown on Dispose +### 2️⃣ Shutdown on Dispose ```csharp protected override void Dispose(bool disposing) @@ -77,24 +92,60 @@ protected override void Dispose(bool disposing) } ``` +🎉 **That's it!** Your extension is now observable! + --- -## Usage +## 🎛️ Telemetry Modes + +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 | + +### 💡 Pro Tip: Development vs Production + +```csharp +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(); +``` -### Tracing +In Debug builds, telemetry automatically outputs to the VS **Output** window! 🔍 + +--- + +## 📊 Usage + +### 🔍 Tracing Create spans to track operations and their duration: ```csharp -// Simple span +// 🎯 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) +// ⚡ VS command span (with pre-configured attributes) using var commandSpan = VsixTelemetry.StartCommandActivity("MyExtension.DoSomething"); -// Nested spans for detailed tracing +// 🪆 Nested spans for detailed tracing using var outer = VsixTelemetry.Tracer.StartActivity("LoadProject"); { using var inner = VsixTelemetry.Tracer.StartActivity("ParseProjectFile"); @@ -102,7 +153,7 @@ using var outer = VsixTelemetry.Tracer.StartActivity("LoadProject"); } ``` -#### Error Handling in Spans +#### ⚠️ Error Handling in Spans ```csharp using var activity = VsixTelemetry.StartActivity("RiskyOperation"); @@ -120,12 +171,12 @@ catch (Exception ex) --- -### Metrics +### 📈 Metrics Record counters, histograms, and gauges: ```csharp -// Counter - track occurrences +// 🔢 Counter - track occurrences var commandCounter = VsixTelemetry.GetOrCreateCounter( "extension.commands.executed", "{command}", @@ -134,7 +185,7 @@ var commandCounter = VsixTelemetry.GetOrCreateCounter( commandCounter?.Add(1, new KeyValuePair("command.name", "FormatDocument")); -// Histogram - track distributions (e.g., durations) +// 📊 Histogram - track distributions (e.g., durations) var durationHistogram = VsixTelemetry.GetOrCreateHistogram( "extension.operation.duration", "ms", @@ -149,16 +200,17 @@ durationHistogram?.Record(stopwatch.ElapsedMilliseconds, --- -### Logging +### 📝 Logging Structured logging with OpenTelemetry integration: ```csharp -// Use the default logger -VsixTelemetry.Logger.LogInformation("Processing file: {FilePath}", filePath); -VsixTelemetry.Logger.LogWarning("File not found, using default: {DefaultPath}", defaultPath); +// 📢 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 +// 🏷️ Create a typed logger for your class public class MyToolWindow { private readonly ILogger _logger = VsixTelemetry.CreateLogger(); @@ -167,29 +219,19 @@ public class MyToolWindow { _logger.LogDebug("Starting work..."); // ... - _logger.LogInformation("Work completed successfully"); + _logger.LogInformation("Work completed successfully! 🎉"); } } - -// Log errors with exceptions -try -{ - // risky operation -} -catch (Exception ex) -{ - VsixTelemetry.Logger.LogError(ex, "Failed to process {FileName}", fileName); -} ``` --- -### Exception Tracking +### 💥 Exception Tracking Track exceptions with full context: ```csharp -// Manual exception tracking +// 🎯 Manual exception tracking try { // risky operation @@ -200,7 +242,7 @@ catch (Exception ex) // Handle or rethrow } -// With additional context +// 📋 With additional context catch (Exception ex) { VsixTelemetry.TrackException(ex, new Dictionary @@ -213,119 +255,148 @@ catch (Exception ex) } ``` -> **Note**: Global unhandled exceptions are automatically captured when `EnableGlobalExceptionHandler` is `true` (default). +> 💡 **Note**: Global unhandled exceptions are automatically captured when `EnableGlobalExceptionHandler` is `true` (default). --- -## Configuration - -| 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` | 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` | `null` | Filter which exceptions to track | -| `ResourceAttributes` | `IDictionary` | empty | Custom resource attributes | -| `ExportTimeoutMilliseconds` | `int` | `30000` | Export timeout | -| `BatchExportScheduledDelayMilliseconds` | `int` | `5000` | Batch export delay | - -### Example: Production Configuration +## ⚙️ Configuration Options + +### 🏗️ Fluent Builder Methods + +| 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 | + +### 📋 Auto-Captured Attributes + +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"` | -```csharp -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 -}); -``` +--- + +## 🔌 Supported Backends + +Otel4Vsix exports telemetry via OTLP, which is supported by: + +| Backend | Link | +|---------|------| +| 🐝 Honeycomb | [honeycomb.io](https://www.honeycomb.io/) | +| 🔵 Azure Monitor | [Application Insights](https://docs.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-overview) | +| 🐕 Datadog | [datadoghq.com](https://www.datadoghq.com/) | +| 🟡 Jaeger | [jaegertracing.io](https://www.jaegertracing.io/) | +| 🔴 Grafana Tempo | [grafana.com/oss/tempo](https://grafana.com/oss/tempo/) | +| 📮 Zipkin | [zipkin.io](https://zipkin.io/) | +| ☁️ AWS X-Ray | [aws.amazon.com/xray](https://aws.amazon.com/xray/) | +| 🌐 Google Cloud Trace | [cloud.google.com/trace](https://cloud.google.com/trace) | +| 🔧 Any OTLP-compatible collector | — | + +--- -### Example: Using Custom Headers (Honeycomb, etc.) +## 📋 Example: Full Production Setup ```csharp -var config = new TelemetryConfiguration -{ - ServiceName = "MyExtension", - OtlpEndpoint = "https://api.honeycomb.io:443", - UseOtlpHttp = true -}; +using CodingWithCalvin.Otel4Vsix; -// Add authentication headers -config.OtlpHeaders["x-honeycomb-team"] = "your-api-key"; -config.OtlpHeaders["x-honeycomb-dataset"] = "your-dataset"; +public sealed class MyExtensionPackage : AsyncPackage +{ + protected override async Task InitializeAsync( + CancellationToken cancellationToken, + IProgress 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 + } -VsixTelemetry.Initialize(config); + protected override void Dispose(bool disposing) + { + if (disposing) + { + VsixTelemetry.Shutdown(); + } + base.Dispose(disposing); + } +} ``` --- -## Supported Backends - -Otel4Vsix exports telemetry via OTLP, which is supported by: +## 📋 Requirements -- [Jaeger](https://www.jaegertracing.io/) -- [Zipkin](https://zipkin.io/) -- [Azure Monitor / Application Insights](https://docs.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-overview) -- [Honeycomb](https://www.honeycomb.io/) -- [Datadog](https://www.datadoghq.com/) -- [Grafana Tempo](https://grafana.com/oss/tempo/) -- [AWS X-Ray](https://aws.amazon.com/xray/) -- [Google Cloud Trace](https://cloud.google.com/trace) -- Any OTLP-compatible collector +| Requirement | Version | +|-------------|---------| +| .NET Framework | 4.8 | +| Visual Studio | 2022 or later | --- -## Requirements +## 🤝 Contributing -- **.NET Framework 4.8** -- **Visual Studio 2022** or later +Contributions are welcome! 🎉 -## Dependencies - -- 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) +1. 🍴 Fork the repository +2. 🌿 Create your feature branch (`git checkout -b feature/AmazingFeature`) +3. 💾 Commit your changes (`git commit -m 'Add some AmazingFeature'`) +4. 📤 Push to the branch (`git push origin feature/AmazingFeature`) +5. 🔃 Open a Pull Request --- -## Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. +## 👥 Contributors -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/AmazingFeature`) -3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) -4. Push to the branch (`git push origin feature/AmazingFeature`) -5. Open a Pull Request + + --- -## License +## 📄 License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details. --- -## Acknowledgments +## 🙏 Acknowledgments -- Built on top of [OpenTelemetry .NET](https://github.com/open-telemetry/opentelemetry-dotnet) -- Inspired by the need for better observability in Visual Studio extensions +- Built on top of [OpenTelemetry .NET](https://github.com/open-telemetry/opentelemetry-dotnet) 🔭 +- Inspired by the need for better observability in Visual Studio extensions 💡 +- Made with ❤️ by [Coding with Calvin](https://github.com/CodingWithCalvin)