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

OpenTelemetry support #9985

Merged
merged 25 commits into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6c76fe5
Initial commit
RohitRanjanMS Apr 10, 2024
93a98af
Fixed UT
RohitRanjanMS Apr 10, 2024
24b7ddc
-
RohitRanjanMS Apr 10, 2024
acba142
Apply suggestions from code review
RohitRanjanMS Apr 10, 2024
7113c05
Code review comments.
RohitRanjanMS Apr 11, 2024
cffa41f
Code review.
RohitRanjanMS Apr 11, 2024
1326913
Fixed UT
RohitRanjanMS Apr 11, 2024
22dfdaa
Adding handling of EventSources that were created before we set the E…
RohitRanjanMS Apr 11, 2024
b2eec42
Updated trace and logs filetring.
RohitRanjanMS Apr 12, 2024
8fd637a
Fixed build.
RohitRanjanMS Apr 12, 2024
109cccd
Do not assign permissions when file does not exist (#9982)
jviau Apr 10, 2024
4657a2a
Ensuring non http invocation responses are not processed by IHttpProx…
kshyju Apr 10, 2024
0cb401d
DotnetIsolated worker artifact clean up - moving symbols (#9983)
kshyju Apr 10, 2024
f42f2b5
Special case EventGrid sourced blob triggers for grouping (#9987)
jviau Apr 11, 2024
7c390e8
Fix CI branches and missing 'minorVersionPrefix' (#9995)
jviau Apr 11, 2024
8c95991
Remove crank tool (#9992)
jviau Apr 11, 2024
5c59f22
Minor code cleanup in FunctionsSyncManager (#9991)
kshyju Apr 11, 2024
44c4a62
Update Node.js Worker to 3.10.0 (#9999)
castrodd Apr 12, 2024
2350dc9
Update WebHost.deps.json (#10005)
jviau Apr 12, 2024
2880141
Merge branch 'dev' of https://github.com/Azure/azure-functions-host i…
RohitRanjanMS Apr 12, 2024
d1e141a
Revert "Fixed build."
RohitRanjanMS Apr 12, 2024
147f7d9
Dev changes.
RohitRanjanMS Apr 12, 2024
5afdf55
Updating deps.json
RohitRanjanMS Apr 12, 2024
85dde60
Updated runassenblies
RohitRanjanMS Apr 12, 2024
161f977
Fixed RuntimeAssembliesUpdateTests
RohitRanjanMS Apr 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
RohitRanjanMS marked this conversation as resolved.
Show resolved Hide resolved

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
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();
RohitRanjanMS marked this conversation as resolved.
Show resolved Hide resolved
}
}

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);
RohitRanjanMS marked this conversation as resolved.
Show resolved Hide resolved
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";
RohitRanjanMS marked this conversation as resolved.
Show resolved Hide resolved
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));
RohitRanjanMS marked this conversation as resolved.
Show resolved Hide resolved
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);
RohitRanjanMS marked this conversation as resolved.
Show resolved Hide resolved
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}";
}
}
}
Loading
Loading