File: Handler\Telemetry\RequestTelemetryLogger.cs
Web Access
Project: src\src\LanguageServer\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol)
// 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.Reflection;
using System.Threading;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Telemetry;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.LanguageServer.Handler;
 
/// <summary>
/// Logs metadata on LSP requests (duration, success / failure metrics)
/// for this particular LSP server instance.
/// </summary>
internal class RequestTelemetryLogger : IDisposable, ILspService
{
    protected readonly string ServerTypeName;
 
    public RequestTelemetryLogger(string serverTypeName)
    {
        ServerTypeName = serverTypeName;
    }
 
    public void UpdateFindDocumentTelemetryData(bool success, string? workspaceKind)
    {
        var workspaceKindTelemetryProperty = success ? workspaceKind : "Failed";
        if (workspaceKindTelemetryProperty != null)
        {
            IncreaseFindDocumentCount(workspaceKindTelemetryProperty);
        }
    }
 
    protected virtual void IncreaseFindDocumentCount(string workspaceCounterMetricName)
    {
        TelemetryLogging.LogAggregatedCounter(FunctionId.LSP_FindDocumentInWorkspace, KeyValueLogMessage.Create(m =>
        {
            m[TelemetryLogging.KeyName] = ServerTypeName + "." + workspaceCounterMetricName;
            m[TelemetryLogging.KeyValue] = 1L;
            m[TelemetryLogging.KeyMetricName] = workspaceCounterMetricName;
            m["server"] = ServerTypeName;
            m["workspace"] = workspaceCounterMetricName;
        }));
    }
 
    public void UpdateUsedForkedSolutionCounter(bool usedForkedSolution)
    {
        var metricName = usedForkedSolution ? "ForkedCount" : "NonForkedCount";
        TelemetryLogging.LogAggregatedCounter(FunctionId.LSP_UsedForkedSolution, KeyValueLogMessage.Create(m =>
        {
            m[TelemetryLogging.KeyName] = ServerTypeName + "." + metricName;
            m[TelemetryLogging.KeyValue] = 1L;
            m[TelemetryLogging.KeyMetricName] = metricName;
            m["server"] = ServerTypeName;
            m["usedForkedSolution"] = usedForkedSolution;
        }));
    }
 
    public void UpdateTelemetryData(
        string methodName,
        string? language,
        TimeSpan queuedDuration,
        TimeSpan requestDuration,
        Result result)
    {
        // Store the request time metrics per LSP method.
        TelemetryLogging.LogAggregatedHistogram(FunctionId.LSP_TimeInQueue, KeyValueLogMessage.Create(m =>
        {
            m[TelemetryLogging.KeyName] = ServerTypeName;
            m[TelemetryLogging.KeyValue] = (long)queuedDuration.TotalMilliseconds;
            m[TelemetryLogging.KeyMetricName] = "TimeInQueue";
            m["server"] = ServerTypeName;
        }));
 
        TelemetryLogging.LogAggregatedHistogram(FunctionId.LSP_RequestDuration, KeyValueLogMessage.Create(m =>
        {
            m[TelemetryLogging.KeyName] = ServerTypeName + "." + methodName + "." + language;
            m[TelemetryLogging.KeyValue] = (long)requestDuration.TotalMilliseconds;
            m[TelemetryLogging.KeyMetricName] = "RequestDuration";
            m["server"] = ServerTypeName;
            m["method"] = methodName;
            m["language"] = language;
        }));
 
        var metricName = result switch
        {
            Result.Succeeded => "SucceededCount",
            Result.Failed => "FailedCount",
            Result.Cancelled => "CancelledCount",
            _ => throw ExceptionUtilities.UnexpectedValue(result)
        };
 
        TelemetryLogging.LogAggregatedCounter(FunctionId.LSP_RequestCounter, KeyValueLogMessage.Create(m =>
        {
            m[TelemetryLogging.KeyName] = ServerTypeName + "." + methodName + "." + language + "." + metricName;
            m[TelemetryLogging.KeyValue] = 1L;
            m[TelemetryLogging.KeyMetricName] = metricName;
            m["server"] = ServerTypeName;
            m["method"] = methodName;
            m["language"] = language;
        }));
    }
 
    public void Dispose()
    {
        // Ensure that telemetry logged for this server instance is flushed before potentially creating a new instance.
        // This is also called on disposal of the telemetry session, but will no-op if already flushed.
        TelemetryLogging.Flush();
    }
 
    internal enum Result
    {
        Succeeded,
        Failed,
        Cancelled
    }
}