diff --git a/src/CodingWithCalvin.GitRanger/CodingWithCalvin.GitRanger.csproj b/src/CodingWithCalvin.GitRanger/CodingWithCalvin.GitRanger.csproj index a0b659b..7d9d29e 100644 --- a/src/CodingWithCalvin.GitRanger/CodingWithCalvin.GitRanger.csproj +++ b/src/CodingWithCalvin.GitRanger/CodingWithCalvin.GitRanger.csproj @@ -9,11 +9,8 @@ bin/$(Configuration)/ - - True - - + diff --git a/src/CodingWithCalvin.GitRanger/Commands/BlameCommands.cs b/src/CodingWithCalvin.GitRanger/Commands/BlameCommands.cs index e23aab8..14479ca 100644 --- a/src/CodingWithCalvin.GitRanger/Commands/BlameCommands.cs +++ b/src/CodingWithCalvin.GitRanger/Commands/BlameCommands.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.ComponentModel.Design; using System.Threading.Tasks; using CodingWithCalvin.GitRanger.Options; +using CodingWithCalvin.Otel4Vsix; using Community.VisualStudio.Toolkit; using Microsoft.VisualStudio.Shell; using Task = System.Threading.Tasks.Task; @@ -60,6 +62,8 @@ private static void OnToggleInlineBlame(object sender, EventArgs e) { ThreadHelper.ThrowIfNotOnUIThread(); + using var activity = VsixTelemetry.StartCommandActivity("GitRanger.ToggleInlineBlame"); + var options = GeneralOptions.Instance; if (options != null) { @@ -67,6 +71,8 @@ private static void OnToggleInlineBlame(object sender, EventArgs e) options.Save(); var status = options.EnableInlineBlame ? "enabled" : "disabled"; + activity?.SetTag("inline_blame.enabled", options.EnableInlineBlame); + VsixTelemetry.LogInformation("Inline blame {Status}", status); VS.StatusBar.ShowMessageAsync($"Git Ranger: Inline blame {status}").FireAndForget(); } } @@ -91,6 +97,8 @@ private static void OnToggleBlameGutter(object sender, EventArgs e) { ThreadHelper.ThrowIfNotOnUIThread(); + using var activity = VsixTelemetry.StartCommandActivity("GitRanger.ToggleBlameGutter"); + var options = GeneralOptions.Instance; if (options != null) { @@ -98,6 +106,8 @@ private static void OnToggleBlameGutter(object sender, EventArgs e) options.Save(); var status = options.EnableBlameGutter ? "enabled" : "disabled"; + activity?.SetTag("blame_gutter.enabled", options.EnableBlameGutter); + VsixTelemetry.LogInformation("Blame gutter {Status}", status); VS.StatusBar.ShowMessageAsync($"Git Ranger: Blame gutter {status}").FireAndForget(); } } @@ -119,6 +129,8 @@ private static async void OnCopyCommitSha(object sender, EventArgs e) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + using var activity = VsixTelemetry.StartCommandActivity("GitRanger.CopyCommitSha"); + try { var docView = await VS.Documents.GetActiveDocumentViewAsync(); @@ -129,24 +141,34 @@ private static async void OnCopyCommitSha(object sender, EventArgs e) if (string.IsNullOrEmpty(filePath)) return; - // Get the current line + // Get the current line var caretPosition = docView.TextView.Caret.Position.BufferPosition; var lineNumber = docView.TextView.TextSnapshot.GetLineNumberFromPosition(caretPosition.Position) + 1; + activity?.SetTag("line.number", lineNumber); + // Get blame for this line var blameInfo = GitRangerPackage.BlameService?.GetBlameForLine(filePath, lineNumber); if (blameInfo != null) { System.Windows.Clipboard.SetText(blameInfo.CommitSha); + activity?.SetTag("commit.sha", blameInfo.ShortSha); + VsixTelemetry.LogInformation("Copied commit SHA {CommitSha} to clipboard", blameInfo.ShortSha); await VS.StatusBar.ShowMessageAsync($"Git Ranger: Copied commit SHA {blameInfo.ShortSha} to clipboard"); } else { + VsixTelemetry.LogInformation("No blame information available for line {LineNumber}", lineNumber); await VS.StatusBar.ShowMessageAsync("Git Ranger: No blame information available for this line"); } } catch (Exception ex) { + activity?.RecordError(ex); + VsixTelemetry.TrackException(ex, new Dictionary + { + { "operation.name", "CopyCommitSha" } + }); await VS.StatusBar.ShowMessageAsync($"Git Ranger: Error copying commit SHA - {ex.Message}"); } } diff --git a/src/CodingWithCalvin.GitRanger/GitRangerPackage.cs b/src/CodingWithCalvin.GitRanger/GitRangerPackage.cs index acb85cd..fbc3c42 100644 --- a/src/CodingWithCalvin.GitRanger/GitRangerPackage.cs +++ b/src/CodingWithCalvin.GitRanger/GitRangerPackage.cs @@ -5,6 +5,7 @@ using CodingWithCalvin.GitRanger.Commands; using CodingWithCalvin.GitRanger.Options; using CodingWithCalvin.GitRanger.Services; +using CodingWithCalvin.Otel4Vsix; using Community.VisualStudio.Toolkit; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; @@ -85,16 +86,32 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke { await base.InitializeAsync(cancellationToken, progress); + // Switch to the main thread for telemetry initialization + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + // Initialize telemetry + var builder = VsixTelemetry.Configure() + .WithServiceName(VsixInfo.DisplayName) + .WithServiceVersion(VsixInfo.Version) + .WithVisualStudioAttributes(this) + .WithEnvironmentAttributes(); + +#if !DEBUG + builder + .WithOtlpHttp("https://api.honeycomb.io") + .WithHeader("x-honeycomb-team", HoneycombConfig.ApiKey); +#endif + + builder.Initialize(); + // Initialize services await InitializeServicesAsync(); - // Switch to the main thread for command registration - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - // Register commands await RegisterCommandsAsync(); // Log successful initialization + VsixTelemetry.LogInformation("Git Ranger initialized successfully"); await VS.StatusBar.ShowMessageAsync("Git Ranger initialized successfully"); } @@ -133,6 +150,16 @@ private async Task RegisterCommandsAsync() await BlameCommands.InitializeAsync(this); // Note: HistoryCommands and GraphCommands are not registered yet (coming soon) } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + VsixTelemetry.Shutdown(); + } + + base.Dispose(disposing); + } } /// diff --git a/src/CodingWithCalvin.GitRanger/HoneycombConfig.cs b/src/CodingWithCalvin.GitRanger/HoneycombConfig.cs new file mode 100644 index 0000000..d0a04f1 --- /dev/null +++ b/src/CodingWithCalvin.GitRanger/HoneycombConfig.cs @@ -0,0 +1,7 @@ +namespace CodingWithCalvin.GitRanger +{ + internal static class HoneycombConfig + { + public const string ApiKey = "PLACEHOLDER"; + } +} diff --git a/src/CodingWithCalvin.GitRanger/Services/BlameService.cs b/src/CodingWithCalvin.GitRanger/Services/BlameService.cs index 35c0b09..3c627a3 100644 --- a/src/CodingWithCalvin.GitRanger/Services/BlameService.cs +++ b/src/CodingWithCalvin.GitRanger/Services/BlameService.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using CodingWithCalvin.GitRanger.Core.Models; +using CodingWithCalvin.Otel4Vsix; namespace CodingWithCalvin.GitRanger.Services { @@ -38,21 +38,28 @@ public BlameService(GitService gitService, ThemeService themeService) /// Blame line information, or empty if not available. public IReadOnlyList GetBlame(string filePath) { - if (string.IsNullOrEmpty(filePath)) + using var activity = VsixTelemetry.StartCommandActivity("BlameService.GetBlame"); + + if (string.IsNullOrEmpty(filePath)) return Array.Empty(); // Check cache if (_cache.TryGetValue(filePath, out var cached) && !cached.IsExpired) { + activity?.SetTag("cache.hit", true); return cached.Lines; } + activity?.SetTag("cache.hit", false); + // Load synchronously var lines = _gitService.GetBlame(filePath).ToList(); // Update cache _cache[filePath] = new BlameCache(lines); + activity?.SetTag("lines.count", lines.Count); + return lines; } @@ -73,11 +80,11 @@ public Task> GetBlameAsync(string filePath, Cancell /// The file path. public void LoadBlameInBackground(string filePath) { - Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - FilePath: {filePath}"); + using var activity = VsixTelemetry.StartCommandActivity("BlameService.LoadBlameInBackground"); - if (string.IsNullOrEmpty(filePath)) + if (string.IsNullOrEmpty(filePath)) { - Debug.WriteLine("[GitRanger] BlameService.LoadBlameInBackground - FilePath is empty"); + VsixTelemetry.LogInformation("LoadBlameInBackground - FilePath is empty"); return; } @@ -85,16 +92,17 @@ public void LoadBlameInBackground(string filePath) { try { - Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - Starting GetBlame for {filePath}"); + VsixTelemetry.LogInformation("Loading blame for file"); var lines = GetBlame(filePath); - Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - Got {lines.Count} lines"); + VsixTelemetry.LogInformation("Loaded {LineCount} blame lines", lines.Count); BlameLoaded?.Invoke(this, new BlameLoadedEventArgs(filePath, lines)); - Debug.WriteLine("[GitRanger] BlameService.LoadBlameInBackground - BlameLoaded event fired"); } catch (Exception ex) { - Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - ERROR: {ex.Message}"); - Debug.WriteLine($"[GitRanger] BlameService.LoadBlameInBackground - StackTrace: {ex.StackTrace}"); + VsixTelemetry.TrackException(ex, new Dictionary + { + { "operation.name", "LoadBlameInBackground" }, + }); } }); }