From: Juan Hoyos Date: Tue, 16 Feb 2021 18:54:01 +0000 (-0800) Subject: Remove dotnet-monitor from the repository (#2005) X-Git-Tag: submit/tizen/20210909.063632~17^2~124 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f02c5a0a1f0c5a03925aae4518730e07123420d5;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Remove dotnet-monitor from the repository (#2005) --- diff --git a/diagnostics.sln b/diagnostics.sln index 5888251d9..dda545db5 100644 --- a/diagnostics.sln +++ b/diagnostics.sln @@ -72,12 +72,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Commands", "Commands", "{C4 src\Tools\Common\Commands\ProcessStatus.cs = src\Tools\Common\Commands\ProcessStatus.cs EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-monitor", "src\Tools\dotnet-monitor\dotnet-monitor.csproj", "{C57F7656-6663-4A3C-BE38-B75C6C57E77D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring", "src\Microsoft.Diagnostics.Monitoring\Microsoft.Diagnostics.Monitoring.csproj", "{CFCF90E5-91CF-44FD-819D-97F530AEF769}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.RestServer", "src\Microsoft.Diagnostics.Monitoring.RestServer\Microsoft.Diagnostics.Monitoring.RestServer.csproj", "{B54DE8DD-6591-45C2-B9F7-22C4A23A384C}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "inc", "inc", "{41BDFD6D-D165-4D67-BEF6-4E539040D30A}" ProjectSection(SolutionItems) = preProject src\inc\arrayholder.h = src\inc\arrayholder.h @@ -1117,46 +1113,6 @@ Global {CED9ABBA-861E-4C0A-9359-22351208EF27}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {CED9ABBA-861E-4C0A-9359-22351208EF27}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {CED9ABBA-861E-4C0A-9359-22351208EF27}.RelWithDebInfo|x86.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|Any CPU.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|ARM.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|ARM.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|ARM64.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|ARM64.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|x64.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|x64.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|x86.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Checked|x86.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|ARM.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|ARM.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|ARM64.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|x64.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|x64.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|x86.ActiveCfg = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Debug|x86.Build.0 = Debug|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|Any CPU.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|ARM.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|ARM.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|ARM64.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|ARM64.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|x64.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|x64.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|x86.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.Release|x86.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU - {C57F7656-6663-4A3C-BE38-B75C6C57E77D}.RelWithDebInfo|x86.Build.0 = Release|Any CPU {CFCF90E5-91CF-44FD-819D-97F530AEF769}.Checked|Any CPU.ActiveCfg = Debug|Any CPU {CFCF90E5-91CF-44FD-819D-97F530AEF769}.Checked|Any CPU.Build.0 = Debug|Any CPU {CFCF90E5-91CF-44FD-819D-97F530AEF769}.Checked|ARM.ActiveCfg = Debug|Any CPU @@ -1197,46 +1153,6 @@ Global {CFCF90E5-91CF-44FD-819D-97F530AEF769}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {CFCF90E5-91CF-44FD-819D-97F530AEF769}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {CFCF90E5-91CF-44FD-819D-97F530AEF769}.RelWithDebInfo|x86.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|Any CPU.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|ARM.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|ARM.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|ARM64.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|ARM64.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|x64.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|x64.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|x86.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Checked|x86.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|ARM.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|ARM.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|ARM64.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|x64.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|x64.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|x86.ActiveCfg = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Debug|x86.Build.0 = Debug|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|Any CPU.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|ARM.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|ARM.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|ARM64.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|ARM64.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|x64.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|x64.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|x86.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.Release|x86.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C}.RelWithDebInfo|x86.Build.0 = Release|Any CPU {6419BA04-6F1A-4D2F-8DE4-5C359E0364A3}.Checked|Any CPU.ActiveCfg = Debug|Any CPU {6419BA04-6F1A-4D2F-8DE4-5C359E0364A3}.Checked|Any CPU.Build.0 = Debug|Any CPU {6419BA04-6F1A-4D2F-8DE4-5C359E0364A3}.Checked|ARM.ActiveCfg = Debug|Any CPU @@ -1735,9 +1651,7 @@ Global {CED9ABBA-861E-4C0A-9359-22351208EF27} = {03479E19-3F18-49A6-910A-F5041E27E7C0} {298AE119-6625-4604-BDE5-0765DC34C856} = {B62728C8-1267-4043-B46F-5537BBAEC692} {C457CBCD-3A8D-4402-9A2B-693A0390D3F9} = {298AE119-6625-4604-BDE5-0765DC34C856} - {C57F7656-6663-4A3C-BE38-B75C6C57E77D} = {B62728C8-1267-4043-B46F-5537BBAEC692} {CFCF90E5-91CF-44FD-819D-97F530AEF769} = {19FAB78C-3351-4911-8F0C-8C6056401740} - {B54DE8DD-6591-45C2-B9F7-22C4A23A384C} = {19FAB78C-3351-4911-8F0C-8C6056401740} {41BDFD6D-D165-4D67-BEF6-4E539040D30A} = {19FAB78C-3351-4911-8F0C-8C6056401740} {33239640-6F4B-4DA4-A780-2F5B26D57EAD} = {41BDFD6D-D165-4D67-BEF6-4E539040D30A} {06730767-421B-465F-BB63-A3A07D72D7A2} = {41BDFD6D-D165-4D67-BEF6-4E539040D30A} diff --git a/eng/Publishing.props b/eng/Publishing.props index 5597ceefb..0202a0cd6 100644 --- a/eng/Publishing.props +++ b/eng/Publishing.props @@ -1,20 +1,14 @@ - - <_SuppressSdkImports>false 3 $(ArtifactsDir)bundledtools/ - $(PublishDependsOnTargets);CollectBundledToolsArchives;CollectPackageArtifactFiles + $(PublishDependsOnTargets);CollectBundledToolsArchives - - @@ -48,73 +42,4 @@ - - - - - %(FullPath).sha512 - - - - - - - - - - - - <_BlobGroupFilePath>%(PackageFile.FullPath).blobgroup - <_ChecksumFilePath>%(PackageFile.FullPath).sha512 - <_VersionFilePath>%(PackageFile.FullPath).version - - - - - - - - - - - - <_CommonArtifactData Include="NonShipping=true" Condition="'%(PackageFile.IsShipping)' != 'true'" /> - - - <_PackageArtifactData Include="@(_CommonArtifactData)" /> - - - <_PackageArtifactData Include="Category=OTHER" /> - - - - - <_BlobItem Include="%(PackageFile.FullPath)"> - @(_PackageArtifactData) - - <_BlobItem Include="$(_ChecksumFilePath)" Condition="Exists('$(_ChecksumFilePath)')"> - @(_CommonArtifactData) - - <_BlobItem Include="$(_VersionFilePath)" Condition="Exists('$(_VersionFilePath)')"> - @(_CommonArtifactData) - - - - - - - diagnostics/$(_BlobGroupName)/%(_BlobItem.Filename)%(_BlobItem.Extension) - true - - - - - diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index c90535e81..d7264ef75 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -34,22 +34,4 @@ Condition="$(NeedsPublishing) == 'true'" DependsOnTargets="$(_BeforePublishNoBuildTargets);$(_CorePublishTargets)" /> - - - - <_BlobGroupVersionMajor>$(PackageVersion.Split('.')[0]) - <_BlobGroupVersionMinor>$(PackageVersion.Split('.')[1]) - <_BlobGroupName>$(BlobGroupPrefix)$(_BlobGroupVersionMajor).$(_BlobGroupVersionMinor) - - - - - - diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/ActionContextExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/ActionContextExtensions.cs deleted file mode 100644 index 043d19cb5..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/ActionContextExtensions.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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 Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Diagnostics.NETCore.Client; -using Microsoft.Extensions.Logging; -using System; -using System.ComponentModel.DataAnnotations; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal static class ActionContextExtensions - { - public static Task ProblemAsync(this ActionContext context, Exception ex) - { - if (context.HttpContext.Features.Get().HasStarted) - { - // If already started writing response, do not rewrite - // as this will throw an InvalidOperationException. - return Task.CompletedTask; - } - else - { - ActionResult result = new BadRequestObjectResult(ex.ToProblemDetails((int)HttpStatusCode.BadRequest)); - - return result.ExecuteResultAsync(context); - } - } - - public static async Task InvokeAsync(this ActionContext context, Func action, ILogger logger) - { - CancellationToken token = context.HttpContext.RequestAborted; - // Exceptions are logged in the "when" clause in order to preview the exception - // from the point of where it was thrown. This allows capturing of the log scopes - // that were active when the exception was thrown. Waiting to log during the exception - // handler will miss any scopes that were added during invocation of action. - try - { - await action(token); - } - catch (ArgumentException ex) when (LogError(logger, ex)) - { - await context.ProblemAsync(ex); - } - catch (DiagnosticsClientException ex) when (LogError(logger, ex)) - { - await context.ProblemAsync(ex); - } - catch (InvalidOperationException ex) when (LogError(logger, ex)) - { - await context.ProblemAsync(ex); - } - catch (OperationCanceledException ex) when (token.IsCancellationRequested && LogInformation(logger, ex)) - { - await context.ProblemAsync(ex); - } - catch (OperationCanceledException ex) when (LogError(logger, ex)) - { - await context.ProblemAsync(ex); - } - catch (MonitoringException ex) when (LogError(logger, ex)) - { - await context.ProblemAsync(ex); - } - catch (ValidationException ex) when (LogError(logger, ex)) - { - await context.ProblemAsync(ex); - } - catch (UnauthorizedAccessException ex) when (LogError(logger, ex)) - { - await context.ProblemAsync(ex); - } - } - - private static bool LogError(ILogger logger, Exception ex) - { - logger.RequestFailed(ex); - return true; - } - - private static bool LogInformation(ILogger logger, Exception ex) - { - logger.RequestCanceled(); - return true; - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/ArtifactMetadataNames.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/ArtifactMetadataNames.cs deleted file mode 100644 index 5aebdc9b1..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/ArtifactMetadataNames.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - /// - /// Metadata keys that represent artfiact information. - /// - internal static class ArtifactMetadataNames - { - /// - /// Represents the type of artifact created from the source. - /// - public const string ArtifactType = nameof(ArtifactType); - - /// - /// Metadata keus that represent the source of an artifact. - /// - public static class ArtifactSource - { - /// - /// The ID of the process from which the artifact was collected. - /// - public const string ProcessId = nameof(ArtifactSource) + "_" + nameof(ProcessId); - - /// - /// The runtime instance cookie of the process from which the artifact was collected. - /// - public const string RuntimeInstanceCookie = nameof(ArtifactSource) + "_" + nameof(RuntimeInstanceCookie); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/ApiKeyConfiguration.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/ApiKeyConfiguration.cs deleted file mode 100644 index ea86f5698..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/ApiKeyConfiguration.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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.Generic; -using System.Text; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal sealed class ApiAuthenticationOptions - { - public const string ConfigurationKey = "ApiAuthentication"; - - public string ApiKeyHash { get; set; } - public string ApiKeyHashType { get; set; } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/AuthConstants.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/AuthConstants.cs deleted file mode 100644 index 948447105..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/AuthConstants.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal static class AuthConstants - { - public const string PolicyName = "AuthorizedUserPolicy"; - public const string NegotiateSchema = "Negotiate"; - public const string NtlmSchema = "NTLM"; - public const string KerberosSchema = "Kerberos"; - public const string ApiKeySchema = "MonitorApiKey"; - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/AuthOptions.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/AuthOptions.cs deleted file mode 100644 index cccf3047b..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/AuthOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal sealed class AuthOptions : IAuthOptions - { - public bool EnableNegotiate => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - public KeyAuthenticationMode KeyAuthenticationMode { get; } - - public bool EnableKeyAuth => (KeyAuthenticationMode == KeyAuthenticationMode.StoredKey) || - (KeyAuthenticationMode == KeyAuthenticationMode.TemporaryKey); - - public AuthOptions(KeyAuthenticationMode mode) - { - KeyAuthenticationMode = mode; - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/IAuthOptions.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/IAuthOptions.cs deleted file mode 100644 index 2368a57a1..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Auth/IAuthOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal enum KeyAuthenticationMode - { - StoredKey, - TemporaryKey, - NoAuth, - } - - internal interface IAuthOptions - { - bool EnableNegotiate { get; } - KeyAuthenticationMode KeyAuthenticationMode { get; } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/ConfigurationHelper.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/ConfigurationHelper.cs deleted file mode 100644 index 2132a2ee4..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/ConfigurationHelper.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal static class ConfigurationHelper - { - private const char ValueSeparator = ';'; - private static readonly char[] ValueSeparatorArray = new char[] { ValueSeparator }; - private static readonly string ValueSeparatorString = ValueSeparator.ToString(); - - public static string MakeKey(string parent, string child) - { - return FormattableString.Invariant($"{parent}:{child}"); - } - - public static string[] SplitValue(string value) - { - return value.Split(ValueSeparatorArray, StringSplitOptions.RemoveEmptyEntries); - } - - public static string JoinValue(string[] values) - { - return string.Join(ValueSeparatorString, values); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/ContentTypes.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/ContentTypes.cs deleted file mode 100644 index f8ba81e9d..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/ContentTypes.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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. - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal static class ContentTypes - { - public const string ApplicationJson = "application/json"; - public const string ApplicationNdJson = "application/x-ndjson"; - public const string ApplicationOctectStream = "application/octet-stream"; - public const string TextEventStream = "text/event-stream"; - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Controllers/DiagController.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Controllers/DiagController.cs deleted file mode 100644 index 8698c3d83..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Controllers/DiagController.cs +++ /dev/null @@ -1,503 +0,0 @@ -// 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 FastSerialization; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Diagnostics.Monitoring.EventPipe; -using Microsoft.Diagnostics.Monitoring.RestServer.Models; -using Microsoft.Diagnostics.Monitoring.RestServer.Validation; -using Microsoft.Diagnostics.NETCore.Client; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer.Controllers -{ - [Route("")] // Root - [ApiController] - [HostRestriction] - [Authorize(Policy = AuthConstants.PolicyName)] - public class DiagController : ControllerBase - { - private const string ArtifactType_Dump = "dump"; - private const string ArtifactType_GCDump = "gcdump"; - private const string ArtifactType_Logs = "logs"; - private const string ArtifactType_Trace = "trace"; - - private const TraceProfile DefaultTraceProfiles = TraceProfile.Cpu | TraceProfile.Http | TraceProfile.Metrics; - private static readonly MediaTypeHeaderValue NdJsonHeader = new MediaTypeHeaderValue(ContentTypes.ApplicationNdJson); - private static readonly MediaTypeHeaderValue EventStreamHeader = new MediaTypeHeaderValue(ContentTypes.TextEventStream); - - private readonly ILogger _logger; - private readonly IDiagnosticServices _diagnosticServices; - - public DiagController(ILogger logger, IServiceProvider serviceProvider) - { - _logger = logger; - _diagnosticServices = serviceProvider.GetRequiredService(); - } - - [HttpGet("processes")] - public Task>> GetProcesses() - { - return this.InvokeService(async () => - { - IList processesIdentifiers = new List(); - foreach (IProcessInfo p in await _diagnosticServices.GetProcessesAsync(HttpContext.RequestAborted)) - { - processesIdentifiers.Add(ProcessIdentifierModel.FromProcessInfo(p)); - } - _logger.WrittenToHttpStream(); - return new ActionResult>(processesIdentifiers); - }, _logger); - } - - [HttpGet("processes/{processFilter}")] - public Task> GetProcess( - ProcessFilter processFilter) - { - return InvokeForProcess(processInfo => - { - ProcessModel processModel = ProcessModel.FromProcessInfo(processInfo); - - _logger.WrittenToHttpStream(); - - return processModel; - }, - processFilter); - } - - [HttpGet("processes/{processFilter}/env")] - public Task>> GetProcessEnvironment( - ProcessFilter processFilter) - { - return InvokeForProcess>(processInfo => - { - var client = new DiagnosticsClient(processInfo.EndpointInfo.Endpoint); - - try - { - Dictionary environment = client.GetProcessEnvironment(); - - _logger.WrittenToHttpStream(); - - return environment; - } - catch (ServerErrorException) - { - throw new InvalidOperationException("Unable to get process environment."); - } - }, - processFilter); - } - - [HttpGet("dump/{processFilter?}")] - public Task GetDump( - ProcessFilter? processFilter, - [FromQuery] DumpType type = DumpType.WithHeap, - [FromQuery] string egressProvider = null) - { - return InvokeForProcess(async processInfo => - { - string dumpFileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? - FormattableString.Invariant($"dump_{GetFileNameTimeStampUtcNow()}.dmp") : - FormattableString.Invariant($"core_{GetFileNameTimeStampUtcNow()}"); - - if (string.IsNullOrEmpty(egressProvider)) - { - Stream dumpStream = await _diagnosticServices.GetDump(processInfo, type, HttpContext.RequestAborted); - - _logger.WrittenToHttpStream(); - //Compression is done automatically by the response - //Chunking is done because the result has no content-length - return File(dumpStream, ContentTypes.ApplicationOctectStream, dumpFileName); - } - else - { - KeyValueLogScope scope = new KeyValueLogScope(); - scope.AddArtifactType(ArtifactType_Dump); - scope.AddEndpointInfo(processInfo.EndpointInfo); - - return new EgressStreamResult( - token => _diagnosticServices.GetDump(processInfo, type, token), - egressProvider, - dumpFileName, - processInfo.EndpointInfo, - ContentTypes.ApplicationOctectStream, - scope); - } - }, processFilter, ArtifactType_Dump); - } - - [HttpGet("gcdump/{processFilter?}")] - public Task GetGcDump( - ProcessFilter? processFilter, - [FromQuery] string egressProvider = null) - { - return InvokeForProcess(processInfo => - { - string fileName = FormattableString.Invariant($"{GetFileNameTimeStampUtcNow()}_{processInfo.EndpointInfo.ProcessId}.gcdump"); - - Func> action = async (token) => { - var graph = new Graphs.MemoryGraph(50_000); - - EventGCPipelineSettings settings = new EventGCPipelineSettings - { - Duration = Timeout.InfiniteTimeSpan, - }; - - var client = new DiagnosticsClient(processInfo.EndpointInfo.Endpoint); - - await using var pipeline = new EventGCDumpPipeline(client, settings, graph); - await pipeline.RunAsync(token); - - return new GCHeapDump(graph) - { - CreationTool = "dotnet-monitor" - }; - }; - - return Result( - ArtifactType_GCDump, - egressProvider, - ConvertFastSerializeAction(action), - fileName, - ContentTypes.ApplicationOctectStream, - processInfo.EndpointInfo); - }, processFilter, ArtifactType_GCDump); - } - - [HttpGet("trace/{processFilter?}")] - public Task Trace( - ProcessFilter? processFilter, - [FromQuery]TraceProfile profile = DefaultTraceProfiles, - [FromQuery][Range(-1, int.MaxValue)] int durationSeconds = 30, - [FromQuery][Range(1, int.MaxValue)] int metricsIntervalSeconds = 1, - [FromQuery] string egressProvider = null) - { - return InvokeForProcess(processInfo => - { - TimeSpan duration = ConvertSecondsToTimeSpan(durationSeconds); - - var configurations = new List(); - if (profile.HasFlag(TraceProfile.Cpu)) - { - configurations.Add(new CpuProfileConfiguration()); - } - if (profile.HasFlag(TraceProfile.Http)) - { - configurations.Add(new HttpRequestSourceConfiguration()); - } - if (profile.HasFlag(TraceProfile.Logs)) - { - configurations.Add(new LoggingSourceConfiguration()); - } - if (profile.HasFlag(TraceProfile.Metrics)) - { - configurations.Add(new MetricSourceConfiguration(metricsIntervalSeconds, Enumerable.Empty())); - } - - var aggregateConfiguration = new AggregateSourceConfiguration(configurations.ToArray()); - - return StartTrace(processInfo, aggregateConfiguration, duration, egressProvider); - }, processFilter, ArtifactType_Trace); - } - - [HttpPost("trace/{processFilter?}")] - public Task TraceCustomConfiguration( - ProcessFilter? processFilter, - [FromBody][Required] EventPipeConfigurationModel configuration, - [FromQuery][Range(-1, int.MaxValue)] int durationSeconds = 30, - [FromQuery] string egressProvider = null) - { - return InvokeForProcess(processInfo => - { - TimeSpan duration = ConvertSecondsToTimeSpan(durationSeconds); - - var providers = new List(); - - foreach (EventPipeProviderModel providerModel in configuration.Providers) - { - if (!IntegerOrHexStringAttribute.TryParse(providerModel.Keywords, out long keywords, out string parseError)) - { - throw new InvalidOperationException(parseError); - } - - providers.Add(new EventPipeProvider( - providerModel.Name, - providerModel.EventLevel, - keywords, - providerModel.Arguments - )); - } - - var traceConfiguration = new EventPipeProviderSourceConfiguration( - providers: providers.ToArray(), - requestRundown: configuration.RequestRundown, - bufferSizeInMB: configuration.BufferSizeInMB); - - return StartTrace(processInfo, traceConfiguration, duration, egressProvider); - }, processFilter, ArtifactType_Trace); - } - - [HttpGet("logs/{processFilter?}")] - [Produces(ContentTypes.TextEventStream, ContentTypes.ApplicationNdJson, ContentTypes.ApplicationJson)] - public Task Logs( - ProcessFilter? processFilter, - [FromQuery][Range(-1, int.MaxValue)] int durationSeconds = 30, - [FromQuery] LogLevel level = LogLevel.Debug, - [FromQuery] string egressProvider = null) - { - return InvokeForProcess(processInfo => - { - TimeSpan duration = ConvertSecondsToTimeSpan(durationSeconds); - - LogFormat format = ComputeLogFormat(Request.GetTypedHeaders().Accept); - if (format == LogFormat.None) - { - return this.NotAcceptable(); - } - - string fileName = FormattableString.Invariant($"{GetFileNameTimeStampUtcNow()}_{processInfo.EndpointInfo.ProcessId}.txt"); - string contentType = format == LogFormat.EventStream ? ContentTypes.TextEventStream : ContentTypes.ApplicationNdJson; - - Func action = async (outputStream, token) => - { - using var loggerFactory = new LoggerFactory(); - - loggerFactory.AddProvider(new StreamingLoggerProvider(outputStream, format, level)); - - var settings = new EventLogsPipelineSettings - { - Duration = duration, - LogLevel = level, - }; - - var client = new DiagnosticsClient(processInfo.EndpointInfo.Endpoint); - - await using EventLogsPipeline pipeline = new EventLogsPipeline(client, settings, loggerFactory); - await pipeline.RunAsync(token); - }; - - return Result( - ArtifactType_Logs, - egressProvider, - action, - fileName, - contentType, - processInfo.EndpointInfo, - format != LogFormat.EventStream); - }, processFilter, ArtifactType_Logs); - } - - private ActionResult StartTrace( - IProcessInfo processInfo, - MonitoringSourceConfiguration configuration, - TimeSpan duration, - string egressProvider) - { - string fileName = FormattableString.Invariant($"{GetFileNameTimeStampUtcNow()}_{processInfo.EndpointInfo.ProcessId}.nettrace"); - - Func action = async (outputStream, token) => - { - Func streamAvailable = async (Stream eventStream, CancellationToken token) => - { - //Buffer size matches FileStreamResult - //CONSIDER Should we allow client to change the buffer size? - await eventStream.CopyToAsync(outputStream, 0x10000, token); - }; - - var client = new DiagnosticsClient(processInfo.EndpointInfo.Endpoint); - - await using EventTracePipeline pipeProcessor = new EventTracePipeline(client, new EventTracePipelineSettings - { - Configuration = configuration, - Duration = duration, - }, streamAvailable); - - await pipeProcessor.RunAsync(token); - }; - - return Result( - ArtifactType_Trace, - egressProvider, - action, - fileName, - ContentTypes.ApplicationOctectStream, - processInfo.EndpointInfo); - } - - private static TimeSpan ConvertSecondsToTimeSpan(int durationSeconds) - { - return durationSeconds < 0 ? - Timeout.InfiniteTimeSpan : - TimeSpan.FromSeconds(durationSeconds); - } - - private static string GetFileNameTimeStampUtcNow() - { - return DateTime.UtcNow.ToString("yyyyMMdd_HHmmss"); - } - - private static LogFormat ComputeLogFormat(IList acceptedHeaders) - { - if (acceptedHeaders == null) - { - return LogFormat.None; - } - - if (acceptedHeaders.Contains(EventStreamHeader)) - { - return LogFormat.EventStream; - } - if (acceptedHeaders.Contains(NdJsonHeader)) - { - return LogFormat.Json; - } - if (acceptedHeaders.Any(h => EventStreamHeader.IsSubsetOf(h))) - { - return LogFormat.EventStream; - } - if (acceptedHeaders.Any(h => NdJsonHeader.IsSubsetOf(h))) - { - return LogFormat.Json; - } - return LogFormat.None; - } - - private ActionResult Result( - string artifactType, - string providerName, - Func action, - string fileName, - string contentType, - IEndpointInfo endpointInfo, - bool asAttachment = true) - { - KeyValueLogScope scope = new KeyValueLogScope(); - scope.AddArtifactType(artifactType); - scope.AddEndpointInfo(endpointInfo); - - if (string.IsNullOrEmpty(providerName)) - { - return new OutputStreamResult( - action, - contentType, - asAttachment ? fileName : null, - scope); - } - else - { - return new EgressStreamResult( - action, - providerName, - fileName, - endpointInfo, - contentType, - scope); - } - } - - private static Func ConvertFastSerializeAction(Func> action) - { - return async (stream, token) => - { - IFastSerializable fastSerializable = await action(token); - - // FastSerialization requests the length of the stream before serializing to the stream. - // If the stream is a response stream, requesting the length or setting the position is - // not supported. Create an intermediate buffer if testing the stream fails. - // This can use a huge amount of memory if the IFastSerializable is very large. - // CONSIDER: Update FastSerialization to not get the length or attempt to reset the position. - bool useIntermediateStream = false; - try - { - _ = stream.Length; - } - catch (NotSupportedException) - { - useIntermediateStream = true; - } - - if (useIntermediateStream) - { - using var intermediateStream = new MemoryStream(); - - var serializer = new Serializer(intermediateStream, fastSerializable, leaveOpen: true); - serializer.Close(); - - intermediateStream.Position = 0; - - await intermediateStream.CopyToAsync(stream, 0x10000, token); - } - else - { - var serializer = new Serializer(stream, fastSerializable, leaveOpen: true); - serializer.Close(); - } - }; - } - - private Task InvokeForProcess(Func func, ProcessFilter? filter, string artifactType = null) - { - Func> asyncFunc = - processInfo => Task.FromResult(func(processInfo)); - - return InvokeForProcess(asyncFunc, filter, artifactType); - } - - private async Task InvokeForProcess(Func> func, ProcessFilter? filter, string artifactType) - { - ActionResult result = await InvokeForProcess(async processInfo => await func(processInfo), filter, artifactType); - - return result.Result; - } - - private Task> InvokeForProcess(Func> func, ProcessFilter? filter, string artifactType = null) - { - return InvokeForProcess(processInfo => Task.FromResult(func(processInfo)), filter, artifactType); - } - - private async Task> InvokeForProcess(Func>> func, ProcessFilter? filter, string artifactType = null) - { - IDisposable artifactTypeRegistration = null; - if (!string.IsNullOrEmpty(artifactType)) - { - KeyValueLogScope artifactTypeScope = new KeyValueLogScope(); - artifactTypeScope.AddArtifactType(artifactType); - artifactTypeRegistration = _logger.BeginScope(artifactTypeScope); - } - - try - { - return await this.InvokeService(async () => - { - IProcessInfo processInfo = await _diagnosticServices.GetProcessAsync(filter, HttpContext.RequestAborted); - - KeyValueLogScope processInfoScope = new KeyValueLogScope(); - processInfoScope.AddEndpointInfo(processInfo.EndpointInfo); - using var _ = _logger.BeginScope(processInfoScope); - - _logger.ResolvedTargetProcess(); - - return await func(processInfo); - }, _logger); - } - finally - { - artifactTypeRegistration?.Dispose(); - } - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Controllers/DiagControllerExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Controllers/DiagControllerExtensions.cs deleted file mode 100644 index 3dc273c21..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Controllers/DiagControllerExtensions.cs +++ /dev/null @@ -1,105 +0,0 @@ -// 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 Microsoft.AspNetCore.Mvc; -using Microsoft.Diagnostics.NETCore.Client; -using Microsoft.Extensions.Logging; -using System; -using System.ComponentModel.DataAnnotations; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 - -namespace Microsoft.Diagnostics.Monitoring.RestServer.Controllers -{ - internal static class DiagControllerExtensions - { - public static ActionResult NotAcceptable(this ControllerBase controller) - { - return new StatusCodeResult((int)HttpStatusCode.NotAcceptable); - } - - public static ActionResult InvokeService(this ControllerBase controller, Func serviceCall, ILogger logger) - { - //We can convert ActionResult to ActionResult - //and then safely convert back. - return controller.InvokeService(() => serviceCall(), logger).Result; - } - - public static ActionResult InvokeService(this ControllerBase controller, Func> serviceCall, ILogger logger) - { - //Convert from ActionResult to Task> - //and safely convert back. - return controller.InvokeService(() => Task.FromResult(serviceCall()), logger).Result; - } - - public static async Task InvokeService(this ControllerBase controller, Func> serviceCall, ILogger logger) - { - //Task -> Task> - //Then unwrap the result back to ActionResult - ActionResult result = await controller.InvokeService(async () => await serviceCall(), logger); - return result.Result; - } - - public static async Task> InvokeService(this ControllerBase controller, Func>> serviceCall, ILogger logger) - { - CancellationToken token = controller.HttpContext.RequestAborted; - // Exceptions are logged in the "when" clause in order to preview the exception - // from the point of where it was thrown. This allows capturing of the log scopes - // that were active when the exception was thrown. Waiting to log during the exception - // handler will miss any scopes that were added during invocation of serviceCall. - try - { - return await serviceCall(); - } - catch (ArgumentException e) when (LogError(logger, e)) - { - return controller.Problem(e); - } - catch (DiagnosticsClientException e) when (LogError(logger, e)) - { - return controller.Problem(e); - } - catch (InvalidOperationException e) when (LogError(logger, e)) - { - return controller.Problem(e); - } - catch (OperationCanceledException e) when (token.IsCancellationRequested && LogInformation(logger, e)) - { - return controller.Problem(e); - } - catch (OperationCanceledException e) when (LogError(logger, e)) - { - return controller.Problem(e); - } - catch (MonitoringException e) when (LogError(logger, e)) - { - return controller.Problem(e); - } - catch (ValidationException e) when (LogError(logger, e)) - { - return controller.Problem(e); - } - } - - public static ObjectResult Problem(this ControllerBase controller, Exception ex) - { - return controller.BadRequest(ex.ToProblemDetails((int)HttpStatusCode.BadRequest)); - } - - private static bool LogError(ILogger logger, Exception ex) - { - logger.RequestFailed(ex); - return true; - } - - private static bool LogInformation(ILogger logger, Exception ex) - { - logger.RequestCanceled(); - return true; - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Controllers/MetricsController.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Controllers/MetricsController.cs deleted file mode 100644 index 92eca0ebb..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Controllers/MetricsController.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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 Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; - -namespace Microsoft.Diagnostics.Monitoring.RestServer.Controllers -{ - [Route("")] - [ApiController] - public class MetricsController : ControllerBase - { - private const string ArtifactType_Metrics = "metrics"; - - private readonly ILogger _logger; - private readonly MetricsStoreService _metricsStore; - private readonly MetricsOptions _metricsOptions; - - public MetricsController(ILogger logger, - IServiceProvider serviceProvider, - IOptions metricsOptions) - { - _logger = logger; - _metricsStore = serviceProvider.GetService(); - _metricsOptions = metricsOptions.Value; - } - - [HttpGet("metrics")] - public ActionResult Metrics() - { - return this.InvokeService(() => - { - if (!_metricsOptions.Enabled) - { - throw new InvalidOperationException("Metrics was not enabled"); - } - - KeyValueLogScope scope = new KeyValueLogScope(); - scope.AddArtifactType(ArtifactType_Metrics); - - return new OutputStreamResult(async (outputStream, token) => - { - await _metricsStore.MetricsStore.SnapshotMetrics(outputStream, token); - }, - "text/plain; version=0.0.4", - null, - scope); - }, _logger); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/CorsConfiguration.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/CorsConfiguration.cs deleted file mode 100644 index 3d7fddb55..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/CorsConfiguration.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - public class CorsConfiguration - { - public string AllowedOrigins { get; set; } - - public string[] GetOrigins() => AllowedOrigins?.Split(';'); - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/DiagnosticServices.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/DiagnosticServices.cs deleted file mode 100644 index 6768608f0..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/DiagnosticServices.cs +++ /dev/null @@ -1,310 +0,0 @@ -// 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.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Diagnostics.Monitoring.EventPipe; -using Microsoft.Diagnostics.NETCore.Client; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal sealed class DiagnosticServices : IDiagnosticServices - { - // The value of the operating system field of the ProcessInfo result when the target process is running - // on a Windows operating system. - private const string ProcessOperatingSystemWindowsValue = "windows"; - - // A Docker container's entrypoint process ID is 1 - private static readonly ProcessFilter DockerEntrypointProcessFilter = new ProcessFilter(1); - - // The amount of time to wait when checking if the docker entrypoint process is a .NET process - // with a diagnostics transport connection. - private static readonly TimeSpan DockerEntrypointWaitTimeout = TimeSpan.FromMilliseconds(250); - // The amount of time to wait before cancelling get additional process information (e.g. getting - // the process command line if the IEndpointInfo doesn't provide it). - private static readonly TimeSpan ExtendedProcessInfoTimeout = TimeSpan.FromMilliseconds(500); - - private readonly IEndpointInfoSourceInternal _endpointInfoSource; - private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); - - public DiagnosticServices(IEndpointInfoSource endpointInfoSource) - { - _endpointInfoSource = (IEndpointInfoSourceInternal)endpointInfoSource; - } - - public async Task> GetProcessesAsync(CancellationToken token) - { - try - { - using CancellationTokenSource extendedInfoCancellation = CancellationTokenSource.CreateLinkedTokenSource(token); - IList> processInfoTasks = new List>(); - foreach (IEndpointInfo endpointInfo in await _endpointInfoSource.GetEndpointInfoAsync(token)) - { - processInfoTasks.Add(ProcessInfo.FromEndpointInfoAsync(endpointInfo, extendedInfoCancellation.Token)); - } - - // FromEndpointInfoAsync can fill in the command line for .NET Core 3.1 processes by invoking the - // event pipe and capturing the ProcessInfo event. Timebox this operation with the cancellation token - // so that getting the process list does not take a long time or wait indefinitely. - extendedInfoCancellation.CancelAfter(ExtendedProcessInfoTimeout); - - await Task.WhenAll(processInfoTasks); - - return processInfoTasks.Select(t => t.Result); - } - catch (UnauthorizedAccessException) - { - throw new InvalidOperationException("Unable to enumerate processes."); - } - } - - public async Task GetDump(IProcessInfo pi, DumpType mode, CancellationToken token) - { - string dumpFilePath = Path.Combine(Path.GetTempPath(), FormattableString.Invariant($"{Guid.NewGuid()}_{pi.EndpointInfo.ProcessId}")); - NETCore.Client.DumpType dumpType = MapDumpType(mode); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Get the process - Process process = Process.GetProcessById(pi.EndpointInfo.ProcessId); - await Dumper.CollectDumpAsync(process, dumpFilePath, dumpType); - } - else - { - await Task.Run(() => - { - var client = new DiagnosticsClient(pi.EndpointInfo.Endpoint); - client.WriteDump(dumpType, dumpFilePath); - }); - } - - return new AutoDeleteFileStream(dumpFilePath); - } - - private static NETCore.Client.DumpType MapDumpType(DumpType dumpType) - { - switch (dumpType) - { - case DumpType.Full: - return NETCore.Client.DumpType.Full; - case DumpType.WithHeap: - return NETCore.Client.DumpType.WithHeap; - case DumpType.Triage: - return NETCore.Client.DumpType.Triage; - case DumpType.Mini: - return NETCore.Client.DumpType.Normal; - default: - throw new ArgumentException("Unexpected dumpType", nameof(dumpType)); - } - } - - public async Task GetProcessAsync(ProcessFilter? filter, CancellationToken token) - { - var endpointInfos = await _endpointInfoSource.GetEndpointInfoAsync(token); - - if (filter.HasValue) - { - return await GetSingleProcessInfoAsync( - endpointInfos, - filter); - } - - // Short-circuit for when running in a Docker container. - if (RuntimeInfo.IsInDockerContainer) - { - try - { - IProcessInfo processInfo = await GetSingleProcessInfoAsync( - endpointInfos, - DockerEntrypointProcessFilter); - - using var timeoutSource = new CancellationTokenSource(DockerEntrypointWaitTimeout); - - var client = new DiagnosticsClient(processInfo.EndpointInfo.Endpoint); - await client.WaitForConnectionAsync(timeoutSource.Token); - - return processInfo; - } - catch - { - // Process ID 1 doesn't exist, didn't advertise in connect mode, or is not a .NET process. - } - } - - return await GetSingleProcessInfoAsync( - endpointInfos, - filter: null); - } - - private async Task GetSingleProcessInfoAsync(IEnumerable endpointInfos, ProcessFilter? filter) - { - if (filter.HasValue) - { - if (filter.Value.RuntimeInstanceCookie.HasValue) - { - Guid cookie = filter.Value.RuntimeInstanceCookie.Value; - endpointInfos = endpointInfos.Where(info => info.RuntimeInstanceCookie == cookie); - } - - if (filter.Value.ProcessId.HasValue) - { - int pid = filter.Value.ProcessId.Value; - endpointInfos = endpointInfos.Where(info => info.ProcessId == pid); - } - } - - IEndpointInfo[] endpointInfoArray = endpointInfos.ToArray(); - switch (endpointInfoArray.Length) - { - case 0: - throw new ArgumentException("Unable to discover a target process."); - case 1: - return await ProcessInfo.FromEndpointInfoAsync(endpointInfoArray[0]); - default: -#if DEBUG - IEndpointInfo endpointInfo = endpointInfoArray.FirstOrDefault(info => string.Equals(Process.GetProcessById(info.ProcessId).ProcessName, "iisexpress", StringComparison.OrdinalIgnoreCase)); - if (endpointInfo != null) - { - return await ProcessInfo.FromEndpointInfoAsync(endpointInfo); - } -#endif - throw new ArgumentException("Unable to select a single target process because multiple target processes have been discovered."); - } - } - - public void Dispose() - { - _tokenSource.Cancel(); - } - - /// - /// We want to make sure we destroy files we finish streaming. - /// We want to make sure that we stream out files since we compress on the fly; the size cannot be known upfront. - /// CONSIDER The above implies knowledge of how the file is used by the rest api. - /// - private sealed class AutoDeleteFileStream : FileStream - { - public AutoDeleteFileStream(string path) : base(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete, - bufferSize: 4096, FileOptions.DeleteOnClose) - { - } - - public override bool CanSeek => false; - } - - - private sealed class ProcessInfo : IProcessInfo - { - // String returned for a process field when its value could not be retrieved. This is the same - // value that is returned by the runtime when it could not determine the value for each of those fields. - private const string ProcessFieldUnknownValue = "unknown"; - - public ProcessInfo( - IEndpointInfo endpointInfo, - string commandLine, - string processName) - { - EndpointInfo = endpointInfo; - - // The GetProcessInfo command will return "unknown" for values for which it does - // not know the value, such as operating system and process architecture if the - // process is running on one that is not predefined. Mimic the same behavior here - // when the extra process information was not provided. - CommandLine = commandLine ?? ProcessFieldUnknownValue; - ProcessName = processName ?? ProcessFieldUnknownValue; - } - - public static async Task FromEndpointInfoAsync(IEndpointInfo endpointInfo) - { - using CancellationTokenSource extendedInfoCancellation = new CancellationTokenSource(ExtendedProcessInfoTimeout); - return await FromEndpointInfoAsync(endpointInfo, extendedInfoCancellation.Token); - } - - // Creates a ProcessInfo object from the IEndpointInfo. Attempts to get the command line using event pipe - // if the endpoint information doesn't provide it. The cancelation token can be used to timebox this fallback - // mechansim. - public static async Task FromEndpointInfoAsync(IEndpointInfo endpointInfo, CancellationToken extendedInfoCancellationToken) - { - if (null == endpointInfo) - { - throw new ArgumentNullException(nameof(endpointInfo)); - } - - var client = new DiagnosticsClient(endpointInfo.Endpoint); - - string commandLine = endpointInfo.CommandLine; - if (string.IsNullOrEmpty(commandLine)) - { - try - { - var infoSettings = new EventProcessInfoPipelineSettings - { - Duration = Timeout.InfiniteTimeSpan, - }; - - await using var pipeline = new EventProcessInfoPipeline(client, infoSettings, - (cmdLine, token) => { commandLine = cmdLine; return Task.CompletedTask; }); - - await pipeline.RunAsync(extendedInfoCancellationToken); - } - catch - { - } - } - - string processName = null; - if (!string.IsNullOrEmpty(commandLine)) - { - // Get the process name from the command line - bool isWindowsProcess = false; - if (string.IsNullOrEmpty(endpointInfo.OperatingSystem)) - { - // If operating system is null, the process is likely .NET Core 3.1 (which doesn't have the GetProcessInfo command). - // Since the underlying diagnostic communication channel used by the .NET runtime requires that the diagnostic process - // must be running on the same type of operating system as the target process (e.g. dotnet-monitor must be running on Windows - // if the target process is running on Windows), then checking the local operating system should be a sufficient heuristic - // to determine the operating system of the target process. - isWindowsProcess = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - } - else - { - isWindowsProcess = ProcessOperatingSystemWindowsValue.Equals(endpointInfo.OperatingSystem, StringComparison.OrdinalIgnoreCase); - } - - string processPath = CommandLineHelper.ExtractExecutablePath(commandLine, isWindowsProcess); - if (!string.IsNullOrEmpty(processPath)) - { - processName = Path.GetFileName(processPath); - if (isWindowsProcess) - { - // Remove the extension on Windows to match the behavior of Process.ProcessName - processName = Path.GetFileNameWithoutExtension(processName); - } - } - } - - return new ProcessInfo( - endpointInfo, - commandLine, - processName); - } - - public IEndpointInfo EndpointInfo { get; } - - public string CommandLine { get; } - - public string OperatingSystem => EndpointInfo.OperatingSystem ?? ProcessFieldUnknownValue; - - public string ProcessArchitecture => EndpointInfo.ProcessArchitecture ?? ProcessFieldUnknownValue; - - public string ProcessName { get; } - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/EgressResult.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/EgressResult.cs deleted file mode 100644 index 446702dc7..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/EgressResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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. - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal struct EgressResult - { - public EgressResult(string name, string value) - { - Name = name; - Value = value; - } - - public string Name { get; } - - public string Value { get; } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/EgressStreamResult.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/EgressStreamResult.cs deleted file mode 100644 index e3d143b2f..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/EgressStreamResult.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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 Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal class EgressStreamResult : ActionResult - { - private readonly Func> _egress; - private readonly KeyValueLogScope _scope; - - public EgressStreamResult(Func> action, string endpointName, string artifactName, IEndpointInfo source, string contentType, KeyValueLogScope scope) - { - _egress = (service, token) => service.EgressAsync(endpointName, action, artifactName, contentType, source, token); - _scope = scope; - } - - public EgressStreamResult(Func action, string endpointName, string artifactName, IEndpointInfo source, string contentType, KeyValueLogScope scope) - { - _egress = (service, token) => service.EgressAsync(endpointName, action, artifactName, contentType, source, token); - _scope = scope; - } - - public override async Task ExecuteResultAsync(ActionContext context) - { - ILogger logger = context.HttpContext.RequestServices - .GetRequiredService() - .CreateLogger(); - - using var _ = logger.BeginScope(_scope); - - await context.InvokeAsync(async (token) => - { - IEgressService egressService = context.HttpContext.RequestServices - .GetRequiredService(); - - EgressResult egressResult = await _egress(egressService, token); - - logger.EgressedArtifact(egressResult.Value); - - // The remaining code is creating a JSON object with a single property and scalar value - // that indiates where the stream data was egressed. Because the name of the artifact is - // automatically generated by the REST API and the caller of the endpoint might not know - // the specific configuration information for the egress provider, this value allows the - // caller to more easily find the artifact after egress has completed. - IDictionary data = new Dictionary(StringComparer.Ordinal); - data.Add(egressResult.Name, egressResult.Value); - - ActionResult jsonResult = new JsonResult(data); - await jsonResult.ExecuteResultAsync(context); - }, logger); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/ExceptionExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/ExceptionExtensions.cs deleted file mode 100644 index 30cf8a1ba..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/ExceptionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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 Microsoft.AspNetCore.Mvc; -using System; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal static class ExceptionExtensions - { - public static ProblemDetails ToProblemDetails(this Exception ex, int statusCode) - { - return new ProblemDetails - { - Detail = ex.Message, - Status = statusCode - }; - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/HostRestrictionAttribute.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/HostRestrictionAttribute.cs deleted file mode 100644 index aa3c740c2..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/HostRestrictionAttribute.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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 Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using System; -using System.Linq; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - /// - /// We want to restrict the Prometheus scraping endpoint to only the /metrics call. - /// To do this, we determine what port the request is on, and disallow other actions on the prometheus port. - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] - internal sealed class HostRestrictionAttribute : Attribute, IActionConstraintFactory - { - private sealed class HostConstraint : IActionConstraint - { - private readonly int?[] _restrictedPorts; - - public HostConstraint(int?[] restrictedPorts) - { - _restrictedPorts = restrictedPorts; - } - - public int Order => 0; - - public bool Accept(ActionConstraintContext context) - { - return !_restrictedPorts.Any(port => context.RouteContext.HttpContext.Request.Host.Port == port); - } - } - - public bool IsReusable => true; - - public IActionConstraint CreateInstance(IServiceProvider services) - { - var metricsOptions = services.GetRequiredService>(); - return new HostConstraint(metricsOptions.Value.Enabled ? metricsOptions.Value.Ports : Array.Empty()); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/IDiagnosticServices.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/IDiagnosticServices.cs deleted file mode 100644 index 45f118a5f..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/IDiagnosticServices.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - /// - /// Set of services provided by the monitoring tool. These are consumed by - /// the REST Api. - /// - internal interface IDiagnosticServices : IDisposable - { - Task> GetProcessesAsync(CancellationToken token); - - Task GetProcessAsync(ProcessFilter? filter, CancellationToken token); - - Task GetDump(IProcessInfo pi, DumpType mode, CancellationToken token); - } - - - internal interface IProcessInfo - { - IEndpointInfo EndpointInfo { get; } - - string CommandLine { get; } - - public string OperatingSystem { get; } - - public string ProcessArchitecture { get; } - - string ProcessName { get; } - } - - public enum DumpType - { - Full = 1, - Mini, - WithHeap, - Triage - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/IEgressService.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/IEgressService.cs deleted file mode 100644 index adb60feb4..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/IEgressService.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal interface IEgressService - { - Task EgressAsync( - string endpointName, - Func> action, - string fileName, - string contentType, - IEndpointInfo source, - CancellationToken token); - - Task EgressAsync( - string endpointName, - Func action, - string fileName, - string contentType, - IEndpointInfo source, - CancellationToken token); - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/IMetricsStore.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/IMetricsStore.cs deleted file mode 100644 index b3c18706f..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/IMetricsStore.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring.EventPipe; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring -{ - /// - /// Used to store metrics. A snapshot will be requested periodically. - /// - internal interface IMetricsStore : IDisposable - { - void AddMetric(ICounterPayload metric); - - Task SnapshotMetrics(Stream stream, CancellationToken token); - - void Clear(); - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/KeyValueLogScope.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/KeyValueLogScope.cs deleted file mode 100644 index 9e689637d..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/KeyValueLogScope.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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.Collections; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - // Logger implementations have different ways of serializing log scopes. This class helps those loggers - // serialize the scope information in the best way possible for each of the implementations. - // - // Handled examples: - // - Simple Console Logger: only calls ToString, thus the data needs to be formatted in the ToString method. - // - JSON Console Logger: checks for IReadOnlyCollection> and formats each value - // in the enumeration; otherwise falls back to ToString. - // - Event Log Logger: checks for IEnumerable> and formats each value - // in the enumeration; otherwise falls back to ToString. - internal class KeyValueLogScope : IReadOnlyCollection> - { - public IDictionary Values = - new Dictionary(); - - IEnumerator> IEnumerable>.GetEnumerator() - { - return Values.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)Values).GetEnumerator(); - } - - int IReadOnlyCollection>.Count => Values.Count; - - public override string ToString() - { - StringBuilder builder = new StringBuilder(); - foreach (var kvp in Values) - { - if (builder.Length > 0) - { - builder.Append(" "); - } - builder.Append(kvp.Key); - builder.Append(":"); - builder.Append(kvp.Value); - } - return builder.ToString(); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/KeyValueLogScopeExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/KeyValueLogScopeExtensions.cs deleted file mode 100644 index 3a25e2c08..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/KeyValueLogScopeExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.Globalization; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal static class KeyValueLogScopeExtensions - { - public static void AddArtifactType(this KeyValueLogScope scope, string artifactType) - { - scope.Values.Add("ArtifactType", artifactType); - } - - public static void AddEndpointInfo(this KeyValueLogScope scope, IEndpointInfo endpointInfo) - { - scope.Values.Add( - ArtifactMetadataNames.ArtifactSource.ProcessId, - endpointInfo.ProcessId.ToString(CultureInfo.InvariantCulture)); - scope.Values.Add( - ArtifactMetadataNames.ArtifactSource.RuntimeInstanceCookie, - endpointInfo.RuntimeInstanceCookie.ToString("N")); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/LogFormat.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/LogFormat.cs deleted file mode 100644 index b111f6d61..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/LogFormat.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - public enum LogFormat - { - None = 0, - Json = 1, - EventStream = 2 - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/LoggingExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/LoggingExtensions.cs deleted file mode 100644 index 3acb09ebf..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/LoggingExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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 Microsoft.Extensions.Logging; -using System; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal static class LoggingExtensions - { - private static readonly Action _requestFailed = - LoggerMessage.Define( - eventId: new EventId(1, "RequestFailed"), - logLevel: LogLevel.Error, - formatString: "Request failed."); - - private static readonly Action _requestCanceled = - LoggerMessage.Define( - eventId: new EventId(2, "RequestCanceled"), - logLevel: LogLevel.Information, - formatString: "Request canceled."); - - private static readonly Action _resolvedTargetProcess = - LoggerMessage.Define( - eventId: new EventId(3, "ResolvedTargetProcess"), - logLevel: LogLevel.Debug, - formatString: "Resolved target process."); - - private static readonly Action _egressedArtifact = - LoggerMessage.Define( - eventId: new EventId(4, "EgressedArtifact"), - logLevel: LogLevel.Information, - formatString: "Egressed artifact to {location}"); - - private static readonly Action _writtenToHttpStream = - LoggerMessage.Define( - eventId: new EventId(5, "WrittenToHttpStream"), - logLevel: LogLevel.Information, - formatString: "Written to HTTP stream."); - - public static void RequestFailed(this ILogger logger, Exception ex) - { - _requestFailed(logger, ex); - } - - public static void RequestCanceled(this ILogger logger) - { - _requestCanceled(logger, null); - } - - public static void ResolvedTargetProcess(this ILogger logger) - { - _resolvedTargetProcess(logger, null); - } - - public static void EgressedArtifact(this ILogger logger, string location) - { - _egressedArtifact(logger, location, null); - } - - public static void WrittenToHttpStream(this ILogger logger) - { - _writtenToHttpStream(logger, null); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsLogger.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsLogger.cs deleted file mode 100644 index 4aaee3fd8..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsLogger.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring.EventPipe; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Microsoft.Diagnostics.Monitoring -{ - internal sealed class MetricsLogger : ICountersLogger - { - private readonly IMetricsStore _store; - - public MetricsLogger(IMetricsStore metricsStore) - { - _store = metricsStore; - } - - public void Log(ICounterPayload metric) - { - _store.AddMetric(metric); - } - - public void PipelineStarted() - { - } - - public void PipelineStopped() - { - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsOptions.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsOptions.cs deleted file mode 100644 index eb36786d6..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsOptions.cs +++ /dev/null @@ -1,68 +0,0 @@ -// 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 Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using System; -using System.Collections.Generic; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - /// - /// Configuration for prometheus metric collection and retrieval. - /// TODO We may want to expose https endpoints here as well, and make port changes - /// TODO How do we determine which process to scrape in multi-proc situations? How do we configure this - /// for situations where the pid is not known or ambiguous? - /// - public class MetricsOptions - { - public const string ConfigurationKey = "Metrics"; - - private readonly Lazy _ports; - - public MetricsOptions() - { - _ports = new Lazy(() => - { - string[] endpoints = ConfigurationHelper.SplitValue(Endpoints); - int?[] ports = new int?[endpoints.Length]; - for(int i = 0; i < endpoints.Length; i++) - { - //We cannot use Uri[Builder], since we are sometimes parsing invalid hostnames - try - { - UriHelper.FromAbsolute(endpoints[i], out _, out HostString host, out _, out _, out _); - ports[i] = host.Port; - } - catch (FormatException) - { - } - } - return ports; - }); - } - - public bool Enabled { get; set; } - - public string Endpoints { get; set; } - - public int?[] Ports => _ports.Value; - - public int UpdateIntervalSeconds { get; set; } - - public int MetricCount { get; set; } - - public bool IncludeDefaultProviders { get; set; } = true; - - public bool AllowInsecureChannelForCustomMetrics { get; set; } = false; - - public List Providers { get; set; } = new List(0); - } - - public class MetricProvider - { - public string ProviderName { get; set; } - public List CounterNames { get; set; } = new List(0); - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsService.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsService.cs deleted file mode 100644 index 303cd1ac0..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsService.cs +++ /dev/null @@ -1,111 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring.EventPipe; -using Microsoft.Diagnostics.NETCore.Client; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - /// - /// Periodically gets metrics from the app, and persists these to a metrics store. - /// - internal sealed class MetricsService : BackgroundService - { - private EventCounterPipeline _counterPipeline; - private readonly IDiagnosticServices _services; - private readonly MetricsStoreService _store; - private IOptionsMonitor _optionsMonitor; - - public MetricsService(IDiagnosticServices services, - IOptionsMonitor optionsMonitor, - MetricsStoreService metricsStore) - { - _store = metricsStore; - _services = services; - _optionsMonitor = optionsMonitor; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - stoppingToken.ThrowIfCancellationRequested(); - - try - { - //TODO In multi-process scenarios, how do we decide which process to choose? - //One possibility is to enable metrics after a request to begin polling for metrics - IProcessInfo pi = await _services.GetProcessAsync(filter: null, stoppingToken); - - var client = new DiagnosticsClient(pi.EndpointInfo.Endpoint); - - MetricsOptions options = _optionsMonitor.CurrentValue; - using var optionsTokenSource = new CancellationTokenSource(); - - //If metric options change, we need to cancel the existing metrics pipeline and restart with the new settings. - using IDisposable monitorListener = _optionsMonitor.OnChange((_, _) => optionsTokenSource.Cancel()); - - EventPipeCounterGroup[] counterGroups = Array.Empty(); - if (options.Providers.Count > 0) - { - //In the dotnet-monitor case, custom metrics are additive to the default counters. - var eventPipeCounterGroups = new List(); - if (options.IncludeDefaultProviders) - { - eventPipeCounterGroups.Add(new EventPipeCounterGroup { ProviderName = MonitoringSourceConfiguration.SystemRuntimeEventSourceName }); - eventPipeCounterGroups.Add(new EventPipeCounterGroup { ProviderName = MonitoringSourceConfiguration.MicrosoftAspNetCoreHostingEventSourceName }); - eventPipeCounterGroups.Add(new EventPipeCounterGroup { ProviderName = MonitoringSourceConfiguration.GrpcAspNetCoreServer }); - } - - foreach (MetricProvider customProvider in options.Providers) - { - var customCounterGroup = new EventPipeCounterGroup { ProviderName = customProvider.ProviderName }; - if (customProvider.CounterNames.Count > 0) - { - customCounterGroup.CounterNames = customProvider.CounterNames.ToArray(); - } - eventPipeCounterGroups.Add(customCounterGroup); - } - counterGroups = eventPipeCounterGroups.ToArray(); - } - - _counterPipeline = new EventCounterPipeline(client, new EventPipeCounterPipelineSettings - { - CounterGroups = counterGroups, - Duration = Timeout.InfiniteTimeSpan, - RefreshInterval = TimeSpan.FromSeconds(options.UpdateIntervalSeconds) - }, loggers: new[] { new MetricsLogger(_store.MetricsStore) }); - - using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, optionsTokenSource.Token); - await _counterPipeline.RunAsync(linkedTokenSource.Token); - } - catch (Exception e) when (e is not OperationCanceledException || !stoppingToken.IsCancellationRequested) - { - //Most likely we failed to resolve the pid or metric configuration change. Attempt to do this again. - if (_counterPipeline != null) - { - await _counterPipeline.DisposeAsync(); - } - await Task.Delay(5000); - } - } - } - - public override async void Dispose() - { - base.Dispose(); - if (_counterPipeline != null) - { - await _counterPipeline.DisposeAsync(); - } - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsStore.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsStore.cs deleted file mode 100644 index 02cde46c3..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsStore.cs +++ /dev/null @@ -1,169 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring.EventPipe; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring -{ - /// - /// Stores metrics, and produces a snapshot in Prometheus exposition format. - /// - internal sealed class MetricsStore : IMetricsStore - { - private static readonly Dictionary KnownUnits = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - {string.Empty, string.Empty}, - {"count", string.Empty}, - {"B", "_bytes" }, - {"MB", "_bytes" }, - {"%", "_ratio" }, - }; - - private sealed class MetricKey - { - private ICounterPayload _metric; - - public MetricKey(ICounterPayload metric) - { - _metric = metric; - } - - public override int GetHashCode() - { - HashCode code = new HashCode(); - code.Add(_metric.Provider); - code.Add(_metric.Name); - return code.ToHashCode(); - } - - public override bool Equals(object obj) - { - if (obj is MetricKey metricKey) - { - return CompareMetrics(_metric, metricKey._metric); - } - return false; - } - } - - private Dictionary> _allMetrics = new Dictionary>(); - private readonly int _maxMetricCount; - - public MetricsStore(int maxMetricCount) - { - if (maxMetricCount < 1) - { - throw new ArgumentException("Invalid metric count"); - } - _maxMetricCount = maxMetricCount; - } - - public void AddMetric(ICounterPayload metric) - { - lock (_allMetrics) - { - var metricKey = new MetricKey(metric); - if (!_allMetrics.TryGetValue(metricKey, out Queue metrics)) - { - metrics = new Queue(); - _allMetrics.Add(metricKey, metrics); - } - metrics.Enqueue(metric); - if (metrics.Count > _maxMetricCount) - { - metrics.Dequeue(); - } - } - } - - public async Task SnapshotMetrics(Stream outputStream, CancellationToken token) - { - Dictionary> copy = null; - lock (_allMetrics) - { - copy = new Dictionary>(); - foreach (var metricGroup in _allMetrics) - { - copy.Add(metricGroup.Key, new Queue(metricGroup.Value)); - } - } - - using var writer = new StreamWriter(outputStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), bufferSize: 1024, leaveOpen: true); - writer.NewLine = "\n"; - - foreach (var metricGroup in copy) - { - ICounterPayload metricInfo = metricGroup.Value.First(); - string metricName = GetPrometheusMetric(metricInfo, out string metricValue); - string metricType = "gauge"; - - //TODO Some clr metrics claim to be incrementing, but are really gauges. - - await writer.WriteLineAsync(FormattableString.Invariant($"# HELP {metricName} {metricInfo.DisplayName}")); - await writer.WriteLineAsync(FormattableString.Invariant($"# TYPE {metricName} {metricType}")); - - foreach (var metric in metricGroup.Value) - { - await WriteMetricDetails(writer, metric, metricName, metricValue); - } - } - } - - private static async Task WriteMetricDetails( - StreamWriter writer, - ICounterPayload metric, - string metricName, - string metricValue) - { - await writer.WriteAsync(metricName); - await writer.WriteLineAsync(FormattableString.Invariant($" {metricValue} {new DateTimeOffset(metric.Timestamp).ToUnixTimeMilliseconds()}")); - } - - private static string GetPrometheusMetric(ICounterPayload metric, out string metricValue) - { - string unitSuffix = string.Empty; - - if ((metric.Unit != null) && (!KnownUnits.TryGetValue(metric.Unit, out unitSuffix))) - { - //TODO The prometheus data model does not allow certain characters. Units we are not expecting could cause a scrape failure. - unitSuffix = "_" + metric.Unit; - } - - double value = metric.Value; - if (string.Equals(metric.Unit, "MB", StringComparison.OrdinalIgnoreCase)) - { - value *= 1_000_000; //Note that the metric uses MB not MiB - } - - metricValue = value.ToString(CultureInfo.InvariantCulture); - return FormattableString.Invariant($"{metric.Provider.Replace(".", string.Empty).ToLowerInvariant()}_{metric.Name.Replace('-', '_')}{unitSuffix}"); - } - - private static bool CompareMetrics(ICounterPayload first, ICounterPayload second) - { - return string.Equals(first.Name, second.Name); - } - - public void Clear() - { - lock (_allMetrics) - { - _allMetrics.Clear(); - } - } - - public void Dispose() - { - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsStoreService.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsStoreService.cs deleted file mode 100644 index 8a3fe4bf2..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/MetricsStoreService.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 Microsoft.Extensions.Options; -using System; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal sealed class MetricsStoreService : IDisposable - { - public MetricsStore MetricsStore { get; } - - public MetricsStoreService( - IOptions options) - { - MetricsStore = new MetricsStore(options.Value.MetricCount); - } - - public void Dispose() - { - MetricsStore.Dispose(); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Microsoft.Diagnostics.Monitoring.RestServer.csproj b/src/Microsoft.Diagnostics.Monitoring.RestServer/Microsoft.Diagnostics.Monitoring.RestServer.csproj deleted file mode 100644 index f32bcad44..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Microsoft.Diagnostics.Monitoring.RestServer.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - netstandard2.0;netcoreapp3.1 - ;1591;1701 - REST Api surface for dotnet-monitor - - true - Diagnostic - $(Description) - false - true - - false - Library - - 5.0.0 - preview - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeConfigurationModel.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeConfigurationModel.cs deleted file mode 100644 index 77738329a..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeConfigurationModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; - -namespace Microsoft.Diagnostics.Monitoring.RestServer.Models -{ - [DataContract(Name = "EventPipeConfiguration")] - public class EventPipeConfigurationModel - { - [DataMember(Name = "providers", IsRequired = true)] - public EventPipeProviderModel[] Providers { get; set; } - - [DataMember(Name = "requestRundown")] - public bool RequestRundown { get; set; } = true; - - [DataMember(Name = "bufferSizeInMB")] - [Range(1, 1024)] - public int BufferSizeInMB { get; set; } = 256; - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeProviderModel.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeProviderModel.cs deleted file mode 100644 index 71a0e959e..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeProviderModel.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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.Collections.Generic; -using System.Diagnostics.Tracing; -using System.Runtime.Serialization; -using Microsoft.Diagnostics.Monitoring.RestServer.Validation; - -namespace Microsoft.Diagnostics.Monitoring.RestServer.Models -{ - [DataContract(Name = "EventPipeProvider")] - public class EventPipeProviderModel - { - [DataMember(Name = "name", IsRequired = true)] - public string Name { get; set; } - - [DataMember(Name = "keywords")] - [IntegerOrHexString] - public string Keywords { get; set; } = "0x" + EventKeywords.All.ToString("X"); - - [DataMember(Name = "eventLevel")] - public EventLevel EventLevel { get; set; } = EventLevel.Verbose; - - [DataMember(Name = "arguments")] - public IDictionary Arguments { get; set; } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/ProcessIdentifierModel.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/ProcessIdentifierModel.cs deleted file mode 100644 index 088aabfd4..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/ProcessIdentifierModel.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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.Runtime.Serialization; - -namespace Microsoft.Diagnostics.Monitoring.RestServer.Models -{ - [DataContract(Name = "Process")] - public class ProcessIdentifierModel - { - [DataMember(Name = "pid")] - public int Pid { get; set; } - - [DataMember(Name = "uid")] - public Guid Uid { get; set; } - - internal static ProcessIdentifierModel FromProcessInfo(IProcessInfo processInfo) - { - return new ProcessIdentifierModel() - { - Pid = processInfo.EndpointInfo.ProcessId, - Uid = processInfo.EndpointInfo.RuntimeInstanceCookie - }; - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/ProcessModel.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/ProcessModel.cs deleted file mode 100644 index 6d2429f55..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/ProcessModel.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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.Runtime.Serialization; - -namespace Microsoft.Diagnostics.Monitoring.RestServer.Models -{ - [DataContract(Name = "Process")] - public class ProcessModel - { - [DataMember(Name = "pid")] - public int Pid { get; set; } - - [DataMember(Name = "uid")] - public Guid Uid { get; set; } - - [DataMember(Name = "name")] - public string Name { get; private set; } - - [DataMember(Name = "commandLine")] - public string CommandLine { get; private set; } - - [DataMember(Name = "os")] - public string OperatingSystem { get; private set; } - - [DataMember(Name = "architecture")] - public string ProcessArchitecture { get; private set; } - - internal static ProcessModel FromProcessInfo(IProcessInfo processInfo) - { - return new ProcessModel() - { - CommandLine = processInfo.CommandLine, - Name = processInfo.ProcessName, - OperatingSystem = processInfo.OperatingSystem, - ProcessArchitecture = processInfo.ProcessArchitecture, - Pid = processInfo.EndpointInfo.ProcessId, - Uid = processInfo.EndpointInfo.RuntimeInstanceCookie - }; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/OutputStreamResult.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/OutputStreamResult.cs deleted file mode 100644 index 7765bd52c..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/OutputStreamResult.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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 Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - internal sealed class OutputStreamResult : ActionResult - { - private readonly Func _action; - private readonly string _contentType; - private readonly string _fileDownloadName; - private readonly KeyValueLogScope _scope; - - public OutputStreamResult(Func action, string contentType, string fileDownloadName, KeyValueLogScope scope) - { - _contentType = contentType; - _fileDownloadName = fileDownloadName; - _action = action; - _scope = scope; - } - - public override async Task ExecuteResultAsync(ActionContext context) - { - ILogger logger = context.HttpContext.RequestServices - .GetRequiredService() - .CreateLogger(); - - using var _ = logger.BeginScope(_scope); - - await context.InvokeAsync(async (token) => - { - if (_fileDownloadName != null) - { - ContentDispositionHeaderValue contentDispositionHeaderValue = new ContentDispositionHeaderValue("attachment"); - contentDispositionHeaderValue.FileName = _fileDownloadName; - context.HttpContext.Response.Headers["Content-Disposition"] = contentDispositionHeaderValue.ToString(); - } - context.HttpContext.Response.Headers["Content-Type"] = _contentType; - -#if !NETSTANDARD2_0 - context.HttpContext.Features.Get()?.DisableBuffering(); -#else - context.HttpContext.Features.Get()?.DisableResponseBuffering(); -#endif - - await _action(context.HttpContext.Response.Body, token); - - logger.WrittenToHttpStream(); - }, logger); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/ProcessFilter.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/ProcessFilter.cs deleted file mode 100644 index ae0bfdae0..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/ProcessFilter.cs +++ /dev/null @@ -1,66 +0,0 @@ -// 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.ComponentModel; -using System.Globalization; - -namespace Microsoft.Diagnostics.Monitoring -{ - [TypeConverter(typeof(ProcessFilterTypeConverter))] - public struct ProcessFilter - { - public ProcessFilter(int processId) - { - ProcessId = processId; - RuntimeInstanceCookie = null; - } - - public ProcessFilter(Guid runtimeInstanceCookie) - { - ProcessId = null; - RuntimeInstanceCookie = runtimeInstanceCookie; - } - - public int? ProcessId { get; } - - public Guid? RuntimeInstanceCookie { get; } - } - - internal class ProcessFilterTypeConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (null == sourceType) - { - throw new ArgumentNullException(nameof(sourceType)); - } - return sourceType == typeof(string) || sourceType == typeof(ProcessFilter); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value is string valueString) - { - if (string.IsNullOrEmpty(valueString)) - { - return null; - } - else if (Guid.TryParse(valueString, out Guid cookie)) - { - return new ProcessFilter(cookie); - } - else if (int.TryParse(valueString, out int processId)) - { - return new ProcessFilter(processId); - } - } - else if (value is ProcessFilter identifier) - { - return identifier; - } - throw new FormatException(); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/ScopeState.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/ScopeState.cs deleted file mode 100644 index 370674908..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/ScopeState.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Diagnostics.Monitoring -{ - internal sealed class ScopeState : IEnumerable>> - { - private readonly Stack>> _scopes = new Stack>>(); - - private sealed class ScopeEntry : IDisposable - { - private readonly Stack>> _scopes; - - public ScopeEntry(Stack>> scopes, IReadOnlyList> scope) - { - _scopes = scopes; - _scopes.Push(scope); - } - - public void Dispose() - { - _scopes.Pop(); - } - } - - public IDisposable Push(IReadOnlyList> scope) - { - return new ScopeEntry(_scopes, scope); - } - - public IEnumerator>> GetEnumerator() - { - return _scopes.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/StreamingLogger.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/StreamingLogger.cs deleted file mode 100644 index baa05e5e9..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/StreamingLogger.cs +++ /dev/null @@ -1,213 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring.EventPipe; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.Json; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - /// - /// This class is used to write structured event data in json format to an output stream. - /// - public sealed class StreamingLoggerProvider : ILoggerProvider - { - private readonly Stream _outputStream; - private readonly LogFormat _format; - private readonly LogLevel _logLevel; - - public StreamingLoggerProvider(Stream outputStream, LogFormat logFormat, LogLevel logLevel = LogLevel.Debug) - { - _outputStream = outputStream; - _format = logFormat; - _logLevel = logLevel; - } - - public ILogger CreateLogger(string categoryName) - { - return new StreamingLogger(categoryName, _outputStream, _format, _logLevel); - } - - public void Dispose() - { - } - } - - public sealed class StreamingLogger : ILogger - { - private readonly ScopeState _scopes = new ScopeState(); - private readonly Stream _outputStream; - private readonly string _categoryName; - private readonly LogFormat _logFormat; - private readonly LogLevel _logLevel; - - public StreamingLogger(string category, Stream outputStream, LogFormat format, LogLevel logLevel) - { - _outputStream = outputStream; - _categoryName = category; - _logFormat = format; - _logLevel = logLevel; - } - - public IDisposable BeginScope(TState state) - { - if (state is LogObject logObject) - { - return _scopes.Push(logObject); - } - return null; - } - - public bool IsEnabled(LogLevel logLevel) => logLevel <= _logLevel; - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - if (_logFormat == LogFormat.Json) - { - LogJson(logLevel, eventId, state, exception, formatter); - } - else - { - LogEventStream(logLevel, eventId, state, exception, formatter); - } - } - - private void LogJson(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - Stream outputStream = _outputStream; - - //CONSIDER Should we cache up the loggers and writers? - using (var jsonWriter = new Utf8JsonWriter(outputStream, new JsonWriterOptions { Indented = false })) - { - jsonWriter.WriteStartObject(); - jsonWriter.WriteString("LogLevel", logLevel.ToString()); - jsonWriter.WriteString("EventId", eventId.ToString()); - jsonWriter.WriteString("Category", _categoryName); - if (exception != null) - { - jsonWriter.WriteString("Exception", formatter(state, exception)); - } - else - { - jsonWriter.WriteString("Message", formatter(state, exception)); - } - - //Write out scope data - jsonWriter.WriteStartObject("Scopes"); - foreach (IReadOnlyList> scope in _scopes) - { - foreach (KeyValuePair scopeValue in scope) - { - WriteKeyValuePair(jsonWriter, scopeValue); - } - } - jsonWriter.WriteEndObject(); - - //Write out structured data - jsonWriter.WriteStartObject("Arguments"); - if (state is IEnumerable> values) - { - foreach (KeyValuePair arg in values) - { - WriteKeyValuePair(jsonWriter, arg); - } - } - jsonWriter.WriteEndObject(); - - jsonWriter.WriteEndObject(); - jsonWriter.Flush(); - } - - outputStream.WriteByte((byte)'\n'); - outputStream.Flush(); - } - - private void LogEventStream(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - Stream outputStream = _outputStream; - - using var writer = new StreamWriter(outputStream, Encoding.UTF8, 1024, leaveOpen: true) { NewLine = "\n" }; - - //event: eventName (if exists) - //data: level category[eventId] - //data: message - //data: => scope1, scope2 => scope3, scope4 - //\n - - if (!string.IsNullOrEmpty(eventId.Name)) - { - writer.Write("event: "); - writer.WriteLine(eventId.Name); - } - writer.Write("data: "); - writer.Write(logLevel); - writer.Write(" "); - writer.Write(_categoryName); - writer.Write('['); - writer.Write(eventId.Id); - writer.WriteLine(']'); - writer.Write("data: "); - writer.WriteLine(formatter(state, exception)); - - bool firstScope = true; - foreach (IReadOnlyList> scope in _scopes) - { - bool firstScopeEntry = true; - foreach (KeyValuePair scopeValue in scope) - { - if (firstScope) - { - writer.Write("data:"); - firstScope = false; - } - - if (firstScopeEntry) - { - writer.Write(" => "); - firstScopeEntry = false; - } - else - { - writer.Write(", "); - } - writer.Write(scopeValue.Key); - writer.Write(':'); - writer.Write(scopeValue.Value); - } - } - if (!firstScope) - { - writer.WriteLine(); - } - writer.WriteLine(); - } - - private static void WriteKeyValuePair(Utf8JsonWriter jsonWriter, KeyValuePair kvp) - { - jsonWriter.WritePropertyName(kvp.Key); - switch (kvp.Value) - { - case string s: - jsonWriter.WriteStringValue(s); - break; - case int i: - jsonWriter.WriteNumberValue(i); - break; - case bool b: - jsonWriter.WriteBooleanValue(b); - break; - case null: - jsonWriter.WriteNullValue(); - break; - default: - jsonWriter.WriteStringValue(kvp.Value.ToString()); - break; - } - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/TraceProfile.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/TraceProfile.cs deleted file mode 100644 index 8b49c204a..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/TraceProfile.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Monitoring.RestServer -{ - [Flags] - public enum TraceProfile - { - Cpu = 0x1, - Http = 0x2, - Logs = 0x4, - Metrics = 0x8 - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Validation/IntegerOrHexStringAttribute.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Validation/IntegerOrHexStringAttribute.cs deleted file mode 100644 index 1f5632744..000000000 --- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Validation/IntegerOrHexStringAttribute.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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.ComponentModel.DataAnnotations; -using System.Globalization; - -namespace Microsoft.Diagnostics.Monitoring.RestServer.Validation -{ - public class IntegerOrHexStringAttribute : ValidationAttribute - { - protected override ValidationResult IsValid(object value, ValidationContext validationContext) - { - if (!(value is string stringValue)) - { - return new ValidationResult("Value must be of type string."); - } - else if (!TryParse(stringValue, out _, out string error)) - { - return new ValidationResult(error); - } - return ValidationResult.Success; - } - - public static bool TryParse(string value, out long result, out string error) - { - result = 0; - error = null; - - if (string.IsNullOrWhiteSpace(value)) - { - error = "Value cannot be null, empty, or whitespace."; - return false; - } - else if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) - { - // AllowHexSpecifier requires that the "0x" is removed before attempting to parse. - // It parses the actual value, not the "0x" syntax prefix. - if (!long.TryParse(value.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out result)) - { - error = FormattableString.Invariant($"The value '{value}' is not a valid hexadecimal number."); - return false; - } - } - else - { - if (!long.TryParse(value, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out result)) - { - error = FormattableString.Invariant($"The value '{value}' is not a valid integer."); - return false; - } - } - - return true; - } - } -} diff --git a/src/Tools/dotnet-monitor/ActivityExtensions.cs b/src/Tools/dotnet-monitor/ActivityExtensions.cs deleted file mode 100644 index 6da548453..000000000 --- a/src/Tools/dotnet-monitor/ActivityExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - internal static class ActivityExtensions - { - public static string GetSpanId(this Activity activity) - { - switch (activity.IdFormat) - { - case ActivityIdFormat.Hierarchical: - return activity.Id; - case ActivityIdFormat.W3C: - return activity.SpanId.ToHexString(); - } - return string.Empty; - } - - public static string GetParentId(this Activity activity) - { - switch (activity.IdFormat) - { - case ActivityIdFormat.Hierarchical: - return activity.ParentId; - case ActivityIdFormat.W3C: - return activity.ParentSpanId.ToHexString(); - } - return string.Empty; - } - - public static string GetTraceId(this Activity activity) - { - switch (activity.IdFormat) - { - case ActivityIdFormat.Hierarchical: - return activity.RootId; - case ActivityIdFormat.W3C: - return activity.TraceId.ToHexString(); - } - return string.Empty; - } - } -} diff --git a/src/Tools/dotnet-monitor/ActivityMetadataNames.cs b/src/Tools/dotnet-monitor/ActivityMetadataNames.cs deleted file mode 100644 index 0c16986bc..000000000 --- a/src/Tools/dotnet-monitor/ActivityMetadataNames.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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. - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - /// - /// Metadata keys that correspond to properties. - /// - internal static class ActivityMetadataNames - { - /// - /// Represents the ID of the parent activity. - /// - /// - /// This name is the same as logged by the ActivityLogScope. - /// - public const string ParentId = nameof(ParentId); - - /// - /// Represents the ID of the current activity. - /// - /// - /// This name is the same as logged by the ActivityLogScope. - /// - public const string SpanId = nameof(SpanId); - - /// - /// Represents the trace ID of the activity. - /// - /// - /// This name is the same as logged by the ActivityLogScope. - /// - public const string TraceId = nameof(TraceId); - } -} diff --git a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandler.cs b/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandler.cs deleted file mode 100644 index 43f5ff582..000000000 --- a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandler.cs +++ /dev/null @@ -1,124 +0,0 @@ -// 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 Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; -using Microsoft.Diagnostics.Monitoring.RestServer; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; -using Microsoft.Net.Http.Headers; -using System; -using System.Buffers.Text; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text.Encodings.Web; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - /// - /// Authenticates against the ApiKey stored on the server. - /// - internal sealed class ApiKeyAuthenticationHandler : AuthenticationHandler - { - private static readonly string[] DisallowedHashAlgorithms = new string[] - { - "SHA", "SHA1", "System.Security.Cryptography.SHA1", "System.Security.Cryptography.HashAlgorithm", "MD5", "System.Security.Cryptography.MD5" - }; - - public ApiKeyAuthenticationHandler(IOptionsMonitor options, ILoggerFactory loggerFactory, UrlEncoder encoder, ISystemClock clock) - : base(options, loggerFactory, encoder, clock) - { - } - - protected override Task HandleAuthenticateAsync() - { - //We are expecting a header such as Authorization: - //If this is not present, we will return NoResult and move on to the next authentication handler. - if (!Request.Headers.TryGetValue(HeaderNames.Authorization, out StringValues values) || - !values.Any()) - { - return Task.FromResult(AuthenticateResult.NoResult()); - } - - if (!AuthenticationHeaderValue.TryParse(values.First(), out AuthenticationHeaderValue authHeader)) - { - return Task.FromResult(AuthenticateResult.Fail("Invalid authentication header")); - } - - if (!string.Equals(authHeader.Scheme, Scheme.Name, StringComparison.OrdinalIgnoreCase)) - { - return Task.FromResult(AuthenticateResult.NoResult()); - } - - //The user is passing a base 64-encoded version of the secret - //We will be hash this and compare it to the secret in our configuration. - byte[] secret = new byte[32]; - Span span = new Span(secret); - if (!Convert.TryFromBase64String(authHeader.Parameter, span, out int bytesWritten) || bytesWritten < 32) - { - return Task.FromResult(AuthenticateResult.Fail("Invalid Api Key format")); - } - - //HACK IOptionsMonitor and similiar do not properly update here even though the underlying - //configuration is updated. We get the value directly from IConfiguration. - - var authenticationOptions = new ApiAuthenticationOptions(); - IConfiguration configService = Context.RequestServices.GetRequiredService(); - configService.Bind(ApiAuthenticationOptions.ConfigurationKey, authenticationOptions); - string apiKeyHash = authenticationOptions.ApiKeyHash; - if (apiKeyHash == null) - { - return Task.FromResult(AuthenticateResult.Fail("Server does not contain Api Key")); - } - if (string.IsNullOrEmpty(authenticationOptions.ApiKeyHashType)) - { - return Task.FromResult(AuthenticateResult.Fail("Missing hash algorithm")); - } - if (DisallowedHashAlgorithms.Contains(authenticationOptions.ApiKeyHashType, StringComparer.OrdinalIgnoreCase)) - { - return Task.FromResult(AuthenticateResult.Fail($"Disallowed hash algorithm {authenticationOptions.ApiKeyHashType}")); - } - - using HashAlgorithm algorithm = HashAlgorithm.Create(authenticationOptions.ApiKeyHashType); - if (algorithm == null) - { - return Task.FromResult(AuthenticateResult.Fail($"Invalid hash algorithm {authenticationOptions.ApiKeyHashType}")); - } - - byte[] hashedSecret = algorithm.ComputeHash(secret); - - //ApiKeyHash is represented as a hex string. e.g. AABBCCDDEEFF - byte[] apiKeyHashBytes = new byte[apiKeyHash.Length / 2]; - for (int i = 0; i < apiKeyHash.Length; i += 2) - { - if (!byte.TryParse(apiKeyHash.AsSpan(i, 2), NumberStyles.HexNumber, provider: NumberFormatInfo.InvariantInfo, result: out byte resultByte)) - { - return Task.FromResult(AuthenticateResult.Fail("Invalid Api Key hash")); - } - apiKeyHashBytes[i / 2] = resultByte; - } - - if (hashedSecret.SequenceEqual(apiKeyHashBytes)) - { - return Task.FromResult(AuthenticateResult.Success( - new AuthenticationTicket( - new ClaimsPrincipal(new[] { new ClaimsIdentity(AuthConstants.ApiKeySchema) }), - AuthConstants.ApiKeySchema))); - - } - else - { - return Task.FromResult(AuthenticateResult.Fail("Invalid Api Key")); - } - } - } -} diff --git a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandlerOptions.cs b/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandlerOptions.cs deleted file mode 100644 index b7bd7c84b..000000000 --- a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandlerOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 Microsoft.AspNetCore.Authentication; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - internal sealed class ApiKeyAuthenticationHandlerOptions : AuthenticationSchemeOptions - { - } -} diff --git a/src/Tools/dotnet-monitor/Auth/AuthorizedUserRequirement.cs b/src/Tools/dotnet-monitor/Auth/AuthorizedUserRequirement.cs deleted file mode 100644 index c6696c3a5..000000000 --- a/src/Tools/dotnet-monitor/Auth/AuthorizedUserRequirement.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 Microsoft.AspNetCore.Authorization; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - internal sealed class AuthorizedUserRequirement : IAuthorizationRequirement - { - } -} diff --git a/src/Tools/dotnet-monitor/Auth/UserAuthorizationHandler.cs b/src/Tools/dotnet-monitor/Auth/UserAuthorizationHandler.cs deleted file mode 100644 index 31ea2b315..000000000 --- a/src/Tools/dotnet-monitor/Auth/UserAuthorizationHandler.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 Microsoft.AspNetCore.Authentication.Negotiate; -using Microsoft.AspNetCore.Authorization; -using Microsoft.Diagnostics.Monitoring.RestServer; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Security.Principal; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - /// - /// Handles authorization for both Negotiate and ApiKey authentication. - /// - internal sealed class UserAuthorizationHandler : AuthorizationHandler - { - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizedUserRequirement requirement) - { - // If the schema type is ApiKey, we do not need further authorization. - if (context.User.Identity.AuthenticationType == AuthConstants.ApiKeySchema) - { - context.Succeed(requirement); - } - else if ((context.User.Identity.AuthenticationType == AuthConstants.NtlmSchema) || - (context.User.Identity.AuthenticationType == AuthConstants.KerberosSchema) || - (context.User.Identity.AuthenticationType == AuthConstants.NegotiateSchema)) - { - //Negotiate, Kerberos, or NTLM. - //CONSIDER In the future, we may want to have configuration around a dotnet-monitor group sid instead. - //We cannot check the user against BUILTIN\Administrators group membership, since the browser user - //has a deny claim on Administrator group. - //Validate that the user that logged in matches the user that is running dotnet-monitor - //Do not allow at all if running as Administrator. - WindowsIdentity currentUser = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new WindowsPrincipal(currentUser); - if (principal.IsInRole(WindowsBuiltInRole.Administrator)) - { - return Task.CompletedTask; - } - - Claim currentUserClaim = currentUser.Claims.FirstOrDefault(claim => string.Equals(claim.Type, ClaimTypes.PrimarySid)); - if ((currentUserClaim != null) && context.User.HasClaim(currentUserClaim.Type, currentUserClaim.Value)) - { - context.Succeed(requirement); - } - } - - return Task.CompletedTask; - } - } -} diff --git a/src/Tools/dotnet-monitor/DiagnosticPortOptions.cs b/src/Tools/dotnet-monitor/DiagnosticPortOptions.cs deleted file mode 100644 index 0595e31b1..000000000 --- a/src/Tools/dotnet-monitor/DiagnosticPortOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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. - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - public class DiagnosticPortOptions - { - public const string ConfigurationKey = "DiagnosticPort"; - - public DiagnosticPortConnectionMode ConnectionMode { get; set; } - - public string EndpointName { get; set; } - - public int? MaxConnections { get; set; } - } - - public enum DiagnosticPortConnectionMode - { - Connect, - Listen - } -} diff --git a/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs b/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs deleted file mode 100644 index 25186ec72..000000000 --- a/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs +++ /dev/null @@ -1,303 +0,0 @@ -// 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 Microsoft.AspNetCore; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Negotiate; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.Diagnostics.Monitoring; -using Microsoft.Diagnostics.Monitoring.RestServer; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.CommandLine; -using System.CommandLine.IO; -using System.IO; -using System.Linq; -using System.Net; -using System.Runtime.InteropServices; -using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - internal sealed class DiagnosticsMonitorCommandHandler - { - private const string ConfigPrefix = "DotnetMonitor_"; - private const string SettingsFileName = "settings.json"; - private const string ProductFolderName = "dotnet-monitor"; - - // Location where shared dotnet-monitor configuration is stored. - // Windows: "%ProgramData%\dotnet-monitor - // Other: /etc/dotnet-monitor - private static readonly string SharedConfigDirectoryPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), ProductFolderName) : - Path.Combine("/etc", ProductFolderName); - - private static readonly string SharedSettingsPath = Path.Combine(SharedConfigDirectoryPath, SettingsFileName); - - // Location where user's dotnet-monitor configuration is stored. - // Windows: "%USERPROFILE%\.dotnet-monitor" - // Other: "%XDG_CONFIG_HOME%/dotnet-monitor" OR "%HOME%/.config/dotnet-monitor" - private static readonly string UserConfigDirectoryPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "." + ProductFolderName) : - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ProductFolderName); - - private static readonly string UserSettingsPath = Path.Combine(UserConfigDirectoryPath, SettingsFileName); - - public async Task Start(CancellationToken token, IConsole console, string[] urls, string[] metricUrls, bool metrics, string diagnosticPort, bool noAuth) - { - //CONSIDER The console logger uses the standard AddConsole, and therefore disregards IConsole. - using IHost host = CreateHostBuilder(console, urls, metricUrls, metrics, diagnosticPort, noAuth).Build(); - await host.RunAsync(token); - return 0; - } - - public IHostBuilder CreateHostBuilder(IConsole console, string[] urls, string[] metricUrls, bool metrics, string diagnosticPort, bool noAuth) - { - return Host.CreateDefaultBuilder() - .UseContentRoot(AppContext.BaseDirectory) // Use the application root instead of the current directory - .ConfigureAppConfiguration((IConfigurationBuilder builder) => - { - //Note these are in precedence order. - ConfigureEndpointInfoSource(builder, diagnosticPort); - ConfigureMetricsEndpoint(builder, metrics, metricUrls); - builder.AddCommandLine(new[] { "--urls", ConfigurationHelper.JoinValue(urls) }); - - builder.AddJsonFile(UserSettingsPath, optional: true, reloadOnChange: true); - builder.AddJsonFile(SharedSettingsPath, optional: true, reloadOnChange: true); - - //HACK Workaround for https://github.com/dotnet/runtime/issues/36091 - //KeyPerFile provider uses a file system watcher to trigger changes. - //The watcher does not follow symlinks inside the watched directory, such as mounted files - //in Kubernetes. - //We get around this by watching the target folder of the symlink instead. - //See https://github.com/kubernetes/kubernetes/master/pkg/volume/util/atomic_writer.go - string path = SharedConfigDirectoryPath; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && RuntimeInfo.IsInKubernetes) - { - string symlinkTarget = Path.Combine(SharedConfigDirectoryPath, "..data"); - if (Directory.Exists(symlinkTarget)) - { - path = symlinkTarget; - } - } - - builder.AddKeyPerFile(path, optional: true, reloadOnChange: true); - builder.AddEnvironmentVariables(ConfigPrefix); - }) - .ConfigureServices((HostBuilderContext context, IServiceCollection services) => - { - //TODO Many of these service additions should be done through extension methods - - AuthOptions authenticationOptions = new AuthOptions(noAuth ? KeyAuthenticationMode.NoAuth : KeyAuthenticationMode.StoredKey); - services.AddSingleton(authenticationOptions); - - List authSchemas = null; - if (authenticationOptions.EnableKeyAuth) - { - //Add support for Authentication and Authorization. - AuthenticationBuilder authBuilder = services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = AuthConstants.ApiKeySchema; - options.DefaultChallengeScheme = AuthConstants.ApiKeySchema; - }) - .AddScheme(AuthConstants.ApiKeySchema, _ => { }); - - authSchemas = new List { AuthConstants.ApiKeySchema }; - - if (authenticationOptions.EnableNegotiate) - { - //On Windows add Negotiate package. This will use NTLM to perform Windows Authentication. - authBuilder.AddNegotiate(); - authSchemas.Add(AuthConstants.NegotiateSchema); - } - } - - //Apply Authorization Policy for NTLM. Without Authorization, any user with a valid login/password will be authorized. We only - //want to authorize the same user that is running dotnet-monitor, at least for now. - //Note this policy applies to both Authorization schemas. - services.AddAuthorization(authOptions => - { - if (authenticationOptions.EnableKeyAuth) - { - authOptions.AddPolicy(AuthConstants.PolicyName, (builder) => - { - builder.AddRequirements(new AuthorizedUserRequirement()); - builder.RequireAuthenticatedUser(); - builder.AddAuthenticationSchemes(authSchemas.ToArray()); - - }); - } - else - { - authOptions.AddPolicy(AuthConstants.PolicyName, (builder) => - { - builder.RequireAssertion((_) => true); - }); - } - }); - - if (authenticationOptions.EnableKeyAuth) - { - services.AddSingleton(); - } - - services.Configure(context.Configuration.GetSection(DiagnosticPortOptions.ConfigurationKey)); - services.AddSingleton(); - services.AddHostedService(); - services.AddSingleton(); - services.ConfigureEgress(context.Configuration); - services.ConfigureMetrics(context.Configuration); - services.AddSingleton(); - }) - .ConfigureLogging(builder => - { - // Always allow the experimental tool message to be logged - ExperimentalToolLogger.AddLogFilter(builder); - }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.ConfigureKestrel((context, options) => - { - //Note our priorities for hosting urls don't match the default behavior. - //Default Kestrel behavior priority - //1) ConfigureKestrel settings - //2) Command line arguments (--urls) - //3) Environment variables (ASPNETCORE_URLS, then DOTNETCORE_URLS) - - //Our precedence - //1) Environment variables (ASPNETCORE_URLS, DotnetMonitor_Metrics__Endpoints) - //2) Command line arguments (these have defaults) --urls, --metricUrls - //3) ConfigureKestrel is used for fine control of the server, but honors the first two configurations. - - string hostingUrl = context.Configuration.GetValue(WebHostDefaults.ServerUrlsKey); - urls = ConfigurationHelper.SplitValue(hostingUrl); - - var metricsOptions = new MetricsOptions(); - context.Configuration.Bind(MetricsOptions.ConfigurationKey, metricsOptions); - - if (metricsOptions.Enabled) - { - metricUrls = ProcessMetricUrls(metricUrls, metricsOptions); - urls = urls.Concat(metricUrls).ToArray(); - } - - bool boundListeningPort = false; - - //Workaround for lack of default certificate. See https://github.com/dotnet/aspnetcore/issues/28120 - options.Configure(context.Configuration.GetSection("Kestrel")).Load(); - - //By default, we bind to https for sensitive data (such as dumps and traces) and bind http for - //non-sensitive data such as metrics. We may be missing a certificate for https binding. We want to continue with the - //http binding in that scenario. - foreach (BindingAddress url in urls.Select(BindingAddress.Parse)) - { - Action configureListenOptions = (listenOptions) => - { - if (url.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) - { - listenOptions.UseHttps(); - } - }; - - try - { - if (url.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) - { - options.ListenLocalhost(url.Port, configureListenOptions); - } - else if (IPAddress.TryParse(url.Host, out IPAddress ipAddress)) - { - options.Listen(ipAddress, url.Port, configureListenOptions); - } - else - { - options.ListenAnyIP(url.Port, configureListenOptions); - } - boundListeningPort = true; - } - catch (InvalidOperationException e) - { - //This binding failure is typically due to missing default certificate - console.Error.WriteLine($"Unable to bind to {url}. Dotnet-monitor functionality will be limited."); - console.Error.WriteLine(e.Message); - } - } - - //If we end up not binding any ports, Kestrel defaults to port 5000. Make sure we don't attempt this. - if (!boundListeningPort) - { - throw new InvalidOperationException("Unable to bind any urls."); - } - }) - .UseStartup(); - }); - } - - private static string[] ProcessMetricUrls(string[] metricUrls, MetricsOptions metricsOptions) - { - string metricUrlFromConfig = metricsOptions.Endpoints; - if (!string.IsNullOrEmpty(metricUrlFromConfig)) - { - metricUrls = ConfigurationHelper.SplitValue(metricUrlFromConfig); - } - - //If we have custom metrics we want to upgrade the metrics transport channel to https, but - //also respect the user's configuration to leave it insecure. - if ((metricsOptions.Providers.Count > 0) && - (!metricsOptions.AllowInsecureChannelForCustomMetrics) && - (metricsOptions.Providers.Any(provider => - !Monitoring.EventPipe.MonitoringSourceConfiguration.DefaultMetricProviders.Contains(provider.ProviderName, StringComparer.OrdinalIgnoreCase)))) - { - for (int i = 0; i < metricUrls.Length; i++) - { - BindingAddress metricUrl = BindingAddress.Parse(metricUrls[i]); - - //Upgrade http to https by default. - if (metricUrl.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) - { - //Based on BindAddress.ToString - metricUrls[i] = string.Concat(Uri.UriSchemeHttps.ToLowerInvariant(), - Uri.SchemeDelimiter, - metricUrl.Host.ToLowerInvariant(), - ":", - metricUrl.Port.ToString(System.Globalization.CultureInfo.InvariantCulture), - metricUrl.PathBase); - } - } - } - - return metricUrls; - } - - private static void ConfigureMetricsEndpoint(IConfigurationBuilder builder, bool enableMetrics, string[] metricEndpoints) - { - builder.AddInMemoryCollection(new Dictionary - { - {ConfigurationHelper.MakeKey(MetricsOptions.ConfigurationKey, nameof(MetricsOptions.Endpoints)), string.Join(';', metricEndpoints)}, - {ConfigurationHelper.MakeKey(MetricsOptions.ConfigurationKey, nameof(MetricsOptions.Enabled)), enableMetrics.ToString()}, - {ConfigurationHelper.MakeKey(MetricsOptions.ConfigurationKey, nameof(MetricsOptions.UpdateIntervalSeconds)), 10.ToString()}, - {ConfigurationHelper.MakeKey(MetricsOptions.ConfigurationKey, nameof(MetricsOptions.MetricCount)), 3.ToString()} - }); - } - - private static void ConfigureEndpointInfoSource(IConfigurationBuilder builder, string diagnosticPort) - { - DiagnosticPortConnectionMode connectionMode = string.IsNullOrEmpty(diagnosticPort) ? DiagnosticPortConnectionMode.Connect : DiagnosticPortConnectionMode.Listen; - builder.AddInMemoryCollection(new Dictionary - { - {ConfigurationHelper.MakeKey(DiagnosticPortOptions.ConfigurationKey, nameof(DiagnosticPortOptions.ConnectionMode)), connectionMode.ToString()}, - {ConfigurationHelper.MakeKey(DiagnosticPortOptions.ConfigurationKey, nameof(DiagnosticPortOptions.EndpointName)), diagnosticPort} - }); - } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/AzureBlob/AzureBlobEgressProvider.cs b/src/Tools/dotnet-monitor/Egress/AzureBlob/AzureBlobEgressProvider.cs deleted file mode 100644 index f6865a3bb..000000000 --- a/src/Tools/dotnet-monitor/Egress/AzureBlob/AzureBlobEgressProvider.cs +++ /dev/null @@ -1,222 +0,0 @@ -// 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 Azure; -using Azure.Storage; -using Azure.Storage.Blobs; -using Azure.Storage.Blobs.Models; -using Azure.Storage.Blobs.Specialized; -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress.AzureStorage -{ - /// - /// Egress provider for egressing stream data to an Azure blob storage account. - /// - /// - /// Blobs created through this provider will overwrite existing blobs if they have the same blob name. - /// - internal class AzureBlobEgressProvider : - EgressProvider - { - public AzureBlobEgressProvider(AzureBlobEgressProviderOptions options, ILogger logger = null) - : base(options, logger) - { - } - - public override async Task EgressAsync( - Func> action, - string name, - AzureBlobEgressStreamOptions streamOptions, - CancellationToken token) - { - LogAndValidateOptions(streamOptions, name); - - try - { - var containerClient = await GetBlobContainerClientAsync(token); - - BlobClient blobClient = containerClient.GetBlobClient(GetBlobName(name)); - - Logger?.EgressProviderInvokeStreamAction(EgressProviderTypes.AzureBlobStorage); - using var stream = await action(token); - - // Write blob content, headers, and metadata - await blobClient.UploadAsync(stream, CreateHttpHeaders(streamOptions), streamOptions.Metadata, cancellationToken: token); - - string blobUriString = GetBlobUri(blobClient); - Logger?.EgressProviderSavedStream(EgressProviderTypes.AzureBlobStorage, blobUriString); - return blobUriString; - } - catch (AggregateException ex) when (ex.InnerException is RequestFailedException innerException) - { - throw CreateException(innerException); - } - catch (RequestFailedException ex) - { - throw CreateException(ex); - } - } - - public override async Task EgressAsync( - Func action, - string name, - AzureBlobEgressStreamOptions streamOptions, - CancellationToken token) - { - LogAndValidateOptions(streamOptions, name); - - try - { - var containerClient = await GetBlobContainerClientAsync(token); - - BlockBlobClient blobClient = containerClient.GetBlockBlobClient(GetBlobName(name)); - - // Write blob content - using (Stream blobStream = await blobClient.OpenWriteAsync(overwrite: true, cancellationToken: token)) - { - Logger?.EgressProviderInvokeStreamAction(EgressProviderTypes.AzureBlobStorage); - await action(blobStream, token); - - await blobStream.FlushAsync(token); - } - - // Write blob headers - await blobClient.SetHttpHeadersAsync(CreateHttpHeaders(streamOptions), cancellationToken: token); - - // Write blob metadata - await blobClient.SetMetadataAsync(streamOptions.Metadata, cancellationToken: token); - - string blobUriString = GetBlobUri(blobClient); - Logger?.EgressProviderSavedStream(EgressProviderTypes.AzureBlobStorage, blobUriString); - return blobUriString; - } - catch (AggregateException ex) when (ex.InnerException is RequestFailedException innerException) - { - throw CreateException(innerException); - } - catch (RequestFailedException ex) - { - throw CreateException(ex); - } - } - - private void LogAndValidateOptions(AzureBlobEgressStreamOptions streamOptions, string fileName) - { - Logger?.EgressProviderOptionValue(EgressProviderTypes.AzureBlobStorage, nameof(Options.AccountKey), Options.AccountKey, redact: true); - Logger?.EgressProviderOptionValue(EgressProviderTypes.AzureBlobStorage, nameof(Options.AccountUri), GetAccountUri(out _)); - Logger?.EgressProviderOptionValue(EgressProviderTypes.AzureBlobStorage, nameof(Options.BlobPrefix), Options.BlobPrefix); - Logger?.EgressProviderOptionValue(EgressProviderTypes.AzureBlobStorage, nameof(Options.ContainerName), Options.ContainerName); - Logger?.EgressProviderOptionValue(EgressProviderTypes.AzureBlobStorage, nameof(Options.SharedAccessSignature), Options.SharedAccessSignature, redact: true); - Logger?.EgressStreamOptionValue(EgressProviderTypes.AzureBlobStorage, nameof(streamOptions.ContentEncoding), streamOptions.ContentEncoding); - Logger?.EgressStreamOptionValue(EgressProviderTypes.AzureBlobStorage, nameof(streamOptions.ContentType), streamOptions.ContentType); - Logger?.EgressStreamOptionValue(EgressProviderTypes.AzureBlobStorage, nameof(streamOptions.Metadata), "[" + string.Join(", ", streamOptions.Metadata.Keys) + "]"); - Logger?.EgressProviderFileName(EgressProviderTypes.AzureBlobStorage, fileName); - - ValidateOptions(); - } - - private Uri GetAccountUri(out string accountName) - { - var blobUriBuilder = new BlobUriBuilder(Options.AccountUri); - blobUriBuilder.Query = null; - blobUriBuilder.BlobName = null; - blobUriBuilder.BlobContainerName = null; - - accountName = blobUriBuilder.AccountName; - - return blobUriBuilder.ToUri(); - } - - private async Task GetBlobContainerClientAsync(CancellationToken token) - { - BlobServiceClient serviceClient; - if (!string.IsNullOrWhiteSpace(Options.SharedAccessSignature)) - { - var serviceUriBuilder = new UriBuilder(Options.AccountUri) - { - Query = Options.SharedAccessSignature - }; - - serviceClient = new BlobServiceClient(serviceUriBuilder.Uri); - } - else if (!string.IsNullOrEmpty(Options.AccountKey)) - { - // Remove Query in case SAS token was specified - Uri accountUri = GetAccountUri(out string accountName); - - StorageSharedKeyCredential credential = new StorageSharedKeyCredential( - accountName, - Options.AccountKey); - - serviceClient = new BlobServiceClient(accountUri, credential); - } - else - { - throw CreateException("SharedAccessSignature or AccountKey must be specified."); - } - - BlobContainerClient containerClient = serviceClient.GetBlobContainerClient(Options.ContainerName); - await containerClient.CreateIfNotExistsAsync(cancellationToken: token); - - return containerClient; - } - - private string GetBlobName(string fileName) - { - if (string.IsNullOrEmpty(Options.BlobPrefix)) - { - return fileName; - } - else - { - return string.Concat(Options.BlobPrefix, "/", fileName); - } - } - - private BlobHttpHeaders CreateHttpHeaders(AzureBlobEgressStreamOptions streamOptions) - { - BlobHttpHeaders headers = new BlobHttpHeaders(); - headers.ContentEncoding = streamOptions.ContentEncoding; - headers.ContentType = streamOptions.ContentType; - return headers; - } - - private static string GetBlobUri(BlobBaseClient client) - { - // The BlobClient URI has the SAS token as the query parameter - // Remove the SAS token before returning the URI - UriBuilder outputBuilder = new UriBuilder(client.Uri); - outputBuilder.Query = null; - - return outputBuilder.Uri.ToString(); - } - - private static EgressException CreateException(string message) - { - return new EgressException(WrapMessage(message)); - } - - private static EgressException CreateException(Exception innerException) - { - return new EgressException(WrapMessage(innerException.Message), innerException); - } - - private static string WrapMessage(string innerMessage) - { - if (!string.IsNullOrEmpty(innerMessage)) - { - return $"Azure blob egress failed: {innerMessage}"; - } - else - { - return "Azure blob egress failed."; - } - } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/AzureBlob/AzureBlobEgressProviderOptions.cs b/src/Tools/dotnet-monitor/Egress/AzureBlob/AzureBlobEgressProviderOptions.cs deleted file mode 100644 index 95e0927c6..000000000 --- a/src/Tools/dotnet-monitor/Egress/AzureBlob/AzureBlobEgressProviderOptions.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress.AzureStorage -{ - /// - /// Egress provider options for Azure blob storage. - /// - internal class AzureBlobEgressProviderOptions : - EgressProviderOptions, - IValidatableObject - { - /// - /// The URI of the Azure blob storage account. - /// - [Required] - public Uri AccountUri { get; set; } - - /// - /// The acount key used to access the Azure blob storage account. - /// - /// - /// If not provided, must be specified. - /// - public string AccountKey { get; set; } - - /// - /// The shared access signature (SAS) used to access the azure blob storage account. - /// - /// - /// If not provided, must be specified. - /// - public string SharedAccessSignature { get; set; } - - /// - /// The name of the container to which the blob will be egressed. If egressing to the root container, - /// use the "$root" sentinel value. - /// - [Required] - public string ContainerName { get; set; } - - /// - /// The prefix to prepend to the blob name. - /// - public string BlobPrefix { get; set; } - - IEnumerable IValidatableObject.Validate(ValidationContext validationContext) - { - IList results = new List(); - - // One of the authentication keys/tokens is required - if (string.IsNullOrEmpty(AccountKey) && string.IsNullOrEmpty(SharedAccessSignature)) - { - results.Add(new ValidationResult($"The {nameof(AccountKey)} field or the {nameof(SharedAccessSignature)} field is required.")); - } - - return results; - } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/AzureBlob/AzureBlobEgressStreamOptions.cs b/src/Tools/dotnet-monitor/Egress/AzureBlob/AzureBlobEgressStreamOptions.cs deleted file mode 100644 index 65ef036c7..000000000 --- a/src/Tools/dotnet-monitor/Egress/AzureBlob/AzureBlobEgressStreamOptions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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.Generic; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress.AzureStorage -{ - /// - /// Egress stream options for Azure blob storage. - /// - internal class AzureBlobEgressStreamOptions - { - /// - /// The content encoding of the blob to be created. - /// - public string ContentEncoding { get; set; } - - /// - /// The content type of the blob to be created. - /// - public string ContentType { get; set; } - - /// - /// The metadata of the blob to be created. - /// - public Dictionary Metadata { get; } - = new Dictionary(StringComparer.Ordinal); - } -} diff --git a/src/Tools/dotnet-monitor/Egress/AzureBlobEgressFactory.cs b/src/Tools/dotnet-monitor/Egress/AzureBlobEgressFactory.cs deleted file mode 100644 index a86d1f8bf..000000000 --- a/src/Tools/dotnet-monitor/Egress/AzureBlobEgressFactory.cs +++ /dev/null @@ -1,167 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring; -using Microsoft.Diagnostics.Monitoring.RestServer; -using Microsoft.Diagnostics.Tools.Monitor.Egress.AzureStorage; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - /// - /// Creates for Azure blob storage egress. - /// - internal class AzureBlobEgressFactory : EgressFactory - { - private readonly ILoggerFactory _loggerFactory; - - public AzureBlobEgressFactory(ILoggerFactory loggerFactory) - : base(loggerFactory.CreateLogger()) - { - _loggerFactory = loggerFactory; - } - - public override bool TryCreate( - string providerName, - IConfigurationSection providerSection, - Dictionary egressProperties, - out ConfiguredEgressProvider provider) - { - var options = providerSection.Get(); - - // If account key was not provided but the name was provided, - // lookup the account key property value from EgressOptions.Properties - if (string.IsNullOrEmpty(options.AccountKey) && - !string.IsNullOrEmpty(options.AccountKeyName)) - { - if (TryGetPropertyValue(providerName, egressProperties, options.AccountKeyName, out string key)) - { - options.AccountKey = key; - } - } - - // If shared access signature (SAS) was not provided but the name was provided, - // lookup the SAS property value from EgressOptions.Properties - if (string.IsNullOrEmpty(options.SharedAccessSignature) && - !string.IsNullOrEmpty(options.SharedAccessSignatureName)) - { - if (TryGetPropertyValue(providerName, egressProperties, options.SharedAccessSignatureName, out string signature)) - { - options.SharedAccessSignature = signature; - } - } - - if (!TryValidateOptions(options, providerName)) - { - provider = null; - return false; - } - - provider = new Provider(options, _loggerFactory); - return true; - } - - private bool TryGetPropertyValue(string providerName, IDictionary egressProperties, string propertyName, out string value) - { - if (!egressProperties.TryGetValue(propertyName, out value)) - { - Logger.EgressProviderUnableToFindPropertyKey(providerName, propertyName); - return false; - } - return true; - } - - private class Provider : ConfiguredEgressProvider - { - private readonly AzureBlobEgressProvider _provider; - - public Provider(AzureBlobEgressProviderOptions options, ILoggerFactory loggerFactory) - { - _provider = new AzureBlobEgressProvider(options, loggerFactory.CreateLogger()); - } - - public override async Task EgressAsync( - Func> action, - string fileName, - string contentType, - IEndpointInfo source, - CancellationToken token) - { - var streamOptions = new AzureBlobEgressStreamOptions(); - streamOptions.ContentType = contentType; - FillBlobMetadata(streamOptions.Metadata, source); - - string blobUri = await _provider.EgressAsync(action, fileName, streamOptions, token); - - return new EgressResult("uri", blobUri); - } - - public override async Task EgressAsync( - Func action, - string fileName, - string contentType, - IEndpointInfo source, - CancellationToken token) - { - var streamOptions = new AzureBlobEgressStreamOptions(); - streamOptions.ContentType = contentType; - FillBlobMetadata(streamOptions.Metadata, source); - - string blobUri = await _provider.EgressAsync(action, fileName, streamOptions, token); - - return new EgressResult("uri", blobUri); - } - - private static void FillBlobMetadata(IDictionary metadata, IEndpointInfo source) - { - // Activity metadata - Activity activity = Activity.Current; - if (null != activity) - { - metadata.Add( - ActivityMetadataNames.ParentId, - activity.GetParentId()); - metadata.Add( - ActivityMetadataNames.SpanId, - activity.GetSpanId()); - metadata.Add( - ActivityMetadataNames.TraceId, - activity.GetTraceId()); - } - - // Artifact metadata - metadata.Add( - ArtifactMetadataNames.ArtifactSource.ProcessId, - source.ProcessId.ToString(CultureInfo.InvariantCulture)); - metadata.Add( - ArtifactMetadataNames.ArtifactSource.RuntimeInstanceCookie, - source.RuntimeInstanceCookie.ToString("N")); - } - } - - /// - /// Egress provider options for Azure blob storage with additional options. - /// - private class ConfigurationOptions : AzureBlobEgressProviderOptions - { - /// - /// The name of the account key used to look up the value from the map. - /// - public string AccountKeyName { get; set; } - - /// - /// The name of the shared access signature (SAS) used to look up the value from the map. - /// - public string SharedAccessSignatureName { get; set; } - } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/Configuration/EgressConfigurationExtensions.cs b/src/Tools/dotnet-monitor/Egress/Configuration/EgressConfigurationExtensions.cs deleted file mode 100644 index ba50f3d62..000000000 --- a/src/Tools/dotnet-monitor/Egress/Configuration/EgressConfigurationExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 Microsoft.Extensions.Configuration; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress.Configuration -{ - internal static class EgressConfigurationExtensions - { - /// - /// Get the Egress configuration section from the specified configuration. - /// - public static IConfigurationSection GetEgressSection(this IConfiguration configuration) - { - return configuration.GetSection(EgressOptions.ConfigurationKey); - } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/Configuration/EgressConfigureOptions.cs b/src/Tools/dotnet-monitor/Egress/Configuration/EgressConfigureOptions.cs deleted file mode 100644 index 948971f4b..000000000 --- a/src/Tools/dotnet-monitor/Egress/Configuration/EgressConfigureOptions.cs +++ /dev/null @@ -1,115 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring.RestServer; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress.Configuration -{ - /* - * == Egress Configuration Design == - * - Each type of egress is called an egress type. The following are the defined egress types: - * - AzureBlobStorage: Allows egressing stream data to a blob in Azure blob storage. - * - FileSystem: Allows egressing stream data to the file system. - * - An egress type in combination with its well defined set of options is called a egress provider. - * Each egress provider is named in the egress configuration in order to identify individual providers. - * - All egress configuration information is found in the root Egress configuration section. This section - * has two immediate subsections: - * - Providers: a mapping of egress provider names to egress provider options + the egress type. - * - Each provider must have a 'type' field, which must have a value that is one of the egress types. - * This field informs the egress configuration the type of egress provider that should be constructed - * with the remaining options. - * - If a provider's options fail validation, the failure is reported and the provider will not be - * available to be used as a means of egress. - * - Properties: a mapping of named values, typically for storing secrets (account keys, SAS, etc) by name. - */ - - /// - /// Binds egress configuration information to an instance. - /// - internal class EgressConfigureOptions : IConfigureOptions - { - private readonly IConfiguration _configuration; - private readonly ILogger _logger; - private readonly IDictionary _factories - = new Dictionary(StringComparer.OrdinalIgnoreCase); - - public EgressConfigureOptions( - ILoggerFactory loggerFactory, - IConfiguration configuration) - { - _configuration = configuration; - _logger = loggerFactory.CreateLogger(); - - // Register egress providers - _factories.Add(EgressProviderTypes.AzureBlobStorage, new AzureBlobEgressFactory(loggerFactory)); - _factories.Add(EgressProviderTypes.FileSystem, new FileSystemEgressFactory(loggerFactory)); - } - - public void Configure(EgressOptions options) - { - IConfigurationSection egressSection = _configuration.GetEgressSection(); - - IConfigurationSection propertiesSection = egressSection.GetSection(nameof(EgressOptions.Properties)); - propertiesSection.Bind(options.Properties); - - IConfigurationSection providersSection = egressSection.GetSection(nameof(EgressOptions.Providers)); - foreach (var providerSection in providersSection.GetChildren()) - { - string providerName = providerSection.Key; - - KeyValueLogScope providerNameScope = new KeyValueLogScope(); - providerNameScope.Values.Add("EgressProviderName", providerName); - using var providerNameRegistration = _logger.BeginScope(providerNameScope); - - CommonEgressProviderOptions commonOptions = new CommonEgressProviderOptions(); - providerSection.Bind(commonOptions); - - EgressProviderValidation validation = new EgressProviderValidation(providerName, _logger); - if (!validation.TryValidate(commonOptions)) - { - _logger.EgressProviderInvalidOptions(providerName); - } - - string providerType = commonOptions.Type; - KeyValueLogScope providerTypeScope = new KeyValueLogScope(); - providerTypeScope.Values.Add("EgressProviderType", providerType); - using var providerTypeRegistration = _logger.BeginScope(providerTypeScope); - - if (!_factories.TryGetValue(providerType, out EgressFactory factory)) - { - _logger.EgressProviderInvalidType(providerName, providerType); - continue; - } - - if (!factory.TryCreate(providerName, providerSection, options.Properties, out ConfiguredEgressProvider provider)) - { - _logger.EgressProviderInvalidOptions(providerName); - continue; - } - - options.Providers.Add(providerName, provider); - - _logger.EgressProviderAdded(providerName); - } - } - - /// - /// Configuration options to all egress providers. - /// - private class CommonEgressProviderOptions - { - /// - /// The type of the egress provider. - /// - [Required] - public string Type { get; set; } - } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/Configuration/EgressOptions.cs b/src/Tools/dotnet-monitor/Egress/Configuration/EgressOptions.cs deleted file mode 100644 index f9f7c4c5a..000000000 --- a/src/Tools/dotnet-monitor/Egress/Configuration/EgressOptions.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Generic; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress.Configuration -{ - /// - /// Configuration options for specifying egress providers. - /// - internal class EgressOptions - { - public const string ConfigurationKey = "Egress"; - - /// - /// Mapping of egress provider names to egress provider implementations. - /// - public Dictionary Providers { get; } - = new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Mapping of keyed values, typically used for naming a secret such as a storage account - /// key or shared access signature rather than embedding values directly in the egress provider options. - /// - public Dictionary Properties { get; } - = new Dictionary(StringComparer.OrdinalIgnoreCase); - } -} diff --git a/src/Tools/dotnet-monitor/Egress/ConfiguredEgressProvider.cs b/src/Tools/dotnet-monitor/Egress/ConfiguredEgressProvider.cs deleted file mode 100644 index 65f787f84..000000000 --- a/src/Tools/dotnet-monitor/Egress/ConfiguredEgressProvider.cs +++ /dev/null @@ -1,52 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring; -using Microsoft.Diagnostics.Monitoring.RestServer; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - /// - /// Base class for configured egress providers. A configured egress provider is an egress provider instance - /// that does not require any additional configuration beyond information about the stream that will be egressed. - /// - internal abstract class ConfiguredEgressProvider - { - /// - /// Egress a stream via a callback by returning the stream from the callback. - /// - /// Callback that is invoked in order to get the stream to be egressed. - /// The name of the stream data, typically used as the file name. - /// The type of content contained by the stream. - /// The source of the egress artifact; for for interrogation purposed to fill out additional stream option data. - /// The token to monitor for cancellation requests. - /// A task that completes with an describing the completion of the egress operation. - public abstract Task EgressAsync( - Func> action, - string fileName, - string contentType, - IEndpointInfo source, - CancellationToken token); - - /// - /// Egress a stream via a callback by writing to the provided stream. - /// - /// Callback that is invoked in order to write data to the provided stream. - /// The name of the stream data, typically used as the file name. - /// The type of content contained by the stream. - /// The source of the egress artifact; for for interrogation purposed to fill out additional stream option data. - /// The token to monitor for cancellation requests. - /// A task that completes with an describing the completion of the egress operation. - public abstract Task EgressAsync( - Func action, - string fileName, - string contentType, - IEndpointInfo source, - CancellationToken token); - } -} diff --git a/src/Tools/dotnet-monitor/Egress/EgressException.cs b/src/Tools/dotnet-monitor/Egress/EgressException.cs deleted file mode 100644 index 2402fddc3..000000000 --- a/src/Tools/dotnet-monitor/Egress/EgressException.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring; -using System; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - /// - /// Exception that egress providers can throw when an operational error occurs (e.g. failed to write the stream data). - /// - internal class EgressException : MonitoringException - { - public EgressException(string message) : base(message) { } - - public EgressException(string message, Exception innerException) : base(message, innerException) { } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/EgressFactory.cs b/src/Tools/dotnet-monitor/Egress/EgressFactory.cs deleted file mode 100644 index 00a2a0f87..000000000 --- a/src/Tools/dotnet-monitor/Egress/EgressFactory.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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 Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using System.Collections.Generic; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - /// - /// Base class for creating configured egress providers. - /// - internal abstract class EgressFactory - { - public EgressFactory(ILogger logger) - { - Logger = logger; - } - - /// - /// Attempts to create a from the provided - /// and . - /// - /// The name of the egress provider. - /// The configuration section containing the provider options. - /// The mapping of egress properties. - /// The created . - /// True if the provider was created; otherwise, false. - public abstract bool TryCreate( - string providerName, - IConfigurationSection providerSection, - Dictionary egressProperties, - out ConfiguredEgressProvider provider); - - protected bool TryValidateOptions(object value, string providerName) - { - Logger.EgressProviderValidatingOptions(providerName); - EgressProviderValidation validation = new EgressProviderValidation(providerName, Logger); - return validation.TryValidate(value); - } - - protected ILogger Logger { get; } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/EgressProvider.cs b/src/Tools/dotnet-monitor/Egress/EgressProvider.cs deleted file mode 100644 index cb4f2438b..000000000 --- a/src/Tools/dotnet-monitor/Egress/EgressProvider.cs +++ /dev/null @@ -1,114 +0,0 @@ -// 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 Microsoft.Extensions.Logging; -using System; -using System.ComponentModel.DataAnnotations; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - /* - * == Egress Provider Design == - * - Each type of egress is implemented as an EgressProvider. The following are the built-in providers: - * - AzureBlobEgressProvider: Allows egressing stream data to a blob in Azure blob storage. - * - FileSystemEgressProvider: Allows egressing stream data to the file system. - * - When constructing an egress provider, the options of the provider must be passed via the constructor. - * These options are typically use for describing to where stream data is to be egressed. - * - When invoking an egress provider, an action for acquiring the stream data, a file name, and stream options - * are required. The acquisition action can either provide the stream or allow the provider to provision the - * stream, which is passed into the action. The stream options represent additional data about the storage - * of the stream. - * - When an egress provider finishes egressing stream data, it will return a value that identifies the location - * of where the stream data was egressed. - */ - - /// - /// Base class for all egress implementations. - /// - /// Type of provider options class. - /// Type of stream options class. - /// - /// The type is typically used for providing information - /// about to where a stream is egressed (e.g. directory path, blob storage account, etc). - /// The type is typically used for providing information - /// about the storage of the stream itself (e.g. file system permissions, file metadata, etc). - /// Egress providers should throw when operational error occurs - /// (e.g. unable to write out stream data). Nearly all other exceptions are treats as programming - /// errors with the exception of and . - internal abstract class EgressProvider - where TProviderOptions : EgressProviderOptions - { - protected EgressProvider(TProviderOptions options, ILogger logger = null) - { - Logger = logger; - Options = options; - } - - /// - /// Egress a stream via a callback by returning the stream from the callback. - /// - /// Callback that is invoked in order to get the stream to be egressed. - /// The name of the stream, typically used as a file name. - /// Additional information to apply to the storage of the stream data. - /// The token to monitor for cancellation requests. - /// A task that completes with a value of the identifier of the egress result. Typically, - /// this is a path to access the stream without any information indicating whether any particular - /// user has access to it (e.g. no file system permissions or SAS tokens). - public virtual Task EgressAsync( - Func> action, - string name, - TStreamOptions streamOptions, - CancellationToken token) - { - Func wrappingAction = async (targetStream, token) => - { - using var sourceStream = await action(token); - - int copyBufferSize = Options.CopyBufferSize.GetValueOrDefault(0x100000); - - Logger?.EgressCopyActionStreamToEgressStream(copyBufferSize); - - await sourceStream.CopyToAsync( - targetStream, - copyBufferSize, - token); - }; - - return EgressAsync( - wrappingAction, - name, - streamOptions, - token); - } - - /// - /// Egress a stream via a callback by writing to the provided stream. - /// - /// Callback that is invoked in order to write data to the provided stream. - /// The name of the stream, typically used as a file name. - /// Additional information to apply to the storage of the stream data. - /// The token to monitor for cancellation requests. - /// A task that completes with a value of the identifier of the egress result. Typically, - /// this is a path to access the stream without any information indicating whether any particular - /// user has access to it (e.g. no file system permissions or SAS tokens). - public abstract Task EgressAsync( - Func action, - string name, - TStreamOptions streamOptions, - CancellationToken token); - - protected void ValidateOptions() - { - ValidationContext context = new ValidationContext(Options); - Validator.ValidateObject(Options, context, validateAllProperties: true); - } - - protected ILogger Logger { get; } - - protected TProviderOptions Options { get; } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/EgressProviderOptions.cs b/src/Tools/dotnet-monitor/Egress/EgressProviderOptions.cs deleted file mode 100644 index d96131b3c..000000000 --- a/src/Tools/dotnet-monitor/Egress/EgressProviderOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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. - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - /// - /// Egress provider options common to all egress providers. - /// - internal abstract class EgressProviderOptions - { - /// - /// Buffer size used when copying data from an egress callback returning a stream - /// to the egress callback that is provided a stream to which data is written. - /// - public int? CopyBufferSize { get; set; } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/EgressProviderTypes.cs b/src/Tools/dotnet-monitor/Egress/EgressProviderTypes.cs deleted file mode 100644 index 2c4d895d8..000000000 --- a/src/Tools/dotnet-monitor/Egress/EgressProviderTypes.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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. - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - internal static class EgressProviderTypes - { - public const string AzureBlobStorage = nameof(AzureBlobStorage); - - public const string FileSystem = nameof(FileSystem); - } -} diff --git a/src/Tools/dotnet-monitor/Egress/EgressProviderValidation.cs b/src/Tools/dotnet-monitor/Egress/EgressProviderValidation.cs deleted file mode 100644 index 87dd35616..000000000 --- a/src/Tools/dotnet-monitor/Egress/EgressProviderValidation.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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 Microsoft.Extensions.Logging; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel.DataAnnotations; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - /// - /// Helper class for validating egress options. - /// - internal class EgressProviderValidation - { - private readonly ILogger _logger; - private readonly string _providerName; - - public EgressProviderValidation(string providerName, ILogger logger = null) - { - _logger = logger; - _providerName = providerName; - } - - /// - /// Validates that the egress options pass the self-described validation. - /// - /// The instance of the options object. - /// True if the options object is valid; otherwise, false. - /// - /// Validation errors are logged as warnings. - /// - public bool TryValidate(object value) - { - ValidationContext validationContext = new ValidationContext(value); - ICollection results = new Collection(); - if (!Validator.TryValidateObject(value, validationContext, results, validateAllProperties: true)) - { - if (null != _logger) - { - foreach (ValidationResult result in results) - { - if (ValidationResult.Success != result) - { - _logger.EgressProviderOptionsValidationWarning(_providerName, result.ErrorMessage); - } - } - } - return false; - } - return true; - } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/EgressService.cs b/src/Tools/dotnet-monitor/Egress/EgressService.cs deleted file mode 100644 index 61145203b..000000000 --- a/src/Tools/dotnet-monitor/Egress/EgressService.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring; -using Microsoft.Diagnostics.Monitoring.RestServer; -using Microsoft.Diagnostics.Tools.Monitor.Egress.Configuration; -using Microsoft.Extensions.Options; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - /// - /// Egress service implementation required by the REST server. - /// - internal class EgressService : IEgressService - { - private readonly IOptionsMonitor _egressOptions; - - public EgressService(IOptionsMonitor egressOptions) - { - _egressOptions = egressOptions; - } - - public Task EgressAsync(string providerName, Func> action, string fileName, string contentType, IEndpointInfo source, CancellationToken token) - { - if (_egressOptions.CurrentValue.Providers.TryGetValue(providerName, out ConfiguredEgressProvider provider)) - { - return provider.EgressAsync(action, fileName, contentType, source, token); - } - throw new EgressException($"Egress provider '{providerName}' does not exist."); - } - - public Task EgressAsync(string providerName, Func action, string fileName, string contentType, IEndpointInfo source, CancellationToken token) - { - if (_egressOptions.CurrentValue.Providers.TryGetValue(providerName, out ConfiguredEgressProvider provider)) - { - return provider.EgressAsync(action, fileName, contentType, source, token); - } - throw new EgressException($"Egress provider '{providerName}' does not exist."); - } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/FileSystem/FileSystemEgressProvider.cs b/src/Tools/dotnet-monitor/Egress/FileSystem/FileSystemEgressProvider.cs deleted file mode 100644 index 8209b2bdf..000000000 --- a/src/Tools/dotnet-monitor/Egress/FileSystem/FileSystemEgressProvider.cs +++ /dev/null @@ -1,172 +0,0 @@ -// 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 Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Security; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress.FileSystem -{ - /// - /// Egress provider for egressing stream data to the file system. - /// - internal class FileSystemEgressProvider : - EgressProvider - { - public FileSystemEgressProvider(FileSystemEgressProviderOptions options, ILogger logger = null) - : base(options, logger) - { - } - - public override async Task EgressAsync( - Func action, - string name, - FileSystemEgressStreamOptions streamOptions, - CancellationToken token) - { - LogAndValidateOptions(name); - - if (!Directory.Exists(Options.DirectoryPath)) - { - WrapException(() => Directory.CreateDirectory(Options.DirectoryPath)); - } - - string targetPath = Path.Combine(Options.DirectoryPath, name); - - if (!string.IsNullOrEmpty(Options.IntermediateDirectoryPath)) - { - if (!Directory.Exists(Options.IntermediateDirectoryPath)) - { - WrapException(() => Directory.CreateDirectory(Options.IntermediateDirectoryPath)); - } - - string intermediateFilePath = null; - try - { - int remainingAttempts = 10; - bool intermediatePathExists; - do - { - intermediateFilePath = Path.Combine(Options.IntermediateDirectoryPath, Path.GetRandomFileName()); - intermediatePathExists = File.Exists(intermediateFilePath); - remainingAttempts--; - } - while (intermediatePathExists && remainingAttempts > 0); - - if (intermediatePathExists) - { - throw CreateException($"Unable to create unique intermediate file in '{Options.IntermediateDirectoryPath}' directory."); - } - - await WriteFileAsync(action, intermediateFilePath, token); - - WrapException(() => File.Move(intermediateFilePath, targetPath)); - } - finally - { - // Attempt to delete the intermediate file if it exists. - try - { - if (File.Exists(intermediateFilePath)) - { - File.Delete(intermediateFilePath); - } - } - catch (Exception) - { - } - } - } - else - { - await WriteFileAsync(action, targetPath, token); - } - - Logger?.EgressProviderSavedStream(EgressProviderTypes.FileSystem, targetPath); - return targetPath; - } - - private void LogAndValidateOptions(string fileName) - { - Logger?.EgressProviderOptionValue(EgressProviderTypes.FileSystem, nameof(Options.DirectoryPath), Options.DirectoryPath); - Logger?.EgressProviderOptionValue(EgressProviderTypes.FileSystem, nameof(Options.IntermediateDirectoryPath), Options.IntermediateDirectoryPath); - Logger?.EgressProviderFileName(EgressProviderTypes.FileSystem, fileName); - - ValidateOptions(); - } - - private async Task WriteFileAsync(Func action, string filePath, CancellationToken token) - { - using Stream fileStream = WrapException( - () => new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)); - - Logger?.EgressProviderInvokeStreamAction(EgressProviderTypes.FileSystem); - await action(fileStream, token); - - await fileStream.FlushAsync(token); - } - - private static void WrapException(Action action) - { - WrapException(() => { action(); return true; }); - } - - private static T WrapException(Func func) - { - try - { - return func(); - } - catch (DirectoryNotFoundException ex) - { - throw CreateException(ex); - } - catch (PathTooLongException ex) - { - throw CreateException(ex); - } - catch (IOException ex) - { - throw CreateException(ex); - } - catch (NotSupportedException ex) - { - throw CreateException(ex); - } - catch (SecurityException ex) - { - throw CreateException(ex); - } - catch (UnauthorizedAccessException ex) - { - throw CreateException(ex); - } - } - - private static EgressException CreateException(string message) - { - return new EgressException(WrapMessage(message)); - } - - private static EgressException CreateException(Exception innerException) - { - return new EgressException(WrapMessage(innerException.Message), innerException); - } - - private static string WrapMessage(string innerMessage) - { - if (!string.IsNullOrEmpty(innerMessage)) - { - return $"File system egress failed: {innerMessage}"; - } - else - { - return "File system egress failed."; - } - } - } -} \ No newline at end of file diff --git a/src/Tools/dotnet-monitor/Egress/FileSystem/FileSystemEgressProviderOptions.cs b/src/Tools/dotnet-monitor/Egress/FileSystem/FileSystemEgressProviderOptions.cs deleted file mode 100644 index e3156e66d..000000000 --- a/src/Tools/dotnet-monitor/Egress/FileSystem/FileSystemEgressProviderOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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.ComponentModel.DataAnnotations; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress.FileSystem -{ - /// - /// Egress provider options for file system egress. - /// - internal class FileSystemEgressProviderOptions : - EgressProviderOptions - { - /// - /// The directory path to which the stream data will be egressed. - /// - [Required] - public string DirectoryPath { get; set; } - - /// - /// The directory path to which the stream data will initially be written, if specified; the file will then - /// be moved/renamed to the directory specified in . - /// - public string IntermediateDirectoryPath { get; set; } - } -} diff --git a/src/Tools/dotnet-monitor/Egress/FileSystem/FileSystemEgressStreamOptions.cs b/src/Tools/dotnet-monitor/Egress/FileSystem/FileSystemEgressStreamOptions.cs deleted file mode 100644 index a3a19fdda..000000000 --- a/src/Tools/dotnet-monitor/Egress/FileSystem/FileSystemEgressStreamOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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. - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress.FileSystem -{ - /// - /// Egress stream options for file system egress. - /// - internal class FileSystemEgressStreamOptions - { - } -} diff --git a/src/Tools/dotnet-monitor/Egress/FileSystemEgressFactory.cs b/src/Tools/dotnet-monitor/Egress/FileSystemEgressFactory.cs deleted file mode 100644 index ff4558132..000000000 --- a/src/Tools/dotnet-monitor/Egress/FileSystemEgressFactory.cs +++ /dev/null @@ -1,87 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring; -using Microsoft.Diagnostics.Monitoring.RestServer; -using Microsoft.Diagnostics.Tools.Monitor.Egress.FileSystem; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor.Egress -{ - /// - /// Creates for file system egress. - /// - internal class FileSystemEgressFactory : EgressFactory - { - private ILoggerFactory _loggerFactory; - - public FileSystemEgressFactory(ILoggerFactory loggerFactory) - : base(loggerFactory.CreateLogger()) - { - _loggerFactory = loggerFactory; - } - - public override bool TryCreate( - string providerName, - IConfigurationSection providerSection, - Dictionary egressProperties, - out ConfiguredEgressProvider provider) - { - var options = providerSection.Get(); - - if (!TryValidateOptions(options, providerName)) - { - provider = null; - return false; - } - - provider = new Provider(options, _loggerFactory); - return true; - } - - private class Provider : ConfiguredEgressProvider - { - private readonly FileSystemEgressProvider _provider; - - public Provider(FileSystemEgressProviderOptions options, ILoggerFactory loggerFactory) - { - _provider = new FileSystemEgressProvider(options, loggerFactory.CreateLogger()); - } - - public override async Task EgressAsync( - Func> action, - string fileName, - string contentType, - IEndpointInfo source, - CancellationToken token) - { - var streamOptions = new FileSystemEgressStreamOptions(); - - string filepath = await _provider.EgressAsync(action, fileName, streamOptions, token); - - return new EgressResult("path", filepath); - } - - public override async Task EgressAsync( - Func action, - string fileName, - string contentType, - IEndpointInfo source, - CancellationToken token) - { - var streamOptions = new FileSystemEgressStreamOptions(); - - string filepath = await _provider.EgressAsync(action, fileName, streamOptions, token); - - return new EgressResult("path", filepath); - } - } - } -} diff --git a/src/Tools/dotnet-monitor/ExperimentalToolLogger.cs b/src/Tools/dotnet-monitor/ExperimentalToolLogger.cs deleted file mode 100644 index 5fa5bafd2..000000000 --- a/src/Tools/dotnet-monitor/ExperimentalToolLogger.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 Microsoft.Extensions.Logging; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - // FUTURE: This log message should be removed when dotnet-monitor is no longer an experimental tool - internal class ExperimentalToolLogger - { - private const string ExperimentMessage = "WARNING: dotnet-monitor is experimental and is not intended for production environments yet."; - private const string NoAuthMessage = "WARNING: Authentication has been disabled. This can pose a security risk and is not intended for production environments."; - private const string InsecureAuthMessage = "WARNING: Authentication is enabled over insecure http transport. This can pose a security risk and is not intended for production environments."; - - private readonly ILogger _logger; - - public ExperimentalToolLogger(ILogger logger) - { - _logger = logger; - } - - public void LogExperimentMessage() - { - _logger.LogWarning(ExperimentMessage); - } - - public void LogNoAuthMessage() - { - _logger.LogWarning(NoAuthMessage); - } - - public void LogInsecureAuthMessage() - { - _logger.LogWarning(InsecureAuthMessage); - } - - public static void AddLogFilter(ILoggingBuilder builder) - { - builder.AddFilter(typeof(ExperimentalToolLogger).FullName, LogLevel.Warning); - } - } -} diff --git a/src/Tools/dotnet-monitor/FilteredEndpointInfoSource.cs b/src/Tools/dotnet-monitor/FilteredEndpointInfoSource.cs deleted file mode 100644 index fd5881b70..000000000 --- a/src/Tools/dotnet-monitor/FilteredEndpointInfoSource.cs +++ /dev/null @@ -1,110 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring; -using Microsoft.Diagnostics.NETCore.Client; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - /// - /// Wraps an based on the provided configuration - /// and filters the current process from consideration. - /// - internal class FilteredEndpointInfoSource : IEndpointInfoSourceInternal, IAsyncDisposable - { - private readonly DiagnosticPortOptions _portOptions; - private readonly int? _processIdToFilterOut; - private readonly Guid? _runtimeInstanceCookieToFilterOut; - private readonly IEndpointInfoSourceInternal _source; - - public FilteredEndpointInfoSource(IOptions portOptions) - { - _portOptions = portOptions.Value; - switch (_portOptions.ConnectionMode) - { - case DiagnosticPortConnectionMode.Connect: - _source = new ClientEndpointInfoSource(); - break; - case DiagnosticPortConnectionMode.Listen: - _source = new ServerEndpointInfoSource(_portOptions.EndpointName); - break; - default: - throw new InvalidOperationException($"Unhandled connection mode: {_portOptions.ConnectionMode}"); - } - - // Filter out the current process based on the connection mode. - if (RuntimeInfo.IsDiagnosticsEnabled) - { - int pid = Process.GetCurrentProcess().Id; - - // Regardless of connection mode, can use the runtime instance cookie to filter self out. - try - { - var client = new DiagnosticsClient(pid); - Guid runtimeInstanceCookie = client.GetProcessInfo().RuntimeInstanceCookie; - if (Guid.Empty != runtimeInstanceCookie) - { - _runtimeInstanceCookieToFilterOut = runtimeInstanceCookie; - } - } - catch (Exception) - { - } - - // If connecting to runtime instances, filter self out. In listening mode, it's likely - // that multiple processes have the same PID in multi-container scenarios. - if (DiagnosticPortConnectionMode.Connect == portOptions.Value.ConnectionMode) - { - _processIdToFilterOut = pid; - } - } - } - - public async Task> GetEndpointInfoAsync(CancellationToken token) - { - var endpointInfos = await _source.GetEndpointInfoAsync(token); - - // Apply process ID filter - if (_processIdToFilterOut.HasValue) - { - endpointInfos = endpointInfos.Where(info => info.ProcessId != _processIdToFilterOut.Value); - } - - // Apply runtime instance cookie filter - if (_runtimeInstanceCookieToFilterOut.HasValue) - { - endpointInfos = endpointInfos.Where(info => info.RuntimeInstanceCookie != _runtimeInstanceCookieToFilterOut.Value); - } - - return endpointInfos; - } - - public async ValueTask DisposeAsync() - { - if (_source is IDisposable disposable) - { - disposable.Dispose(); - } - else if (_source is IAsyncDisposable asyncDisposable) - { - await asyncDisposable.ConfigureAwait(false).DisposeAsync(); - } - } - - public void Start() - { - if (_source is ServerEndpointInfoSource source) - { - source.Start(_portOptions.MaxConnections.GetValueOrDefault(ReversedDiagnosticsServer.MaxAllowedConnections)); - } - } - } -} diff --git a/src/Tools/dotnet-monitor/FilteredEndpointInfoSourceHostedService.cs b/src/Tools/dotnet-monitor/FilteredEndpointInfoSourceHostedService.cs deleted file mode 100644 index d72de2b6f..000000000 --- a/src/Tools/dotnet-monitor/FilteredEndpointInfoSourceHostedService.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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.Threading; -using System.Threading.Tasks; -using Microsoft.Diagnostics.Monitoring; -using Microsoft.Extensions.Hosting; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - /// - /// A hosted service that ensures the - /// starts monitoring for connectable processes. - /// - internal class FilteredEndpointInfoSourceHostedService : IHostedService - { - private readonly FilteredEndpointInfoSource _source; - - public FilteredEndpointInfoSourceHostedService(IEndpointInfoSource source) - { - _source = (FilteredEndpointInfoSource)source; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _source.Start(); - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - } -} diff --git a/src/Tools/dotnet-monitor/GenerateApiKeyCommandHandler.cs b/src/Tools/dotnet-monitor/GenerateApiKeyCommandHandler.cs deleted file mode 100644 index f4ca7bb56..000000000 --- a/src/Tools/dotnet-monitor/GenerateApiKeyCommandHandler.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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.Generic; -using System.CommandLine; -using System.CommandLine.IO; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - /// - /// Used to generate Api Key for authentication. The first output is - /// part of the Authorization header, and is the Base64 encoded key. - /// The second output is a hex encoded string of the hash of the secret. - /// - internal sealed class GenerateApiKeyCommandHandler - { - public Task GenerateApiKey(CancellationToken token, IConsole console) - { - using RandomNumberGenerator rng = RandomNumberGenerator.Create(); - using HashAlgorithm hashAlgorithm = SHA256.Create(); - - byte[] secret = new byte[32]; - rng.GetBytes(secret); - - byte[] hash = hashAlgorithm.ComputeHash(secret); - - Console.Out.WriteLine(FormattableString.Invariant($"Authorization: {Monitoring.RestServer.AuthConstants.ApiKeySchema} {Convert.ToBase64String(secret)}")); - Console.Out.Write("HashedSecret "); - foreach (byte b in hash) - { - console.Out.Write(b.ToString("X2")); - } - console.Out.WriteLine(); - - return Task.FromResult(0); - } - } -} diff --git a/src/Tools/dotnet-monitor/LoggingExtensions.cs b/src/Tools/dotnet-monitor/LoggingExtensions.cs deleted file mode 100644 index 006ba41b2..000000000 --- a/src/Tools/dotnet-monitor/LoggingExtensions.cs +++ /dev/null @@ -1,164 +0,0 @@ -// 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 Microsoft.Extensions.Logging; -using System; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - internal static class LoggingExtensions - { - private static readonly Action _egressProviderAdded = - LoggerMessage.Define( - eventId: new EventId(1, "EgressProviderAdded"), - logLevel: LogLevel.Debug, - formatString: "Provider '{providerName}': Added."); - - private static readonly Action _egressProviderInvalidOptions = - LoggerMessage.Define( - eventId: new EventId(2, "EgressProviderInvalidOptions"), - logLevel: LogLevel.Error, - formatString: "Provider '{providerName}': Invalid options."); - - private static readonly Action _egressProviderInvalidType = - LoggerMessage.Define( - eventId: new EventId(3, "EgressProviderInvalidType"), - logLevel: LogLevel.Error, - formatString: "Provider '{providerName}': Type '{providerType}' is not supported."); - - private static readonly Action _egressProviderValidatingOptions = - LoggerMessage.Define( - eventId: new EventId(4, "EgressProviderValidatingOptions"), - logLevel: LogLevel.Debug, - formatString: "Provider '{providerName}': Validating options."); - - private static readonly Action _egressCopyActionStreamToEgressStream = - LoggerMessage.Define( - eventId: new EventId(5, "EgressCopyActionStreamToEgressStream"), - logLevel: LogLevel.Debug, - formatString: "Copying action stream to egress stream with buffer size {bufferSize}"); - - private static readonly Action _egressProviderOptionsValidationWarning = - LoggerMessage.Define( - eventId: new EventId(6, "EgressProviderOptionsValidationWarning"), - logLevel: LogLevel.Warning, - formatString: "Provider '{providerName}': {validationWarning}"); - - private static readonly Action _egressProviderOptionValue = - LoggerMessage.Define( - eventId: new EventId(7, "EgressProviderOptionValue"), - logLevel: LogLevel.Debug, - formatString: "Provider {providerType}: Provider option {optionName} = {optionValue}"); - - private static readonly Action _egressStreamOptionValue = - LoggerMessage.Define( - eventId: new EventId(8, "EgressStreamOptionValue"), - logLevel: LogLevel.Debug, - formatString: "Provider {providerType}: Stream option {optionName} = {optionValue}"); - - private static readonly Action _egressProviderFileName = - LoggerMessage.Define( - eventId: new EventId(9, "EgressProviderFileName"), - logLevel: LogLevel.Debug, - formatString: "Provider {providerType}: File name = {fileName}"); - - private static readonly Action _egressProviderUnableToFindPropertyKey = - LoggerMessage.Define( - eventId: new EventId(10, "EgressProvideUnableToFindPropertyKey"), - logLevel: LogLevel.Warning, - formatString: "Provider {providerType}: Unable to find '{keyName}' key in egress properties"); - - private static readonly Action _egressProviderInvokeStreamAction = - LoggerMessage.Define( - eventId: new EventId(11, "EgressProviderInvokeStreamAction"), - logLevel: LogLevel.Debug, - formatString: "Provider {providerType}: Invoking stream action."); - - private static readonly Action _egressProviderSavedStream = - LoggerMessage.Define( - eventId: new EventId(12, "EgressProviderSavedStream"), - logLevel: LogLevel.Debug, - formatString: "Provider {providerType}: Saved stream to {path}"); - - public static void EgressProviderAdded(this ILogger logger, string providerName) - { - _egressProviderAdded(logger, providerName, null); - } - - public static void EgressProviderInvalidOptions(this ILogger logger, string providerName) - { - _egressProviderInvalidOptions(logger, providerName, null); - } - - public static void EgressProviderInvalidType(this ILogger logger, string providerName, string providerType) - { - _egressProviderInvalidType(logger, providerName, providerType, null); - } - - public static void EgressProviderValidatingOptions(this ILogger logger, string providerName) - { - _egressProviderValidatingOptions(logger, providerName, null); - } - - public static void EgressCopyActionStreamToEgressStream(this ILogger logger, int bufferSize) - { - _egressCopyActionStreamToEgressStream(logger, bufferSize, null); - } - - public static void EgressProviderOptionsValidationWarning(this ILogger logger, string providerName, string validationWarning) - { - _egressProviderOptionsValidationWarning(logger, providerName, validationWarning, null); - } - - public static void EgressProviderOptionValue(this ILogger logger, string providerName, string optionName, Uri optionValue) - { - logger.EgressProviderOptionValue(providerName, optionName, optionValue?.ToString()); - } - - public static void EgressProviderOptionValue(this ILogger logger, string providerName, string optionName, string optionValue, bool redact = false) - { - if (redact) - { - optionValue = Redact(optionValue); - } - - _egressProviderOptionValue(logger, providerName, optionName, optionValue, null); - } - - public static void EgressStreamOptionValue(this ILogger logger, string providerName, string optionName, string optionValue, bool redact = false) - { - if (redact) - { - optionValue = Redact(optionValue); - } - - _egressStreamOptionValue(logger, providerName, optionName, optionValue, null); - } - - public static void EgressProviderFileName(this ILogger logger, string providerName, string fileName) - { - _egressProviderFileName(logger, providerName, fileName, null); - } - - public static void EgressProviderUnableToFindPropertyKey(this ILogger logger, string providerName, string keyName) - { - _egressProviderUnableToFindPropertyKey(logger, providerName, keyName, null); - } - - public static void EgressProviderInvokeStreamAction(this ILogger logger, string providerName) - { - _egressProviderInvokeStreamAction(logger, providerName, null); - } - - public static void EgressProviderSavedStream(this ILogger logger, string providerName, string path) - { - _egressProviderSavedStream(logger, providerName, path, null); - } - - private static string Redact(string value) - { - return string.IsNullOrEmpty(value) ? value : ""; - } - } -} diff --git a/src/Tools/dotnet-monitor/Program.cs b/src/Tools/dotnet-monitor/Program.cs deleted file mode 100644 index f049e3e53..000000000 --- a/src/Tools/dotnet-monitor/Program.cs +++ /dev/null @@ -1,108 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Tools.Common; -using System; -using System.CommandLine; -using System.CommandLine.Builder; -using System.CommandLine.Invocation; -using System.CommandLine.Parsing; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - [Flags] - internal enum SinkType - { - None = 0, - Console = 1, - All = 0xff - } - - class Program - { - private static Command GenerateApiKeyCommand() => - new Command( - name: "generatekey", - description: "Generate api key and hash for authentication") - { - CommandHandler.Create(new GenerateApiKeyCommandHandler().GenerateApiKey) - }; - - private static Command CollectCommand() => - new Command( - name: "collect", - description: "Monitor logs and metrics in a .NET application send the results to a chosen destination.") - { - // Handler - CommandHandler.Create(new DiagnosticsMonitorCommandHandler().Start), - Urls(), MetricUrls(), ProvideMetrics(), DiagnosticPort(), NoAuth() - }; - - private static Option Urls() => - new Option( - aliases: new[] { "-u", "--urls" }, - description: "Bindings for the REST api.") - { - Argument = new Argument(name: "urls", getDefaultValue: () => new[] { "https://localhost:52323" }) - }; - - private static Option MetricUrls() => - new Option( - aliases: new[] { "--metricUrls" }, - description: "Bindings for metrics") - { - Argument = new Argument(name: "metricUrls", getDefaultValue: () => new[] { GetDefaultMetricsEndpoint() }) - }; - - private static Option ProvideMetrics() => - new Option( - aliases: new[] { "-m", "--metrics" }, - description: "Enable publishing of metrics") - { - Argument = new Argument(name: "metrics", getDefaultValue: () => true) - }; - - private static Option DiagnosticPort() => - new Option( - alias: "--diagnostic-port", - description: "The fully qualified path and filename of the diagnostic port to which runtime instances can connect.") - { - Argument = new Argument(name: "diagnosticPort") - }; - - private static Option NoAuth() => - new Option( - alias: "--no-auth", - description: "Turn off authentication." - ) - { - Argument = new Argument(name: "noAuth", getDefaultValue: () => false) - }; - - private static string GetDefaultMetricsEndpoint() - { - string endpoint = "http://localhost:52325"; - if (RuntimeInfo.IsInDockerContainer) - { - //Necessary for prometheus scraping - endpoint = "http://*:52325"; - } - return endpoint; - } - - public static Task Main(string[] args) - { - var parser = new CommandLineBuilder() - .AddCommand(CollectCommand()) - .AddCommand(GenerateApiKeyCommand()) - .UseDefaults() - .Build(); - return parser.InvokeAsync(args); - } - } -} diff --git a/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs b/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs deleted file mode 100644 index d7c6a92fb..000000000 --- a/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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 Microsoft.Diagnostics.Monitoring.RestServer; -using Microsoft.Diagnostics.Tools.Monitor.Egress; -using Microsoft.Diagnostics.Tools.Monitor.Egress.Configuration; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - internal static class ServiceCollectionExtensions - { - public static IServiceCollection ConfigureMetrics(this IServiceCollection services, IConfiguration configuration) - { - return ConfigureOptions(services, configuration, MetricsOptions.ConfigurationKey); - } - - public static IServiceCollection ConfigureApiKeyConfiguration(this IServiceCollection services, IConfiguration configuration) - { - return ConfigureOptions(services, configuration, ApiAuthenticationOptions.ConfigurationKey); - } - - private static IServiceCollection ConfigureOptions(IServiceCollection services, IConfiguration configuration, string key) where T : class - { - return services.Configure(configuration.GetSection(key)); - } - - public static IServiceCollection ConfigureEgress(this IServiceCollection services, IConfiguration configuration) - { - // Register change token for EgressOptions binding - services.AddSingleton>(new ConfigurationChangeTokenSource(configuration.GetEgressSection())); - - // Configure EgressOptions to bind to the Egress configuration key. - // The options are manually created due to how the Providers property - // holds concrete implementations that are based on the 'type' property - // for each provider entry. - services.AddSingleton, EgressConfigureOptions>(); - - // Register IEgressService implementation that provides egressing - // of artifacts for the REST server. - services.AddSingleton(); - - return services; - } - } -} diff --git a/src/Tools/dotnet-monitor/Startup.cs b/src/Tools/dotnet-monitor/Startup.cs deleted file mode 100644 index 5fc1aec2d..000000000 --- a/src/Tools/dotnet-monitor/Startup.cs +++ /dev/null @@ -1,134 +0,0 @@ -// 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 Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.ResponseCompression; -using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.Diagnostics.Monitoring.RestServer; -using Microsoft.Diagnostics.Monitoring.RestServer.Controllers; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.IO.Compression; -using System.Linq; - -namespace Microsoft.Diagnostics.Tools.Monitor -{ - internal class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddMvc(options => - { - options.Filters.Add(new ProducesAttribute("application/json")); - - options.EnableEndpointRouting = false; - }) - .SetCompatibilityVersion(CompatibilityVersion.Latest) - .AddApplicationPart(typeof(DiagController).Assembly); - - services.Configure(options => - { - options.InvalidModelStateResponseFactory = context => - { - var details = new ValidationProblemDetails(context.ModelState); - var result = new BadRequestObjectResult(details); - result.ContentTypes.Add("application/problem+json"); - return result; - }; - }); - - services.Configure(options => - { - options.Level = CompressionLevel.Optimal; - }); - - services.AddResponseCompression(configureOptions => - { - configureOptions.Providers.Add(); - configureOptions.MimeTypes = new List { "application/octet-stream" }; - }); - - // This is needed to allow the StreamingLogger to synchronously write to the output stream. - // Eventually should switch StreamingLoggger to something that allows for async operations. - services.Configure(options => - { - options.AllowSynchronousIO = true; - }); - - var metricsOptions = new MetricsOptions(); - Configuration.Bind(MetricsOptions.ConfigurationKey, metricsOptions); - if (metricsOptions.Enabled) - { - services.AddSingleton(); - services.AddHostedService(); - } - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure( - IApplicationBuilder app, - IWebHostEnvironment env, - ExperimentalToolLogger logger, - IAuthOptions options) - { - logger.LogExperimentMessage(); - if (options.KeyAuthenticationMode == KeyAuthenticationMode.NoAuth) - { - logger.LogNoAuthMessage(); - } - else - { - //Auth is enabled and we are binding on http. Make sure we log a warning. - - string hostingUrl = Configuration.GetValue(WebHostDefaults.ServerUrlsKey); - string[] urls = ConfigurationHelper.SplitValue(hostingUrl); - foreach(BindingAddress address in urls.Select(BindingAddress.Parse)) - { - if (string.Equals(Uri.UriSchemeHttp, address.Scheme, StringComparison.OrdinalIgnoreCase)) - { - logger.LogInsecureAuthMessage(); - break; - } - } - } - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseHsts(); - } - - app.UseAuthentication(); - app.UseAuthorization(); - - CorsConfiguration corsConfiguration = new CorsConfiguration(); - Configuration.Bind(nameof(CorsConfiguration), corsConfiguration); - if (!string.IsNullOrEmpty(corsConfiguration.AllowedOrigins)) - { - app.UseCors(builder => builder.WithOrigins(corsConfiguration.GetOrigins()).AllowAnyHeader().AllowAnyMethod()); - } - - app.UseResponseCompression(); - app.UseMvc(); - } - } -} diff --git a/src/Tools/dotnet-monitor/appsettings.Development.json b/src/Tools/dotnet-monitor/appsettings.Development.json deleted file mode 100644 index e203e9407..000000000 --- a/src/Tools/dotnet-monitor/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/src/Tools/dotnet-monitor/appsettings.json b/src/Tools/dotnet-monitor/appsettings.json deleted file mode 100644 index aeb12963a..000000000 --- a/src/Tools/dotnet-monitor/appsettings.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Diagnostics": "Information", - "Microsoft.Hosting.Lifetime": "Information" - }, - "Console": { - "FormatterName": "simple", - "FormatterOptions": { - "IncludeScopes": true, - "TimestampFormat": "HH:mm:ss " - } - }, - "EventLog": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Diagnostics": "Information", - "Microsoft.Hosting.Lifetime": "Information" - } - } - }, - "AllowedHosts": "*" -} diff --git a/src/Tools/dotnet-monitor/dotnet-monitor.csproj b/src/Tools/dotnet-monitor/dotnet-monitor.csproj deleted file mode 100644 index d4cc29177..000000000 --- a/src/Tools/dotnet-monitor/dotnet-monitor.csproj +++ /dev/null @@ -1,54 +0,0 @@ - - - - netcoreapp3.1 - linux-x64;linux-musl-x64;win-x64 - linux-x64;linux-musl-x64;win-x64 - Microsoft.Diagnostics.Tools.Monitor - dotnet-monitor - .NET Core Diagnostic Monitoring Tool - Diagnostic - false - $(Description) - Major - - monitor - - 5.0.0 - preview - 4 - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - - - - - -