/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/VisualStudio/DevKit/Impl/Logging/VSCodeTelemetryLogger.cs
184 строки
6 KB
Jason Malinowski
Reuse our fault reporting code from Visual Studio
16 июл 2026, 03:27
16 июл 2026, 03:27
db61e6f
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Composition; using System.Diagnostics; using System.Text; using System.Threading; using Microsoft.CodeAnalysis.Contracts.Telemetry; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Telemetry; namespace Microsoft.CodeAnalysis.LanguageServer.Logging; [Export(typeof(ITelemetryReporter)), Shared] internal sealed class VSCodeTelemetryLogger : ITelemetryReporter { private TelemetrySession? _telemetrySession; private const string CollectorApiKey = "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255"; private readonly ILogger _logger; private static readonly ConcurrentDictionary<int, object> _pendingScopes = new(concurrencyLevel: 2, capacity: 10); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VSCodeTelemetryLogger(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<VSCodeTelemetryLogger>(); } public void InitializeSession(string telemetryLevel, string? sessionId, bool isDefaultSession) { Debug.Assert(_telemetrySession == null); var sessionSettingsJson = CreateSessionSettingsJson(telemetryLevel, sessionId); var session = new TelemetrySession($"{{{sessionSettingsJson}}}"); if (isDefaultSession) { VisualStudio.Telemetry.TelemetryService.SetDefaultSession(session); } session.Start(); session.RegisterForReliabilityEvent(); _logger.LogTrace("Telemetry session started with sessionID: {sessionId}", sessionId); _telemetrySession = session; TelemetryLogger.Create(_telemetrySession, logDelta: false); FaultReporter.InitializeFatalErrorHandlers(); FaultReporter.IncludeServiceHubLogFiles = false; FaultReporter.RegisterTelemetrySesssion(_telemetrySession); } public void Log(string name, List<KeyValuePair<string, object?>> properties) { if (_telemetrySession is null) { return; } var telemetryEvent = new TelemetryEvent(name); SetProperties(telemetryEvent, properties); _telemetrySession.PostEvent(telemetryEvent); } public void LogBlockStart(string eventName, int kind, int blockId) { if (_telemetrySession is null) { return; } _pendingScopes[blockId] = kind switch { 0 => _telemetrySession.StartOperation(eventName), // LogType.Trace 1 => _telemetrySession.StartUserTask(eventName), // LogType.UserAction _ => new InvalidOperationException($"Unknown BlockStart kind: {kind}") }; } public void LogBlockEnd(int blockId, List<KeyValuePair<string, object?>> properties, CancellationToken cancellationToken) { if (!_pendingScopes.TryRemove(blockId, out var scope)) { return; } var endEvent = GetEndEvent(scope); SetProperties(endEvent, properties); var result = cancellationToken.IsCancellationRequested ? TelemetryResult.UserCancel : TelemetryResult.Success; if (scope is TelemetryScope<OperationEvent> operation) operation.End(result); else if (scope is TelemetryScope<UserTaskEvent> userTask) userTask.End(result); else throw new InvalidCastException($"Unexpected value for scope: {scope}"); } public void Dispose() { // Ensure that we flush any pending telemetry *before* we dispose of the telemetry session. TelemetryLogging.Flush(); _telemetrySession?.Dispose(); _telemetrySession = null; } private static string CreateSessionSettingsJson(string telemetryLevel, string? sessionId) { sessionId ??= Guid.NewGuid().ToString(); // Generate a new startTime for process to be consumed by Telemetry Settings using var curProcess = Process.GetCurrentProcess(); var processStartTime = curProcess.StartTime.ToFileTimeUtc().ToString(); var sb = new StringBuilder(); var kvp = new Dictionary<string, string> { { "Id", StringToJsonValue(sessionId) }, { "HostName", StringToJsonValue("Default") }, // Insert Telemetry Level instead of Opt-Out status. The telemetry service handles // validation of this value so there is no need to do so on this end. If it's invalid, // it defaults to off. { "TelemetryLevel", StringToJsonValue(telemetryLevel) }, // this sets the Telemetry Session Created by LSP Server to be the Root Initial session // This means that the SessionID set here by "Id" will be the SessionID used by cloned session // further down stream { "IsInitialSession", "true" }, { "CollectorApiKey", StringToJsonValue(CollectorApiKey) }, // using 1010 to indicate VS Code and not to match it to devenv 1000 { "AppId", "1010" }, { "ProcessStartTime", processStartTime }, }; foreach (var keyValue in kvp) { sb.AppendFormat("\"{0}\":{1},", keyValue.Key, keyValue.Value); } return sb.ToString().TrimEnd(','); static string StringToJsonValue(string? value) { if (value == null) { return "null"; } return '"' + value + '"'; } } private static TelemetryEvent GetEndEvent(object? scope) => scope switch { TelemetryScope<OperationEvent> operation => operation.EndEvent, TelemetryScope<UserTaskEvent> userTask => userTask.EndEvent, _ => throw new InvalidCastException($"Unexpected value for scope: {scope}") }; private static void SetProperties(TelemetryEvent telemetryEvent, List<KeyValuePair<string, object?>> properties) { foreach (var property in properties) { telemetryEvent.Properties.Add(property); } } }