Skip to content

Commit

Permalink
Extend the use of OpenTelemetry to Core API entry points (#2054)
Browse files Browse the repository at this point in the history
* Update projects for OpenTelemetry dependencies

* Activate telemetry on completions endpoint

* Add OpenTelemetry logging provider

* Improve telemetry activity tag names

* Changed OpenTelemetry.Instrumentation.Http and AspNetCore to 1.9.0

---------

Co-authored-by: Carey Payette <[email protected]>
  • Loading branch information
ciprianjichici and codingbandit authored Dec 11, 2024
1 parent 2d32d35 commit 316c8be
Show file tree
Hide file tree
Showing 32 changed files with 253 additions and 85 deletions.
1 change: 0 additions & 1 deletion src/dotnet/Agent/Agent.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 4 additions & 1 deletion src/dotnet/AzureOpenAI/AzureOpenAI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0-beta.1" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>

<ItemGroup>
Expand Down
12 changes: 11 additions & 1 deletion src/dotnet/Common/Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@
<PackageReference Include="Microsoft.DeepDev.TokenizerLib" Version="1.3.3" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.7.2" />
<PackageReference Include="Microsoft.Graph" Version="5.48.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="2.17.4" />
<PackageReference Include="Microsoft.Identity.Web" Version="3.5.0" />
<PackageReference Include="Mime-Detective" Version="24.7.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Asp.Versioning.Http" Version="8.1.0" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.4" />
<PackageReference Include="OpenTelemetry" Version="1.9.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.4.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
Expand All @@ -78,6 +79,10 @@
<LastGenOutput>AuthorizableActionNames.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="Templates\TelemetryActivityNames.tt">
<LastGenOutput>TelemetryActivityNames.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="Templates\AuthorizationKeyVaultSecretNames.tt">
<LastGenOutput>AuthorizationKeyVaultSecretNames.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
Expand Down Expand Up @@ -165,6 +170,11 @@
<AutoGen>True</AutoGen>
<DependentUpon>AuthorizableActionNames.tt</DependentUpon>
</Compile>
<Compile Update="Templates\TelemetryActivityNames.cs">
<DependentUpon>TelemetryActivityNames.tt</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="Templates\AuthorizationKeyVaultSecretNames.cs">
<DependentUpon>AuthorizationKeyVaultSecretNames.tt</DependentUpon>
<DesignTime>True</DesignTime>
Expand Down
13 changes: 13 additions & 0 deletions src/dotnet/Common/Constants/Data/TelemetryActivities.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"telemetry_activity_source": "CoreAPI",
"telemetry_activities": [
{
"name": "AsyncCompletions_StartCompletionOperation"
},
{
"name": "Completions_GetCompletion"
}
]
}
]
33 changes: 33 additions & 0 deletions src/dotnet/Common/Constants/Telemetry/TelemetryActivityTagNames.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace FoundationaLLM.Common.Constants.Telemetry
{
/// <summary>
/// Provides the names of the tags used in telemetry activities.
/// </summary>
public static class TelemetryActivityTagNames
{
/// <summary>
/// The FoundationaLLM instance identifier tag.
/// </summary>
public const string InstanceId = "FoundationaLLM-InstanceId";

/// <summary>
/// The FoundationaLLM conversation identifier tag.
/// </summary>
public const string ConversationId = "FoundationaLLM-ConversationId";

/// <summary>
/// The FoundationaLLM operation identifier tag.
/// </summary>
public const string OperationId = "FoundationaLLM-OperationId";

/// <summary>
/// The FoundationaLLM user principal name tag.
/// </summary>
public const string UPN = "FoundationaLLM-UPN";

/// <summary>
/// The FoundationaLLM user identifier tag.
/// </summary>
public const string UserId = "FoundationaLLM-UserId";
}
}
70 changes: 50 additions & 20 deletions src/dotnet/Common/Services/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Azure.Monitor.OpenTelemetry.AspNetCore;
using Azure.Monitor.OpenTelemetry.Exporter;
using FoundationaLLM.Common.Authentication;
using FoundationaLLM.Common.Constants;
using FoundationaLLM.Common.Constants.Authorization;
using FoundationaLLM.Common.Constants.Configuration;
using FoundationaLLM.Common.Interfaces;
using FoundationaLLM.Common.Models.Configuration.CosmosDB;
Expand All @@ -9,21 +11,18 @@
using FoundationaLLM.Common.Services.Azure;
using FoundationaLLM.Common.Services.Security;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Azure.Cosmos;
using Microsoft.AspNetCore.Identity;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Web;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Authentication;
using FoundationaLLM.Common.Constants.Authorization;
using Microsoft.Graph.Models;
using System.Diagnostics;

namespace FoundationaLLM
{
Expand Down Expand Up @@ -60,23 +59,54 @@ public static void AddOpenTelemetry(this IHostApplicationBuilder builder,
string connectionStringConfigurationKey,
string serviceName)
{
// Add the OpenTelemetry telemetry service and send telemetry data to Azure Monitor.
builder.Services.AddOpenTelemetry().UseAzureMonitor(options =>
var resourceBuilder = ResourceBuilder
.CreateDefault()
.AddAttributes(new Dictionary<string, object>
{
{ "service.name", serviceName },
{ "service.namespace", "FoundationaLLM" },
{ "service.version", builder.Configuration[EnvironmentVariables.FoundationaLLM_Version]! },
{ "service.instance.id", ValidatedEnvironment.MachineName }
});


// Add the OpenTelemetry logging provider and send logs to Azure Monitor.
builder.Logging.AddOpenTelemetry(openTelemetryLoggerOptions =>
{
options.ConnectionString = builder.Configuration[connectionStringConfigurationKey];
openTelemetryLoggerOptions.SetResourceBuilder(resourceBuilder);
openTelemetryLoggerOptions.IncludeFormattedMessage = true;
openTelemetryLoggerOptions.IncludeScopes = true;
openTelemetryLoggerOptions.AddAzureMonitorLogExporter(azureMonitorOptions =>
{
azureMonitorOptions.ConnectionString = builder.Configuration[connectionStringConfigurationKey];
});
});

// Create a dictionary of resource attributes.
var resourceAttributes = new Dictionary<string, object> {
{ "service.name", serviceName },
{ "service.namespace", "FoundationaLLM" },
{ "service.instance.id", ValidatedEnvironment.MachineName }
};

// Configure the OpenTelemetry tracer provider to add the resource attributes to all traces.
builder.Services.ConfigureOpenTelemetryTracerProvider((sp, builder) =>
builder.ConfigureResource(resourceBuilder =>
resourceBuilder.AddAttributes(resourceAttributes)));
// Add the OpenTelemetry telemetry service and send telemetry data to Azure Monitor.
builder.Services.AddOpenTelemetry()
.WithTracing(traceProviderBuilder => traceProviderBuilder
.SetResourceBuilder(resourceBuilder)
.AddSource("Azure.*")
.AddSource(serviceName)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation(httpOptions => httpOptions.FilterHttpRequestMessage = (_) =>
{
// Azure SDKs create their own client span before calling the service using HttpClient
// In this case, we would see two spans corresponding to the same operation
// 1) created by Azure SDK 2) created by HttpClient
// To prevent this duplication we are filtering the span from HttpClient
// as span from Azure SDK contains all relevant information needed.
var parentActivity = Activity.Current?.Parent;
if (parentActivity != null && parentActivity.Source.Name.Equals("Azure.Core.Http"))
{
return false;
}
return true;
})
.AddAzureMonitorTraceExporter(azureMonitorOptions =>
{
azureMonitorOptions.ConnectionString = builder.Configuration[connectionStringConfigurationKey];
}));
}

/// <summary>
Expand Down
16 changes: 16 additions & 0 deletions src/dotnet/Common/Telemetry/TelemetryActivitySources.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using FoundationaLLM.Common.Constants;
using System.Diagnostics;

namespace FoundationaLLM.Common.Telemetry
{
/// <summary>
/// Provides predefined telemetry activity sources for the components of the platform.
/// </summary>
public class TelemetryActivitySources
{
/// <summary>
/// The activity source for the Core API.
/// </summary>
public static readonly ActivitySource CoreAPIActivitySource = new (ServiceNames.CoreAPI);
}
}
19 changes: 19 additions & 0 deletions src/dotnet/Common/Templates/TelemetryActivityNames.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace FoundationaLLM.Common.Constants.Telemetry
{
/// <summary>
/// Defines constants for all telemetry activity names.
/// </summary>
public static class TelemetryActivityNames
{
/// <summary>
/// The telemetry activity name for the CoreAPI AsyncCompletions_StartCompletionOperation action.
/// </summary>
public const string CoreAPI_AsyncCompletions_StartCompletionOperation = "AsyncCompletions_StartCompletionOperation";

/// <summary>
/// The telemetry activity name for the CoreAPI Completions_GetCompletion action.
/// </summary>
public const string CoreAPI_Completions_GetCompletion = "Completions_GetCompletion";

}
}
36 changes: 36 additions & 0 deletions src/dotnet/Common/Templates/TelemetryActivityNames.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.IO" #>
<#@ assembly name="System.Text.Json" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ assembly name="System.Memory" #>
<#@ import namespace="System.Text.Json" #>
<#@ import namespace="System.Text.Json.Nodes" #>
<#@ output extension=".cs" #>
<#
string inputFileName = this.Host.ResolvePath($"../Constants/Data/TelemetryActivities.json");
string inputContent = File.ReadAllText(inputFileName);
var jsonObjects = JsonNode.Parse(inputContent)!;
#>
namespace FoundationaLLM.Common.Constants.Telemetry
{
/// <summary>
/// Defines constants for all telemetry activity names.
/// </summary>
public static class TelemetryActivityNames
{
<#
foreach (var jsonObject in jsonObjects.AsArray())
{
foreach (var telemetryActivity in jsonObject["telemetry_activities"].AsArray())
{#>
/// <summary>
/// The telemetry activity name for the <#= jsonObject["telemetry_activity_source"] #> <#= telemetryActivity["name"] #> action.
/// </summary>
public const string <#= jsonObject["telemetry_activity_source"] #>_<#= telemetryActivity["name"] #> = "<#= telemetryActivity["name"] #>";

<#}
}
#>
}
}
1 change: 0 additions & 1 deletion src/dotnet/Configuration/Configuration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.1" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.7.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="Azure.Data.AppConfiguration" Version="1.4.0" />
</ItemGroup>

Expand Down
7 changes: 5 additions & 2 deletions src/dotnet/Core/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0-beta.1" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
<PackageReference Include="Azure.Search.Documents" Version="11.5.1" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.1" />
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.39.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="Polly.Core" Version="8.3.1" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>

<ItemGroup>
Expand Down
35 changes: 33 additions & 2 deletions src/dotnet/CoreAPI/Controllers/CompletionsController.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
using FoundationaLLM.Common.Authentication;
using FoundationaLLM.Common.Constants.Authorization;
using FoundationaLLM.Common.Constants.ResourceProviders;
using FoundationaLLM.Common.Constants.Telemetry;
using FoundationaLLM.Common.Exceptions;
using FoundationaLLM.Common.Interfaces;
using FoundationaLLM.Common.Models.Orchestration;
using FoundationaLLM.Common.Models.Orchestration.Request;
using FoundationaLLM.Common.Models.ResourceProviders;
using FoundationaLLM.Common.Models.ResourceProviders.Agent;
using FoundationaLLM.Common.Telemetry;
using FoundationaLLM.Core.Interfaces;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;

namespace FoundationaLLM.Core.API.Controllers
{
Expand Down Expand Up @@ -68,10 +71,25 @@ public CompletionsController(ICoreService coreService,
/// <param name="instanceId">The instance ID of the current request.</param>
/// <param name="completionRequest">The user prompt for which to generate a completion.</param>
[HttpPost("completions", Name = "GetCompletion")]
public async Task<IActionResult> GetCompletion(string instanceId, [FromBody] CompletionRequest completionRequest) =>
!string.IsNullOrWhiteSpace(completionRequest.SessionId)
public async Task<IActionResult> GetCompletion(string instanceId, [FromBody] CompletionRequest completionRequest)
{
using var telemetryActivity = TelemetryActivitySources.CoreAPIActivitySource.StartActivity(
TelemetryActivityNames.CoreAPI_Completions_GetCompletion,
ActivityKind.Consumer,
parentContext: default,
tags: new Dictionary<string, object?>
{
{ TelemetryActivityTagNames.InstanceId, instanceId },
{ TelemetryActivityTagNames.ConversationId, completionRequest.SessionId ?? "N/A" },
{ TelemetryActivityTagNames.OperationId, completionRequest.OperationId ?? "N/A" },
{ TelemetryActivityTagNames.UPN, _callContext.CurrentUserIdentity?.UPN ?? "N/A" },
{ TelemetryActivityTagNames.UserId, _callContext.CurrentUserIdentity?.UserId ?? "N/A" }
});

return !string.IsNullOrWhiteSpace(completionRequest.SessionId)
? Ok(await _coreService.GetChatCompletionAsync(instanceId, completionRequest))
: Ok(await _coreService.GetCompletionAsync(instanceId, completionRequest));
}

/// <summary>
/// Begins a completion operation.
Expand All @@ -82,6 +100,19 @@ public async Task<IActionResult> GetCompletion(string instanceId, [FromBody] Com
[HttpPost("async-completions")]
public async Task<ActionResult<LongRunningOperation>> StartCompletionOperation(string instanceId, CompletionRequest completionRequest)
{
using var telemetryActivity = TelemetryActivitySources.CoreAPIActivitySource.StartActivity(
TelemetryActivityNames.CoreAPI_AsyncCompletions_StartCompletionOperation,
ActivityKind.Consumer,
parentContext: default,
tags: new Dictionary<string, object?>
{
{ TelemetryActivityTagNames.InstanceId, instanceId },
{ TelemetryActivityTagNames.ConversationId, completionRequest.SessionId ?? "N/A" },
{ TelemetryActivityTagNames.OperationId, completionRequest.OperationId ?? "N/A" },
{ TelemetryActivityTagNames.UPN, _callContext.CurrentUserIdentity?.UPN ?? "N/A" },
{ TelemetryActivityTagNames.UserId, _callContext.CurrentUserIdentity?.UserId ?? "N/A" }
});

var state = await _coreService.StartCompletionOperation(instanceId, completionRequest);
return Accepted(state);
}
Expand Down
Loading

0 comments on commit 316c8be

Please sign in to comment.