зеркало из https://github.com/dotnet/razor.git
Add Razor language server telemetry to C# DevKit (#9283)
This commit is contained in:
Родитель
309b8bcc9a
Коммит
9ff3387560
|
@ -27,6 +27,7 @@
|
|||
"src\\Compiler\\tools\\Microsoft.CodeAnalysis.Razor.Tooling.Internal\\Microsoft.CodeAnalysis.Razor.Tooling.Internal.csproj",
|
||||
"src\\Razor\\src\\Microsoft.CodeAnalysis.Razor.Workspaces\\Microsoft.CodeAnalysis.Razor.Workspaces.csproj",
|
||||
"src\\Razor\\src\\Microsoft.CodeAnalysis.Remote.Razor\\Microsoft.CodeAnalysis.Remote.Razor.csproj",
|
||||
"src\\Razor\\src\\Microsoft.VisualStudio.DevKit.Razor\\Microsoft.VisualStudio.DevKit.Razor.csproj",
|
||||
"src\\Razor\\src\\Microsoft.VisualStudio.Editor.Razor\\Microsoft.VisualStudio.Editor.Razor.csproj",
|
||||
"src\\Razor\\src\\Microsoft.VisualStudio.LanguageServer.ContainedLanguage\\Microsoft.VisualStudio.LanguageServer.ContainedLanguage.csproj",
|
||||
"src\\Razor\\src\\Microsoft.VisualStudio.LanguageServices.Razor\\Microsoft.VisualStudio.LanguageServices.Razor.csproj",
|
||||
|
|
11
Razor.sln
11
Razor.sln
|
@ -179,6 +179,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Razor.Diagnostics.Analyzers
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Razor.Diagnostics.Analyzers.Test", "src\Analyzers\Razor.Diagnostics.Analyzers.Test\Razor.Diagnostics.Analyzers.Test.csproj", "{167F1426-D9AE-49DF-B214-F00536DBC305}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.DevKit.Razor", "src\Razor\src\Microsoft.VisualStudio.DevKit.Razor\Microsoft.VisualStudio.DevKit.Razor.csproj", "{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -739,6 +741,14 @@ Global
|
|||
{167F1426-D9AE-49DF-B214-F00536DBC305}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{167F1426-D9AE-49DF-B214-F00536DBC305}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{167F1426-D9AE-49DF-B214-F00536DBC305}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -820,6 +830,7 @@ Global
|
|||
{BAFE178B-7AD4-41AE-A75D-9B920B9EA050} = {3AE210D1-C435-4693-BF79-2EF13ED554B9}
|
||||
{45B207E2-DDB3-44F0-87C5-DEFB5A9534A5} = {4AA319E0-C81E-47CC-841A-6EFCB6784A1F}
|
||||
{167F1426-D9AE-49DF-B214-F00536DBC305} = {4AA319E0-C81E-47CC-841A-6EFCB6784A1F}
|
||||
{0F2D75D1-89AD-40A6-9F02-D45708AEA3EB} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {0035341D-175A-4D05-95E6-F1C2785A1E26}
|
||||
|
|
|
@ -41,4 +41,35 @@
|
|||
<!-- Build a .zip file from each platform's directory and write it to 'artifacts' -->
|
||||
<ZipDirectory SourceDirectory="%(LanguageServiceBinary.SourceDir)" DestinationFile="%(LanguageServiceBinary.ZipFile)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_ZipDevKitTelemetryBinaries" AfterTargets="_ZipLanguageServerBinaries" Condition="'$(ArcadeBuildFromSource)' != 'true'">
|
||||
<!-- This target is defined in eng/targets/Packaging.targets and included in every project. -->
|
||||
<MSBuild Projects="$(RepoRoot)src\Razor\src\Microsoft.VisualStudio.DevKit.Razor\Microsoft.VisualStudio.DevKit.Razor.csproj"
|
||||
Targets="_GetPackageVersionInfo"
|
||||
SkipNonexistentProjects="false">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_ResolvedPackageVersionInfo" />
|
||||
</MSBuild>
|
||||
|
||||
<!-- Build a .zip file from each platform's directory and write it to 'artifacts' -->
|
||||
<PropertyGroup>
|
||||
<RidsPublishDir>$(ArtifactsDir)DevKitTelemetry\$(Configuration)\</RidsPublishDir>
|
||||
<ZipOutputDir>$(RidsPublishDir)</ZipOutputDir>
|
||||
<_DotNetPath>$(DOTNET_HOST_PATH)</_DotNetPath>
|
||||
<_DotNetPath Condition="'$(_DotNetPath)' == ''">dotnet</_DotNetPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DevKitTelemetryBinaryDir Include="$([System.IO.Directory]::GetDirectories("$(RidsPublishDir)"))" />
|
||||
<DevKitTelemetryBinary Include="@(DevKitTelemetryBinaryDir)">
|
||||
<SourceDir>%(DevKitTelemetryBinaryDir.Identity)</SourceDir>
|
||||
<ZipFile>$(ZipOutputDir)DevKitTelemetry-%(DevKitTelemetryBinaryDir.Filename)-$(_PackageVersion).zip</ZipFile>
|
||||
</DevKitTelemetryBinary>
|
||||
</ItemGroup>
|
||||
|
||||
<MakeDir Directories="$(ZipOutputDir)" />
|
||||
<Delete Files="%(DevKitTelemetryBinary.ZipFile)" />
|
||||
|
||||
<!-- Build a .zip file from each platform's directory and write it to 'artifacts' -->
|
||||
<ZipDirectory SourceDirectory="%(DevKitTelemetryBinary.SourceDir)" DestinationFile="%(DevKitTelemetryBinary.ZipFile)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
@ -15,4 +15,16 @@
|
|||
Targets="PublishAllRids" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_PublishDevKitTelemetryRids" AfterTargets="Pack" Condition="'$(DotNetBuildFromSource)' != 'true'">
|
||||
<PropertyGroup>
|
||||
<DevKitTelemetryProject>$(MSBuildThisFileDirectory)..\src\Razor\src\Microsoft.VisualStudio.DevKit.Razor\Microsoft.VisualStudio.DevKit.Razor.csproj</DevKitTelemetryProject>
|
||||
<RazorSolutionPath>$(MSBuildThisFileDirectory)..\Razor.sln</RazorSolutionPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<MSBuild Projects="$(RazorSolutionPath)"
|
||||
Targets="Restore" />
|
||||
<MSBuild Projects="$(DevKitTelemetryProject)"
|
||||
Targets="PublishAllRids" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<!-- Prepare for _UpdatePublishItems target. -->
|
||||
<_ItemsToPublish Include="$(ArtifactsPackagesDir)**\*.tgz" Condition="'$(OS)' == 'Windows_NT'" />
|
||||
<_ItemsToPublish Include="$(ArtifactsDir)LanguageServer\**\*.zip" />
|
||||
<_ItemsToPublish Include="$(ArtifactsDir)DevKitTelemetry\**\*.zip" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="_UpdatePublishItems">
|
||||
|
@ -28,7 +28,7 @@
|
|||
<!-- Packages can be built on all platforms, but are only published on Windows to avoid collisions from the other
|
||||
platforms. This does not affect the SB intermediate package. -->
|
||||
<ItemsToPushToBlobFeed Remove="$(ArtifactsDir)**\*.nupkg" Condition="'$(OS)' != 'Windows_NT' and '$(ArcadeBuildFromSource)' != 'true'" />
|
||||
|
||||
|
||||
<ItemsToPushToBlobFeed Include="@(_ItemsToPublish)">
|
||||
<IsShipping>false</IsShipping>
|
||||
<ManifestArtifactData>ShipInstaller=dotnetcli;NonShipping=true</ManifestArtifactData>
|
||||
|
|
|
@ -129,7 +129,7 @@
|
|||
<MicrosoftVisualStudioInteropPackageVersion>$(MicrosoftVisualStudioShellPackagesVersion)</MicrosoftVisualStudioInteropPackageVersion>
|
||||
<MicrosoftInternalVisualStudioInteropPackageVersion>$(MicrosoftVisualStudioShellPackagesVersion)</MicrosoftInternalVisualStudioInteropPackageVersion>
|
||||
<MicrosoftVisualStudioRpcContractsPackageVersion>17.7.9</MicrosoftVisualStudioRpcContractsPackageVersion>
|
||||
<MicrosoftVisualStudioTelemetryVersion>17.7.8</MicrosoftVisualStudioTelemetryVersion>
|
||||
<MicrosoftVisualStudioTelemetryVersion>17.8.138</MicrosoftVisualStudioTelemetryVersion>
|
||||
<MicrosoftVisualStudioTextDataPackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioTextDataPackageVersion>
|
||||
<MicrosoftVisualStudioTextImplementationPackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioTextImplementationPackageVersion>
|
||||
<MicrosoftVisualStudioTextLogicPackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioTextLogicPackageVersion>
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Microsoft.VisualStudio.Composition;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Exports;
|
||||
|
||||
internal class CustomExportAssemblyLoader(string baseDirectory) : IAssemblyLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Cache assemblies that are already loaded by AssemblyName comparison
|
||||
/// </summary>
|
||||
private readonly Dictionary<AssemblyName, Assembly> _loadedAssemblies = new(AssemblyNameComparer.Instance);
|
||||
|
||||
/// <summary>
|
||||
/// Base directory to search for <see cref="Assembly.LoadFrom(string)"/> if initial load fails
|
||||
/// </summary>
|
||||
private readonly string _baseDirectory = baseDirectory;
|
||||
|
||||
public Assembly LoadAssembly(AssemblyName assemblyName)
|
||||
{
|
||||
Assembly? value;
|
||||
lock (_loadedAssemblies)
|
||||
{
|
||||
_loadedAssemblies.TryGetValue(assemblyName, out value);
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
// Attempt to load the assembly normally, but fall back to Assembly.LoadFrom in the base
|
||||
// directory if the assembly load fails
|
||||
try
|
||||
{
|
||||
value = Assembly.Load(assemblyName);
|
||||
}
|
||||
catch (FileNotFoundException) when (assemblyName.Name is not null)
|
||||
{
|
||||
var filePath = Path.Combine(_baseDirectory, assemblyName.Name)
|
||||
+ (assemblyName.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
|
||||
? ""
|
||||
: ".dll");
|
||||
|
||||
value = Assembly.LoadFrom(filePath);
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
lock (_loadedAssemblies)
|
||||
{
|
||||
_loadedAssemblies[assemblyName] = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public Assembly LoadAssembly(string assemblyFullName, string? codeBasePath)
|
||||
{
|
||||
var assemblyName = new AssemblyName(assemblyFullName);
|
||||
return LoadAssembly(assemblyName);
|
||||
}
|
||||
|
||||
private class AssemblyNameComparer : IEqualityComparer<AssemblyName>
|
||||
{
|
||||
public static AssemblyNameComparer Instance = new AssemblyNameComparer();
|
||||
|
||||
public bool Equals(AssemblyName? x, AssemblyName? y)
|
||||
{
|
||||
if (x == null && y == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x == null || y == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return x.Name == y.Name;
|
||||
}
|
||||
|
||||
public int GetHashCode([DisallowNull] AssemblyName obj)
|
||||
{
|
||||
return obj.Name?.GetHashCode() ?? 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
#if !NET472
|
||||
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Composition;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Exports;
|
||||
|
||||
internal sealed class ExportProviderBuilder
|
||||
{
|
||||
public static async Task<ExportProvider> CreateExportProviderAsync(string extensionPath)
|
||||
{
|
||||
var baseDirectory = Path.GetDirectoryName(extensionPath);
|
||||
var assemblyLoader = new CustomExportAssemblyLoader(baseDirectory!);
|
||||
var resolver = new Resolver(assemblyLoader);
|
||||
|
||||
var discovery = PartDiscovery.Combine(
|
||||
resolver,
|
||||
new AttributedPartDiscovery(resolver, isNonPublicSupported: true), // "NuGet MEF" attributes (Microsoft.Composition)
|
||||
new AttributedPartDiscoveryV1(resolver));
|
||||
|
||||
// TODO - we should likely cache the catalog so we don't have to rebuild it every time.
|
||||
var parts = await discovery.CreatePartsAsync(new[] { extensionPath! }).ConfigureAwait(true);
|
||||
var catalog = ComposableCatalog.Create(resolver)
|
||||
.AddParts(parts)
|
||||
.WithCompositionService(); // Makes an ICompositionService export available to MEF parts to import
|
||||
|
||||
// Assemble the parts into a valid graph.
|
||||
var config = CompositionConfiguration.Create(catalog);
|
||||
|
||||
// Verify we have no errors.
|
||||
config.ThrowOnErrors();
|
||||
|
||||
// Prepare an ExportProvider factory based on this graph.
|
||||
var exportProviderFactory = config.CreateExportProviderFactory();
|
||||
|
||||
// Create an export provider, which represents a unique container of values.
|
||||
// You can create as many of these as you want, but typically an app needs just one.
|
||||
var exportProvider = exportProviderFactory.CreateExportProvider();
|
||||
|
||||
return exportProvider;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -16,6 +16,7 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.DevKit.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServerClient.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServerClient.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Razor.Telemetry;
|
|||
|
||||
internal interface ITelemetryReporter
|
||||
{
|
||||
void InitializeSession(string telemetryLevel, string? sessionId, bool isDefaultSession);
|
||||
IDisposable BeginBlock(string name, Severity severity);
|
||||
IDisposable BeginBlock(string name, Severity severity, ImmutableDictionary<string, object?> values);
|
||||
IDisposable TrackLspRequest(string lspMethodName, string lspServerName, Guid correlationId);
|
||||
|
|
|
@ -14,6 +14,10 @@ internal class NoOpTelemetryReporter : ITelemetryReporter
|
|||
{
|
||||
}
|
||||
|
||||
public void InitializeSession(string telemetryLevel, string? sessionId, bool isDefaultSession)
|
||||
{
|
||||
}
|
||||
|
||||
public void ReportEvent(string name, Severity severity)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(DefaultNetCoreTargetFrameworks)</TargetFrameworks>
|
||||
<PublishTargetFramework>net7.0</PublishTargetFramework>
|
||||
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains the language server assets for C# DevKit.</Description>
|
||||
<EnableApiCheck>false</EnableApiCheck>
|
||||
<RuntimeIdentifiers Condition="$([MSBuild]::IsOSPlatform('Windows'))">win-x64;win-x86;win-arm64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers Condition="$([MSBuild]::IsOSPlatform('Linux'))">linux-x64;linux-musl-x64;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers Condition="$([MSBuild]::IsOSPlatform('OSX'))">osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<IsShippingPackage>false</IsShippingPackage>
|
||||
<RemoveDevicePlatformSupport>true</RemoveDevicePlatformSupport>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.VisualStudio.Editor.Razor\Telemetry\NullTelemetryScope.cs" Link="Telemetry\NullTelemetryScope.cs" />
|
||||
<Compile Include="..\Microsoft.VisualStudio.Editor.Razor\Telemetry\TelemetryHelpers.cs" Link="Telemetry\TelemetryHelpers.cs" />
|
||||
<Compile Include="..\Microsoft.VisualStudio.Editor.Razor\Telemetry\TelemetryReporter.cs" Link="Telemetry\TelemetryReporter.cs" />
|
||||
<Compile Include="..\Microsoft.VisualStudio.Editor.Razor\Telemetry\TelemetryScope.cs" Link="Telemetry\TelemetryScope.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Telemetry" Version="$(MicrosoftVisualStudioTelemetryVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="$(RepositoryRoot)NOTICE.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.ProjectEngineHost\Microsoft.AspNetCore.Razor.ProjectEngineHost.csproj" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Technique for publishing multiple RIDs from
|
||||
https://github.com/dotnet/cli/issues/9221#issuecomment-387512008
|
||||
Example usage:
|
||||
dotnet msbuild -restore -t:PublishAllRids -p:Configuration=Release
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
|
||||
<!-- Enable roll-forward to latest patch. This allows one restore operation
|
||||
to apply to all of the self-contained publish operations. -->
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<RidsPublishDir>$(ArtifactsDir)DevKitTelemetry\$(Configuration)\</RidsPublishDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PublishAllRids">
|
||||
<ItemGroup>
|
||||
<!-- Transform RuntimeIdentifiers property to item -->
|
||||
<RuntimeIdentifierForPublish Include="$(RuntimeIdentifiers)" />
|
||||
|
||||
<!-- Transform RuntimeIdentifierForPublish items to project items to pass to MSBuild task -->
|
||||
<ProjectToPublish Include="@(RuntimeIdentifierForPublish->'$(MSBuildProjectFullPath)')">
|
||||
<AdditionalProperties>RuntimeIdentifier=%(RuntimeIdentifierForPublish.Identity);PublishDir=$(RidsPublishDir)%(RuntimeIdentifierForPublish.Identity)\;TargetFramework=$(PublishTargetFramework)</AdditionalProperties>
|
||||
</ProjectToPublish>
|
||||
|
||||
<ProjectToPublish_PlatformAgnostic Include="$(MSBuildProjectFullPath)">
|
||||
<AdditionalProperties>PublishDir=$(RidsPublishDir)\PlatformAgnostic\;UseAppHost=false;TargetFramework=$(PublishTargetFramework)</AdditionalProperties>
|
||||
</ProjectToPublish_PlatformAgnostic>
|
||||
</ItemGroup>
|
||||
|
||||
<MSBuild Projects="@(ProjectToPublish)" Targets="Publish" BuildInParallel="false" />
|
||||
<MSBuild Projects="@(ProjectToPublish_PlatformAgnostic)" Targets="Publish" BuildInParallel="false" Condition="'$(OS)' == 'WINDOWS_NT'" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Razor.Telemetry;
|
||||
using Microsoft.VisualStudio.Telemetry;
|
||||
|
||||
namespace Microsoft.VisualStudio.DevKit.Razor.Telemetry;
|
||||
|
||||
[Shared]
|
||||
[Export(typeof(ITelemetryReporter))]
|
||||
internal sealed class DevKitTelemetryReporter : TelemetryReporter
|
||||
{
|
||||
private const string CollectorApiKey = "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255";
|
||||
|
||||
[ImportingConstructor]
|
||||
public DevKitTelemetryReporter() : base(telemetrySessions: [])
|
||||
{
|
||||
}
|
||||
|
||||
public override void InitializeSession(string telemetryLevel, string? sessionId, bool isDefaultSession)
|
||||
{
|
||||
Debug.Assert(TelemetrySessions.IsDefaultOrEmpty);
|
||||
|
||||
var sessionSettingsJson = CreateSessionSettingsJson(telemetryLevel, sessionId);
|
||||
var session = new TelemetrySession($"{{{sessionSettingsJson}}}");
|
||||
|
||||
if (isDefaultSession)
|
||||
{
|
||||
TelemetryService.SetDefaultSession(session);
|
||||
}
|
||||
|
||||
session.Start();
|
||||
session.RegisterForReliabilityEvent();
|
||||
|
||||
TelemetrySessions = ImmutableArray.Create<TelemetrySession>(session);
|
||||
}
|
||||
|
||||
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 + '"';
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HandleException(Exception exception, string? message, params object?[] @params)
|
||||
=> false;
|
||||
|
||||
public override void LogTrace(string? message, params object?[] args)
|
||||
{
|
||||
}
|
||||
|
||||
public override void LogError(Exception exception, string? message, params object?[] args)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Label="String Resources">
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Telemetry;
|
||||
|
||||
internal class NullTelemetryScope : IDisposable
|
||||
{
|
||||
public static NullTelemetryScope Instance { get; } = new NullTelemetryScope();
|
||||
private NullTelemetryScope() { }
|
||||
public void Dispose() { }
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.VisualStudio.Telemetry;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Telemetry;
|
||||
|
||||
internal static class TelemetryHelpers
|
||||
{
|
||||
public static string GetTelemetryName(string name) => "dotnet/razor/" + name;
|
||||
|
||||
public static string GetPropertyName(string name) => "dotnet.razor." + name;
|
||||
|
||||
public static string GetDescription(Exception exception)
|
||||
{
|
||||
const string CodeAnalysisNamespace = nameof(Microsoft) + "." + nameof(CodeAnalysis);
|
||||
const string AspNetCoreNamespace = nameof(Microsoft) + "." + nameof(AspNetCore);
|
||||
|
||||
// Be resilient to failing here. If we can't get a suitable name, just fall back to the standard name we
|
||||
// used to report.
|
||||
try
|
||||
{
|
||||
// walk up the stack looking for the first call from a type that isn't in the ErrorReporting namespace.
|
||||
var frames = new StackTrace(exception).GetFrames();
|
||||
|
||||
// On the .NET Framework, GetFrames() can return null even though it's not documented as such.
|
||||
// At least one case here is if the exception's stack trace itself is null.
|
||||
if (frames != null)
|
||||
{
|
||||
foreach (var frame in frames)
|
||||
{
|
||||
var method = frame?.GetMethod();
|
||||
var methodName = method?.Name;
|
||||
if (methodName is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var declaringTypeName = method?.DeclaringType?.FullName;
|
||||
if (declaringTypeName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!declaringTypeName.StartsWith(CodeAnalysisNamespace) &&
|
||||
!declaringTypeName.StartsWith(AspNetCoreNamespace))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return declaringTypeName + "." + methodName;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// If we couldn't get a stack, do this
|
||||
return exception.Message;
|
||||
}
|
||||
|
||||
public static TelemetrySeverity ToTelemetrySeverity(Severity severity)
|
||||
=> severity switch
|
||||
{
|
||||
Severity.Normal => TelemetrySeverity.Normal,
|
||||
Severity.Low => TelemetrySeverity.Low,
|
||||
Severity.High => TelemetrySeverity.High,
|
||||
_ => throw new InvalidOperationException($"Unknown severity: {severity}")
|
||||
};
|
||||
|
||||
public static bool IsNumeric(object? o)
|
||||
=> o is not null &&
|
||||
!o.GetType().IsEnum &&
|
||||
Type.GetTypeCode(o.GetType()) switch
|
||||
{
|
||||
TypeCode.Char or
|
||||
TypeCode.SByte or
|
||||
TypeCode.Byte or
|
||||
TypeCode.Int16 or
|
||||
TypeCode.Int32 or
|
||||
TypeCode.Int64 or
|
||||
TypeCode.Double or
|
||||
TypeCode.Single or
|
||||
TypeCode.UInt16 or
|
||||
TypeCode.UInt32 or
|
||||
TypeCode.UInt64
|
||||
=> true,
|
||||
_ => false
|
||||
};
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.VisualStudio.Telemetry;
|
||||
|
||||
#if DEBUG
|
||||
using System.Linq;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Telemetry;
|
||||
|
||||
internal abstract class TelemetryReporter : ITelemetryReporter
|
||||
{
|
||||
public ImmutableArray<TelemetrySession> TelemetrySessions { get; set; }
|
||||
|
||||
public TelemetryReporter(ImmutableArray<TelemetrySession> telemetrySessions)
|
||||
{
|
||||
// Get the DefaultSession for telemetry. This is set by VS with
|
||||
// TelemetryService.SetDefaultSession and provides the correct
|
||||
// appinsights keys etc
|
||||
TelemetrySessions = telemetrySessions;
|
||||
}
|
||||
|
||||
public void ReportEvent(string name, Severity severity)
|
||||
{
|
||||
var telemetryEvent = new TelemetryEvent(TelemetryHelpers.GetTelemetryName(name), TelemetryHelpers.ToTelemetrySeverity(severity));
|
||||
Report(telemetryEvent);
|
||||
}
|
||||
|
||||
public void ReportEvent(string name, Severity severity, ImmutableDictionary<string, object?> values)
|
||||
{
|
||||
var telemetryEvent = new TelemetryEvent(TelemetryHelpers.GetTelemetryName(name), TelemetryHelpers.ToTelemetrySeverity(severity));
|
||||
foreach (var (propertyName, propertyValue) in values)
|
||||
{
|
||||
if (TelemetryHelpers.IsNumeric(propertyValue))
|
||||
{
|
||||
telemetryEvent.Properties.Add(TelemetryHelpers.GetPropertyName(propertyName), propertyValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
telemetryEvent.Properties.Add(TelemetryHelpers.GetPropertyName(propertyName), new TelemetryComplexProperty(propertyValue));
|
||||
}
|
||||
}
|
||||
|
||||
Report(telemetryEvent);
|
||||
}
|
||||
|
||||
public void ReportFault(Exception exception, string? message, params object?[] @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (exception is OperationCanceledException { InnerException: { } oceInnerException })
|
||||
{
|
||||
ReportFault(oceInnerException, message, @params);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is AggregateException aggregateException)
|
||||
{
|
||||
// We (potentially) have multiple exceptions; let's just report each of them
|
||||
foreach (var innerException in aggregateException.Flatten().InnerExceptions)
|
||||
{
|
||||
ReportFault(innerException, message, @params);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (HandleException(exception, message, @params))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentProcess = Process.GetCurrentProcess();
|
||||
|
||||
var faultEvent = new FaultEvent(
|
||||
eventName: TelemetryHelpers.GetTelemetryName("fault"),
|
||||
description: TelemetryHelpers.GetDescription(exception),
|
||||
FaultSeverity.General,
|
||||
exceptionObject: exception,
|
||||
gatherEventDetails: faultUtility =>
|
||||
{
|
||||
foreach (var data in @params)
|
||||
{
|
||||
if (data is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
faultUtility.AddErrorInformation(data.ToString());
|
||||
}
|
||||
|
||||
// Returning "0" signals that, if sampled, we should send data to Watson.
|
||||
// Any other value will cancel the Watson report. We never want to trigger a process dump manually,
|
||||
// we'll let TargetedNotifications determine if a dump should be collected.
|
||||
// See https://aka.ms/roslynnfwdocs for more details
|
||||
return 0;
|
||||
});
|
||||
|
||||
Report(faultEvent);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void Report(TelemetryEvent telemetryEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if !DEBUG
|
||||
foreach (var session in TelemetrySessions)
|
||||
{
|
||||
session.PostEvent(telemetryEvent);
|
||||
}
|
||||
#else
|
||||
// In debug we only log to normal logging. This makes it much easier to add and debug telemetry events
|
||||
// before we're ready to send them to the cloud
|
||||
var name = telemetryEvent.Name;
|
||||
var propertyString = string.Join(",", telemetryEvent.Properties.Select(kvp => $"[ {kvp.Key}:{kvp.Value} ]"));
|
||||
LogTrace("Telemetry Event: {name} \n Properties: {propertyString}\n", name, propertyString);
|
||||
|
||||
if (telemetryEvent is FaultEvent)
|
||||
{
|
||||
var eventType = telemetryEvent.GetType();
|
||||
var description = eventType.GetProperty("Description", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(telemetryEvent, null);
|
||||
var exception = eventType.GetProperty("ExceptionObject", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(telemetryEvent, null);
|
||||
var message = $"Fault Event: {name} \n Exception Info: {exception ?? description} \n Properties: {propertyString}";
|
||||
|
||||
Debug.Assert(true, message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// No need to do anything here. We failed to report telemetry
|
||||
// which isn't good, but not catastrophic for a user
|
||||
LogError(e, "Failed logging telemetry event");
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable BeginBlock(string name, Severity severity)
|
||||
{
|
||||
return BeginBlock(name, severity, ImmutableDictionary<string, object?>.Empty);
|
||||
}
|
||||
|
||||
public IDisposable BeginBlock(string name, Severity severity, ImmutableDictionary<string, object?> values)
|
||||
{
|
||||
return new TelemetryScope(this, name, severity, values.ToImmutableDictionary((tuple) => tuple.Key, (tuple) => (object?)tuple.Value));
|
||||
}
|
||||
|
||||
public IDisposable TrackLspRequest(string lspMethodName, string languageServerName, Guid correlationId)
|
||||
{
|
||||
if (correlationId == Guid.Empty)
|
||||
{
|
||||
return NullTelemetryScope.Instance;
|
||||
}
|
||||
|
||||
return BeginBlock("TrackLspRequest", Severity.Normal, ImmutableDictionary.CreateRange(new KeyValuePair<string, object?>[]
|
||||
{
|
||||
new("eventscope.method", lspMethodName),
|
||||
new("eventscope.languageservername", languageServerName),
|
||||
new("eventscope.correlationid", correlationId),
|
||||
}));
|
||||
}
|
||||
|
||||
public abstract void InitializeSession(string telemetryLevel, string? sessionId, bool isDefaultSession);
|
||||
|
||||
public abstract bool HandleException(Exception exception, string? message, params object?[] @params);
|
||||
|
||||
public abstract void LogTrace(string? message, params object?[] args);
|
||||
|
||||
public abstract void LogError(Exception exception, string? message, params object?[] args);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Telemetry;
|
||||
|
||||
internal class TelemetryScope : IDisposable
|
||||
{
|
||||
private readonly ITelemetryReporter _telemetryReporter;
|
||||
private readonly string _name;
|
||||
private readonly Severity _severity;
|
||||
private readonly ImmutableDictionary<string, object?> _values;
|
||||
private readonly Stopwatch _stopwatch;
|
||||
private bool _disposed;
|
||||
|
||||
public TelemetryScope(
|
||||
ITelemetryReporter telemetryReporter,
|
||||
string name,
|
||||
Severity severity,
|
||||
ImmutableDictionary<string, object?> values)
|
||||
{
|
||||
_telemetryReporter = telemetryReporter;
|
||||
_name = name;
|
||||
_severity = severity;
|
||||
_values = values;
|
||||
_stopwatch = StopwatchPool.Default.Get();
|
||||
_stopwatch.Restart();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
_stopwatch.Stop();
|
||||
var values = _values.Add("eventscope.ellapsedms", _stopwatch.ElapsedMilliseconds);
|
||||
_telemetryReporter.ReportEvent(_name, _severity, values);
|
||||
StopwatchPool.Default.Return(_stopwatch);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Composition;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.Telemetry;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Telemetry;
|
||||
|
||||
[Shared]
|
||||
[Export(typeof(ITelemetryReporter))]
|
||||
internal class VSTelemetryReporter : TelemetryReporter
|
||||
{
|
||||
private readonly IEnumerable<IFaultExceptionHandler> _faultExceptionHandlers;
|
||||
private readonly ILogger? _logger;
|
||||
|
||||
[ImportingConstructor]
|
||||
public VSTelemetryReporter(
|
||||
[Import(AllowDefault = true)] ILoggerFactory? loggerFactory = null,
|
||||
[ImportMany] IEnumerable<IFaultExceptionHandler>? faultExceptionHandlers = null)
|
||||
// Get the DefaultSession for telemetry. This is set by VS with
|
||||
// TelemetryService.SetDefaultSession and provides the correct
|
||||
// appinsights keys etc
|
||||
: base(ImmutableArray.Create(TelemetryService.DefaultSession))
|
||||
{
|
||||
_faultExceptionHandlers = faultExceptionHandlers ?? Array.Empty<IFaultExceptionHandler>();
|
||||
_logger = loggerFactory?.CreateLogger<VSTelemetryReporter>();
|
||||
}
|
||||
|
||||
public override bool HandleException(Exception exception, string? message, params object?[] @params)
|
||||
{
|
||||
var handled = false;
|
||||
foreach (var handler in _faultExceptionHandlers)
|
||||
{
|
||||
if (handler.HandleException(this, exception, message, @params))
|
||||
{
|
||||
// This behavior means that each handler still gets a chance
|
||||
// to respond to the exception. There's no real reason for this other
|
||||
// than best guess. When it was added, there was only one handler but
|
||||
// it was intended to be easy to add more.
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
public override void InitializeSession(string telemetryLevel, string? sessionId, bool isDefaultSession)
|
||||
{
|
||||
// We don't need to do anything here. We're using the default session
|
||||
// which is already initialized by VS.
|
||||
throw new Exception("InitializeSession should not be called in VS.");
|
||||
}
|
||||
|
||||
public override void LogTrace(string? message, params object?[] args)
|
||||
=> _logger?.LogTrace(message, args);
|
||||
|
||||
public override void LogError(Exception exception, string? message, params object?[] args)
|
||||
=> _logger?.LogError(exception, message, args);
|
||||
|
||||
|
||||
}
|
|
@ -1,318 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Composition;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.Telemetry;
|
||||
|
||||
#if DEBUG
|
||||
using System.Linq;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Telemetry;
|
||||
|
||||
[Shared]
|
||||
[Export(typeof(ITelemetryReporter))]
|
||||
internal class TelemetryReporter : ITelemetryReporter
|
||||
{
|
||||
private readonly ImmutableArray<TelemetrySession> _telemetrySessions;
|
||||
private readonly IEnumerable<IFaultExceptionHandler> _faultExceptionHandlers;
|
||||
private readonly ILogger? _logger;
|
||||
|
||||
[ImportingConstructor]
|
||||
public TelemetryReporter(
|
||||
[Import(AllowDefault = true)] ILoggerFactory? loggerFactory = null,
|
||||
[ImportMany] IEnumerable<IFaultExceptionHandler>? faultExceptionHandlers = null)
|
||||
{
|
||||
// Get the DefaultSession for telemetry. This is set by VS with
|
||||
// TelemetryService.SetDefaultSession and provides the correct
|
||||
// appinsights keys etc
|
||||
_telemetrySessions = ImmutableArray.Create(TelemetryService.DefaultSession);
|
||||
_faultExceptionHandlers = faultExceptionHandlers ?? Array.Empty<IFaultExceptionHandler>();
|
||||
_logger = loggerFactory?.CreateLogger<TelemetryReporter>();
|
||||
}
|
||||
|
||||
public void ReportEvent(string name, Severity severity)
|
||||
{
|
||||
var telemetryEvent = new TelemetryEvent(GetTelemetryName(name), ToTelemetrySeverity(severity));
|
||||
Report(telemetryEvent);
|
||||
}
|
||||
|
||||
public void ReportEvent(string name, Severity severity, ImmutableDictionary<string, object?> values)
|
||||
{
|
||||
var telemetryEvent = new TelemetryEvent(GetTelemetryName(name), ToTelemetrySeverity(severity));
|
||||
foreach (var (propertyName, propertyValue) in values)
|
||||
{
|
||||
if (IsNumeric(propertyValue))
|
||||
{
|
||||
telemetryEvent.Properties.Add(GetPropertyName(propertyName), propertyValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
telemetryEvent.Properties.Add(GetPropertyName(propertyName), new TelemetryComplexProperty(propertyValue));
|
||||
}
|
||||
}
|
||||
|
||||
Report(telemetryEvent);
|
||||
}
|
||||
|
||||
public void ReportFault(Exception exception, string? message, params object?[] @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (exception is OperationCanceledException { InnerException: { } oceInnerException })
|
||||
{
|
||||
ReportFault(oceInnerException, message, @params);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is AggregateException aggregateException)
|
||||
{
|
||||
// We (potentially) have multiple exceptions; let's just report each of them
|
||||
foreach (var innerException in aggregateException.Flatten().InnerExceptions)
|
||||
{
|
||||
ReportFault(innerException, message, @params);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var handled = false;
|
||||
foreach (var handler in _faultExceptionHandlers)
|
||||
{
|
||||
if (handler.HandleException(this, exception, message, @params))
|
||||
{
|
||||
// This behavior means that each handler still gets a chance
|
||||
// to respond to the exception. There's no real reason for this other
|
||||
// than best guess. When it was added, there was only one handler but
|
||||
// it was intended to be easy to add more.
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentProcess = Process.GetCurrentProcess();
|
||||
|
||||
var faultEvent = new FaultEvent(
|
||||
eventName: GetTelemetryName("fault"),
|
||||
description: GetDescription(exception),
|
||||
FaultSeverity.General,
|
||||
exceptionObject: exception,
|
||||
gatherEventDetails: faultUtility =>
|
||||
{
|
||||
foreach (var data in @params)
|
||||
{
|
||||
if (data is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
faultUtility.AddErrorInformation(data.ToString());
|
||||
}
|
||||
|
||||
// Returning "0" signals that, if sampled, we should send data to Watson.
|
||||
// Any other value will cancel the Watson report. We never want to trigger a process dump manually,
|
||||
// we'll let TargetedNotifications determine if a dump should be collected.
|
||||
// See https://aka.ms/roslynnfwdocs for more details
|
||||
return 0;
|
||||
});
|
||||
|
||||
Report(faultEvent);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetTelemetryName(string name) => "dotnet/razor/" + name;
|
||||
private static string GetPropertyName(string name) => "dotnet.razor." + name;
|
||||
|
||||
private void Report(TelemetryEvent telemetryEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if !DEBUG
|
||||
foreach (var session in _telemetrySessions)
|
||||
{
|
||||
session.PostEvent(telemetryEvent);
|
||||
}
|
||||
#else
|
||||
// In debug we only log to normal logging. This makes it much easier to add and debug telemetry events
|
||||
// before we're ready to send them to the cloud
|
||||
var name = telemetryEvent.Name;
|
||||
var propertyString = string.Join(",", telemetryEvent.Properties.Select(kvp => $"[ {kvp.Key}:{kvp.Value} ]"));
|
||||
_logger?.LogTrace("Telemetry Event: {name} \n Properties: {propertyString}\n", name, propertyString);
|
||||
|
||||
if (telemetryEvent is FaultEvent)
|
||||
{
|
||||
var eventType = telemetryEvent.GetType();
|
||||
var description = eventType.GetProperty("Description", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(telemetryEvent, null);
|
||||
var exception = eventType.GetProperty("ExceptionObject", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(telemetryEvent, null);
|
||||
var message = $"Fault Event: {name} \n Exception Info: {exception ?? description} \n Properties: {propertyString}";
|
||||
|
||||
Debug.Assert(true, message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// No need to do anything here. We failed to report telemetry
|
||||
// which isn't good, but not catastrophic for a user
|
||||
_logger?.LogError(e, "Failed logging telemetry event");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDescription(Exception exception)
|
||||
{
|
||||
const string CodeAnalysisNamespace = nameof(Microsoft) + "." + nameof(CodeAnalysis);
|
||||
const string AspNetCoreNamespace = nameof(Microsoft) + "." + nameof(AspNetCore);
|
||||
|
||||
// Be resilient to failing here. If we can't get a suitable name, just fall back to the standard name we
|
||||
// used to report.
|
||||
try
|
||||
{
|
||||
// walk up the stack looking for the first call from a type that isn't in the ErrorReporting namespace.
|
||||
var frames = new StackTrace(exception).GetFrames();
|
||||
|
||||
// On the .NET Framework, GetFrames() can return null even though it's not documented as such.
|
||||
// At least one case here is if the exception's stack trace itself is null.
|
||||
if (frames != null)
|
||||
{
|
||||
foreach (var frame in frames)
|
||||
{
|
||||
var method = frame?.GetMethod();
|
||||
var methodName = method?.Name;
|
||||
if (methodName is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var declaringTypeName = method?.DeclaringType?.FullName;
|
||||
if (declaringTypeName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!declaringTypeName.StartsWith(CodeAnalysisNamespace) &&
|
||||
!declaringTypeName.StartsWith(AspNetCoreNamespace))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return declaringTypeName + "." + methodName;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// If we couldn't get a stack, do this
|
||||
return exception.Message;
|
||||
}
|
||||
|
||||
private static TelemetrySeverity ToTelemetrySeverity(Severity severity)
|
||||
=> severity switch
|
||||
{
|
||||
Severity.Normal => TelemetrySeverity.Normal,
|
||||
Severity.Low => TelemetrySeverity.Low,
|
||||
Severity.High => TelemetrySeverity.High,
|
||||
_ => throw new InvalidOperationException($"Unknown severity: {severity}")
|
||||
};
|
||||
|
||||
public IDisposable BeginBlock(string name, Severity severity)
|
||||
{
|
||||
return BeginBlock(name, severity, ImmutableDictionary<string, object?>.Empty);
|
||||
}
|
||||
|
||||
public IDisposable BeginBlock(string name, Severity severity, ImmutableDictionary<string, object?> values)
|
||||
{
|
||||
return new TelemetryScope(this, name, severity, values.ToImmutableDictionary((tuple) => tuple.Key, (tuple) => (object?)tuple.Value));
|
||||
}
|
||||
|
||||
public IDisposable TrackLspRequest(string lspMethodName, string languageServerName, Guid correlationId)
|
||||
{
|
||||
if (correlationId == Guid.Empty)
|
||||
{
|
||||
return NullTelemetryScope.Instance;
|
||||
}
|
||||
|
||||
return BeginBlock("TrackLspRequest", Severity.Normal, ImmutableDictionary.CreateRange(new KeyValuePair<string, object?>[]
|
||||
{
|
||||
new("eventscope.method", lspMethodName),
|
||||
new("eventscope.languageservername", languageServerName),
|
||||
new("eventscope.correlationid", correlationId),
|
||||
}));
|
||||
}
|
||||
|
||||
private static bool IsNumeric(object? o)
|
||||
=> o is not null &&
|
||||
!o.GetType().IsEnum &&
|
||||
Type.GetTypeCode(o.GetType()) switch
|
||||
{
|
||||
TypeCode.Char or
|
||||
TypeCode.SByte or
|
||||
TypeCode.Byte or
|
||||
TypeCode.Int16 or
|
||||
TypeCode.Int32 or
|
||||
TypeCode.Int64 or
|
||||
TypeCode.Double or
|
||||
TypeCode.Single or
|
||||
TypeCode.UInt16 or
|
||||
TypeCode.UInt32 or
|
||||
TypeCode.UInt64
|
||||
=> true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
private class NullTelemetryScope : IDisposable
|
||||
{
|
||||
public static NullTelemetryScope Instance { get; } = new NullTelemetryScope();
|
||||
private NullTelemetryScope() { }
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
private class TelemetryScope : IDisposable
|
||||
{
|
||||
private readonly ITelemetryReporter _telemetryReporter;
|
||||
private string _name;
|
||||
private Severity _severity;
|
||||
private ImmutableDictionary<string, object?> _values;
|
||||
private bool _disposed;
|
||||
private Stopwatch _stopwatch;
|
||||
|
||||
public TelemetryScope(ITelemetryReporter telemetryReporter, string name, Severity severity, ImmutableDictionary<string, object?> values)
|
||||
{
|
||||
_telemetryReporter = telemetryReporter;
|
||||
_name = name;
|
||||
_severity = severity;
|
||||
_values = values;
|
||||
_stopwatch = StopwatchPool.Default.Get();
|
||||
_stopwatch.Restart();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
_stopwatch.Stop();
|
||||
var values = _values.Add("eventscope.ellapsedms", _stopwatch.ElapsedMilliseconds);
|
||||
_telemetryReporter.ReportEvent(_name, _severity, values);
|
||||
StopwatchPool.Default.Return(_stopwatch);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Exports;
|
||||
using Microsoft.AspNetCore.Razor.Telemetry;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer;
|
||||
|
@ -15,6 +17,9 @@ public class Program
|
|||
public static async Task Main(string[] args)
|
||||
{
|
||||
var trace = Trace.Messages;
|
||||
var telemetryLevel = string.Empty;
|
||||
var sessionId = string.Empty;
|
||||
var telemetryExtensionPath = string.Empty;
|
||||
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
|
@ -49,16 +54,42 @@ public class Program
|
|||
await Console.Error.WriteLineAsync($"Invalid Razor trace '{traceArg}'. Defaulting to {trace}.").ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (args[i] == "--telemetryLevel" && i + 1 < args.Length)
|
||||
{
|
||||
telemetryLevel = args[++i];
|
||||
}
|
||||
|
||||
if (args[i] == "--sessionId" && i + 1 < args.Length)
|
||||
{
|
||||
sessionId = args[++i];
|
||||
}
|
||||
|
||||
if (args[i] == "--telemetryExtensionPath" && i + 1 < args.Length)
|
||||
{
|
||||
telemetryExtensionPath = args[++i];
|
||||
}
|
||||
}
|
||||
|
||||
var languageServerFeatureOptions = new ConfigurableLanguageServerFeatureOptions(args);
|
||||
|
||||
ITelemetryReporter? devKitTelemetryReporter = null;
|
||||
if (!telemetryExtensionPath.IsNullOrEmpty())
|
||||
{
|
||||
using var exportProvider = await ExportProviderBuilder.CreateExportProviderAsync(
|
||||
telemetryExtensionPath).ConfigureAwait(true);
|
||||
|
||||
// Initialize the telemetry reporter if available
|
||||
devKitTelemetryReporter = exportProvider.GetExports<ITelemetryReporter>().SingleOrDefault()?.Value;
|
||||
devKitTelemetryReporter?.InitializeSession(telemetryLevel, sessionId, isDefaultSession: true);
|
||||
}
|
||||
|
||||
var logger = new LspLogger(trace);
|
||||
var server = RazorLanguageServerWrapper.Create(
|
||||
Console.OpenStandardInput(),
|
||||
Console.OpenStandardOutput(),
|
||||
logger,
|
||||
NoOpTelemetryReporter.Instance,
|
||||
devKitTelemetryReporter ?? NoOpTelemetryReporter.Instance,
|
||||
featureOptions: languageServerFeatureOptions);
|
||||
|
||||
logger.LogInformation("Razor Language Server started successfully.");
|
||||
|
|
|
@ -27,6 +27,7 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.ProjectEngineHost, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.DevKit.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServer.ContainedLanguage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServerClient.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
Загрузка…
Ссылка в новой задаче