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

adding AI project #944

Merged
merged 17 commits into from
Jul 29, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions DotNetWorker.sln
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage.Blobs", "extensions\Worker.Extensions.Storage.Blobs\src\Worker.Extensions.Storage.Blobs.csproj", "{FC352905-BD72-4049-8D32-3CBB9304FDC8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage.Tables", "extensions\Worker.Extensions.Storage.Tables\src\Worker.Extensions.Storage.Tables.csproj", "{2B2B47E9-2973-4269-AC5D-E5C32BDD5346}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Generators", "sdk\Sdk.Generators\Sdk.Generators.csproj", "{F77CCCE6-2DC3-48AA-8FE8-1B135B76B90E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Generator.Tests", "test\Sdk.Generator.Tests\Sdk.Generator.Tests.csproj", "{18A09B24-8646-40A6-BD85-2773AF567453}"
Expand All @@ -112,6 +113,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Sample-In
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetFxWorker", "samples\NetFxWorker\NetFxWorker.csproj", "{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.ApplicationInsights", "src\DotNetWorker.ApplicationInsights\DotNetWorker.ApplicationInsights.csproj", "{65DE66B6-568F-46AC-8F0D-C79A02F48214}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -274,6 +277,10 @@ Global
{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Release|Any CPU.Build.0 = Release|Any CPU
{65DE66B6-568F-46AC-8F0D-C79A02F48214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65DE66B6-568F-46AC-8F0D-C79A02F48214}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65DE66B6-568F-46AC-8F0D-C79A02F48214}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65DE66B6-568F-46AC-8F0D-C79A02F48214}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -322,6 +329,7 @@ Global
{922A387F-8595-4C74-ABF1-AEFF9530950C} = {B5821230-6E0A-4535-88A9-ED31B6F07596}
{22FCE0DF-65FE-4650-8202-765832C40E6D} = {922A387F-8595-4C74-ABF1-AEFF9530950C}
{B37E6BAC-F16B-4366-94FB-8B94B52A08C9} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6}
{65DE66B6-568F-46AC-8F0D-C79A02F48214} = {083592CA-7DAB-44CE-8979-44FAFA46AEC3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {497D2ED4-A13E-4BCA-8D29-F30CA7D0EA4A}
Expand Down
1 change: 1 addition & 0 deletions build/DotNetWorker.Core.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"sdk\\Sdk.Analyzers\\Sdk.Analyzers.csproj",
"sdk\\Sdk.Generators\\Sdk.Generators.csproj",
"sdk\\Sdk\\Sdk.csproj",
"src\\DotNetWorker.ApplicationInsights\\DotNetWorker.ApplicationInsights.csproj",
"src\\DotNetWorker.Core\\DotNetWorker.Core.csproj",
"src\\DotNetWorker.Grpc\\DotNetWorker.Grpc.csproj",
"src\\DotNetWorker\\DotNetWorker.csproj"
Expand Down
1 change: 1 addition & 0 deletions samples/FunctionApp/FunctionApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<ProjectReference Include="..\..\extensions\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.Http\src\Worker.Extensions.Http.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.Storage\src\Worker.Extensions.Storage.csproj" />
<ProjectReference Include="..\..\src\DotNetWorker.ApplicationInsights\DotNetWorker.ApplicationInsights.csproj" />
<ProjectReference Include="..\..\src\DotNetWorker\DotNetWorker.csproj" />
</ItemGroup>
<ItemGroup>
Expand Down
8 changes: 7 additions & 1 deletion samples/FunctionApp/Program.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.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

Expand All @@ -17,7 +18,12 @@ static async Task Main(string[] args)
//<docsnippet_startup>
var host = new HostBuilder()
//<docsnippet_configure_defaults>
.ConfigureFunctionsWorkerDefaults()
.ConfigureFunctionsWorkerDefaults(builder =>
{
builder
.AddApplicationInsights()
Copy link
Member

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?

Copy link
Member Author

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.

.AddApplicationInsightsLogger();
})
//</docsnippet_configure_defaults>
//<docsnippet_dependency_injection>
.ConfigureServices(s =>
Expand Down
10 changes: 5 additions & 5 deletions samples/FunctionApp/local.settings.json
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>
33 changes: 33 additions & 0 deletions src/DotNetWorker.ApplicationInsights/FunctionActivitySource.cs
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also register GrpcFunctionsHostLogWriter in DotNetWorker.Grpc/GrpcServiceCollectionExtensions.cs. So that is going to send logs to host (duplicate). Correct?

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);
Copy link
Member

Choose a reason for hiding this comment

The 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 AddApplicationInsights method in their Program.cs, which will cause the this middleware to be executed only after the customer middleware and any logging coming from customer m/w will miss the code from FunctionActivitySource.StartInvoke? I am guessing when we move this to Core project eventually, that may not be a problem as we can register this m/w before customer m/w?

Copy link
Member Author

@brettsam brettsam Jul 25, 2022

Choose a reason for hiding this comment

The 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 FunctionsApplication.InvokeFunctionAsync() so that it's (effectively) un-overrideable.

};
});
}

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;
}
}

}
}
}
}
49 changes: 49 additions & 0 deletions src/DotNetWorker.ApplicationInsights/FunctionsTelemetryModule.cs
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();
}
}
}
Loading