Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flex] Adding support for OpenTelemetry #10094

Merged
merged 8 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/python.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.PythonWorker" Version="4.27.0" />
<PackageReference Include="Microsoft.Azure.Functions.PythonWorker" Version="4.28.1" />
</ItemGroup>
</Project>
7 changes: 6 additions & 1 deletion release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
<!-- Please add your release notes in the following format:
- My change description (#PR)
-->
- Update Python Worker Version to [4.28.1](https://github.com/Azure/azure-functions-python-worker/releases/tag/4.28.1)
- DotnetIsolated worker artifact clean up (#9976)
RohitRanjanMS marked this conversation as resolved.
Show resolved Hide resolved
- Move symbols from dotnet-isolated worker to symbols package
- Removed linux executables from dotnet-isolated worker.
- Removed linux executables from dotnet-isolated worker.
- Update Azure.Identity to 1.11.0 (#10002)
- Update PowerShell worker 7.2 to [4.0.3220](https://github.com/Azure/azure-functions-powershell-worker/releases/tag/v4.0.3220)
- Update PowerShell worker 7.4 to [4.0.3219](https://github.com/Azure/azure-functions-powershell-worker/releases/tag/v4.0.3219)
- Update Node.js Worker Version to [3.10.0](https://github.com/Azure/azure-functions-nodejs-worker/releases/tag/v3.10.0) (#9999)
53 changes: 39 additions & 14 deletions src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Microsoft.Azure.WebJobs.Script.Config;
using Microsoft.Azure.WebJobs.Script.Description;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
using Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry;
using Microsoft.Azure.WebJobs.Script.Eventing;
using Microsoft.Azure.WebJobs.Script.Extensions;
using Microsoft.Azure.WebJobs.Script.Grpc.Eventing;
Expand Down Expand Up @@ -565,6 +566,12 @@ internal void ApplyCapabilities(IDictionary<string, string> capabilities, GrpcCa
_isWorkerApplicationInsightsLoggingEnabled = true;
}

if (bool.TryParse(_workerCapabilities.GetCapabilityState(RpcWorkerConstants.WorkerOpenTelemetryEnabled), out bool otelEnabled) &&
otelEnabled)
{
ScriptHost.WorkerOpenTelemetryEnabled = true;
}

// If http proxying is enabled, we need to get the proxying endpoint of this worker
var httpUri = _workerCapabilities.GetCapabilityState(RpcWorkerConstants.HttpUri);
if (!string.IsNullOrEmpty(httpUri))
Expand Down Expand Up @@ -1670,29 +1677,47 @@ private void AddSample<T>(List<T> samples, T sample)

private void AddAdditionalTraceContext(MapField<string, string> attributes, ScriptInvocationContext context)
{
// This is only applicable for AI agents running along side worker
if (_environment.IsApplicationInsightsAgentEnabled())
bool isOtelEnabled = _scriptHostOptions?.Value.TelemetryMode == TelemetryMode.OpenTelemetry;
bool isAIEnabled = _environment.IsApplicationInsightsAgentEnabled();

if (isOtelEnabled || isAIEnabled)
{
attributes[ScriptConstants.LogPropertyProcessIdKey] = Convert.ToString(_rpcWorkerProcess.Id);
if (context.FunctionMetadata.Properties.TryGetValue(ScriptConstants.LogPropertyHostInstanceIdKey, out var hostInstanceIdValue))
{
attributes[ScriptConstants.LogPropertyHostInstanceIdKey] = Convert.ToString(hostInstanceIdValue);
}
if (context.FunctionMetadata.Properties.TryGetValue(LogConstants.CategoryNameKey, out var categoryNameValue))
{
attributes[LogConstants.CategoryNameKey] = Convert.ToString(categoryNameValue);
string id = Convert.ToString(hostInstanceIdValue);

if (isOtelEnabled)
{
Activity.Current?.AddTag(ResourceSemanticConventions.FaaSInstance, id);
}
if (isAIEnabled)
{
attributes[ScriptConstants.LogPropertyHostInstanceIdKey] = id;
}
}
string sessionid = Activity.Current?.GetBaggageItem(ScriptConstants.LiveLogsSessionAIKey);
if (!string.IsNullOrEmpty(sessionid))
}

if (isAIEnabled)
{
attributes[ScriptConstants.LogPropertyProcessIdKey] = Convert.ToString(_rpcWorkerProcess.Id);
attributes[ScriptConstants.OperationNameKey] = context.FunctionMetadata.Name;
string sessionId = Activity.Current?.GetBaggageItem(ScriptConstants.LiveLogsSessionAIKey);
if (!string.IsNullOrEmpty(sessionId))
{
attributes[ScriptConstants.LiveLogsSessionAIKey] = sessionid;
attributes[ScriptConstants.LiveLogsSessionAIKey] = sessionId;
}
string operationName = context.FunctionMetadata.Name;
if (!string.IsNullOrEmpty(operationName))

if (context.FunctionMetadata.Properties.TryGetValue(LogConstants.CategoryNameKey, out var categoryNameValue))
{
attributes[ScriptConstants.OperationNameKey] = operationName;
attributes[LogConstants.CategoryNameKey] = Convert.ToString(categoryNameValue);
}
}

if (isOtelEnabled)
{
Activity.Current?.AddTag(ResourceSemanticConventions.FaaSName, context.FunctionMetadata.Name);
Activity.Current?.AddTag(ResourceSemanticConventions.FaaSTrigger, OpenTelemetryConstants.ResolveTriggerType(context.FunctionMetadata?.Trigger?.Type));
}
}

private sealed class ExecutingInvocation : IDisposable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Script.Description;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
using Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry;
using Microsoft.Azure.WebJobs.Script.Extensions;
using Microsoft.Azure.WebJobs.Script.Host;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -58,6 +60,9 @@ public async Task ExecuteAsync(HttpRequest request, CancellationToken cancellati
{
coldStartData.Add("dispatchDuration", dispatchStopwatch.GetElapsedTime().TotalMilliseconds);
}

// Add tag for cold start
Activity.Current?.AddTag(ResourceSemanticConventions.FaaSColdStart, true);
}

var sw = ValueStopwatch.StartNew();
Expand Down
3 changes: 2 additions & 1 deletion src/WebJobs.Script.WebHost/Metrics/HostMetricsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Linq;
using System.Threading;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry;
using Microsoft.Azure.WebJobs.Script.Metrics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -60,7 +61,7 @@ public void Start()
{
listener.EnableMeasurementEvents(instrument);

var instanceIdTag = instrument.Meter.Tags.FirstOrDefault(t => t.Key == TelemetryAttributes.ServiceInstanceId);
var instanceIdTag = instrument.Meter.Tags.FirstOrDefault(t => t.Key == ResourceSemanticConventions.ServiceInstanceId);
InstanceId = instanceIdTag.Value?.ToString() ?? string.Empty;
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.10.2" />
<PackageReference Include="Azure.Identity" Version="1.11.2" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.2.0" />
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.22.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" />
Expand Down
26 changes: 26 additions & 0 deletions src/WebJobs.Script.WebHost/WebJobsScriptHostService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using static Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames;
using IApplicationLifetime = Microsoft.AspNetCore.Hosting.IApplicationLifetime;

Expand Down Expand Up @@ -872,6 +875,7 @@ private async Task Orphan(IHost instance, CancellationToken cancellationToken =
else
{
DisposeDependencyTrackingModule(instance);
FlushOpenTelemetry(instance);
instance.Dispose();
_logger.LogDebug("ScriptHost disposed");
}
Expand Down Expand Up @@ -958,6 +962,28 @@ public object GetService(Type serviceType)
return Services?.GetService(serviceType);
}

private void FlushOpenTelemetry(IHost host)
{
var logger = GetHostLogger(host);
foreach (var meterProvider in host.Services.GetServices<MeterProvider>())
{
logger.LogDebug(@"Flushing {providerName} ...", meterProvider);
meterProvider.ForceFlush();
}

foreach (var tracerProvider in host.Services.GetServices<TracerProvider>())
{
logger.LogDebug(@"Flushing {providerName} ...", tracerProvider);
tracerProvider.ForceFlush();
}

foreach (var logProvider in host.Services.GetServices<ILoggerProvider>().Where(i => i is OpenTelemetryLoggerProvider))
{
logger.LogDebug(@"Disposing {providerName} ...", logProvider);
logProvider.Dispose();
}
}

protected virtual void Dispose(bool disposing)
{
if (!_disposed)
Expand Down
1 change: 1 addition & 0 deletions src/WebJobs.Script/Config/ConfigurationSectionNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ public static class ConfigurationSectionNames
public const string Retry = "retry";
public const string SequentialJobHostRestart = JobHost + ":sequentialRestart";
public const string SendCanceledInvocationsToWorker = "sendCanceledInvocationsToWorker";
public const string TelemetryMode = "telemetryMode";
}
}
8 changes: 7 additions & 1 deletion src/WebJobs.Script/Config/ScriptJobHostOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using Microsoft.Azure.WebJobs.Script.Description;
using Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry;

namespace Microsoft.Azure.WebJobs.Script
{
Expand Down Expand Up @@ -42,7 +43,7 @@ public string RootScriptPath
public string InstanceId { get; }

/// <summary>
/// Gets or sets NugetFallBackPath
/// Gets or sets NugetFallBackPath.
/// </summary>
public string NugetFallBackPath { get; set; }

Expand Down Expand Up @@ -129,5 +130,10 @@ public string RootScriptPath
/// invocation to the worker.
/// </summary>
public bool SendCanceledInvocationsToWorker { get; set; } = true;

/// <summary>
/// Gets or sets the telemetry mode.
/// </summary>
internal TelemetryMode TelemetryMode { get; set; } = TelemetryMode.ApplicationInsights;
}
}
11 changes: 11 additions & 0 deletions src/WebJobs.Script/Config/ScriptJobHostOptionsSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

Expand Down Expand Up @@ -73,6 +74,16 @@ public void Configure(ScriptJobHostOptions options)
options.TestDataPath = webHostOptions.TestDataPath;
options.IsFileSystemReadOnly = webHostOptions.IsFileSystemReadOnly;
options.IsStandbyConfiguration = webHostOptions.IsStandbyConfiguration;

var telemetryModeSection = jobHostSection.GetSection(ConfigurationSectionNames.TelemetryMode);
if (telemetryModeSection.Exists() && Enum.TryParse(telemetryModeSection.Value, true, out TelemetryMode telemetryMode))
{
options.TelemetryMode = telemetryMode;
}
else
{
options.TelemetryMode = TelemetryMode.ApplicationInsights;
}
}

private void ConfigureFunctionTimeout(ScriptJobHostOptions options)
Expand Down
6 changes: 6 additions & 0 deletions src/WebJobs.Script/Config/ScriptSettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ public virtual string ApplicationInsightsConnectionString
set => SetSetting(EnvironmentSettingNames.AppInsightsConnectionString, value);
}

public virtual string OtlpEndpoint
{
get => GetSetting(EnvironmentSettingNames.OtlpEndpoint);
set => SetSetting(EnvironmentSettingNames.OtlpEndpoint, value);
}

public virtual string GetSetting(string settingKey)
{
if (string.IsNullOrEmpty(settingKey))
Expand Down
2 changes: 2 additions & 0 deletions src/WebJobs.Script/Diagnostics/MetricEventNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public static class MetricEventNames
public const string ApplicationStartLatency = "host.application.start";
public const string ApplicationInsightsEnabled = "host.applicationinsights.enabled";
public const string ApplicationInsightsDisabled = "host.applicationinsights.disabled";
public const string OpenTelemetryAzMonEnabled = "host.otel.azmon.enabled";
public const string OpenTelemetryOtlpEnabled = "host.otel.otlp.enabled";
public const string HostStartupLatency = "host.startup.latency";
public const string HostStartupReadFunctionMetadataLatency = "host.startup.readfunctionmetadata.latency";
public const string HostStartupInitializeBindingProvidersLatency = "host.startup.initializebindingproviders.latency";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Azure.WebJobs.Logging;
using OpenTelemetry;

namespace Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry
{
internal class ActivitySanitizingProcessor : BaseProcessor<Activity>
{
private static readonly IReadOnlyCollection<string> TagsToSanitize = new HashSet<string> { ResourceSemanticConventions.QueryUrl, ResourceSemanticConventions.FullUrl };

private ActivitySanitizingProcessor() { }

public static ActivitySanitizingProcessor Instance { get; } = new ActivitySanitizingProcessor();

public override void OnEnd(Activity data)
{
Sanitize(data);

base.OnEnd(data);
}

private static void Sanitize(Activity data)
{
foreach (var t in TagsToSanitize)
{
if (data.GetTagItem(t) is string s and not null)
{
var sanitizedValue = Sanitizer.Sanitize(s);
data.SetTag(t, sanitizedValue);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using OpenTelemetry.Resources;

namespace Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry
{
internal sealed class FunctionsResourceDetector : IResourceDetector
{
public Resource Detect()
{
List<KeyValuePair<string, object>> attributeList = new(9);
try
{
string serviceName = Environment.GetEnvironmentVariable(OpenTelemetryConstants.SiteNameEnvVar);
string version = typeof(ScriptHost).Assembly.GetName().Version.ToString();

attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.ServiceVersion, version));
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.AISDKPrefix, $@"{OpenTelemetryConstants.SDKPrefix}:{version}"));
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.ProcessId, Process.GetCurrentProcess().Id));
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.FaaSVersion, version));

// Add these attributes only if running in Azure.
if (!string.IsNullOrEmpty(serviceName))
{
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.ServiceName, serviceName));
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.CloudProvider, OpenTelemetryConstants.AzureCloudProviderValue));
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.CloudPlatform, OpenTelemetryConstants.AzurePlatformValue));

string region = Environment.GetEnvironmentVariable(OpenTelemetryConstants.RegionNameEnvVar);
if (!string.IsNullOrEmpty(region))
{
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.CloudRegion, region));
}

var azureResourceUri = GetAzureResourceURI(serviceName);
if (azureResourceUri != null)
{
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.CloudResourceId, azureResourceUri));
}
}
}
catch
{
// return empty resource.
return Resource.Empty;
}

return new Resource(attributeList);
}

private static string GetAzureResourceURI(string websiteSiteName)
{
string websiteResourceGroup = Environment.GetEnvironmentVariable(OpenTelemetryConstants.ResourceGroupEnvVar);
string websiteOwnerName = Environment.GetEnvironmentVariable(OpenTelemetryConstants.OwnerNameEnvVar) ?? string.Empty;
int idx = websiteOwnerName.IndexOf('+', StringComparison.Ordinal);
string subscriptionId = idx > 0 ? websiteOwnerName.Substring(0, idx) : websiteOwnerName;

if (string.IsNullOrEmpty(websiteResourceGroup) || string.IsNullOrEmpty(subscriptionId))
{
return null;
}

return $"/subscriptions/{subscriptionId}/resourceGroups/{websiteResourceGroup}/providers/Microsoft.Web/sites/{websiteSiteName}";
}
}
}