-
Notifications
You must be signed in to change notification settings - Fork 192
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
adding AI project #944
adding AI project #944
Changes from all commits
440085f
e9f91d3
841d9b4
2bce664
001f0c8
de9e366
b6fc6ff
9040883
cddaa8d
4541263
f1fbb21
70ca92e
abe437f
cceeb8a
835186e
7900e31
e652b1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
{ | ||
"IsEncrypted": false, | ||
"Values": { | ||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", | ||
"AzureWebJobsStorage": "UseDevelopmentStorage=true" | ||
} | ||
"IsEncrypted": false, | ||
"Values": { | ||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", | ||
"AzureWebJobsStorage": "UseDevelopmentStorage=true" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<PackageId>Microsoft.Azure.Functions.Worker.ApplicationInsights</PackageId> | ||
<AssemblyName>Microsoft.Azure.Functions.Worker.ApplicationInsights</AssemblyName> | ||
<RootNamespace>Microsoft.Azure.Functions.Worker.ApplicationInsights</RootNamespace> | ||
<MajorProductVersion>1</MajorProductVersion> | ||
<MinorProductVersion>0</MinorProductVersion> | ||
<PatchProductVersion>0</PatchProductVersion> | ||
<VersionSuffix>-preview1</VersionSuffix> | ||
</PropertyGroup> | ||
|
||
<Import Project="..\..\build\Common.props" /> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.20.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\DotNetWorker.Core\DotNetWorker.Core.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System.Diagnostics; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.Core.Diagnostics | ||
{ | ||
/// Note: This class will eventually move in to the core worker assembly. Including it in the | ||
/// ApplicationInsights package so we can utilize it during preview. | ||
internal static class FunctionActivitySource | ||
{ | ||
private const string InvocationIdKey = "InvocationId"; | ||
private const string NameKey = "Name"; | ||
private const string ProcessIdKey = "ProcessId"; | ||
|
||
private static readonly ActivitySource _activitySource = new("Microsoft.Azure.Functions.Worker"); | ||
liliankasem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private static readonly string _processId = Process.GetCurrentProcess().Id.ToString(); | ||
|
||
public static Activity? StartInvoke(FunctionContext context) | ||
{ | ||
var activity = _activitySource.StartActivity("Invoke", ActivityKind.Internal, context.TraceContext.TraceParent); | ||
|
||
if (activity is not null) | ||
{ | ||
activity.AddTag(InvocationIdKey, context.InvocationId); | ||
activity.AddTag(NameKey, context.FunctionDefinition.Name); | ||
activity.AddTag(ProcessIdKey, _processId); | ||
} | ||
|
||
return activity; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System.Threading.Tasks; | ||
using Microsoft.Azure.Functions.Worker.Core.Diagnostics; | ||
using Microsoft.Azure.Functions.Worker.Middleware; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights; | ||
|
||
internal class FunctionActivitySourceMiddleware : IFunctionsWorkerMiddleware | ||
{ | ||
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) | ||
{ | ||
using (FunctionActivitySource.StartInvoke(context)) | ||
{ | ||
await next.Invoke(context); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// 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.Linq; | ||
using Microsoft.ApplicationInsights.Extensibility; | ||
using Microsoft.ApplicationInsights.WorkerService; | ||
using Microsoft.Azure.Functions.Worker.ApplicationInsights; | ||
using Microsoft.Azure.Functions.Worker.Logging; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Logging.ApplicationInsights; | ||
|
||
namespace Microsoft.Azure.Functions.Worker | ||
{ | ||
public static class FunctionsApplicationInsightsExtensions | ||
{ | ||
/// <summary> | ||
/// Adds Application Insights support by internally calling <see cref="ApplicationInsightsExtensions.AddApplicationInsightsTelemetryWorkerService(IServiceCollection)"/>. | ||
/// </summary> | ||
/// <param name="builder">The <see cref="IFunctionsWorkerApplicationBuilder"/></param> | ||
/// <param name="configureOptions">Action to configure ApplicationInsights services.</param> | ||
/// <returns>The <see cref="IFunctionsWorkerApplicationBuilder"/></returns> | ||
public static IFunctionsWorkerApplicationBuilder AddApplicationInsights(this IFunctionsWorkerApplicationBuilder builder, Action<ApplicationInsightsServiceOptions>? configureOptions = null) | ||
{ | ||
builder.AddCommonServices(); | ||
|
||
builder.Services.AddApplicationInsightsTelemetryWorkerService(options => | ||
{ | ||
configureOptions?.Invoke(options); | ||
}); | ||
|
||
return builder; | ||
} | ||
|
||
/// <summary> | ||
/// Adds the <see cref="ApplicationInsightsLoggerProvider"/> and disables the Functions host passthrough logger. | ||
/// </summary> | ||
/// <param name="builder">The <see cref="IFunctionsWorkerApplicationBuilder"/></param> | ||
/// <param name="configureOptions">Action to configure ApplicationInsights logger.</param> | ||
/// <returns>The <see cref="IFunctionsWorkerApplicationBuilder"/></returns> | ||
public static IFunctionsWorkerApplicationBuilder AddApplicationInsightsLogger(this IFunctionsWorkerApplicationBuilder builder, Action<ApplicationInsightsLoggerOptions>? configureOptions = null) | ||
{ | ||
builder.AddCommonServices(); | ||
|
||
builder.Services.AddLogging(logging => | ||
{ | ||
logging.AddApplicationInsights(options => | ||
{ | ||
options.IncludeScopes = false; | ||
brettsam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
configureOptions?.Invoke(options); | ||
}); | ||
}); | ||
|
||
return builder; | ||
} | ||
|
||
private static IFunctionsWorkerApplicationBuilder AddCommonServices(this IFunctionsWorkerApplicationBuilder builder) | ||
{ | ||
builder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ITelemetryInitializer), typeof(FunctionsTelemetryInitializer), ServiceLifetime.Singleton)); | ||
builder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ITelemetryModule), typeof(FunctionsTelemetryModule), ServiceLifetime.Singleton)); | ||
|
||
// User logs will be written directly to Application Insights; this prevents duplicate logging. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We also register |
||
builder.Services.AddSingleton<IUserLogWriter>(_ => NullUserLogWriter.Instance); | ||
|
||
// This middleware is temporary for the preview. Eventually this behavior will move into the | ||
// core worker assembly. | ||
if (!builder.Services.Any(p => p.ImplementationType == typeof(FunctionActivitySourceMiddleware))) | ||
{ | ||
builder.Services.AddSingleton<FunctionActivitySourceMiddleware>(); | ||
builder.Use(next => | ||
{ | ||
return async context => | ||
{ | ||
var middleware = context.InstanceServices.GetRequiredService<FunctionActivitySourceMiddleware>(); | ||
await middleware.Invoke(context, next); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible that the customer can register a middleware before calling the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's why we'll move this behavior to the Core bits before releasing. It won't actually be middleware in the end, we'll put it directly into the |
||
}; | ||
}); | ||
} | ||
|
||
return builder; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// 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.Diagnostics; | ||
using System.Reflection; | ||
using Microsoft.ApplicationInsights.Channel; | ||
using Microsoft.ApplicationInsights.DataContracts; | ||
using Microsoft.ApplicationInsights.Extensibility; | ||
using Microsoft.ApplicationInsights.Extensibility.Implementation; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights | ||
{ | ||
internal class FunctionsTelemetryInitializer : ITelemetryInitializer | ||
{ | ||
private const string NameKey = "Name"; | ||
|
||
private readonly string _sdkVersion; | ||
private readonly string _roleInstanceName; | ||
|
||
internal FunctionsTelemetryInitializer(string sdkVersion, string roleInstanceName) | ||
{ | ||
_sdkVersion = sdkVersion; | ||
_roleInstanceName = roleInstanceName; | ||
} | ||
|
||
public FunctionsTelemetryInitializer() : | ||
this(GetSdkVersion(), GetRoleInstanceName()) | ||
{ | ||
} | ||
|
||
private static string GetSdkVersion() | ||
{ | ||
return "azurefunctions-netiso: " + typeof(FunctionsTelemetryInitializer).Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>()!.Version; | ||
brettsam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
private static string GetRoleInstanceName() | ||
{ | ||
const string ComputerNameKey = "COMPUTERNAME"; | ||
const string WebSiteInstanceIdKey = "WEBSITE_INSTANCE_ID"; | ||
const string ContainerNameKey = "CONTAINER_NAME"; | ||
|
||
string? instanceName = Environment.GetEnvironmentVariable(WebSiteInstanceIdKey); | ||
if (string.IsNullOrEmpty(instanceName)) | ||
{ | ||
instanceName = Environment.GetEnvironmentVariable(ComputerNameKey); | ||
if (string.IsNullOrEmpty(instanceName)) | ||
{ | ||
instanceName = Environment.GetEnvironmentVariable(ContainerNameKey); | ||
} | ||
} | ||
|
||
return instanceName ?? Environment.MachineName; | ||
} | ||
|
||
public void Initialize(ITelemetry telemetry) | ||
{ | ||
if (telemetry == null) | ||
{ | ||
return; | ||
} | ||
|
||
telemetry.Context.Cloud.RoleInstance = _roleInstanceName; | ||
telemetry.Context.GetInternalContext().SdkVersion = _sdkVersion; | ||
|
||
telemetry.Context.Location.Ip ??= "0.0.0.0"; | ||
|
||
if (Activity.Current is not null) | ||
{ | ||
foreach (var tag in Activity.Current.Tags) | ||
{ | ||
switch (tag.Key) | ||
{ | ||
case NameKey: | ||
telemetry.Context.Operation.Name = tag.Value; | ||
continue; | ||
default: | ||
break; | ||
} | ||
|
||
if (telemetry is ISupportProperties properties && !tag.Key.StartsWith("ai_")) | ||
{ | ||
properties.Properties[tag.Key] = tag.Value; | ||
} | ||
} | ||
|
||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// 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.Diagnostics; | ||
using Microsoft.ApplicationInsights; | ||
using Microsoft.ApplicationInsights.DataContracts; | ||
using Microsoft.ApplicationInsights.Extensibility; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.ApplicationInsights | ||
{ | ||
internal class FunctionsTelemetryModule : ITelemetryModule, IDisposable | ||
{ | ||
private TelemetryClient _telemetryClient = default!; | ||
private ActivityListener? _listener; | ||
|
||
public void Initialize(TelemetryConfiguration configuration) | ||
{ | ||
_telemetryClient = new TelemetryClient(configuration); | ||
|
||
_listener = new ActivityListener | ||
{ | ||
ShouldListenTo = source => source.Name.StartsWith("Microsoft.Azure.Functions.Worker"), | ||
ActivityStarted = activity => | ||
{ | ||
var dependency = new DependencyTelemetry("Azure.Functions", activity.OperationName, activity.OperationName, null); | ||
brettsam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
activity.SetCustomProperty("_depTel", dependency); | ||
dependency.Start(); | ||
}, | ||
ActivityStopped = activity => | ||
{ | ||
var dependency = activity.GetCustomProperty("_depTel") as DependencyTelemetry; | ||
dependency.Stop(); | ||
_telemetryClient.TrackDependency(dependency); | ||
}, | ||
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData, | ||
SampleUsingParentId = (ref ActivityCreationOptions<string> _) => ActivitySamplingResult.AllData | ||
}; | ||
|
||
ActivitySource.AddActivityListener(_listener); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_telemetryClient?.Flush(); | ||
_listener?.Dispose(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we consider exposing a single method (which internally calls these 2) for the convenience of user?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally yes... but App Insights has these as two separate concepts. I'd like to try to mirror their usage as much as possible so we can offload the documentation to them. I agree it's kinda odd... but we can review before making this GA.