Skip to content

Commit 46f1ad1

Browse files
authored
[azsdk-cli] Add open telemetry (#11934)
* Add open telemetry * Only log traces to console with --debug
1 parent e73e4be commit 46f1ad1

7 files changed

Lines changed: 77 additions & 70 deletions

File tree

tools/azsdk-cli/Azure.Sdk.Tools.Cli/Azure.Sdk.Tools.Cli.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
1919
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.12.0" />
2020
<PackageReference Include="Microsoft.Graph" Version="5.79.0" />
21+
<PackageReference Include="OpenTelemetry" Version="1.12.0" />
22+
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
23+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
24+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
25+
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
26+
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
2127
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
2228
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
2329
<PackageReference Include="Azure.Identity" Version="1.14.2" />

tools/azsdk-cli/Azure.Sdk.Tools.Cli/Configuration/Constants.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System.IO;
2-
31
namespace Azure.Sdk.Tools.Cli.Configuration;
42

53
public static class Constants
@@ -18,4 +16,6 @@ public static class Constants
1816
public const string AZURE_SDK_TOOLS_PATH = "azure-sdk-tools";
1917
public const string AZURE_COMMON_LABELS_PATH = "tools/github/data/common-labels.csv";
2018
public const string AZURE_CODEOWNERS_PATH = ".github/CODEOWNERS";
19+
20+
public const string TOOLS_ACTIVITY_SOURCE = "azsdk.tools";
2121
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using OpenTelemetry;
2+
using System.Diagnostics;
3+
4+
namespace Azure.Sdk.Tools.Cli.Core;
5+
6+
public sealed class TelemetryProcessor : BaseProcessor<Activity>
7+
{
8+
public override void OnStart(Activity activity)
9+
{
10+
}
11+
12+
public override void OnEnd(Activity activity)
13+
{
14+
// TODO: Add progress/logging for MCP clients so we can see the spans in debug mode
15+
// without it being treated as a parse failure when using AddConsoleExporter()
16+
17+
// Do any post-processing work here
18+
/*
19+
var toolName = activity.GetTagItem("mcp.tool.name") as string;
20+
if (!string.IsNullOrEmpty(toolName))
21+
{
22+
activity.SetTag("CustomToolProperty", toolName);
23+
}
24+
*/
25+
}
26+
}

tools/azsdk-cli/Azure.Sdk.Tools.Cli/Program.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
using System.CommandLine;
44
using System.CommandLine.Builder;
55
using System.CommandLine.Parsing;
6+
using OpenTelemetry;
7+
using OpenTelemetry.Trace;
68
using Azure.Sdk.Tools.Cli.Commands;
9+
using Azure.Sdk.Tools.Cli.Core;
710
using Azure.Sdk.Tools.Cli.Helpers;
811
using Azure.Sdk.Tools.Cli.Models;
912
using Azure.Sdk.Tools.Cli.Services;
13+
using Azure.Sdk.Tools.Cli.Configuration;
1014

1115
namespace Azure.Sdk.Tools.Cli;
1216

@@ -33,13 +37,23 @@ public static async Task<int> Main(string[] args)
3337
public static WebApplicationBuilder CreateAppBuilder(string[] args)
3438
{
3539
var isCLI = IsCLI(args);
40+
var (outputFormat, debug) = SharedOptions.GetGlobalOptionValues(args);
41+
var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
3642

3743
// Any args that ASP.NET doesn't recognize will be _ignored_ by the CreateBuilder, so we don't need to ONLY
3844
// pass unmatched ASP.NET config values like --ASPNET_URLS to the builder. It'll just quietly ignore everything
3945
// it doesn't recognize.
4046
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
4147

42-
var (outputFormat, debug) = SharedOptions.GetGlobalOptionValues(args);
48+
builder.Services.AddOpenTelemetry()
49+
.WithTracing(b => {
50+
b.AddSource(Constants.TOOLS_ACTIVITY_SOURCE)
51+
.AddAspNetCoreInstrumentation()
52+
.AddHttpClientInstrumentation()
53+
.AddProcessor(new TelemetryProcessor());
54+
if (debug) { b.AddConsoleExporter(); }
55+
})
56+
.UseOtlpExporter();
4357

4458
// Log everything to stderr in mcp mode so the client doesn't try to interpret stdout messages that aren't json rpc
4559
var logErrorThreshold = isCLI ? LogLevel.Error : LogLevel.Debug;
@@ -62,8 +76,6 @@ public static WebApplicationBuilder CreateAppBuilder(string[] args)
6276
});
6377

6478
// add the console logger
65-
var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
66-
6779
builder.Services.AddLogging(l =>
6880
{
6981
l.AddConsole();

tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/TelemetryService.cs

Lines changed: 0 additions & 60 deletions
This file was deleted.

tools/azsdk-cli/Azure.Sdk.Tools.Cli/Tools/Example/ExampleTool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ public async Task<ExampleServiceResponse> DemonstrateAzureService(string? tenant
178178
var credential = azureService.GetCredential(tenantId);
179179

180180
// Get token for demonstration (but don't log the actual token)
181-
var tokenResult = await credential.GetTokenAsync(new Core.TokenRequestContext(["https://management.azure.com/.default"]), ct);
181+
var tokenResult = await credential.GetTokenAsync(new Azure.Core.TokenRequestContext(["https://management.azure.com/.default"]), ct);
182182

183183
var details = new Dictionary<string, string>
184184
{

tools/azsdk-cli/Azure.Sdk.Tools.Cli/Tools/HostServer/TelemetryWrapper.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,48 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
3+
using System.Diagnostics;
34
using ModelContextProtocol.Protocol;
45
using ModelContextProtocol.Server;
5-
using Azure.Sdk.Tools.Cli.Services;
6+
using Azure.Sdk.Tools.Cli.Configuration;
7+
using System.Text.Json;
68

79
namespace Azure.Sdk.Tools.Cli.Tools;
810

911
public class InstrumentedTool(ILogger logger, McpServerTool innerTool, string toolName) : DelegatingMcpServerTool(innerTool)
1012
{
13+
private static readonly ActivitySource source = new(Constants.TOOLS_ACTIVITY_SOURCE);
14+
15+
private readonly JsonSerializerOptions serializerOptions = new()
16+
{
17+
WriteIndented = false,
18+
};
19+
1120
public override async ValueTask<CallToolResult> InvokeAsync(RequestContext<CallToolRequestParams> request, CancellationToken ct = default)
1221
{
22+
using var activity = source.StartActivity("tool.invoke", ActivityKind.Internal);
23+
if (activity == null)
24+
{
25+
logger.LogError("Null activity created for tool {ToolName}", toolName);
26+
}
27+
1328
try
1429
{
15-
TelemetryService.InstrumentationBefore(logger, toolName, request.Params?.Arguments, ct);
30+
activity?.SetTag("name", toolName);
31+
var args = JsonSerializer.Serialize(request.Params?.Arguments, serializerOptions);
32+
activity?.SetTag("args", args);
33+
1634
var result = await base.InvokeAsync(request, ct);
17-
TelemetryService.InstrumentationAfter(logger, toolName, result, ct);
35+
36+
var content = JsonSerializer.Serialize(result.Content);
37+
activity?.SetTag("result", content);
38+
activity?.SetStatus(ActivityStatusCode.Ok);
39+
1840
return result;
1941
}
2042
catch (Exception ex)
2143
{
22-
TelemetryService.InstrumentationError(logger, toolName, ex, ct);
44+
activity?.AddException(ex);
45+
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
2346
throw;
2447
}
2548
}

0 commit comments

Comments
 (0)