├── .github ├── CODEOWNERS ├── workflows │ ├── skipped-build.yml │ └── security-scanning.yml └── dependabot.yml ├── .nuke └── parameters.json ├── src └── AzureFunctionsTelemetry │ ├── icon.png │ ├── ApplicationInsights │ ├── StringHelper.cs │ ├── ApplicationDescriptor.cs │ ├── ServiceBusRequestInitializer.cs │ ├── ApplicationInitializer.cs │ ├── HealthRequestFilter.cs │ ├── CustomApplicationInsightsOptionsBuilder.cs │ ├── ServiceBusTriggerFilter.cs │ ├── DuplicateExceptionsFilter.cs │ ├── CustomApplicationInsightsOptions.cs │ ├── FunctionExecutionTracesFilter.cs │ ├── FunctionsFinder.cs │ ├── TelemetryHelper.cs │ └── CustomApplicationInsightsConfig.cs │ ├── FunctionRuntimeEventName.cs │ ├── FunctionRuntimeEventId.cs │ ├── FunctionRuntimeCategory.cs │ ├── Logging │ └── LoggingServiceCollectionExtensions.cs │ ├── AzureFunctionsTelemetry.csproj │ └── README.md ├── .vscode ├── settings.json └── extensions.json ├── docs ├── img │ ├── broken-function-monitor-blade.png │ ├── demo │ │ ├── inprocess-v4-custom-dependency-function.png │ │ ├── isolated-v4-default-dependency-function.png │ │ ├── isolated-v4-default-service-bus-binding.png │ │ ├── inprocess-v4-default-dependency-function.png │ │ ├── inprocess-v4-default-service-bus-binding.png │ │ ├── inprocess-v4-custom-trace-log-live-metrics.png │ │ ├── inprocess-v4-custom-console-stack-trace-present.png │ │ ├── inprocess-v4-custom-service-bus-binding-exception.png │ │ ├── inprocess-v4-default-console-stack-trace-absent.png │ │ ├── isolated-v4-default-console-stack-trace-present.png │ │ ├── isolated-v4-default-service-bus-binding-exception.png │ │ ├── inprocess-v4-default-service-bus-binding-exception.png │ │ ├── inprocess-v4-custom-http-binding-exception-logged-once.png │ │ ├── inprocess-v4-custom-telemetry-configuration-registered.png │ │ ├── inprocess-v4-custom-cloud-role-name-application-version.png │ │ ├── inprocess-v4-custom-telemetry-processor-is-being-called.png │ │ ├── inprocess-v4-default-cloud-role-name-application-version.png │ │ ├── inprocess-v4-default-http-binding-exception-logged-twice.png │ │ ├── inprocess-v4-custom-system-trace-middleware-suppressed-logs.png │ │ ├── inprocess-v4-default-telemetry-configuration-not-registered.png │ │ ├── inprocess-v4-default-telemetry-processor-not-being-called.png │ │ ├── isolated-v4-default-http-binding-exception-logged-twice-host.png │ │ ├── inprocess-v4-custom-service-bus-binding-request-url-and-response-code.png │ │ ├── isolated-v4-default-service-bus-binding-no-request-url-no-response-code.png │ │ ├── inprocess-v4-default-service-bus-binding-no-request-url-no-response-code.png │ │ ├── isolated-v4-default-telemetry-processor-is-being-called-for-all-but-requests.png │ │ └── inprocess-v4-default-system-trace-middleware-local-two-logs-http-function-execution.png │ └── noun-design-tools-155462.svg ├── postman │ ├── FunctionsTelemetryAzure.postman_environment.json │ └── FunctionsTelemetryLocal.postman_environment.json ├── Migrate-from-v1-to-v2.md └── Isolated.md ├── tests ├── AzureFunctionsTelemetryIntegrationTests │ ├── TestInfrastructure │ │ ├── MetricItem.cs │ │ ├── ExceptionItem.cs │ │ ├── DependencyItem.cs │ │ ├── TraceBaseData.cs │ │ ├── TraceData.cs │ │ ├── RequestData.cs │ │ ├── TraceItem.cs │ │ ├── RequestItem.cs │ │ ├── RequestBaseData.cs │ │ ├── CustomTelemetryFunctionClient.cs │ │ ├── DefaultTelemetryFunctionClient.cs │ │ ├── TelemetryItem.cs │ │ ├── TelemetryItemTags.cs │ │ ├── ListOfTelemetryItemExtensions.cs │ │ └── TelemetryItemConverter.cs │ ├── DefaultTelemetryTests.cs │ ├── CustomTelemetryTests.cs │ ├── AzureFunctionsTelemetryIntegrationTests.csproj │ ├── DefaultServiceBusTests.cs │ ├── CustomServiceBusTests.cs │ ├── DefaultHttpTests.cs │ └── CustomHttpTests.cs ├── AppInsightsConnectionStringIntegrationTests │ ├── TestInfrastructure │ │ ├── CustomTelemetryFunctionClient.cs │ │ ├── DefaultTelemetryFunctionClient.cs │ │ └── TelemetryFunctionClient.cs │ ├── CustomTelemetryClientResolutionTests.cs │ ├── DefaultTelemetryClientResolutionTests.cs │ └── AppInsightsConnectionStringIntegrationTests.csproj └── AzureFunctionsTelemetryTests │ ├── TestInfrastructure │ ├── Mocks │ │ └── MockTelemetryProcessor.cs │ ├── Functions │ │ ├── StaticServiceBusFunction.cs │ │ ├── InstanceServiceBusFunction.cs │ │ └── HttpFunction.cs │ └── Builders │ │ ├── RequestTelemetryBuilder.cs │ │ └── ExceptionTelemetryBuilder.cs │ ├── ApplicationInsights │ ├── CustomApplicationInsightsConfigTests.cs │ ├── FunctionsFinderTests.cs │ ├── DuplicateExceptionsFilterServiceBusBindingTests.cs │ ├── ServiceBusTriggerFilterTests.cs │ ├── CustomApplicationInsightsConfigBuilderTests.cs │ ├── HealthRequestFilterTests.cs │ ├── DuplicateExceptionsFilterHttpBindingTests.cs │ ├── ApplicationInsightsServiceCollectionExtensionsTests.cs │ ├── ApplicationInitializerTests.cs │ ├── ServiceBusRequestInitializerTests.cs │ └── FunctionExecutionTracesFilterTests.cs │ └── AzureFunctionsTelemetryTests.csproj ├── nuget.config ├── samples ├── CustomV4InProcessFunction │ ├── Infrastructure │ │ ├── Telemetry │ │ │ ├── TestingOptions.cs │ │ │ ├── CustomHttpDependencyFilter.cs │ │ │ ├── TestingChannel.cs │ │ │ ├── TelemetryCounterInitializer.cs │ │ │ ├── TelemetryCounterProcessor.cs │ │ │ └── TelemetryCounterInstanceInitializer.cs │ │ └── ServiceCollectionExtension.cs │ ├── Functions │ │ ├── UserSecret │ │ │ ├── SecretOptions.cs │ │ │ └── UserSecretFunction.cs │ │ ├── ServiceBus │ │ │ └── ServiceBusFunction.cs │ │ ├── Health │ │ │ └── HealthFunction.cs │ │ ├── ServiceBusExceptionThrowing │ │ │ └── ServiceBusExceptionThrowingFunction.cs │ │ ├── HttpExceptionThrowing │ │ │ └── HttpExceptionThrowingFunction.cs │ │ ├── TriggerServiceBus │ │ │ └── TriggerServiceBusFunction.cs │ │ ├── TraceLog │ │ │ └── TraceLogFunction.cs │ │ ├── TriggerServiceBusExceptionThrowing │ │ │ └── TriggerServiceBusExceptionThrowingFunction.cs │ │ ├── CustomEvent │ │ │ └── CustomEventFunction.cs │ │ ├── Availability │ │ │ └── AvailabilityFunction.cs │ │ ├── Dependency │ │ │ └── DependencyFunction.cs │ │ ├── Processor │ │ │ └── ProcessorFunction.cs │ │ ├── Initializer │ │ │ └── InitializerFunction.cs │ │ ├── InstanceInitializer │ │ │ └── InstanceInitializerFunction.cs │ │ └── Telemetry │ │ │ └── TelemetryFunction.cs │ ├── host.json │ ├── local.settings.json │ ├── CustomV4InProcessFunction.csproj │ └── Startup.cs ├── DefaultV4InProcessFunction │ ├── Infrastructure │ │ ├── Telemetry │ │ │ ├── TestingOptions.cs │ │ │ ├── TelemetryCounterProcessor.cs │ │ │ ├── TestingChannel.cs │ │ │ └── TelemetryCounterInitializer.cs │ │ └── ServiceCollectionExtension.cs │ ├── Functions │ │ ├── UserSecret │ │ │ ├── SecretOptions.cs │ │ │ └── UserSecretFunction.cs │ │ ├── ServiceBus │ │ │ └── ServiceBusFunction.cs │ │ ├── Health │ │ │ └── HealthFunction.cs │ │ ├── ServiceBusExceptionThrowing │ │ │ └── ServiceBusExceptionThrowingFunction.cs │ │ ├── HttpExceptionThrowing │ │ │ └── HttpExceptionThrowingFunction.cs │ │ ├── Processor │ │ │ └── ProcessorFunction.cs │ │ ├── TriggerServiceBus │ │ │ └── TriggerServiceBusFunction.cs │ │ ├── TraceLog │ │ │ └── TraceLogFunction.cs │ │ ├── TriggerServiceBusExceptionThrowing │ │ │ └── TriggerServiceBusExceptionThrowingFunction.cs │ │ ├── CustomEvent │ │ │ └── CustomEventFunction.cs │ │ ├── Availability │ │ │ └── AvailabilityFunction.cs │ │ ├── Dependency │ │ │ └── DependencyFunction.cs │ │ ├── Initializer │ │ │ └── InitializerFunction.cs │ │ └── Telemetry │ │ │ └── TelemetryFunction.cs │ ├── local.settings.json │ ├── host.json │ ├── Startup.cs │ └── DefaultV4InProcessFunction.csproj └── DefaultV4IsolatedFunction │ ├── Functions │ ├── UserSecret │ │ ├── SecretOptions.cs │ │ └── UserSecretFunction.cs │ ├── TriggerServiceBus │ │ ├── TriggerServiceBusFunctionOutput.cs │ │ └── TriggerServiceBusFunction.cs │ ├── Health │ │ └── HealthFunction.cs │ ├── TriggerServiceBusExceptionThrowing │ │ ├── TriggerServiceBusExceptionThrowingFunctionOutput.cs │ │ └── TriggerServiceBusExceptionThrowingFunction.cs │ ├── ServiceBus │ │ └── ServiceBusFunction.cs │ ├── ServiceBusExceptionThrowing │ │ └── ServiceBusExceptionThrowingFunction.cs │ ├── HttpExceptionThrowing │ │ └── HttpExceptionThrowingFunction.cs │ ├── TraceLog │ │ └── TraceLogFunction.cs │ ├── CustomEvent │ │ └── CustomEventFunction.cs │ ├── Availability │ │ └── AvailabilityFunction.cs │ ├── Dependency │ │ └── DependencyFunction.cs │ ├── Initializer │ │ └── InitializerFunction.cs │ └── Processor │ │ └── ProcessorFunction.cs │ ├── local.settings.json │ ├── host.json │ ├── Infrastructure │ ├── ServiceCollectionExtension.cs │ └── Telemetry │ │ ├── CustomHttpDependencyFilter.cs │ │ ├── HealthRequestFilter.cs │ │ ├── TelemetryCounterProcessor.cs │ │ └── TelemetryCounterInitializer.cs │ ├── DefaultV4IsolatedFunction.csproj │ └── Program.cs ├── .gitignore ├── .gitattributes ├── LICENSE ├── AzureFunctionsTelemetry.sln.DotSettings ├── CONTRIBUTING.md ├── Directory.Packages.props ├── scripts └── testing │ ├── testing.bicep │ └── Deploy-Testing.ps1 └── deploy └── functionApp.bicep /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @gabrielweyer 2 | -------------------------------------------------------------------------------- /.nuke/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./build.schema.json", 3 | "Solution": "AzureFunctionsTelemetry.sln" 4 | } -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/src/AzureFunctionsTelemetry/icon.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.schemas": { 3 | "https://json.schemastore.org/github-workflow.json": "/.github/workflows/*.yml" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /docs/img/broken-function-monitor-blade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/broken-function-monitor-blade.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-dependency-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-dependency-function.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-dependency-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/isolated-v4-default-dependency-function.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-service-bus-binding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/isolated-v4-default-service-bus-binding.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-dependency-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-dependency-function.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-service-bus-binding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-service-bus-binding.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-trace-log-live-metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-trace-log-live-metrics.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-console-stack-trace-present.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-console-stack-trace-present.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-service-bus-binding-exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-service-bus-binding-exception.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-console-stack-trace-absent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-console-stack-trace-absent.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-console-stack-trace-present.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/isolated-v4-default-console-stack-trace-present.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-service-bus-binding-exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/isolated-v4-default-service-bus-binding-exception.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-service-bus-binding-exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-service-bus-binding-exception.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-http-binding-exception-logged-once.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-http-binding-exception-logged-once.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-telemetry-configuration-registered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-telemetry-configuration-registered.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-cloud-role-name-application-version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-cloud-role-name-application-version.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-telemetry-processor-is-being-called.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-telemetry-processor-is-being-called.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-cloud-role-name-application-version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-cloud-role-name-application-version.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-http-binding-exception-logged-twice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-http-binding-exception-logged-twice.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-system-trace-middleware-suppressed-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-system-trace-middleware-suppressed-logs.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-telemetry-configuration-not-registered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-telemetry-configuration-not-registered.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-telemetry-processor-not-being-called.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-telemetry-processor-not-being-called.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-http-binding-exception-logged-twice-host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/isolated-v4-default-http-binding-exception-logged-twice-host.png -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/MetricItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class MetricItem : TelemetryItem 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/ExceptionItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class ExceptionItem : TelemetryItem 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/DependencyItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class DependencyItem : TelemetryItem 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-service-bus-binding-request-url-and-response-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-custom-service-bus-binding-request-url-and-response-code.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-service-bus-binding-no-request-url-no-response-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/isolated-v4-default-service-bus-binding-no-request-url-no-response-code.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-service-bus-binding-no-request-url-no-response-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-service-bus-binding-no-request-url-no-response-code.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-telemetry-processor-is-being-called-for-all-but-requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/isolated-v4-default-telemetry-processor-is-being-called-for-all-but-requests.png -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Infrastructure/Telemetry/TestingOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 2 | 3 | public class TestingOptions 4 | { 5 | public bool IsEnabled { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Infrastructure/Telemetry/TestingOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure.Telemetry; 2 | 3 | public class TestingOptions 4 | { 5 | public bool IsEnabled { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/TraceBaseData.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class TraceBaseData 4 | { 5 | public string Message { get; set; } = default!; 6 | } 7 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/TraceData.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class TraceData 4 | { 5 | public TraceBaseData BaseData { get; set; } = new(); 6 | } 7 | -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-system-trace-middleware-local-two-logs-http-function-execution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/HEAD/docs/img/demo/inprocess-v4-default-system-trace-middleware-local-two-logs-http-function-execution.png -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/RequestData.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class RequestData 4 | { 5 | public RequestBaseData BaseData { get; set; } = new(); 6 | } 7 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/TraceItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class TraceItem : TelemetryItem 4 | { 5 | public TraceData Data { get; set; } = new(); 6 | } 7 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/UserSecret/SecretOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.UserSecret; 2 | 3 | public class SecretOptions 4 | { 5 | public string ReallySecretValue { get; set; } = default!; 6 | } 7 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/UserSecret/SecretOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.UserSecret; 2 | 3 | public class SecretOptions 4 | { 5 | public string ReallySecretValue { get; set; } = default!; 6 | } 7 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/UserSecret/SecretOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.UserSecret; 2 | 3 | public class SecretOptions 4 | { 5 | public string ReallySecretValue { get; set; } = default!; 6 | } 7 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/RequestItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class RequestItem : TelemetryItem 4 | { 5 | public RequestData Data { get; set; } = new(); 6 | } 7 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/StringHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | internal static class StringHelper 4 | { 5 | public static bool IsSame(string a, string b) => string.Equals(a, b, StringComparison.OrdinalIgnoreCase); 6 | } 7 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/FunctionRuntimeEventName.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry; 2 | 3 | internal static class FunctionRuntimeEventName 4 | { 5 | public const string FunctionStarted = "FunctionStarted"; 6 | public const string FunctionCompleted = "FunctionCompleted"; 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build detritus 2 | bin/ 3 | obj/ 4 | 5 | # Rider 6 | .idea/ 7 | 8 | # Azurite 9 | __azurite_db_blob__.json 10 | __azurite_db_blob_extent__.json 11 | __blobstorage__/ 12 | 13 | # User settings 14 | *.user 15 | 16 | # NUKE 17 | .nuke/temp 18 | artifacts/ 19 | 20 | # Git 21 | *.orig 22 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/RequestBaseData.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class RequestBaseData 4 | { 5 | public string ResponseCode { get; set; } = "0"; 6 | public string? Url { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Postman 2 | docs/postman/*.json eol=lf 3 | 4 | # Icon 5 | docs/img/noun-design-tools-155462.svg eol=lf 6 | 7 | # Rider 8 | *.DotSettings eol=lf 9 | 10 | # Bicep 11 | *.bicep eol=lf 12 | 13 | # Vendored extensions 14 | *.sh linguist-vendored 15 | *.ps1 linguist-vendored 16 | *.bicep linguist-vendored 17 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/FunctionRuntimeEventId.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry; 2 | 3 | internal static class FunctionRuntimeEventId 4 | { 5 | public const string FunctionStarted = "1"; 6 | public const string FunctionCompletedSucceeded = "2"; 7 | public const string FunctionCompletedFailed = "3"; 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Azurite.azurite", 4 | "ms-azuretools.vscode-azurefunctions", 5 | "ms-azuretools.vscode-bicep", 6 | "ms-dotnettools.csharp", 7 | "ms-vscode.powershell", 8 | "EditorConfig.EditorConfig", 9 | "redhat.vscode-yaml" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/CustomTelemetryFunctionClient.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class CustomTelemetryFunctionClient : TelemetryFunctionClient 4 | { 5 | public CustomTelemetryFunctionClient() : base(7074) 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/DefaultTelemetryFunctionClient.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class DefaultTelemetryFunctionClient : TelemetryFunctionClient 4 | { 5 | public DefaultTelemetryFunctionClient() : base(7073) 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/AppInsightsConnectionStringIntegrationTests/TestInfrastructure/CustomTelemetryFunctionClient.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryAppInsightsConnectionStringIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class CustomTelemetryFunctionClient : TelemetryFunctionClient 4 | { 5 | public CustomTelemetryFunctionClient() : base(7074) 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/AppInsightsConnectionStringIntegrationTests/TestInfrastructure/DefaultTelemetryFunctionClient.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryAppInsightsConnectionStringIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class DefaultTelemetryFunctionClient : TelemetryFunctionClient 4 | { 5 | public DefaultTelemetryFunctionClient() : base(7073) 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/FunctionRuntimeCategory.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry; 2 | 3 | internal static class FunctionRuntimeCategory 4 | { 5 | public const string HostResults = "Host.Results"; 6 | public const string HostExecutor = "Host.Executor"; 7 | public const string ServiceBusListener = "Microsoft.Azure.WebJobs.ServiceBus.Listeners.ServiceBusListener"; 8 | } 9 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/ApplicationDescriptor.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | internal class ApplicationDescriptor 4 | { 5 | public ApplicationDescriptor(string name, string version) 6 | { 7 | Name = name; 8 | Version = version; 9 | } 10 | 11 | public string Name { get; } 12 | public string Version { get; } 13 | } 14 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", 6 | "ServiceBusConnection": "", 7 | "logging:logLevel:Microsoft.Azure.WebJobs.Script.WebHost.Middleware.SystemTraceMiddleware": "None" 8 | }, 9 | "Host": { 10 | "LocalHttpPort": 7075 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/TelemetryItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal abstract class TelemetryItem 4 | { 5 | public string Name { get; set; } = default!; 6 | public TelemetryItemTags Tags { get; set; } = default!; 7 | public string OperationId => Tags.OperationId; 8 | public string OperationName => Tags.OperationName; 9 | } 10 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet", 6 | "FUNCTIONS_INPROC_NET8_ENABLED": "1", 7 | "ServiceBusConnection": "", 8 | "logging:logLevel:Microsoft.Azure.WebJobs.Script.WebHost.Middleware.SystemTraceMiddleware": "None", 9 | "Testing:IsEnabled": false 10 | }, 11 | "Host": { 12 | "LocalHttpPort": 7073 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/TestInfrastructure/Mocks/MockTelemetryProcessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.Extensibility; 3 | 4 | namespace Gabo.AzureFunctionsTelemetryTests.TestInfrastructure.Mocks; 5 | 6 | public class MockTelemetryProcessor : ITelemetryProcessor 7 | { 8 | public bool WasProcessorCalled { get; private set; } 9 | 10 | public void Process(ITelemetry item) 11 | { 12 | WasProcessorCalled = true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "logLevel": { 5 | "default": "Information", 6 | "Azure.Messaging.ServiceBus": "Warning", 7 | "Azure.Core": "Error", 8 | "Microsoft.AspNetCore": "Warning" 9 | }, 10 | "applicationInsights": { 11 | "samplingSettings": { 12 | "isEnabled": true, 13 | "excludedTypes": "Request" 14 | } 15 | } 16 | }, 17 | "extensions": { 18 | "http": { 19 | "routePrefix": "" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "logLevel": { 5 | "default": "Information", 6 | "Azure.Messaging.ServiceBus": "Warning", 7 | "Azure.Core": "Error", 8 | "Microsoft.AspNetCore": "Warning" 9 | }, 10 | "applicationInsights": { 11 | "samplingSettings": { 12 | "isEnabled": true, 13 | "excludedTypes": "Request" 14 | } 15 | } 16 | }, 17 | "extensions": { 18 | "http": { 19 | "routePrefix": "" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/TriggerServiceBus/TriggerServiceBusFunctionOutput.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Functions.Worker; 2 | using Microsoft.Azure.Functions.Worker.Http; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.TriggerServiceBus; 5 | 6 | public class TriggerServiceBusFunctionOutput 7 | { 8 | [ServiceBusOutput("defaultv4isolated-queue", Connection = "ServiceBusConnection")] 9 | public string? Message { get; set; } 10 | 11 | public HttpResponseData? Response { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /docs/postman/FunctionsTelemetryAzure.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1d0243ff-8f1f-4efe-a011-871949acbda2", 3 | "name": "FunctionsTelemetryAzure", 4 | "values": [ 5 | { 6 | "key": "DefaultFunctionBaseUri", 7 | "value": "", 8 | "type": "default", 9 | "enabled": true 10 | }, 11 | { 12 | "key": "CustomFunctionBaseUri", 13 | "value": "", 14 | "type": "default", 15 | "enabled": true 16 | } 17 | ], 18 | "_postman_variable_scope": "environment", 19 | "_postman_exported_at": "2022-02-19T02:21:46.361Z", 20 | "_postman_exported_using": "Postman/9.13.2" 21 | } -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "logLevel": { 5 | "default": "Information", 6 | "Azure.Messaging.ServiceBus": "Warning", 7 | "Azure.Core": "Error", 8 | "Microsoft.AspNetCore": "Warning", 9 | "Host.Controllers.Host": "Warning" 10 | }, 11 | "applicationInsights": { 12 | "samplingSettings": { 13 | "isEnabled": true, 14 | "excludedTypes": "Request" 15 | } 16 | } 17 | }, 18 | "extensions": { 19 | "http": { 20 | "routePrefix": "" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/Health/HealthFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.Azure.Functions.Worker; 3 | using Microsoft.Azure.Functions.Worker.Http; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.Health; 6 | 7 | public static class HealthFunction 8 | { 9 | [Function(nameof(HealthFunction))] 10 | public static HttpResponseData RunHeadHealth( 11 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "health")] 12 | HttpRequestData request) => 13 | request.CreateResponse(HttpStatusCode.OK); 14 | } 15 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/ServiceBus/ServiceBusFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | 3 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.ServiceBus; 4 | 5 | public static class ServiceBusFunction 6 | { 7 | [FunctionName(nameof(ServiceBusFunction))] 8 | public static void Run( 9 | [ServiceBusTrigger("customv4inprocess-queue", Connection = "ServiceBusConnection")] 10 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 11 | string myQueueItem) 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/ServiceBus/ServiceBusFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | 3 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.ServiceBus; 4 | 5 | public static class ServiceBusFunction 6 | { 7 | [FunctionName(nameof(ServiceBusFunction))] 8 | public static void Run( 9 | [ServiceBusTrigger("defaultv4inprocess-queue", Connection = "ServiceBusConnection")] 10 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 11 | string myQueueItem) 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/postman/FunctionsTelemetryLocal.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "c9e7b030-b5f7-44ea-8a38-f62a51c1ea7d", 3 | "name": "FunctionsTelemetryLocal", 4 | "values": [ 5 | { 6 | "key": "DefaultFunctionBaseUri", 7 | "value": "http://localhost:7073", 8 | "type": "default", 9 | "enabled": true 10 | }, 11 | { 12 | "key": "CustomFunctionBaseUri", 13 | "value": "http://localhost:7074", 14 | "type": "default", 15 | "enabled": true 16 | } 17 | ], 18 | "_postman_variable_scope": "environment", 19 | "_postman_exported_at": "2022-02-19T02:21:26.716Z", 20 | "_postman_exported_using": "Postman/9.13.2" 21 | } -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/TriggerServiceBusExceptionThrowing/TriggerServiceBusExceptionThrowingFunctionOutput.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Functions.Worker; 2 | using Microsoft.Azure.Functions.Worker.Http; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.TriggerServiceBusExceptionThrowing; 5 | 6 | public class TriggerServiceBusExceptionThrowingFunctionOutput 7 | { 8 | [ServiceBusOutput("defaultv4isolated-exception-queue", Connection = "ServiceBusConnection")] 9 | public string? Message { get; set; } 10 | 11 | public HttpResponseData? Response { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet", 6 | "FUNCTIONS_INPROC_NET8_ENABLED": "1", 7 | "ServiceBusConnection": "", 8 | "logging:logLevel:Microsoft.Azure.WebJobs.Script.WebHost.Middleware.SystemTraceMiddleware": "None", 9 | "Testing:IsEnabled": false, 10 | "ApplicationInsights:DiscardServiceBusTrigger": true, 11 | "ApplicationInsights:HealthCheckFunctionName": "HealthFunction" 12 | }, 13 | "Host": { 14 | "LocalHttpPort": 7074 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/TelemetryItemTags.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class TelemetryItemTags 4 | { 5 | [JsonProperty("ai.application.ver")] 6 | public string? ApplicationVersion { get; set; } 7 | 8 | [JsonProperty("ai.cloud.role")] 9 | public string? CloudRoleName { get; set; } 10 | 11 | [JsonProperty("ai.operation.id")] 12 | public string OperationId { get; set; } = default!; 13 | 14 | [JsonProperty("ai.operation.name")] 15 | public string OperationName { get; set; } = default!; 16 | } 17 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/TestInfrastructure/Functions/StaticServiceBusFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.Azure.WebJobs; 3 | 4 | namespace Gabo.AzureFunctionsTelemetryTests.TestInfrastructure.Functions; 5 | 6 | public static class StaticServiceBusFunction 7 | { 8 | [FunctionName(nameof(StaticServiceBusFunction))] 9 | public static void Run( 10 | [ServiceBusTrigger("static-queue", Connection = "ServiceBusConnection")] 11 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 12 | string myQueueItem) 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/ServiceBus/ServiceBusFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.Azure.Functions.Worker; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.ServiceBus; 5 | 6 | public static class ServiceBusFunction 7 | { 8 | [Function(nameof(ServiceBusFunction))] 9 | public static void Run( 10 | [ServiceBusTrigger("defaultv4isolated-queue", Connection = "ServiceBusConnection")] 11 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 12 | string myQueueItem) 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/TestInfrastructure/Functions/InstanceServiceBusFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.Azure.WebJobs; 3 | 4 | namespace Gabo.AzureFunctionsTelemetryTests.TestInfrastructure.Functions; 5 | 6 | public static class InstanceServiceBusFunction 7 | { 8 | [FunctionName(nameof(InstanceServiceBusFunction))] 9 | public static void Run( 10 | [ServiceBusTrigger("instance-queue", Connection = "ServiceBusConnection")] 11 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 12 | string myQueueItem) 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/CustomApplicationInsightsConfigTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 2 | 3 | public class CustomApplicationInsightsConfigTests 4 | { 5 | [Theory] 6 | [InlineData("")] 7 | [InlineData(" ")] 8 | public void GivenEmptyOrWhiteSpaceConfigurationSectionName_ThenThrows(string configurationSectionName) 9 | { 10 | // Act & Assert 11 | Assert.ThrowsAny(() => 12 | new CustomApplicationInsightsConfig( 13 | "some-name", 14 | typeof(StringHelper), 15 | configurationSectionName)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/ServiceBusRequestInitializer.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | internal class ServiceBusRequestInitializer : ITelemetryInitializer 4 | { 5 | public void Initialize(ITelemetry telemetry) 6 | { 7 | if (telemetry is RequestTelemetry requestTelemetry && 8 | requestTelemetry.Url == null && 9 | requestTelemetry.Success.HasValue) 10 | { 11 | requestTelemetry.ResponseCode = requestTelemetry.Success.Value ? "200" : "500"; 12 | requestTelemetry.Url = new Uri($"/{requestTelemetry.Name}", UriKind.Relative); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/ApplicationInitializer.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | internal class ApplicationInitializer : ITelemetryInitializer 4 | { 5 | private readonly ApplicationDescriptor _applicationDescriptor; 6 | 7 | public ApplicationInitializer(ApplicationDescriptor applicationDescriptor) 8 | { 9 | _applicationDescriptor = applicationDescriptor; 10 | } 11 | 12 | public void Initialize(ITelemetry telemetry) 13 | { 14 | telemetry.Context.Cloud.RoleName = _applicationDescriptor.Name; 15 | telemetry.Context.Component.Version = _applicationDescriptor.Version; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/ServiceBusExceptionThrowing/ServiceBusExceptionThrowingFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Functions.Worker; 2 | 3 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.ServiceBusExceptionThrowing; 4 | 5 | public static class ServiceBusExceptionThrowingFunction 6 | { 7 | [Function(nameof(ServiceBusExceptionThrowingFunction))] 8 | public static void Run( 9 | [ServiceBusTrigger("defaultv4isolated-exception-queue", Connection = "ServiceBusConnection")] 10 | string myQueueItem) 11 | { 12 | throw new InvalidOperationException("The only goal of this function is to throw an Exception."); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/skipped-build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | paths: 8 | - .github/workflows/security-scanning.yml 9 | - .github/workflows/skipped-build.yml 10 | - '.vscode/**' 11 | - 'docs/**' 12 | - CONTRIBUTING.md 13 | - LICENSE 14 | - README.md 15 | 16 | jobs: 17 | build: 18 | name: Build 19 | runs-on: ubuntu-latest 20 | steps: 21 | - run: echo "Handling skipped but required check, see https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks" 22 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/HttpExceptionThrowing/HttpExceptionThrowingFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Functions.Worker; 2 | using Microsoft.Azure.Functions.Worker.Http; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.HttpExceptionThrowing; 5 | 6 | public static class HttpExceptionThrowingFunction 7 | { 8 | [Function(nameof(HttpExceptionThrowingFunction))] 9 | public static HttpResponseData RunGetException( 10 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "http-exception")] 11 | HttpRequestData request) 12 | { 13 | throw new InvalidOperationException("The only goal of this function is to throw an Exception."); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/Health/HealthFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.Health; 7 | 8 | public static class HealthFunction 9 | { 10 | [HttpGet] 11 | [FunctionName(nameof(HealthFunction))] 12 | public static IActionResult RunHeadHealth( 13 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "health")] 14 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 15 | HttpRequest request) => 16 | new OkResult(); 17 | } 18 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/Health/HealthFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.Health; 7 | 8 | public static class HealthFunction 9 | { 10 | [HttpGet] 11 | [FunctionName(nameof(HealthFunction))] 12 | public static IActionResult RunHeadHealth( 13 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "health")] 14 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 15 | HttpRequest request) => 16 | new OkResult(); 17 | } 18 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/ListOfTelemetryItemExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal static class ListOfTelemetryItemExtensions 4 | { 5 | public static List GetOperationItems(this List items, string operationName, string operationId) where T : TelemetryItem 6 | { 7 | return items 8 | .Where(i => i is T) 9 | .Cast() 10 | .Where(i => operationId.Equals(i.OperationId, StringComparison.Ordinal) && 11 | (operationName.Equals(i.OperationName, StringComparison.Ordinal) || string.IsNullOrEmpty(i.OperationName))) 12 | .ToList(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Infrastructure/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure; 5 | 6 | public static class ServiceCollectionExtension 7 | { 8 | public static IServiceCollection AddMyOptions(this IServiceCollection services, string sectionName) 9 | where TOptions : class 10 | { 11 | services.AddOptions() 12 | .Configure((settings, configuration) => 13 | { 14 | configuration.GetSection(sectionName).Bind(settings); 15 | }); 16 | 17 | return services; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Infrastructure/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure; 5 | 6 | public static class ServiceCollectionExtension 7 | { 8 | public static IServiceCollection AddMyOptions(this IServiceCollection services, string sectionName) 9 | where TOptions : class 10 | { 11 | services.AddOptions() 12 | .Configure((settings, configuration) => 13 | { 14 | configuration.GetSection(sectionName).Bind(settings); 15 | }); 16 | 17 | return services; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Infrastructure/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Infrastructure; 5 | 6 | public static class ServiceCollectionExtension 7 | { 8 | public static IServiceCollection AddMyOptions(this IServiceCollection services, string sectionName) 9 | where TOptions : class 10 | { 11 | services.AddOptions() 12 | .Configure((settings, configuration) => 13 | { 14 | configuration.GetSection(sectionName).Bind(settings); 15 | }); 16 | 17 | return services; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/TestInfrastructure/Functions/HttpFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Azure.WebJobs; 5 | using Microsoft.Azure.WebJobs.Extensions.Http; 6 | 7 | namespace Gabo.AzureFunctionsTelemetryTests.TestInfrastructure.Functions; 8 | 9 | public static class HttpFunction 10 | { 11 | [HttpGet] 12 | [FunctionName(nameof(HttpFunction))] 13 | public static IActionResult Run( 14 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "http")] 15 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 16 | HttpRequest request) => 17 | new OkResult(); 18 | } 19 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/ServiceBusExceptionThrowing/ServiceBusExceptionThrowingFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | 3 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.ServiceBusExceptionThrowing; 4 | 5 | public static class ServiceBusExceptionThrowingFunction 6 | { 7 | [FunctionName(nameof(ServiceBusExceptionThrowingFunction))] 8 | public static void Run( 9 | [ServiceBusTrigger("customv4inprocess-exception-queue", Connection = "ServiceBusConnection")] 10 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 11 | string myQueueItem) 12 | { 13 | throw new InvalidOperationException("The only goal of this function is to throw an Exception."); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/ServiceBusExceptionThrowing/ServiceBusExceptionThrowingFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | 3 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.ServiceBusExceptionThrowing; 4 | 5 | public static class ServiceBusExceptionThrowingFunction 6 | { 7 | [FunctionName(nameof(ServiceBusExceptionThrowingFunction))] 8 | public static void Run( 9 | [ServiceBusTrigger("defaultv4inprocess-exception-queue", Connection = "ServiceBusConnection")] 10 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 11 | string myQueueItem) 12 | { 13 | throw new InvalidOperationException("The only goal of this function is to throw an Exception."); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/HealthRequestFilter.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | internal class HealthRequestFilter : ITelemetryProcessor 4 | { 5 | private readonly ITelemetryProcessor _next; 6 | private readonly string _healthCheckFunctionName; 7 | 8 | public HealthRequestFilter(ITelemetryProcessor next, string healthCheckFunctionName) 9 | { 10 | _next = next; 11 | _healthCheckFunctionName = healthCheckFunctionName; 12 | } 13 | 14 | public void Process(ITelemetry item) 15 | { 16 | if (item is RequestTelemetry { ResponseCode: "200" } request && 17 | StringHelper.IsSame(_healthCheckFunctionName, request.Name)) 18 | { 19 | return; 20 | } 21 | 22 | _next.Process(item); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/AppInsightsConnectionStringIntegrationTests/TestInfrastructure/TelemetryFunctionClient.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryAppInsightsConnectionStringIntegrationTests.TestInfrastructure; 2 | 3 | internal abstract class TelemetryFunctionClient : IDisposable 4 | { 5 | private readonly HttpClient _httpClient; 6 | 7 | protected TelemetryFunctionClient(int port) 8 | { 9 | _httpClient = new HttpClient(); 10 | _httpClient.Timeout = TimeSpan.FromSeconds(5); 11 | _httpClient.BaseAddress = new Uri($"http://localhost:{port}/"); 12 | } 13 | 14 | public Task EmitCustomEventAsync() 15 | { 16 | return _httpClient.GetAsync(new Uri("event", UriKind.Relative)); 17 | } 18 | 19 | public void Dispose() 20 | { 21 | _httpClient.Dispose(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Infrastructure/Telemetry/TelemetryCounterProcessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.Extensibility; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure.Telemetry; 5 | 6 | public class TelemetryCounterProcessor : ITelemetryProcessor 7 | { 8 | private readonly ITelemetryProcessor _next; 9 | private static int _invocationCount; 10 | public static int InvocationCount => Interlocked.CompareExchange(ref _invocationCount, 0, 0); 11 | 12 | public TelemetryCounterProcessor(ITelemetryProcessor next) 13 | { 14 | _next = next; 15 | } 16 | 17 | public void Process(ITelemetry item) 18 | { 19 | Interlocked.Increment(ref _invocationCount); 20 | _next.Process(item); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/TriggerServiceBus/TriggerServiceBusFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.Azure.Functions.Worker; 3 | using Microsoft.Azure.Functions.Worker.Http; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.TriggerServiceBus; 6 | 7 | public static class TriggerServiceBusFunction 8 | { 9 | [Function(nameof(TriggerServiceBusFunction))] 10 | public static TriggerServiceBusFunctionOutput RunGetTriggerServiceBus( 11 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "service-bus")] 12 | HttpRequestData request) 13 | { 14 | return new TriggerServiceBusFunctionOutput 15 | { 16 | Message = "{ 'Name': 'SomeName' }", 17 | Response = request.CreateResponse(HttpStatusCode.OK) 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/TestInfrastructure/Builders/RequestTelemetryBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.DataContracts; 2 | 3 | namespace Gabo.AzureFunctionsTelemetryTests.TestInfrastructure.Builders; 4 | 5 | public static class RequestTelemetryBuilder 6 | { 7 | public static RequestTelemetry AsServiceBus() 8 | { 9 | return new RequestTelemetry 10 | { 11 | Success = true, 12 | Name = "ServiceBusFunction", 13 | ResponseCode = "0", 14 | Url = null 15 | }; 16 | } 17 | 18 | public static RequestTelemetry AsHttp() 19 | { 20 | return new RequestTelemetry 21 | { 22 | Success = true, 23 | Name = "HttpFunction", 24 | ResponseCode = "200", 25 | Url = new Uri("http://localhost:7071/http") 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/AppInsightsConnectionStringIntegrationTests/CustomTelemetryClientResolutionTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryAppInsightsConnectionStringIntegrationTests; 2 | 3 | [Collection("Custom")] 4 | public sealed class CustomTelemetryClientResolutionTests : IDisposable 5 | { 6 | private readonly CustomTelemetryFunctionClient _client; 7 | 8 | public CustomTelemetryClientResolutionTests() 9 | { 10 | _client = new CustomTelemetryFunctionClient(); 11 | } 12 | 13 | [Fact] 14 | public async Task GivenNoAppInsightsConnectionString_WhenResolvingTelemetryClient_ThenSuccess() 15 | { 16 | // Act 17 | var response = await _client.EmitCustomEventAsync(); 18 | 19 | // Assert 20 | Assert.True(response.IsSuccessStatusCode); 21 | } 22 | 23 | public void Dispose() 24 | { 25 | _client.Dispose(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/AppInsightsConnectionStringIntegrationTests/DefaultTelemetryClientResolutionTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryAppInsightsConnectionStringIntegrationTests; 2 | 3 | [Collection("Default")] 4 | public sealed class DefaultTelemetryClientResolutionTests : IDisposable 5 | { 6 | private readonly DefaultTelemetryFunctionClient _client; 7 | 8 | public DefaultTelemetryClientResolutionTests() 9 | { 10 | _client = new DefaultTelemetryFunctionClient(); 11 | } 12 | 13 | [Fact] 14 | public async Task GivenNoAppInsightsConnectionString_WhenResolvingTelemetryClient_ThenError() 15 | { 16 | // Act 17 | var response = await _client.EmitCustomEventAsync(); 18 | 19 | // Assert 20 | Assert.False(response.IsSuccessStatusCode); 21 | } 22 | 23 | public void Dispose() 24 | { 25 | _client.Dispose(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Infrastructure/Telemetry/CustomHttpDependencyFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 6 | 7 | public class CustomHttpDependencyFilter : ITelemetryProcessor 8 | { 9 | private readonly ITelemetryProcessor _next; 10 | 11 | public CustomHttpDependencyFilter(ITelemetryProcessor next) 12 | { 13 | _next = next; 14 | } 15 | 16 | public void Process(ITelemetry item) 17 | { 18 | if (item is DependencyTelemetry dependency && 19 | "CustomHTTP".Equals(dependency.Type, StringComparison.Ordinal)) 20 | { 21 | return; 22 | } 23 | 24 | _next.Process(item); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/HttpExceptionThrowing/HttpExceptionThrowingFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.HttpExceptionThrowing; 7 | 8 | public static class HttpExceptionThrowingFunction 9 | { 10 | [FunctionName(nameof(HttpExceptionThrowingFunction))] 11 | public static IActionResult RunGetException( 12 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "http-exception")] 13 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 14 | HttpRequest request) 15 | { 16 | throw new InvalidOperationException("The only goal of this function is to throw an Exception."); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/HttpExceptionThrowing/HttpExceptionThrowingFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.HttpExceptionThrowing; 7 | 8 | public static class HttpExceptionThrowingFunction 9 | { 10 | [FunctionName(nameof(HttpExceptionThrowingFunction))] 11 | public static IActionResult RunGetException( 12 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "http-exception")] 13 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 14 | HttpRequest request) 15 | { 16 | throw new InvalidOperationException("The only goal of this function is to throw an Exception."); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "nuget" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "saturday" 8 | ignore: 9 | - dependency-name: "FluentAssertions" 10 | update-types: ["version-update:semver-major"] 11 | - dependency-name: "Microsoft.NET.Sdk.Functions" 12 | update-types: ["version-update:semver-major"] 13 | - dependency-name: "Microsoft.Extensions.DependencyInjection" 14 | update-types: ["version-update:semver-major"] 15 | - dependency-name: "Microsoft.Extensions.Logging.Console" 16 | update-types: ["version-update:semver-major"] 17 | - dependency-name: "Microsoft.Extensions.Configuration.UserSecrets" 18 | update-types: ["version-update:semver-major"] 19 | groups: 20 | xunit: 21 | patterns: 22 | - xunit 23 | - xunit.analyzers 24 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/Processor/ProcessorFunction.cs: -------------------------------------------------------------------------------- 1 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure.Telemetry; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Azure.WebJobs; 5 | using Microsoft.Azure.WebJobs.Extensions.Http; 6 | 7 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.Processor; 8 | 9 | public static class ProcessorFunction 10 | { 11 | [FunctionName(nameof(ProcessorFunction))] 12 | public static IActionResult RunGetProcessor( 13 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "processor")] 14 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 15 | HttpRequest request) 16 | { 17 | return new OkObjectResult(new { TelemetryCounterProcessor.InvocationCount }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/TraceLog/TraceLogFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.Azure.Functions.Worker; 3 | using Microsoft.Azure.Functions.Worker.Http; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.TraceLog; 7 | 8 | public class TraceLogFunction 9 | { 10 | private readonly ILogger _logger; 11 | 12 | public TraceLogFunction(ILogger logger) 13 | { 14 | _logger = logger; 15 | } 16 | 17 | [Function(nameof(TraceLogFunction))] 18 | public HttpResponseData RunGetTraceLog( 19 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "trace-log")] 20 | HttpRequestData request) 21 | { 22 | _logger.LogTrace("I'm a log with Personally Identifiable Information"); 23 | return request.CreateResponse(HttpStatusCode.Accepted); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/TriggerServiceBusExceptionThrowing/TriggerServiceBusExceptionThrowingFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.Azure.Functions.Worker; 3 | using Microsoft.Azure.Functions.Worker.Http; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.TriggerServiceBusExceptionThrowing; 6 | 7 | public static class TriggerServiceBusExceptionThrowingFunction 8 | { 9 | [Function(nameof(TriggerServiceBusExceptionThrowingFunction))] 10 | public static TriggerServiceBusExceptionThrowingFunctionOutput RunGetTriggerServiceBusException( 11 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "service-bus-exception")] 12 | HttpRequestData request) 13 | { 14 | return new TriggerServiceBusExceptionThrowingFunctionOutput 15 | { 16 | Message = "{ 'Name': 'SomeName' }", 17 | Response = request.CreateResponse(HttpStatusCode.OK) 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/TriggerServiceBus/TriggerServiceBusFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.TriggerServiceBus; 7 | 8 | public static class TriggerServiceBusFunction 9 | { 10 | [FunctionName(nameof(TriggerServiceBusFunction))] 11 | public static IActionResult RunGetTriggerServiceBus( 12 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "service-bus")] 13 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 14 | HttpRequest request, 15 | [ServiceBus("customv4inprocess-queue", Connection = "ServiceBusConnection")] 16 | out string message) 17 | { 18 | message = "{ 'Name': 'SomeName' }"; 19 | 20 | return new OkResult(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/UserSecret/UserSecretFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.UserSecret; 8 | 9 | public class UserSecretFunction 10 | { 11 | private readonly SecretOptions _options; 12 | 13 | public UserSecretFunction(IOptions options) 14 | { 15 | _options = options.Value; 16 | } 17 | 18 | [FunctionName(nameof(UserSecretFunction))] 19 | public IActionResult RunGetSecret( 20 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "secret")] 21 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 22 | HttpRequest request) 23 | { 24 | return new OkObjectResult(_options); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/TriggerServiceBus/TriggerServiceBusFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.TriggerServiceBus; 7 | 8 | public static class TriggerServiceBusFunction 9 | { 10 | [FunctionName(nameof(TriggerServiceBusFunction))] 11 | public static IActionResult RunGetTriggerServiceBus( 12 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "service-bus")] 13 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 14 | HttpRequest request, 15 | [ServiceBus("defaultv4inprocess-queue", Connection = "ServiceBusConnection")] 16 | out string message) 17 | { 18 | message = "{ 'Name': 'SomeName' }"; 19 | 20 | return new OkResult(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/UserSecret/UserSecretFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.UserSecret; 8 | 9 | public class UserSecretFunction 10 | { 11 | private readonly SecretOptions _options; 12 | 13 | public UserSecretFunction(IOptions options) 14 | { 15 | _options = options.Value; 16 | } 17 | 18 | [FunctionName(nameof(UserSecretFunction))] 19 | public IActionResult RunGetSecret( 20 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "secret")] 21 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 22 | HttpRequest request) 23 | { 24 | return new OkObjectResult(_options); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Infrastructure/Telemetry/CustomHttpDependencyFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Infrastructure.Telemetry; 6 | 7 | #pragma warning disable CA1812 // This type is instantiated by the Inversion of Control container 8 | internal sealed class CustomHttpDependencyFilter : ITelemetryProcessor 9 | { 10 | private readonly ITelemetryProcessor _next; 11 | 12 | public CustomHttpDependencyFilter(ITelemetryProcessor next) 13 | { 14 | _next = next; 15 | } 16 | 17 | public void Process(ITelemetry item) 18 | { 19 | if (item is DependencyTelemetry dependency && 20 | "CustomHTTP".Equals(dependency.Type, StringComparison.Ordinal)) 21 | { 22 | return; 23 | } 24 | 25 | _next.Process(item); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Infrastructure/Telemetry/HealthRequestFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Infrastructure.Telemetry; 6 | 7 | #pragma warning disable CA1812 // This type is instantiated by the Inversion of Control container 8 | internal sealed class HealthRequestFilter : ITelemetryProcessor 9 | { 10 | private readonly ITelemetryProcessor _next; 11 | 12 | public HealthRequestFilter(ITelemetryProcessor next) 13 | { 14 | _next = next; 15 | } 16 | 17 | public void Process(ITelemetry item) 18 | { 19 | if (item is RequestTelemetry { ResponseCode: "200" } request && 20 | "HealthFunction".Equals(request.Name, StringComparison.OrdinalIgnoreCase)) 21 | { 22 | return; 23 | } 24 | 25 | _next.Process(item); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/UserSecret/UserSecretFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.Azure.Functions.Worker; 3 | using Microsoft.Azure.Functions.Worker.Http; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.UserSecret; 7 | 8 | public class UserSecretFunction 9 | { 10 | private readonly SecretOptions _options; 11 | 12 | public UserSecretFunction(IOptions options) 13 | { 14 | _options = options.Value; 15 | } 16 | 17 | [Function(nameof(UserSecretFunction))] 18 | public async Task RunGetSecretAsync( 19 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "secret")] 20 | HttpRequestData request) 21 | { 22 | var response = request.CreateResponse(HttpStatusCode.OK); 23 | await response.WriteAsJsonAsync(_options, cancellationToken: request.FunctionContext.CancellationToken); 24 | return response; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Infrastructure/Telemetry/TestingChannel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using Microsoft.ApplicationInsights.Channel; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 5 | 6 | public sealed class TestingChannel : ITelemetryChannel 7 | { 8 | public bool? DeveloperMode { get; set; } 9 | public string? EndpointAddress { get; set; } 10 | 11 | private readonly ConcurrentQueue _telemetryItems = new(); 12 | public ICollection TelemetryItems => _telemetryItems.ToArray(); 13 | 14 | public void Send(ITelemetry item) 15 | { 16 | _telemetryItems.Enqueue(item); 17 | } 18 | 19 | public void Clear() 20 | { 21 | _telemetryItems.Clear(); 22 | } 23 | 24 | public void Flush() 25 | { 26 | throw new NotSupportedException("We keep the telemetry in memory, so flushing is meaningless."); 27 | } 28 | 29 | public void Dispose() 30 | { 31 | _telemetryItems.Clear(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Infrastructure/Telemetry/TestingChannel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using Microsoft.ApplicationInsights.Channel; 3 | 4 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure.Telemetry; 5 | 6 | public sealed class TestingChannel : ITelemetryChannel 7 | { 8 | public bool? DeveloperMode { get; set; } 9 | public string? EndpointAddress { get; set; } 10 | 11 | private readonly ConcurrentQueue _telemetryItems = new(); 12 | public ICollection TelemetryItems => _telemetryItems.ToArray(); 13 | 14 | public void Send(ITelemetry item) 15 | { 16 | _telemetryItems.Enqueue(item); 17 | } 18 | 19 | public void Clear() 20 | { 21 | _telemetryItems.Clear(); 22 | } 23 | 24 | public void Flush() 25 | { 26 | throw new NotSupportedException("We keep the telemetry in memory, so flushing is meaningless."); 27 | } 28 | 29 | public void Dispose() 30 | { 31 | _telemetryItems.Clear(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/TraceLog/TraceLogFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.TraceLog; 8 | 9 | public class TraceLogFunction 10 | { 11 | private readonly ILogger _logger; 12 | 13 | public TraceLogFunction(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | [FunctionName(nameof(TraceLogFunction))] 19 | public IActionResult RunGetVerboseLog( 20 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "trace-log")] 21 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 22 | HttpRequest request) 23 | { 24 | _logger.LogTrace("I'm a log with Personally Identifiable Information"); 25 | 26 | return new AcceptedResult(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/TraceLog/TraceLogFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.TraceLog; 8 | 9 | public class TraceLogFunction 10 | { 11 | private readonly ILogger _logger; 12 | 13 | public TraceLogFunction(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | [FunctionName(nameof(TraceLogFunction))] 19 | public IActionResult RunGetVerboseLog( 20 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "trace-log")] 21 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 22 | HttpRequest request) 23 | { 24 | _logger.LogTrace("I'm a log with Personally Identifiable Information"); 25 | 26 | return new AcceptedResult(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/TriggerServiceBusExceptionThrowing/TriggerServiceBusExceptionThrowingFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.TriggerServiceBusExceptionThrowing; 7 | 8 | public static class TriggerServiceBusExceptionThrowingFunction 9 | { 10 | [FunctionName(nameof(TriggerServiceBusExceptionThrowingFunction))] 11 | public static IActionResult RunGetTriggerServiceBusException( 12 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "service-bus-exception")] 13 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 14 | HttpRequest request, 15 | [ServiceBus("customv4inprocess-exception-queue", Connection = "ServiceBusConnection")] 16 | out string message) 17 | { 18 | message = "{ 'Name': 'SomeName' }"; 19 | 20 | return new OkResult(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/TriggerServiceBusExceptionThrowing/TriggerServiceBusExceptionThrowingFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | 6 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.TriggerServiceBusExceptionThrowing; 7 | 8 | public static class TriggerServiceBusExceptionThrowingFunction 9 | { 10 | [FunctionName(nameof(TriggerServiceBusExceptionThrowingFunction))] 11 | public static IActionResult RunGetTriggerServiceBusException( 12 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "service-bus-exception")] 13 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 14 | HttpRequest request, 15 | [ServiceBus("defaultv4inprocess-exception-queue", Connection = "ServiceBusConnection")] 16 | out string message) 17 | { 18 | message = "{ 'Name': 'SomeName' }"; 19 | 20 | return new OkResult(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/FunctionsFinderTests.cs: -------------------------------------------------------------------------------- 1 | using Gabo.AzureFunctionsTelemetryTests.TestInfrastructure.Functions; 2 | 3 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 4 | 5 | public class FunctionsFinderTests 6 | { 7 | private readonly List _foundFunctions; 8 | 9 | public FunctionsFinderTests() 10 | { 11 | _foundFunctions = FunctionsFinder.GetServiceBusTriggeredFunctionNames(typeof(FunctionsFinderTests)); 12 | } 13 | 14 | [Fact] 15 | public void GivenStaticServiceBusFunction_ThenFound() 16 | { 17 | // Assert 18 | _foundFunctions.Should().Contain(nameof(StaticServiceBusFunction)); 19 | } 20 | 21 | [Fact] 22 | public void GivenInstanceServiceBusFunction_ThenFound() 23 | { 24 | // Assert 25 | _foundFunctions.Should().Contain(nameof(InstanceServiceBusFunction)); 26 | } 27 | 28 | [Fact] 29 | public void GivenHttpFunction_ThenNotFound() 30 | { 31 | // Assert 32 | _foundFunctions.Should().NotContain(nameof(HttpFunction)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/DuplicateExceptionsFilterServiceBusBindingTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 2 | 3 | public class DuplicateExceptionsFilterServiceBusBindingTests 4 | { 5 | private readonly DuplicateExceptionsFilter _target; 6 | 7 | private readonly MockTelemetryProcessor _innerProcessor; 8 | 9 | public DuplicateExceptionsFilterServiceBusBindingTests() 10 | { 11 | _innerProcessor = new MockTelemetryProcessor(); 12 | 13 | _target = new DuplicateExceptionsFilter(_innerProcessor, new List { "QueueFunction" }); 14 | } 15 | 16 | [Fact] 17 | public void 18 | GivenFunctionExecutionEndsInException_WhenFunctionIsKnownToBeServiceBusBinding_ThenFilterOutTelemetry() 19 | { 20 | // Arrange 21 | var exceptionTelemetry = ExceptionTelemetryBuilder 22 | .AsFunctionCompletedFailed("Function.QueueFunction"); 23 | 24 | // Act 25 | _target.Process(exceptionTelemetry); 26 | 27 | // Assert 28 | Assert.False(_innerProcessor.WasProcessorCalled); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gabriel Weyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/DefaultTelemetryTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests; 2 | 3 | [Collection("Default")] 4 | public sealed class DefaultTelemetryTests : IDisposable 5 | { 6 | private readonly DefaultTelemetryFunctionClient _client; 7 | 8 | public DefaultTelemetryTests() 9 | { 10 | _client = new DefaultTelemetryFunctionClient(); 11 | } 12 | 13 | [Fact] 14 | public async Task GivenTelemetry_ThenBothCloudRoleNameAndApplicationVersionAreEmpty() 15 | { 16 | // Act 17 | var telemetries = await _client.GetTelemetryAsync(); 18 | 19 | // Assert 20 | var actualTelemetry = telemetries.First(); 21 | var expectedTags = new TelemetryItemTags 22 | { 23 | ApplicationVersion = null, 24 | CloudRoleName = null 25 | }; 26 | actualTelemetry.Tags.Should().BeEquivalentTo(expectedTags, options => options 27 | .Including(t => t.ApplicationVersion) 28 | .Including(t => t.CloudRoleName)); 29 | } 30 | 31 | public void Dispose() 32 | { 33 | _client.Dispose(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/CustomApplicationInsightsOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | /// 4 | /// This has been replaced by . 5 | /// 6 | [Obsolete( 7 | "This has been replaced by CustomApplicationInsightsConfigBuilder. See migration guide: https://github.com/gabrielweyer/azure-functions-telemetry/blob/main/docs/Migrate-from-v1-to-v2.md.", 8 | true)] 9 | public class CustomApplicationInsightsOptionsBuilder 10 | { 11 | /// 12 | /// This has been replaced by . 13 | /// 14 | /// 15 | /// 16 | [Obsolete( 17 | "This has been replaced by CustomApplicationInsightsConfigBuilder. See migration guide: https://github.com/gabrielweyer/azure-functions-telemetry/blob/main/docs/Migrate-from-v1-to-v2.md.", 18 | true)] 19 | public CustomApplicationInsightsOptionsBuilder(string applicationName, Type typeFromEntryAssembly) 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/security-scanning.yml: -------------------------------------------------------------------------------- 1 | name: "C# security scanning" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - .github/workflows/build.yml 9 | - .github/workflows/skipped-build.yml 10 | - '.vscode/**' 11 | - 'deploy/**' 12 | - 'docs/**' 13 | - build.ps1 14 | - build.sh 15 | - CONTRIBUTING.md 16 | - LICENSE 17 | - README.md 18 | schedule: 19 | - cron: '25 17 * * 5' 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: [ 'csharp' ] 34 | 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | 39 | - name: Initialize CodeQL 40 | uses: github/codeql-action/init@v3 41 | with: 42 | languages: ${{ matrix.language }} 43 | 44 | - name: Autobuild 45 | uses: github/codeql-action/autobuild@v3 46 | 47 | - name: Perform CodeQL Analysis 48 | uses: github/codeql-action/analyze@v3 49 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/ServiceBusTriggerFilter.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | internal class ServiceBusTriggerFilter : ITelemetryProcessor 4 | { 5 | // This string literal is kept as a single-line so that it can be searched 6 | private const string ServiceBusTriggerLogTemplate = 7 | "Trigger Details: MessageId: {MessageId}, SequenceNumber: {SequenceNumber}, DeliveryCount: {DeliveryCount}, EnqueuedTimeUtc: {EnqueuedTimeUtc}, LockedUntilUtc: {LockedUntilUtc}, SessionId: {SessionId}"; 8 | 9 | private readonly ITelemetryProcessor _next; 10 | 11 | public ServiceBusTriggerFilter(ITelemetryProcessor next) 12 | { 13 | _next = next; 14 | } 15 | 16 | public void Process(ITelemetry item) 17 | { 18 | if (item is TraceTelemetry trace && HasTriggerDetailsTemplate(trace)) 19 | { 20 | return; 21 | } 22 | 23 | _next.Process(item); 24 | } 25 | 26 | private static bool HasTriggerDetailsTemplate(ISupportProperties trace) => 27 | trace.Properties.TryGetValue("prop__{OriginalFormat}", out var template) && 28 | StringHelper.IsSame(ServiceBusTriggerLogTemplate, template); 29 | } 30 | -------------------------------------------------------------------------------- /docs/Migrate-from-v1-to-v2.md: -------------------------------------------------------------------------------- 1 | # Migrate from v1 to v2 2 | 3 | The goal for v2 is to make the customisation configurable using application settings. To configure v1, you had to modify the code and redeploy. Two fluent configurations were obsoleted and replaced by application settings: 4 | 5 | - WithServiceBusTriggerFilter 6 | - WithHealthRequestFilter 7 | 8 | ## Type rename 9 | 10 | `CustomApplicationInsightsOptionsBuilder` has been renamed to `CustomApplicationInsightsConfigBuilder`. 11 | 12 | ## Service Bus trigger filter 13 | 14 | `WithServiceBusTriggerFilter()` has been deprecated. It has been replaced by the application setting `ApplicationInsights:DiscardServiceBusTrigger`. 15 | 16 | The default value is `false`, meaning that the Service Bus trigger traces will not be discarded. 17 | 18 | You can either set it to `true` or `false`. 19 | 20 | ## Health request filter 21 | 22 | `WithHealthRequestFilter(string healthCheckFunctionName)` has been deprecated. It has been replaced by the application setting `ApplicationInsights:HealthCheckFunctionName`. 23 | 24 | The default value is `null`, meaning that no request will be discaded. 25 | 26 | You can provide it a Function name if you want to discard a specific Function's requests. 27 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/ServiceBusTriggerFilterTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 2 | 3 | public class ServiceBusTriggerFilterTests 4 | { 5 | private readonly ServiceBusTriggerFilter _target; 6 | private readonly MockTelemetryProcessor _innerProcessor; 7 | 8 | public ServiceBusTriggerFilterTests() 9 | { 10 | _innerProcessor = new MockTelemetryProcessor(); 11 | _target = new ServiceBusTriggerFilter(_innerProcessor); 12 | } 13 | 14 | [Fact] 15 | public void GivenTriggerTrace_ThenDiscardTelemetry() 16 | { 17 | // Arrange 18 | var traceTelemetry = TraceTelemetryBuilder.AsServiceBusTrigger(); 19 | 20 | // Act 21 | _target.Process(traceTelemetry); 22 | 23 | // Assert 24 | Assert.False(_innerProcessor.WasProcessorCalled); 25 | } 26 | 27 | [Fact] 28 | public void GivenOtherTrace_ThenKeepTelemetry() 29 | { 30 | // Arrange 31 | var traceTelemetry = TraceTelemetryBuilder.AsFunctionStarted(); 32 | 33 | // Act 34 | _target.Process(traceTelemetry); 35 | 36 | // Assert 37 | Assert.True(_innerProcessor.WasProcessorCalled); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/CustomEvent/CustomEventFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.ApplicationInsights; 3 | using Microsoft.ApplicationInsights.DataContracts; 4 | using Microsoft.ApplicationInsights.Extensibility; 5 | using Microsoft.Azure.Functions.Worker; 6 | using Microsoft.Azure.Functions.Worker.Http; 7 | 8 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.CustomEvent; 9 | 10 | public class CustomEventFunction 11 | { 12 | private readonly TelemetryClient _telemetryClient; 13 | 14 | public CustomEventFunction(TelemetryConfiguration telemetryConfiguration) 15 | { 16 | _telemetryClient = new TelemetryClient(telemetryConfiguration); 17 | } 18 | 19 | [Function(nameof(CustomEventFunction))] 20 | public HttpResponseData RunGetAppInsightsEvent( 21 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "event")] 22 | HttpRequestData request) 23 | { 24 | var @event = new EventTelemetry 25 | { 26 | Name = "SomethingHappened", 27 | Properties = 28 | { 29 | { "ImportantEventProperty", "SomeValue" } 30 | } 31 | }; 32 | 33 | _telemetryClient.TrackEvent(@event); 34 | 35 | return request.CreateResponse(HttpStatusCode.Accepted); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/Availability/AvailabilityFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.ApplicationInsights; 3 | using Microsoft.ApplicationInsights.DataContracts; 4 | using Microsoft.ApplicationInsights.Extensibility; 5 | using Microsoft.Azure.Functions.Worker; 6 | using Microsoft.Azure.Functions.Worker.Http; 7 | 8 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.Availability; 9 | 10 | public class AvailabilityFunction 11 | { 12 | private readonly TelemetryClient _telemetryClient; 13 | 14 | public AvailabilityFunction(TelemetryConfiguration telemetryConfiguration) 15 | { 16 | _telemetryClient = new TelemetryClient(telemetryConfiguration); 17 | } 18 | 19 | [Function(nameof(AvailabilityFunction))] 20 | public HttpResponseData RunGetAppInsightsAvailability( 21 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "availability")] 22 | HttpRequestData request) 23 | { 24 | var availability = new AvailabilityTelemetry( 25 | "WeAreAvailable", 26 | DateTimeOffset.UtcNow, 27 | TimeSpan.FromMilliseconds(125), 28 | "FromSomewhere", 29 | true); 30 | 31 | _telemetryClient.TrackAvailability(availability); 32 | 33 | return request.CreateResponse(HttpStatusCode.Accepted); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/CustomTelemetryTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction; 3 | 4 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests; 5 | 6 | [Collection("Custom")] 7 | public sealed class CustomTelemetryTests : IDisposable 8 | { 9 | private readonly CustomTelemetryFunctionClient _client; 10 | 11 | public CustomTelemetryTests() 12 | { 13 | _client = new CustomTelemetryFunctionClient(); 14 | } 15 | 16 | [Fact] 17 | public async Task GivenTelemetry_ThenExpectedCloudRoleNameAndApplicationVersion() 18 | { 19 | // Act 20 | var telemetries = await _client.GetTelemetryAsync(); 21 | 22 | // Assert 23 | var actualTelemetry = telemetries.First(); 24 | var expectedVersion = FileVersionInfo.GetVersionInfo(typeof(Startup).Assembly.Location).ProductVersion; 25 | var expectedTags = new TelemetryItemTags 26 | { 27 | ApplicationVersion = expectedVersion, 28 | CloudRoleName = "customv4inprocess" 29 | }; 30 | actualTelemetry.Tags.Should().BeEquivalentTo(expectedTags, options => options 31 | .Including(t => t.ApplicationVersion) 32 | .Including(t => t.CloudRoleName)); 33 | } 34 | 35 | public void Dispose() 36 | { 37 | _client.Dispose(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/TelemetryItemConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 4 | 5 | internal sealed class TelemetryItemConverter : JsonConverter 6 | { 7 | public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) 8 | { 9 | throw new NotSupportedException("We only use this converter to read."); 10 | } 11 | 12 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) 13 | { 14 | var jsonObject = JObject.Load(reader); 15 | var name = (string?)jsonObject["name"]; 16 | 17 | TelemetryItem item = name switch 18 | { 19 | "AppMetrics" => new MetricItem(), 20 | "AppTraces" => new TraceItem(), 21 | "AppRequests" => new RequestItem(), 22 | "AppExceptions" => new ExceptionItem(), 23 | "AppDependencies" => new DependencyItem(), 24 | _ => throw new NotSupportedException($"The telemetry type {name} is not supported.") 25 | }; 26 | 27 | serializer.Populate(jsonObject.CreateReader(), item); 28 | 29 | return item; 30 | } 31 | 32 | public override bool CanConvert(Type objectType) 33 | { 34 | return typeof(TelemetryItem).IsAssignableFrom(objectType); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/Dependency/DependencyFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.ApplicationInsights; 3 | using Microsoft.ApplicationInsights.DataContracts; 4 | using Microsoft.ApplicationInsights.Extensibility; 5 | using Microsoft.Azure.Functions.Worker; 6 | using Microsoft.Azure.Functions.Worker.Http; 7 | 8 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.Dependency; 9 | 10 | public class DependencyFunction 11 | { 12 | private readonly TelemetryClient _telemetryClient; 13 | 14 | public DependencyFunction(TelemetryConfiguration telemetryConfiguration) 15 | { 16 | _telemetryClient = new TelemetryClient(telemetryConfiguration); 17 | } 18 | 19 | [Function(nameof(DependencyFunction))] 20 | public HttpResponseData RunGetAppInsightsDependency( 21 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "dependency")] 22 | HttpRequestData request) 23 | { 24 | var dependency = new DependencyTelemetry( 25 | "CustomHTTP", 26 | "AnotherSystem", 27 | "VeryImportantDependency", 28 | "/whatever", 29 | DateTimeOffset.UtcNow, 30 | TimeSpan.FromMilliseconds(125), 31 | "200", 32 | true); 33 | 34 | _telemetryClient.TrackDependency(dependency); 35 | 36 | return request.CreateResponse(HttpStatusCode.Accepted); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AzureFunctionsTelemetry.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True 12 | True -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/CustomEvent/CustomEventFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | 9 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.CustomEvent; 10 | 11 | public class CustomEventFunction 12 | { 13 | private readonly TelemetryClient _telemetryClient; 14 | 15 | public CustomEventFunction(TelemetryConfiguration telemetryConfiguration) 16 | { 17 | _telemetryClient = new TelemetryClient(telemetryConfiguration); 18 | } 19 | 20 | [FunctionName(nameof(CustomEventFunction))] 21 | public IActionResult RunGetAppInsightsEvent( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "event")] 23 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 24 | HttpRequest request) 25 | { 26 | var @event = new EventTelemetry 27 | { 28 | Name = "SomethingHappened", 29 | Properties = 30 | { 31 | { "ImportantEventProperty", "SomeValue" } 32 | } 33 | }; 34 | 35 | _telemetryClient.TrackEvent(@event); 36 | 37 | return new AcceptedResult(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/CustomEvent/CustomEventFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | 9 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.CustomEvent; 10 | 11 | public class CustomEventFunction 12 | { 13 | private readonly TelemetryClient _telemetryClient; 14 | 15 | public CustomEventFunction(TelemetryConfiguration telemetryConfiguration) 16 | { 17 | _telemetryClient = new TelemetryClient(telemetryConfiguration); 18 | } 19 | 20 | [FunctionName(nameof(CustomEventFunction))] 21 | public IActionResult RunGetAppInsightsEvent( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "event")] 23 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 24 | HttpRequest request) 25 | { 26 | var @event = new EventTelemetry 27 | { 28 | Name = "SomethingHappened", 29 | Properties = 30 | { 31 | { "ImportantEventProperty", "SomeValue" } 32 | } 33 | }; 34 | 35 | _telemetryClient.TrackEvent(@event); 36 | 37 | return new AcceptedResult(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/DuplicateExceptionsFilter.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | internal class DuplicateExceptionsFilter : ITelemetryProcessor 4 | { 5 | private readonly ITelemetryProcessor _next; 6 | private readonly List _serviceBusFunctionCategories; 7 | 8 | public DuplicateExceptionsFilter(ITelemetryProcessor next, List serviceBusTriggeredFunctionNames) 9 | { 10 | _next = next; 11 | _serviceBusFunctionCategories = 12 | serviceBusTriggeredFunctionNames.Select(name => $"Function.{name}").ToList(); 13 | } 14 | 15 | public void Process(ITelemetry item) 16 | { 17 | if (item is ExceptionTelemetry exceptionTelemetry) 18 | { 19 | if (TelemetryHelper.TryGetCategory(exceptionTelemetry, out var category) && category != null) 20 | { 21 | if (StringHelper.IsSame(FunctionRuntimeCategory.HostResults, category)) 22 | { 23 | return; 24 | } 25 | 26 | if (_serviceBusFunctionCategories.Count > 0 && 27 | _serviceBusFunctionCategories.Contains(category, StringComparer.Ordinal) && 28 | TelemetryHelper.IsFunctionCompletedTelemetry(exceptionTelemetry)) 29 | { 30 | return; 31 | } 32 | } 33 | } 34 | 35 | _next.Process(item); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/Availability/AvailabilityFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | 9 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.Availability; 10 | 11 | public class AvailabilityFunction 12 | { 13 | private readonly TelemetryClient _telemetryClient; 14 | 15 | public AvailabilityFunction(TelemetryConfiguration telemetryConfiguration) 16 | { 17 | _telemetryClient = new TelemetryClient(telemetryConfiguration); 18 | } 19 | 20 | [FunctionName(nameof(AvailabilityFunction))] 21 | public IActionResult RunGetAppInsightsAvailability( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "availability")] 23 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 24 | HttpRequest request) 25 | { 26 | var availability = new AvailabilityTelemetry( 27 | "WeAreAvailable", 28 | DateTimeOffset.UtcNow, 29 | TimeSpan.FromMilliseconds(125), 30 | "FromSomewhere", 31 | true); 32 | 33 | _telemetryClient.TrackAvailability(availability); 34 | 35 | return new AcceptedResult(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/Availability/AvailabilityFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | 9 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.Availability; 10 | 11 | public class AvailabilityFunction 12 | { 13 | private readonly TelemetryClient _telemetryClient; 14 | 15 | public AvailabilityFunction(TelemetryConfiguration telemetryConfiguration) 16 | { 17 | _telemetryClient = new TelemetryClient(telemetryConfiguration); 18 | } 19 | 20 | [FunctionName(nameof(AvailabilityFunction))] 21 | public IActionResult RunGetAppInsightsAvailability( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "availability")] 23 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 24 | HttpRequest request) 25 | { 26 | var availability = new AvailabilityTelemetry( 27 | "WeAreAvailable", 28 | DateTimeOffset.UtcNow, 29 | TimeSpan.FromMilliseconds(125), 30 | "FromSomewhere", 31 | true); 32 | 33 | _telemetryClient.TrackAvailability(availability); 34 | 35 | return new AcceptedResult(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/Dependency/DependencyFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | 9 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.Dependency; 10 | 11 | public class DependencyFunction 12 | { 13 | private readonly TelemetryClient _telemetryClient; 14 | 15 | public DependencyFunction(TelemetryConfiguration telemetryConfiguration) 16 | { 17 | _telemetryClient = new TelemetryClient(telemetryConfiguration); 18 | } 19 | 20 | [FunctionName(nameof(DependencyFunction))] 21 | public IActionResult RunGetAppInsightsDependency( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "dependency")] 23 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 24 | HttpRequest request) 25 | { 26 | var dependency = new DependencyTelemetry( 27 | "CustomHTTP", 28 | "AnotherSystem", 29 | "VeryImportantDependency", 30 | "/whatever", 31 | DateTimeOffset.UtcNow, 32 | TimeSpan.FromMilliseconds(125), 33 | "200", 34 | true); 35 | 36 | _telemetryClient.TrackDependency(dependency); 37 | 38 | return new AcceptedResult(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/Dependency/DependencyFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | 9 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.Dependency; 10 | 11 | public class DependencyFunction 12 | { 13 | private readonly TelemetryClient _telemetryClient; 14 | 15 | public DependencyFunction(TelemetryConfiguration telemetryConfiguration) 16 | { 17 | _telemetryClient = new TelemetryClient(telemetryConfiguration); 18 | } 19 | 20 | [FunctionName(nameof(DependencyFunction))] 21 | public IActionResult RunGetAppInsightsDependency( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "dependency")] 23 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 24 | HttpRequest request) 25 | { 26 | var dependency = new DependencyTelemetry( 27 | "CustomHTTP", 28 | "AnotherSystem", 29 | "VeryImportantDependency", 30 | "/whatever", 31 | DateTimeOffset.UtcNow, 32 | TimeSpan.FromMilliseconds(125), 33 | "200", 34 | true); 35 | 36 | _telemetryClient.TrackDependency(dependency); 37 | 38 | return new AcceptedResult(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/AppInsightsConnectionStringIntegrationTests/AppInsightsConnectionStringIntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | latest 6 | enable 7 | enable 8 | Gabo.AzureFunctionsTelemetryAppInsightsConnectionStringIntegrationTests 9 | Gabo.AzureFunctionsTelemetryAppInsightsConnectionStringIntegrationTests 10 | false 11 | 7035;CA1707;CA2007;VSTHRD200;CA1515 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | all 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Startup.cs: -------------------------------------------------------------------------------- 1 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction; 2 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.UserSecret; 3 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure; 4 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure.Telemetry; 5 | using Microsoft.ApplicationInsights.Channel; 6 | using Microsoft.ApplicationInsights.Extensibility; 7 | using Microsoft.Azure.Functions.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | 11 | [assembly: FunctionsStartup(typeof(Startup))] 12 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction; 13 | 14 | public class Startup : FunctionsStartup 15 | { 16 | public override void Configure(IFunctionsHostBuilder builder) 17 | { 18 | var testingOptions = builder.GetContext().Configuration.GetSection("Testing").Get(); 19 | 20 | if (testingOptions?.IsEnabled == true) 21 | { 22 | builder.Services.AddSingleton(); 23 | } 24 | 25 | builder.Services 26 | .AddMyOptions("Testing") 27 | .AddMyOptions("Secret"); 28 | 29 | builder.Services 30 | .AddApplicationInsightsTelemetryProcessor() 31 | .AddSingleton(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/CustomApplicationInsightsOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 4 | 5 | /// 6 | /// Used to configure the Application Insights integration. Expected to be populated through the 7 | /// abstraction. 8 | /// 9 | public class CustomApplicationInsightsOptions 10 | { 11 | /// 12 | /// This will discard the Service Bus trigger trace telemetry. 13 | /// The default value is false. 14 | /// Recommended on high-traffic services. 15 | /// 16 | public bool DiscardServiceBusTrigger { get; set; } 17 | /// 18 | /// This will discard the 'Executing ...' and 'Executed ...' Function execution traces. 19 | /// The default value is true. 20 | /// Typically you'll only disable this when raising an Azure support ticket for a performance issue. 21 | /// 22 | public bool DiscardFunctionExecutionTraces { get; set; } = true; 23 | /// 24 | /// We will discard all '200' HTTP status code requests for the specified Function. 25 | /// The default value is null (no request will be discarded). 26 | /// We only support a single health check function per Function App, but you can create a telemetry processor 27 | /// to discard other requests. 28 | /// 29 | public string? HealthCheckFunctionName { get; set; } 30 | } 31 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/TestInfrastructure/Builders/ExceptionTelemetryBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.DataContracts; 2 | using Microsoft.Azure.WebJobs.Logging; 3 | 4 | namespace Gabo.AzureFunctionsTelemetryTests.TestInfrastructure.Builders; 5 | 6 | public class ExceptionTelemetryBuilder 7 | { 8 | private readonly string _category; 9 | private string? _eventId; 10 | private string? _eventName; 11 | 12 | public ExceptionTelemetryBuilder(string category) 13 | { 14 | _category = category; 15 | } 16 | 17 | private ExceptionTelemetryBuilder WithEventId(string eventId) 18 | { 19 | _eventId = eventId; 20 | return this; 21 | } 22 | 23 | private ExceptionTelemetryBuilder WithEventName(string eventName) 24 | { 25 | _eventName = eventName; 26 | return this; 27 | } 28 | 29 | public static ExceptionTelemetry AsFunctionCompletedFailed(string category) 30 | { 31 | return new ExceptionTelemetryBuilder(category) 32 | .WithEventId(FunctionRuntimeEventId.FunctionCompletedFailed) 33 | .WithEventName(FunctionRuntimeEventName.FunctionCompleted) 34 | .Build(); 35 | } 36 | 37 | public ExceptionTelemetry Build() 38 | { 39 | var exceptionTelemetry = new ExceptionTelemetry(); 40 | exceptionTelemetry.Properties[LogConstants.CategoryNameKey] = _category; 41 | exceptionTelemetry.Properties[LogConstants.EventIdKey] = _eventId; 42 | exceptionTelemetry.Properties[LogConstants.EventNameKey] = _eventName; 43 | return exceptionTelemetry; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/FunctionExecutionTracesFilter.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | internal class FunctionExecutionTracesFilter : ITelemetryProcessor 4 | { 5 | private readonly ITelemetryProcessor _next; 6 | 7 | public FunctionExecutionTracesFilter(ITelemetryProcessor next) 8 | { 9 | _next = next; 10 | } 11 | 12 | public void Process(ITelemetry item) 13 | { 14 | if (item is TraceTelemetry trace) 15 | { 16 | if (TelemetryHelper.IsFunctionStartedTelemetry(trace)) 17 | { 18 | return; 19 | } 20 | 21 | if (TelemetryHelper.IsFunctionCompletedTelemetry(trace)) 22 | { 23 | return; 24 | } 25 | 26 | if (IsServiceBusBindingMessageErrorProcessingTrace(trace)) 27 | { 28 | return; 29 | } 30 | } 31 | 32 | _next.Process(item); 33 | } 34 | 35 | private static bool IsServiceBusBindingMessageErrorProcessingTrace(TraceTelemetry trace) 36 | { 37 | if (trace.SeverityLevel != SeverityLevel.Error) 38 | { 39 | return false; 40 | } 41 | 42 | if (!TelemetryHelper.TryGetCategory(trace, out var category) || category == null) 43 | { 44 | return false; 45 | } 46 | 47 | return StringHelper.IsSame(FunctionRuntimeCategory.ServiceBusListener, category) && 48 | !string.IsNullOrEmpty(trace.Message) && 49 | trace.Message.StartsWith("Message processing error", StringComparison.OrdinalIgnoreCase); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/Processor/ProcessorFunction.cs: -------------------------------------------------------------------------------- 1 | using Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 2 | using Microsoft.ApplicationInsights.Extensibility; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | 8 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.Processor; 9 | 10 | public class ProcessorFunction 11 | { 12 | private readonly TelemetryCounterProcessor _telemetryCounterProcessor; 13 | 14 | public ProcessorFunction(TelemetryConfiguration telemetryConfiguration) 15 | { 16 | _telemetryCounterProcessor = (TelemetryCounterProcessor)telemetryConfiguration.TelemetryProcessors 17 | .Single(p => p is TelemetryCounterProcessor); 18 | } 19 | 20 | [FunctionName(nameof(ProcessorFunction))] 21 | public IActionResult RunGetProcessor( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "processor")] 23 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 24 | HttpRequest request) 25 | { 26 | return new OkObjectResult(new 27 | { 28 | _telemetryCounterProcessor.AvailabilityTelemetryCount, 29 | _telemetryCounterProcessor.DependencyTelemetryCount, 30 | _telemetryCounterProcessor.EventTelemetryCount, 31 | _telemetryCounterProcessor.ExceptionTelemetryCount, 32 | _telemetryCounterProcessor.MetricTelemetryCount, 33 | _telemetryCounterProcessor.RequestTelemetryCount, 34 | _telemetryCounterProcessor.TraceTelemetryCount 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/DefaultV4IsolatedFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | v4 5 | latest 6 | false 7 | enable 8 | enable 9 | Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction 10 | Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction 11 | Exe 12 | 074ca336-270b-4832-9a1a-60baf152b727 13 | CS7035;MA0004;CA2007;CA1848;CA1062;CA1515 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | PreserveNewest 30 | Never 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/DefaultV4InProcessFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | v4 5 | latest 6 | enable 7 | enable 8 | Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction 9 | Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction 10 | false 11 | 074ca336-270b-4832-9a1a-60baf152b727 12 | 1.0.0.0 13 | 2.0.0.0 14 | 3.0.0.0 15 | 7035;CA1062;CA1515 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | PreserveNewest 33 | Never 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/AzureFunctionsTelemetryIntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | latest 6 | enable 7 | enable 8 | Gabo.AzureFunctionsTelemetryIntegrationTests 9 | Gabo.AzureFunctionsTelemetryIntegrationTests 10 | false 11 | 7035;CA1707;CA2007;MA0004;VSTHRD200;CA1515 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | all 26 | 27 | 28 | 29 | 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | all 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/Initializer/InitializerFunction.cs: -------------------------------------------------------------------------------- 1 | using Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 2 | using Microsoft.ApplicationInsights.Extensibility; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | 8 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.Initializer; 9 | 10 | public class InitializerFunction 11 | { 12 | private readonly TelemetryCounterInitializer _telemetryCounterInitializer; 13 | 14 | public InitializerFunction(TelemetryConfiguration telemetryConfiguration) 15 | { 16 | _telemetryCounterInitializer = (TelemetryCounterInitializer)telemetryConfiguration.TelemetryInitializers 17 | .Single(p => p is TelemetryCounterInitializer); 18 | } 19 | 20 | [FunctionName(nameof(InitializerFunction))] 21 | public IActionResult RunGetProcessor( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "initializer")] 23 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 24 | HttpRequest request) 25 | { 26 | return new OkObjectResult(new 27 | { 28 | _telemetryCounterInitializer.AvailabilityTelemetryCount, 29 | _telemetryCounterInitializer.DependencyTelemetryCount, 30 | _telemetryCounterInitializer.EventTelemetryCount, 31 | _telemetryCounterInitializer.ExceptionTelemetryCount, 32 | _telemetryCounterInitializer.MetricTelemetryCount, 33 | _telemetryCounterInitializer.RequestTelemetryCount, 34 | _telemetryCounterInitializer.TraceTelemetryCount 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/CustomV4InProcessFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | v4 5 | latest 6 | enable 7 | enable 8 | Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction 9 | Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction 10 | false 11 | 074ca336-270b-4832-9a1a-60baf152b727 12 | 1.0.0.0 13 | 2.0.0.0 14 | 3.0.0.0 15 | 7035;CA1062;CA1812;CA1515 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | PreserveNewest 32 | Never 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/Initializer/InitializerFunction.cs: -------------------------------------------------------------------------------- 1 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure.Telemetry; 2 | using Microsoft.ApplicationInsights.Extensibility; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | 8 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.Initializer; 9 | 10 | public class InitializerFunction 11 | { 12 | private readonly TelemetryCounterInitializer _telemetryCounterInitializer; 13 | 14 | public InitializerFunction(TelemetryConfiguration telemetryConfiguration) 15 | { 16 | _telemetryCounterInitializer = (TelemetryCounterInitializer)telemetryConfiguration.TelemetryInitializers 17 | .Single(p => p is TelemetryCounterInitializer); 18 | } 19 | 20 | [FunctionName(nameof(InitializerFunction))] 21 | public IActionResult RunGetInitializer( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "initializer")] 23 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 24 | HttpRequest request) 25 | { 26 | return new OkObjectResult( 27 | new 28 | { 29 | _telemetryCounterInitializer.AvailabilityTelemetryCount, 30 | _telemetryCounterInitializer.DependencyTelemetryCount, 31 | _telemetryCounterInitializer.EventTelemetryCount, 32 | _telemetryCounterInitializer.ExceptionTelemetryCount, 33 | _telemetryCounterInitializer.MetricTelemetryCount, 34 | _telemetryCounterInitializer.RequestTelemetryCount, 35 | _telemetryCounterInitializer.TraceTelemetryCount 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/Initializer/InitializerFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Infrastructure.Telemetry; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | using Microsoft.Azure.Functions.Worker; 5 | using Microsoft.Azure.Functions.Worker.Http; 6 | 7 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.Initializer; 8 | 9 | public class InitializerFunction 10 | { 11 | private readonly TelemetryCounterInitializer _telemetryCounterInitializer; 12 | 13 | public InitializerFunction(TelemetryConfiguration telemetryConfiguration) 14 | { 15 | _telemetryCounterInitializer = (TelemetryCounterInitializer)telemetryConfiguration.TelemetryInitializers 16 | .Single(p => p is TelemetryCounterInitializer); 17 | } 18 | 19 | [Function(nameof(InitializerFunction))] 20 | public async Task RunGetInitializerAsync( 21 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "initializer")] 22 | HttpRequestData request) 23 | { 24 | var response = request.CreateResponse(HttpStatusCode.OK); 25 | await response.WriteAsJsonAsync( 26 | new 27 | { 28 | _telemetryCounterInitializer.AvailabilityTelemetryCount, 29 | _telemetryCounterInitializer.DependencyTelemetryCount, 30 | _telemetryCounterInitializer.EventTelemetryCount, 31 | _telemetryCounterInitializer.ExceptionTelemetryCount, 32 | _telemetryCounterInitializer.MetricTelemetryCount, 33 | _telemetryCounterInitializer.RequestTelemetryCount, 34 | _telemetryCounterInitializer.TraceTelemetryCount 35 | }, cancellationToken: request.FunctionContext.CancellationToken); 36 | return response; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Functions/Processor/ProcessorFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Infrastructure.Telemetry; 3 | using Microsoft.ApplicationInsights; 4 | using Microsoft.Azure.Functions.Worker; 5 | using Microsoft.Azure.Functions.Worker.Http; 6 | 7 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.Processor; 8 | 9 | public class ProcessorFunction 10 | { 11 | private readonly TelemetryCounterProcessor _telemetryCounterProcessor; 12 | 13 | public ProcessorFunction(TelemetryClient telemetryClient) 14 | { 15 | var processors = telemetryClient.TelemetryConfiguration.TelemetrySinks.First() 16 | .TelemetryProcessors; 17 | _telemetryCounterProcessor = (TelemetryCounterProcessor)processors.Single(p => p is TelemetryCounterProcessor); 18 | } 19 | 20 | [Function(nameof(ProcessorFunction))] 21 | public async Task RunGetProcessorAsync( 22 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "processor")] 23 | HttpRequestData request) 24 | { 25 | var response = request.CreateResponse(HttpStatusCode.OK); 26 | await response.WriteAsJsonAsync( 27 | new 28 | { 29 | _telemetryCounterProcessor.AvailabilityTelemetryCount, 30 | _telemetryCounterProcessor.DependencyTelemetryCount, 31 | _telemetryCounterProcessor.EventTelemetryCount, 32 | _telemetryCounterProcessor.ExceptionTelemetryCount, 33 | _telemetryCounterProcessor.MetricTelemetryCount, 34 | _telemetryCounterProcessor.RequestTelemetryCount, 35 | _telemetryCounterProcessor.TraceTelemetryCount 36 | }, cancellationToken: request.FunctionContext.CancellationToken); 37 | return response; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/CustomApplicationInsightsConfigBuilderTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 2 | 3 | public class CustomApplicationInsightsConfigBuilderTests 4 | { 5 | private const string DefaultConfigurationSectionName = "ApplicationInsights"; 6 | 7 | [Fact] 8 | public void GivenApplicationNameProvided_ThenSetConfig() 9 | { 10 | // Arrange 11 | var builder = new CustomApplicationInsightsConfigBuilder("some-name", typeof(StringHelper)); 12 | 13 | // Act 14 | var actualConfig = builder.Build(); 15 | 16 | // Assert 17 | var expectedConfig = 18 | new CustomApplicationInsightsConfig("some-name", typeof(StringHelper), DefaultConfigurationSectionName); 19 | actualConfig.Should().BeEquivalentTo(expectedConfig); 20 | } 21 | 22 | [Fact] 23 | public void GivenApplicationNameNotProvided_ThenDontSetApplicationName() 24 | { 25 | // Arrange 26 | var builder = new CustomApplicationInsightsConfigBuilder(typeof(StringHelper)); 27 | 28 | // Act 29 | var actualConfig = builder.Build(); 30 | 31 | // Assert 32 | var expectedConfig = 33 | new CustomApplicationInsightsConfig(applicationName: null, typeof(StringHelper), 34 | DefaultConfigurationSectionName); 35 | actualConfig.Should().BeEquivalentTo(expectedConfig); 36 | } 37 | 38 | [Theory] 39 | [InlineData("")] 40 | [InlineData(" ")] 41 | public void GivenEmptyOrWhiteSpaceSectionConfigurationName_ThenThrows(string configurationSectionName) 42 | { 43 | // Arrange 44 | var builder = new CustomApplicationInsightsConfigBuilder("some-name", typeof(StringHelper)); 45 | 46 | // Act & Assert 47 | Assert.ThrowsAny(() => builder.WithConfigurationSectionName(configurationSectionName)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/HealthRequestFilterTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 2 | 3 | public class HealthRequestFilterTests 4 | { 5 | private readonly HealthRequestFilter _target; 6 | private readonly MockTelemetryProcessor _innerProcessor; 7 | 8 | public HealthRequestFilterTests() 9 | { 10 | _innerProcessor = new MockTelemetryProcessor(); 11 | _target = new HealthRequestFilter(_innerProcessor, "HealthFunction"); 12 | } 13 | 14 | [Fact] 15 | public void GivenSuccessfulHealthRequest_ThenDiscardTelemetry() 16 | { 17 | // Arrange 18 | var requestTelemetry = RequestTelemetryBuilder.AsHttp(); 19 | requestTelemetry.Name = "HealthFunction"; 20 | requestTelemetry.ResponseCode = "200"; 21 | 22 | // Act 23 | _target.Process(requestTelemetry); 24 | 25 | // Assert 26 | Assert.False(_innerProcessor.WasProcessorCalled); 27 | } 28 | 29 | [Fact] 30 | public void GivenUnsuccessfulHealthRequest_ThenKeepTelemetry() 31 | { 32 | // Arrange 33 | var requestTelemetry = RequestTelemetryBuilder.AsHttp(); 34 | requestTelemetry.Name = "HealthFunction"; 35 | requestTelemetry.ResponseCode = "500"; 36 | 37 | // Act 38 | _target.Process(requestTelemetry); 39 | 40 | // Assert 41 | Assert.True(_innerProcessor.WasProcessorCalled); 42 | } 43 | 44 | [Fact] 45 | public void GivenOtherSuccessfulRequest_ThenKeepTelemetry() 46 | { 47 | // Arrange 48 | var requestTelemetry = RequestTelemetryBuilder.AsHttp(); 49 | requestTelemetry.Name = "OtherFunction"; 50 | requestTelemetry.ResponseCode = "200"; 51 | 52 | // Act 53 | _target.Process(requestTelemetry); 54 | 55 | // Assert 56 | Assert.True(_innerProcessor.WasProcessorCalled); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/InstanceInitializer/InstanceInitializerFunction.cs: -------------------------------------------------------------------------------- 1 | using Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 2 | using Microsoft.ApplicationInsights.Extensibility; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | 8 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.InstanceInitializer; 9 | 10 | public class InstanceInitializerFunction 11 | { 12 | private readonly TelemetryCounterInstanceInitializer _telemetryCounterInstanceInitializer; 13 | 14 | public InstanceInitializerFunction(TelemetryConfiguration telemetryConfiguration) 15 | { 16 | _telemetryCounterInstanceInitializer = (TelemetryCounterInstanceInitializer)telemetryConfiguration 17 | .TelemetryInitializers 18 | .Single(p => p is TelemetryCounterInstanceInitializer); 19 | } 20 | 21 | [FunctionName(nameof(InstanceInitializerFunction))] 22 | public IActionResult RunGetProcessor( 23 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "instance-initializer")] 24 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 25 | HttpRequest request) 26 | { 27 | return new OkObjectResult(new 28 | { 29 | _telemetryCounterInstanceInitializer.AvailabilityTelemetryCount, 30 | _telemetryCounterInstanceInitializer.DependencyTelemetryCount, 31 | _telemetryCounterInstanceInitializer.EventTelemetryCount, 32 | _telemetryCounterInstanceInitializer.ExceptionTelemetryCount, 33 | _telemetryCounterInstanceInitializer.MetricTelemetryCount, 34 | _telemetryCounterInstanceInitializer.RequestTelemetryCount, 35 | _telemetryCounterInstanceInitializer.TraceTelemetryCount 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/DuplicateExceptionsFilterHttpBindingTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 2 | 3 | public class DuplicateExceptionsFilterHttpBindingTests 4 | { 5 | private readonly DuplicateExceptionsFilter _target; 6 | 7 | private readonly MockTelemetryProcessor _innerProcessor; 8 | 9 | public DuplicateExceptionsFilterHttpBindingTests() 10 | { 11 | _innerProcessor = new MockTelemetryProcessor(); 12 | 13 | _target = new DuplicateExceptionsFilter(_innerProcessor, new List()); 14 | } 15 | 16 | [Fact] 17 | public void GivenCategoryIsHostResultsAsUsedByAllBindings_WhenTelemetryIsException_ThenFilterOutTelemetry() 18 | { 19 | // Arrange 20 | var exceptionTelemetry = new ExceptionTelemetryBuilder(FunctionRuntimeCategory.HostResults) 21 | .Build(); 22 | 23 | // Act 24 | _target.Process(exceptionTelemetry); 25 | 26 | // Assert 27 | Assert.False(_innerProcessor.WasProcessorCalled); 28 | } 29 | 30 | [Fact] 31 | public void GivenCategoryIsHostExecutorAsUsedByServiceBusBinding_WhenTelemetryIsException_ThenKeepTelemetry() 32 | { 33 | // Arrange 34 | var exceptionTelemetry = new ExceptionTelemetryBuilder(FunctionRuntimeCategory.HostExecutor) 35 | .Build(); 36 | 37 | // Act 38 | _target.Process(exceptionTelemetry); 39 | 40 | // Assert 41 | Assert.True(_innerProcessor.WasProcessorCalled); 42 | } 43 | 44 | [Fact] 45 | public void GivenFunctionCategory_WhenTelemetryIsException_ThenKeepTelemetry() 46 | { 47 | // Arrange 48 | var exceptionTelemetry = new ExceptionTelemetryBuilder("Function.QueueFunction") 49 | .Build(); 50 | 51 | // Act 52 | _target.Process(exceptionTelemetry); 53 | 54 | // Assert 55 | Assert.True(_innerProcessor.WasProcessorCalled); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/ApplicationInsightsServiceCollectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Extensibility; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 5 | 6 | public class ApplicationInsightsServiceCollectionExtensionsTests 7 | { 8 | [Fact] 9 | public void GivenApplicationNameNotProvided_ThenDontRegisterApplicationInitializer() 10 | { 11 | // Arrange 12 | var serviceCollection = new ServiceCollection(); 13 | var config = new CustomApplicationInsightsConfig(applicationName: null, typeof(StringHelper), "Q"); 14 | 15 | // Act 16 | var updatedServiceCollection = serviceCollection.AddCustomApplicationInsights(config); 17 | 18 | // Assert 19 | var registeredTelemetryInitializers = GetTelemetryInitializers(updatedServiceCollection); 20 | registeredTelemetryInitializers.Should().NotContain(typeof(ApplicationInitializer)); 21 | } 22 | 23 | [Fact] 24 | public void GivenApplicationNameProvided_ThenRegisterApplicationInitializer() 25 | { 26 | // Arrange 27 | var serviceCollection = new ServiceCollection(); 28 | var config = new CustomApplicationInsightsConfig("app-name", typeof(StringHelper), "Q"); 29 | 30 | // Act 31 | var updatedServiceCollection = serviceCollection.AddCustomApplicationInsights(config); 32 | 33 | // Assert 34 | var registeredTelemetryInitializers = GetTelemetryInitializers(updatedServiceCollection); 35 | registeredTelemetryInitializers.Should().Contain(typeof(ApplicationInitializer)); 36 | } 37 | 38 | private static List GetTelemetryInitializers(IServiceCollection serviceCollection) 39 | { 40 | return serviceCollection 41 | .Where(s => s.ServiceType == typeof(ITelemetryInitializer) && s.ImplementationType != null) 42 | .Select(s => s.ImplementationType) 43 | .ToList(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Program.cs: -------------------------------------------------------------------------------- 1 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Functions.UserSecret; 2 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Infrastructure; 3 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Infrastructure.Telemetry; 4 | using Microsoft.ApplicationInsights.Extensibility; 5 | using Microsoft.Azure.Functions.Worker; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | 11 | var host = new HostBuilder() 12 | .ConfigureFunctionsWorkerDefaults() 13 | .ConfigureAppConfiguration(builder => 14 | { 15 | builder.AddUserSecrets(); 16 | }) 17 | .ConfigureServices(services => 18 | { 19 | services.AddApplicationInsightsTelemetryWorkerService(); 20 | services.ConfigureFunctionsApplicationInsights(); 21 | 22 | services 23 | .AddMyOptions("Secret") 24 | .AddApplicationInsightsTelemetryProcessor() 25 | .AddApplicationInsightsTelemetryProcessor() 26 | .AddApplicationInsightsTelemetryProcessor() 27 | .AddSingleton(); 28 | 29 | services.Configure(options => 30 | { 31 | // See https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide#start-up-and-configuration 32 | var toRemove = options.Rules.FirstOrDefault(rule => string.Equals(rule.ProviderName 33 | , "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider", 34 | StringComparison.Ordinal)); 35 | if (toRemove is not null) 36 | { 37 | options.Rules.Remove(toRemove); 38 | } 39 | }); 40 | }) 41 | .Build(); 42 | 43 | await host.RunAsync(); 44 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Infrastructure/Telemetry/TelemetryCounterProcessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Infrastructure.Telemetry; 6 | 7 | #pragma warning disable CA1812 // This type is instantiated by the Inversion of Control container 8 | internal sealed class TelemetryCounterProcessor : ITelemetryProcessor 9 | { 10 | private readonly ITelemetryProcessor _next; 11 | 12 | public long AvailabilityTelemetryCount; 13 | public long DependencyTelemetryCount; 14 | public long EventTelemetryCount; 15 | public long ExceptionTelemetryCount; 16 | public long MetricTelemetryCount; 17 | public long RequestTelemetryCount; 18 | public long TraceTelemetryCount; 19 | 20 | public TelemetryCounterProcessor(ITelemetryProcessor next) 21 | { 22 | _next = next; 23 | } 24 | 25 | public void Process(ITelemetry item) 26 | { 27 | switch (item) 28 | { 29 | case AvailabilityTelemetry: 30 | Interlocked.Increment(ref AvailabilityTelemetryCount); 31 | break; 32 | case DependencyTelemetry: 33 | Interlocked.Increment(ref DependencyTelemetryCount); 34 | break; 35 | case EventTelemetry: 36 | Interlocked.Increment(ref EventTelemetryCount); 37 | break; 38 | case ExceptionTelemetry: 39 | Interlocked.Increment(ref ExceptionTelemetryCount); 40 | break; 41 | case MetricTelemetry: 42 | Interlocked.Increment(ref MetricTelemetryCount); 43 | break; 44 | case RequestTelemetry: 45 | Interlocked.Increment(ref RequestTelemetryCount); 46 | break; 47 | case TraceTelemetry: 48 | Interlocked.Increment(ref TraceTelemetryCount); 49 | break; 50 | } 51 | 52 | _next.Process(item); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/ApplicationInitializerTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 2 | 3 | public class ApplicationInitializerTests 4 | { 5 | private readonly ApplicationInitializer _target; 6 | private readonly ApplicationDescriptor _applicationDescriptor; 7 | 8 | public ApplicationInitializerTests() 9 | { 10 | _applicationDescriptor = new ApplicationDescriptor("applicationname-api", "20201202.2"); 11 | 12 | _target = new ApplicationInitializer(_applicationDescriptor); 13 | } 14 | 15 | [Fact] 16 | public void GivenRequestTelemetry_ThenStampApplicationNameAndVersion() 17 | { 18 | // Arrange 19 | var requestTelemetry = RequestTelemetryBuilder 20 | .AsHttp(); 21 | 22 | // Act 23 | _target.Initialize(requestTelemetry); 24 | 25 | // Assert 26 | Assert.Equal(_applicationDescriptor.Name, requestTelemetry.Context.Cloud.RoleName); 27 | Assert.Equal(_applicationDescriptor.Version, requestTelemetry.Context.Component.Version); 28 | } 29 | 30 | [Fact] 31 | public void GivenExceptionTelemetry_ThenStampApplicationNameAndVersion() 32 | { 33 | // Arrange 34 | var requestTelemetry = new ExceptionTelemetryBuilder("Function.FunctionName") 35 | .Build(); 36 | 37 | // Act 38 | _target.Initialize(requestTelemetry); 39 | 40 | // Assert 41 | Assert.Equal(_applicationDescriptor.Name, requestTelemetry.Context.Cloud.RoleName); 42 | Assert.Equal(_applicationDescriptor.Version, requestTelemetry.Context.Component.Version); 43 | } 44 | 45 | [Fact] 46 | public void GivenTraceTelemetry_ThenStampApplicationNameAndVersion() 47 | { 48 | // Arrange 49 | var traceTelemetry = TraceTelemetryBuilder.AsFunctionStarted(); 50 | 51 | // Act 52 | _target.Initialize(traceTelemetry); 53 | 54 | // Assert 55 | Assert.Equal(_applicationDescriptor.Name, traceTelemetry.Context.Cloud.RoleName); 56 | Assert.Equal(_applicationDescriptor.Version, traceTelemetry.Context.Component.Version); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/Logging/LoggingServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Gabo.AzureFunctionsTelemetry.Logging; 5 | 6 | /// 7 | /// This won't actually be displayed 8 | /// 9 | public static class LoggingServiceCollectionExtensions 10 | { 11 | /// 12 | /// Replaces the Azure Functions Core Tools Console Logger by the .NET Core Console Logger. This gives us exception 13 | /// stacktrace in the console. Discards as many duplicate exceptions as can be safely done. 14 | /// 15 | /// The holding all your precious types. 16 | /// The same instance but with the .NET Core Console Logger being 17 | /// registered and a few additional to discard the built-in console 18 | /// logging. 19 | public static IServiceCollection AddCustomConsoleLogging(this IServiceCollection services) 20 | { 21 | services.AddLogging(b => 22 | { 23 | b.AddConsole(); 24 | services.Configure(o => 25 | { 26 | DiscardFunctionsConsoleLoggingProvider(o); 27 | DiscardDuplicateExceptionForConsoleLoggingProvider(o); 28 | }); 29 | }); 30 | 31 | return services; 32 | 33 | void DiscardFunctionsConsoleLoggingProvider(LoggerFilterOptions o) 34 | { 35 | o.Rules.Add(new LoggerFilterRule( 36 | "Azure.Functions.Cli.Diagnostics.ColoredConsoleLoggerProvider", 37 | "*", 38 | LogLevel.None, 39 | null)); 40 | } 41 | 42 | void DiscardDuplicateExceptionForConsoleLoggingProvider(LoggerFilterOptions o) 43 | { 44 | o.Rules.Add(new LoggerFilterRule( 45 | "Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider", 46 | FunctionRuntimeCategory.HostResults, 47 | LogLevel.None, 48 | null)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/DefaultServiceBusTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests; 2 | 3 | [Collection("Default")] 4 | public sealed class DefaultServiceBusTests : IDisposable 5 | { 6 | private readonly DefaultTelemetryFunctionClient _client; 7 | 8 | public DefaultServiceBusTests() 9 | { 10 | _client = new DefaultTelemetryFunctionClient(); 11 | } 12 | 13 | [Fact] 14 | public async Task GivenServiceBusRequest_ThenNullUrlAndZeroResponseCode() 15 | { 16 | // Arrange 17 | await _client.DeleteTelemetryAsync(); 18 | 19 | // Act 20 | await _client.TriggerServiceBusAsync(); 21 | 22 | // Assert 23 | var (request, _) = await _client.PollForTelemetryAsync("ServiceBusFunction"); 24 | Assert.Null(request.Data.BaseData.Url); 25 | request.Data.BaseData.ResponseCode.Should().Be("0"); 26 | } 27 | 28 | [Fact] 29 | public async Task GivenServiceBusRequest_ThenExecutionAndTriggerTracesAreEmitted() 30 | { 31 | // Arrange 32 | await _client.DeleteTelemetryAsync(); 33 | 34 | // Act 35 | await _client.TriggerServiceBusAsync(); 36 | 37 | // Assert 38 | var (request, telemetries) = await _client.PollForTelemetryAsync("ServiceBusFunction"); 39 | var executionTraces = telemetries.GetOperationItems(request.OperationName, request.OperationId); 40 | executionTraces.Should().HaveCount(3); 41 | } 42 | 43 | [Fact] 44 | public async Task GivenServiceBusBinding_WhenException_ThenTriplicateExceptionTelemetry() 45 | { 46 | // Arrange 47 | await _client.DeleteTelemetryAsync(); 48 | 49 | // Act 50 | await _client.TriggerServiceBusExceptionAsync(); 51 | 52 | // Assert 53 | var (request, telemetries) = 54 | await _client.PollForTelemetryAsync("ServiceBusExceptionThrowingFunction"); 55 | var exceptions = telemetries.GetOperationItems(request.OperationName, request.OperationId); 56 | exceptions.Should().HaveCount(3); 57 | } 58 | 59 | public void Dispose() 60 | { 61 | _client.Dispose(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/ServiceBusRequestInitializerTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 2 | 3 | public class ServiceBusRequestInitializerTests 4 | { 5 | private readonly ServiceBusRequestInitializer _target; 6 | 7 | public ServiceBusRequestInitializerTests() 8 | { 9 | _target = new ServiceBusRequestInitializer(); 10 | } 11 | 12 | [Theory] 13 | [InlineData(true, "200")] 14 | [InlineData(false, "500")] 15 | public void GivenServiceBusBinding_ThenSetResponseCodeBasedOnSuccess(bool success, string expectedStatusCode) 16 | { 17 | // Arrange 18 | var requestTelemetry = RequestTelemetryBuilder.AsServiceBus(); 19 | requestTelemetry.Success = success; 20 | 21 | // Act 22 | _target.Initialize(requestTelemetry); 23 | 24 | // Assert 25 | Assert.Equal(expectedStatusCode, requestTelemetry.ResponseCode); 26 | } 27 | 28 | [Fact] 29 | public void GivenServiceBusBinding_ThenSetUrlBasedOnName() 30 | { 31 | // Arrange 32 | var requestTelemetry = RequestTelemetryBuilder.AsServiceBus(); 33 | 34 | // Act 35 | _target.Initialize(requestTelemetry); 36 | 37 | // Assert 38 | Assert.Equal("/ServiceBusFunction", requestTelemetry.Url.ToString()); 39 | } 40 | 41 | [Fact] 42 | public void GivenServiceBusBinding_WhenSuccessIsNotSet_ThenDoNotSetResponseCode() 43 | { 44 | // Arrange 45 | var requestTelemetry = RequestTelemetryBuilder.AsServiceBus(); 46 | requestTelemetry.Success = null; 47 | 48 | // Act 49 | _target.Initialize(requestTelemetry); 50 | 51 | // Assert 52 | Assert.Equal("0", requestTelemetry.ResponseCode); 53 | } 54 | 55 | [Fact] 56 | public void GivenHttpBinding_ThenDoNotOverrideResponseCode() 57 | { 58 | // Arrange 59 | var requestTelemetry = RequestTelemetryBuilder.AsHttp(); 60 | requestTelemetry.ResponseCode = "202"; 61 | 62 | // Act 63 | _target.Initialize(requestTelemetry); 64 | 65 | // Assert 66 | Assert.Equal("202", requestTelemetry.ResponseCode); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/CustomServiceBusTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests; 2 | 3 | [Collection("Custom")] 4 | public sealed class CustomServiceBusTests : IDisposable 5 | { 6 | private readonly CustomTelemetryFunctionClient _client; 7 | 8 | public CustomServiceBusTests() 9 | { 10 | _client = new CustomTelemetryFunctionClient(); 11 | } 12 | 13 | [Fact] 14 | public async Task GivenServiceBusRequest_ThenUrlIsFunctionNameAnd200ResponseCode() 15 | { 16 | // Arrange 17 | await _client.DeleteTelemetryAsync(); 18 | 19 | // Act 20 | await _client.TriggerServiceBusAsync(); 21 | 22 | // Assert 23 | var (request, _) = await _client.PollForTelemetryAsync("ServiceBusFunction"); 24 | request.Data.BaseData.Url.Should().Be("/ServiceBusFunction"); 25 | request.Data.BaseData.ResponseCode.Should().Be("200"); 26 | } 27 | 28 | [Fact] 29 | public async Task GivenServiceBusRequest_ThenDiscardExecutionAndTriggerTraces() 30 | { 31 | // Arrange 32 | await _client.DeleteTelemetryAsync(); 33 | 34 | // Act 35 | await _client.TriggerServiceBusAsync(); 36 | 37 | // Assert 38 | var (request, telemetries) = 39 | await _client.PollForTelemetryAsync("ServiceBusFunction"); 40 | var executionTraces = telemetries.GetOperationItems(request.OperationName, request.OperationId); 41 | executionTraces.Should().BeEmpty(); 42 | } 43 | 44 | [Fact] 45 | public async Task GivenServiceBusBinding_WhenException_ThenNoDuplicateExceptionTelemetry() 46 | { 47 | // Arrange 48 | await _client.DeleteTelemetryAsync(); 49 | 50 | // Act 51 | await _client.TriggerServiceBusExceptionAsync(); 52 | 53 | // Assert 54 | var (request, telemetries) = 55 | await _client.PollForTelemetryAsync("ServiceBusExceptionThrowingFunction"); 56 | var exceptions = telemetries.GetOperationItems(request.OperationName, request.OperationId); 57 | exceptions.Should().HaveCount(1); 58 | } 59 | 60 | public void Dispose() 61 | { 62 | _client.Dispose(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/AzureFunctionsTelemetryTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | latest 6 | enable 7 | enable 8 | Gabo.AzureFunctionsTelemetryTests 9 | Gabo.AzureFunctionsTelemetryTests 10 | false 11 | 7035;CA1707;CA2007;CA1515 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | all 28 | 29 | 30 | 31 | 32 | 33 | 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | all 36 | 37 | 38 | runtime; build; native; contentfiles; analyzers; buildtransitive 39 | all 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Infrastructure/Telemetry/TelemetryCounterInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 6 | 7 | /// 8 | /// This is not really a telemetry initializer as it doesn't enrich the telemetry. The goal is to demonstrate that 9 | /// every telemetry item type is going through the initializer and could potentially be enriched. 10 | /// When running in Azure you might get different results on each request as you might be hitting different 11 | /// instances and the state is kept in-memory. 12 | /// 13 | internal sealed class TelemetryCounterInitializer : ITelemetryInitializer 14 | { 15 | public long AvailabilityTelemetryCount; 16 | public long DependencyTelemetryCount; 17 | public long EventTelemetryCount; 18 | public long ExceptionTelemetryCount; 19 | public long MetricTelemetryCount; 20 | public long RequestTelemetryCount; 21 | public long TraceTelemetryCount; 22 | 23 | public void Initialize(ITelemetry telemetry) 24 | { 25 | switch (telemetry) 26 | { 27 | case AvailabilityTelemetry: 28 | Interlocked.Increment(ref AvailabilityTelemetryCount); 29 | break; 30 | case DependencyTelemetry: 31 | Interlocked.Increment(ref DependencyTelemetryCount); 32 | break; 33 | case EventTelemetry: 34 | Interlocked.Increment(ref EventTelemetryCount); 35 | break; 36 | case ExceptionTelemetry: 37 | Interlocked.Increment(ref ExceptionTelemetryCount); 38 | break; 39 | case MetricTelemetry: 40 | Interlocked.Increment(ref MetricTelemetryCount); 41 | break; 42 | case RequestTelemetry: 43 | Interlocked.Increment(ref RequestTelemetryCount); 44 | break; 45 | case TraceTelemetry: 46 | Interlocked.Increment(ref TraceTelemetryCount); 47 | break; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/FunctionsFinder.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | internal static class FunctionsFinder 4 | { 5 | /// 6 | /// This is a quick and dirty attempt to identity all Service Bus triggered Functions so that we can discard the 7 | /// telemetry without requiring the consumer to maintain a list of Functions. 8 | /// 9 | /// 10 | /// In case of failure, we return an empty list as it's better to emit additional telemetry rather 11 | /// than breaking Application Insights configuration. 12 | public static List GetServiceBusTriggeredFunctionNames(Type typeFromEntryAssembly) 13 | { 14 | try 15 | { 16 | return (from exportedType in typeFromEntryAssembly.Assembly.ExportedTypes 17 | from method in exportedType.GetMethods() 18 | from parameter in method.GetParameters() 19 | let parameterCustomAttributes = parameter.CustomAttributes 20 | from parameterCustomAttribute in parameterCustomAttributes 21 | where "Microsoft.Azure.WebJobs.ServiceBusTriggerAttribute" 22 | .Equals(parameterCustomAttribute.AttributeType.FullName, StringComparison.Ordinal) 23 | from methodCustomAttribute in method.CustomAttributes 24 | where "Microsoft.Azure.WebJobs.FunctionNameAttribute" 25 | .Equals(methodCustomAttribute.AttributeType.FullName, StringComparison.Ordinal) 26 | select (string?)methodCustomAttribute.ConstructorArguments.First().Value) 27 | .WhereNotNull() 28 | .ToList(); 29 | } 30 | #pragma warning disable CA1031 // Catch-all and swallow. Better to emit additional telemetry rather than breaking Application Insights 31 | catch 32 | #pragma warning restore CA1031 33 | { 34 | return new List(); 35 | } 36 | } 37 | 38 | private static IEnumerable WhereNotNull(this IEnumerable source) 39 | where T : class 40 | { 41 | return source.Where(item => item != null)!; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/DefaultHttpTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests; 2 | 3 | [Collection("Default")] 4 | public sealed class DefaultHttpTests : IDisposable 5 | { 6 | private readonly DefaultTelemetryFunctionClient _client; 7 | 8 | public DefaultHttpTests() 9 | { 10 | _client = new DefaultTelemetryFunctionClient(); 11 | } 12 | 13 | [Fact] 14 | public async Task GivenHttpRequest_ThenTwoExecutionTracesAreEmitted() 15 | { 16 | // Arrange 17 | await _client.DeleteTelemetryAsync(); 18 | 19 | // Act 20 | await _client.LogVerboseAsync(); 21 | 22 | // Assert 23 | var (request, telemetries) = await _client.PollForTelemetryAsync("TraceLogFunction"); 24 | var executionTraces = telemetries.GetOperationItems(request.OperationName, request.OperationId); 25 | executionTraces.Should().HaveCount(2); 26 | } 27 | 28 | [Fact] 29 | public async Task GivenHttpBinding_WhenException_ThenDuplicateExceptionTelemetry() 30 | { 31 | // Arrange 32 | await _client.DeleteTelemetryAsync(); 33 | 34 | // Act 35 | await _client.ThrowOnHttpBindingAsync(); 36 | 37 | // Assert 38 | var (request, telemetries) = 39 | await _client.PollForTelemetryAsync("HttpExceptionThrowingFunction"); 40 | var exceptions = telemetries.GetOperationItems(request.OperationName, request.OperationId); 41 | exceptions.Should().HaveCount(2); 42 | } 43 | 44 | [Fact] 45 | public async Task GivenHealthCheck_ThenRequestIsNotDiscarded() 46 | { 47 | // Arrange 48 | await _client.DeleteTelemetryAsync(); 49 | 50 | // Act 51 | await _client.CheckHealthAsync(); 52 | await _client.LogVerboseAsync(); 53 | 54 | // Assert 55 | var telemetries = await _client.GetTelemetryAsync(); 56 | var requests = telemetries.Where(i => i is RequestItem).Cast().ToList(); 57 | requests.Should().Contain(i => i.OperationName == "HealthFunction"); 58 | requests.Should().Contain(i => i.OperationName == "TraceLogFunction"); 59 | } 60 | 61 | public void Dispose() 62 | { 63 | _client.Dispose(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/CustomHttpTests.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests; 2 | 3 | [Collection("Custom")] 4 | public sealed class CustomHttpTests : IDisposable 5 | { 6 | private readonly CustomTelemetryFunctionClient _client; 7 | 8 | public CustomHttpTests() 9 | { 10 | _client = new CustomTelemetryFunctionClient(); 11 | } 12 | 13 | [Fact] 14 | public async Task GivenHttpRequest_ThenDiscardExecutionTraces() 15 | { 16 | // Arrange 17 | await _client.DeleteTelemetryAsync(); 18 | 19 | // Act 20 | await _client.LogVerboseAsync(); 21 | 22 | // Assert 23 | var (request, telemetries) = 24 | await _client.PollForTelemetryAsync("TraceLogFunction"); 25 | var executionTraces = telemetries.GetOperationItems(request.OperationName, request.OperationId); 26 | executionTraces.Should().BeEmpty(); 27 | } 28 | 29 | [Fact] 30 | public async Task GivenHttpBinding_WhenException_ThenNoDuplicateExceptionTelemetry() 31 | { 32 | // Arrange 33 | await _client.DeleteTelemetryAsync(); 34 | 35 | // Act 36 | await _client.ThrowOnHttpBindingAsync(); 37 | 38 | // Assert 39 | var (request, telemetries) = 40 | await _client.PollForTelemetryAsync("HttpExceptionThrowingFunction"); 41 | var exceptions = telemetries.GetOperationItems(request.OperationName, request.OperationId); 42 | exceptions.Should().HaveCount(1); 43 | } 44 | 45 | [Fact] 46 | public async Task GivenHealthCheck_ThenRequestIsDiscarded() 47 | { 48 | // Arrange 49 | await _client.DeleteTelemetryAsync(); 50 | 51 | // Act 52 | await _client.CheckHealthAsync(); 53 | await _client.LogVerboseAsync(); 54 | 55 | // Assert 56 | var telemetries = await _client.GetTelemetryAsync(); 57 | var requests = telemetries.Where(i => i is RequestItem).Cast().ToList(); 58 | requests.Should().NotContain(r => r.OperationName == "HealthFunction"); 59 | requests.Should().Contain(r => r.OperationName == "TraceLogFunction"); 60 | } 61 | 62 | public void Dispose() 63 | { 64 | _client.Dispose(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Integration tests 4 | 5 | Before being able to run the integration tests, you'll need to deploy an Application Insights instance and a Service Bus namespace. This can be done using [scripts\testing\Deploy-Testing.ps1](scripts\testing\Deploy-Testing.ps1). 6 | 7 | Once you have provisioned the infrastructure, write down the Service Bus connection string. 8 | 9 | ### Running the tests in NUKE 10 | 11 | You need to set the Service Bus connection string before running `NUKE`: 12 | 13 | ```powershell 14 | $Env:IntegrationTestServiceBusConnectionString='{testing-connection-string}' 15 | .\build.ps1 16 | ``` 17 | 18 | ### Running the tests manually 19 | 20 | The integration tests rely on both Functions `CustomV4InProcessFunction` and `DefaultV4InProcessFunction` running locally. 21 | 22 | Replace the user secrets: 23 | 24 | ```powershell 25 | dotnet user-secrets set Testing:IsEnabled true --id 074ca336-270b-4832-9a1a-60baf152b727 26 | dotnet user-secrets set ServiceBusConnection '{testing-connection-string}' --id 074ca336-270b-4832-9a1a-60baf152b727 27 | ``` 28 | 29 | Start the custom v4 in-process Function: 30 | 31 | ```powershell 32 | cd samples\CustomV4InProcessFunction 33 | func start 34 | ``` 35 | 36 | Start the default v4 in-process Function: 37 | 38 | ```powershell 39 | cd samples\DefaultV4InProcessFunction 40 | func start 41 | ``` 42 | 43 | Run the [integration tests](tests\AzureFunctionsTelemetryIntegrationTests). 44 | 45 | Once you're done, restore the user secrets: 46 | 47 | ```powershell 48 | dotnet user-secrets remove Testing:IsEnabled 074ca336-270b-4832-9a1a-60baf152b727 49 | dotnet user-secrets set ServiceBusConnection '{connection-string}' --id 074ca336-270b-4832-9a1a-60baf152b727 50 | ``` 51 | 52 | If you want to test the behaviour when the Application Insights connection string is not set, you'll need to remove the secret: 53 | 54 | ```powershell 55 | dotnet user-secrets remove APPLICATIONINSIGHTS_CONNECTION_STRING --id 074ca336-270b-4832-9a1a-60baf152b727 56 | ``` 57 | 58 | Run the [App Insights connection string integration tests](tests\AppInsightsConnectionStringIntegrationTests). 59 | 60 | Once you're done, restore the user secret: 61 | 62 | ```powershell 63 | dotnet user-secrets set APPLICATIONINSIGHTS_CONNECTION_STRING '{connection-string}' --id 074ca336-270b-4832-9a1a-60baf152b727 64 | ``` 65 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Infrastructure/Telemetry/TelemetryCounterInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure.Telemetry; 6 | 7 | /// 8 | /// This is not really a telemetry initializer as it doesn't enrich the telemetry. The goal is to demonstrate that 9 | /// every telemetry item type is going through the initializer and could potentially be enriched. 10 | /// When running in Azure you might get different results on each request as you might be hitting different 11 | /// instances and the state is kept in-memory. 12 | /// 13 | #pragma warning disable CA1812 // This type is instantiated by the Inversion of Control container 14 | internal sealed class TelemetryCounterInitializer : ITelemetryInitializer 15 | { 16 | public long AvailabilityTelemetryCount; 17 | public long DependencyTelemetryCount; 18 | public long EventTelemetryCount; 19 | public long ExceptionTelemetryCount; 20 | public long MetricTelemetryCount; 21 | public long RequestTelemetryCount; 22 | public long TraceTelemetryCount; 23 | 24 | public void Initialize(ITelemetry telemetry) 25 | { 26 | switch (telemetry) 27 | { 28 | case AvailabilityTelemetry: 29 | Interlocked.Increment(ref AvailabilityTelemetryCount); 30 | break; 31 | case DependencyTelemetry: 32 | Interlocked.Increment(ref DependencyTelemetryCount); 33 | break; 34 | case EventTelemetry: 35 | Interlocked.Increment(ref EventTelemetryCount); 36 | break; 37 | case ExceptionTelemetry: 38 | Interlocked.Increment(ref ExceptionTelemetryCount); 39 | break; 40 | case MetricTelemetry: 41 | Interlocked.Increment(ref MetricTelemetryCount); 42 | break; 43 | case RequestTelemetry: 44 | Interlocked.Increment(ref RequestTelemetryCount); 45 | break; 46 | case TraceTelemetry: 47 | Interlocked.Increment(ref TraceTelemetryCount); 48 | break; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/DefaultV4IsolatedFunction/Infrastructure/Telemetry/TelemetryCounterInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4IsolatedFunction.Infrastructure.Telemetry; 6 | 7 | /// 8 | /// This is not really a telemetry initializer as it doesn't enrich the telemetry. The goal is to demonstrate that 9 | /// every telemetry item type is going through the initializer and could potentially be enriched. 10 | /// When running in Azure you might get different results on each request as you might be hitting different 11 | /// instances and the state is kept in-memory. 12 | /// 13 | #pragma warning disable CA1812 // This type is instantiated by the Inversion of Control container 14 | internal sealed class TelemetryCounterInitializer : ITelemetryInitializer 15 | { 16 | public long AvailabilityTelemetryCount; 17 | public long DependencyTelemetryCount; 18 | public long EventTelemetryCount; 19 | public long ExceptionTelemetryCount; 20 | public long MetricTelemetryCount; 21 | public long RequestTelemetryCount; 22 | public long TraceTelemetryCount; 23 | 24 | public void Initialize(ITelemetry telemetry) 25 | { 26 | switch (telemetry) 27 | { 28 | case AvailabilityTelemetry: 29 | Interlocked.Increment(ref AvailabilityTelemetryCount); 30 | break; 31 | case DependencyTelemetry: 32 | Interlocked.Increment(ref DependencyTelemetryCount); 33 | break; 34 | case EventTelemetry: 35 | Interlocked.Increment(ref EventTelemetryCount); 36 | break; 37 | case ExceptionTelemetry: 38 | Interlocked.Increment(ref ExceptionTelemetryCount); 39 | break; 40 | case MetricTelemetry: 41 | Interlocked.Increment(ref MetricTelemetryCount); 42 | break; 43 | case RequestTelemetry: 44 | Interlocked.Increment(ref RequestTelemetryCount); 45 | break; 46 | case TraceTelemetry: 47 | Interlocked.Increment(ref TraceTelemetryCount); 48 | break; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | AllEnabledByDefault 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Infrastructure/Telemetry/TelemetryCounterProcessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 6 | 7 | /// 8 | /// This is not really a telemetry processor as it doesn't discard any telemetry. The goal is to demonstrate that 9 | /// every telemetry item type is going through the processor and could potentially be discarded. 10 | /// When running in Azure you might get different results on each request as you might be hitting different 11 | /// instances and the state is kept in-memory. 12 | /// 13 | internal sealed class TelemetryCounterProcessor : ITelemetryProcessor 14 | { 15 | private readonly ITelemetryProcessor _next; 16 | 17 | public long AvailabilityTelemetryCount; 18 | public long DependencyTelemetryCount; 19 | public long EventTelemetryCount; 20 | public long ExceptionTelemetryCount; 21 | public long MetricTelemetryCount; 22 | public long RequestTelemetryCount; 23 | public long TraceTelemetryCount; 24 | 25 | public TelemetryCounterProcessor(ITelemetryProcessor next) 26 | { 27 | _next = next; 28 | } 29 | 30 | public void Process(ITelemetry item) 31 | { 32 | switch (item) 33 | { 34 | case AvailabilityTelemetry: 35 | Interlocked.Increment(ref AvailabilityTelemetryCount); 36 | break; 37 | case DependencyTelemetry: 38 | Interlocked.Increment(ref DependencyTelemetryCount); 39 | break; 40 | case EventTelemetry: 41 | Interlocked.Increment(ref EventTelemetryCount); 42 | break; 43 | case ExceptionTelemetry: 44 | Interlocked.Increment(ref ExceptionTelemetryCount); 45 | break; 46 | case MetricTelemetry: 47 | Interlocked.Increment(ref MetricTelemetryCount); 48 | break; 49 | case RequestTelemetry: 50 | Interlocked.Increment(ref RequestTelemetryCount); 51 | break; 52 | case TraceTelemetry: 53 | Interlocked.Increment(ref TraceTelemetryCount); 54 | break; 55 | } 56 | 57 | _next.Process(item); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /scripts/testing/testing.bicep: -------------------------------------------------------------------------------- 1 | @description('Location for all resources.') 2 | param location string = resourceGroup().location 3 | 4 | @description('Used to create a unique name. For example, with a "hello" prefix and an Application Insights resource, the resource name will be "hello-appi".') 5 | @maxLength(48) 6 | param resourceNamePrefix string 7 | 8 | var applicationInsightsSettings = { 9 | name: '${resourceNamePrefix}-appi' 10 | workspaceName: '${resourceNamePrefix}-log' 11 | } 12 | 13 | var defaultV4InProcessFunctionDisplayName = 'defaultV4InProcess' 14 | var customV4InProcessFunctionDisplayName = 'customV4InProcess' 15 | 16 | var serviceBusSettings = { 17 | name: '${resourceNamePrefix}sb' 18 | queueNames: [ 19 | '${toLower(defaultV4InProcessFunctionDisplayName)}-queue' 20 | '${toLower(defaultV4InProcessFunctionDisplayName)}-exception-queue' 21 | '${toLower(customV4InProcessFunctionDisplayName)}-queue' 22 | '${toLower(customV4InProcessFunctionDisplayName)}-exception-queue' 23 | ] 24 | } 25 | 26 | resource workspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { 27 | name: applicationInsightsSettings.workspaceName 28 | location: location 29 | properties: { 30 | sku: { 31 | name: 'PerGB2018' 32 | } 33 | retentionInDays: 30 34 | features: { 35 | enableLogAccessUsingOnlyResourcePermissions: true 36 | } 37 | } 38 | } 39 | 40 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { 41 | name: applicationInsightsSettings.name 42 | location: location 43 | kind: 'web' 44 | properties: { 45 | Application_Type: 'web' 46 | WorkspaceResourceId: workspace.id 47 | } 48 | } 49 | 50 | resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2024-01-01' = { 51 | name: serviceBusSettings.name 52 | location: location 53 | sku: { 54 | name: 'Basic' 55 | tier: 'Basic' 56 | } 57 | properties: {} 58 | } 59 | 60 | resource queues 'Microsoft.ServiceBus/namespaces/queues@2024-01-01' = [for queue in serviceBusSettings.queueNames: { 61 | parent: serviceBusNamespace 62 | name: queue 63 | properties: { 64 | lockDuration: 'PT30S' 65 | maxSizeInMegabytes: 1024 66 | requiresDuplicateDetection: false 67 | requiresSession: false 68 | defaultMessageTimeToLive: 'P1D' 69 | deadLetteringOnMessageExpiration: false 70 | enableBatchedOperations: true 71 | duplicateDetectionHistoryTimeWindow: 'PT10M' 72 | maxDeliveryCount: 1 73 | status: 'Active' 74 | enablePartitioning: false 75 | enableExpress: false 76 | } 77 | }] 78 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Startup.cs: -------------------------------------------------------------------------------- 1 | using Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | using Gabo.AzureFunctionsTelemetry.Logging; 3 | using Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction; 4 | using Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.UserSecret; 5 | using Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure; 6 | using Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 7 | using Microsoft.ApplicationInsights.Channel; 8 | using Microsoft.ApplicationInsights.Extensibility; 9 | using Microsoft.Azure.Functions.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | 13 | [assembly: FunctionsStartup(typeof(Startup))] 14 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction; 15 | 16 | public class Startup : FunctionsStartup 17 | { 18 | public override void Configure(IFunctionsHostBuilder builder) 19 | { 20 | var testingOptions = builder.GetContext().Configuration.GetSection("Testing").Get(); 21 | 22 | if (testingOptions?.IsEnabled == true) 23 | { 24 | builder.Services.AddSingleton(); 25 | } 26 | 27 | builder.Services 28 | .AddMyOptions("Testing") 29 | .AddMyOptions("Secret"); 30 | 31 | var appInsightsOptions = new CustomApplicationInsightsConfigBuilder( 32 | "customv4inprocess", 33 | typeof(Startup)) 34 | .Build(); 35 | 36 | builder.Services 37 | .AddApplicationInsightsTelemetryProcessor() 38 | .AddApplicationInsightsTelemetryProcessor() 39 | .AddSingleton() 40 | /* 41 | * When adding an instance of a telemetry initializer, you need to provide the service Type otherwise 42 | * your initializer will not be used. 43 | * 44 | * 45 | * // Do not use: 46 | * .AddSingleton(new TelemetryCounterInstanceInitializer("NiceValue")) 47 | * 48 | */ 49 | .AddSingleton(new TelemetryCounterInstanceInitializer("NiceValue")) 50 | .AddCustomApplicationInsights(appInsightsOptions) 51 | .AddCustomConsoleLogging(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/AzureFunctionsTelemetry.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | latest 6 | enable 7 | enable 8 | Gabo.AzureFunctionsTelemetry 9 | Gabo.AzureFunctionsTelemetry 10 | true 11 | true 12 | true 13 | AzureFunctions.Better.ApplicationInsights 14 | Better Azure Functions Application Insights integration 15 | Gabriel Weyer 16 | Improve Application Insights (App Insights) integration for Azure Function v4. Automatically discard cruft telemetry emitted by Functions runtime. Add your own telemetry processors. 17 | Azure-Functions Application-Insights App-Insights Telemetry Instrumentation Observability Save-Money 18 | https://github.com/gabrielweyer/azure-functions-telemetry 19 | README.md 20 | MIT 21 | Copyright (c) Better Azure Function Application Insights contributors 2022 22 | true 23 | true 24 | snupkg 25 | icon.png 26 | CS7035 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/TelemetryHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs.Logging; 2 | 3 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 4 | 5 | internal static class TelemetryHelper 6 | { 7 | public static bool TryGetCategory(ISupportProperties telemetry, out string? category) => 8 | telemetry.Properties.TryGetValue(LogConstants.CategoryNameKey, out category); 9 | 10 | private static bool TryGetEventId(ISupportProperties telemetry, out string? eventId) => 11 | telemetry.Properties.TryGetValue(LogConstants.EventIdKey, out eventId); 12 | 13 | private static bool TryGetEventName(ISupportProperties telemetry, out string? eventName) => 14 | telemetry.Properties.TryGetValue(LogConstants.EventNameKey, out eventName); 15 | 16 | public static bool IsFunctionStartedTelemetry(ISupportProperties trace) => 17 | HasFunctionStartedEventId(trace) && HasFunctionStartedEventName(trace); 18 | 19 | private static bool HasFunctionStartedEventId(ISupportProperties telemetry) 20 | { 21 | if (!TryGetEventId(telemetry, out var eventId) || eventId == null) 22 | { 23 | return false; 24 | } 25 | 26 | return StringHelper.IsSame(FunctionRuntimeEventId.FunctionStarted, eventId); 27 | } 28 | 29 | private static bool HasFunctionStartedEventName(ISupportProperties telemetry) 30 | { 31 | if (!TryGetEventName(telemetry, out var eventName) || eventName == null) 32 | { 33 | return false; 34 | } 35 | 36 | return StringHelper.IsSame(FunctionRuntimeEventName.FunctionStarted, eventName); 37 | } 38 | 39 | public static bool IsFunctionCompletedTelemetry(ISupportProperties telemetry) => 40 | HasFunctionCompletedEventId(telemetry) && HasFunctionCompletedEventName(telemetry); 41 | 42 | private static bool HasFunctionCompletedEventName(ISupportProperties telemetry) 43 | { 44 | if (!TryGetEventName(telemetry, out var eventName) || eventName == null) 45 | { 46 | return false; 47 | } 48 | 49 | return StringHelper.IsSame(FunctionRuntimeEventName.FunctionCompleted, eventName); 50 | } 51 | 52 | private static bool HasFunctionCompletedEventId(ISupportProperties telemetry) 53 | { 54 | if (!TryGetEventId(telemetry, out var eventId) || eventId == null) 55 | { 56 | return false; 57 | } 58 | 59 | return StringHelper.IsSame(FunctionRuntimeEventId.FunctionCompletedSucceeded, eventId) || 60 | StringHelper.IsSame(FunctionRuntimeEventId.FunctionCompletedFailed, eventId); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/Isolated.md: -------------------------------------------------------------------------------- 1 | # Isolated mode 2 | 3 | The Application Insights integration has been redesigned. Telemetry processors are now supported (unfortunately they're not called for request telemetry items). There is one notable regression: telemetry initializers are not called anymore on request telemetry items. 4 | 5 | :rotating_light: if you want to store the Application Insights connection string in [Secret Manager][secret-manager] when running locally, you'll need to reference `Microsoft.Extensions.Configuration.UserSecrets` and call `AddUserSecrets`. Otherwise the Application Insights integration will be partially broken. 6 | 7 | Improvements compared to in-process: 8 | 9 | - Telemetry processors are supported for all telemetry item types except request 10 | - Console logger displays the exceptions' stack trace 11 | - The integration registers `TelemetryConfiguration` even when the Application Insights connection string is not present 12 | 13 | Regressions compared to in-process: 14 | 15 | - **Telemetry initializers are not supported on requests** 16 | - Each Function invocation records an `Invoke` dependency 17 | 18 | No improvements compared to in-process (but I wish they were): 19 | 20 | - The "_Executing ..._" and "_Executed ..._" traces can't be discarded 21 | - Two dependencies are recorded when using Service Bus output binding (`Message` and `ServiceBusSender.Send`) 22 | - Exceptions are still recorded twice for HTTP binding and three times for Service Bus binding 23 | 24 | NuGet packages: 25 | 26 | - `Microsoft.Azure.Functions.Worker`: `1.20.0` (added automatically when creating the Function, updated later) 27 | - `Microsoft.Azure.Functions.Worker.Sdk`: `1.16.2` (added automatically when creating the Function, updated later) 28 | - `Microsoft.ApplicationInsights.WorkerService`: `2.21.0` (added manually following [Application Insights][direct-app-insights-integration]) 29 | - `Microsoft.Azure.Functions.Worker.ApplicationInsights`: `1.1.0` (added manually following [Application Insights][direct-app-insights-integration]) 30 | 31 | I removed the Application Insights rule that [discarded any traces below Warning][remove-warning-app-insights-rule]. 32 | 33 | The Function team put together a [sample][isolated-worker-sample]. 34 | 35 | [remove-warning-app-insights-rule]: https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide#start-up-and-configuration 36 | [direct-app-insights-integration]: https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide#application-insights 37 | [isolated-worker-sample]: https://github.com/Azure/azure-functions-dotnet-worker/tree/main/samples/FunctionApp 38 | [secret-manager]: https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows 39 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryTests/ApplicationInsights/FunctionExecutionTracesFilterTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.DataContracts; 2 | 3 | namespace Gabo.AzureFunctionsTelemetryTests.ApplicationInsights; 4 | 5 | public class FunctionExecutionTracesFilterTests 6 | { 7 | private readonly FunctionExecutionTracesFilter _target; 8 | 9 | private readonly MockTelemetryProcessor _innerProcessor; 10 | 11 | public FunctionExecutionTracesFilterTests() 12 | { 13 | _innerProcessor = new MockTelemetryProcessor(); 14 | 15 | _target = new FunctionExecutionTracesFilter(_innerProcessor); 16 | } 17 | 18 | [Fact] 19 | public void GivenFunctionStartedTrace_ThenFilterOutTelemetry() 20 | { 21 | // Arrange 22 | var traceTelemetry = TraceTelemetryBuilder.AsFunctionStarted(); 23 | 24 | // Act 25 | _target.Process(traceTelemetry); 26 | 27 | // Assert 28 | Assert.False(_innerProcessor.WasProcessorCalled); 29 | } 30 | 31 | [Fact] 32 | public void GivenFunctionCompletedTrace_ThenFilterOutTelemetry() 33 | { 34 | // Arrange 35 | var traceTelemetry = TraceTelemetryBuilder.AsFunctionCompletedSucceeded(); 36 | 37 | // Act 38 | _target.Process(traceTelemetry); 39 | 40 | // Assert 41 | Assert.False(_innerProcessor.WasProcessorCalled); 42 | } 43 | 44 | [Fact] 45 | public void GivenFunctionCompletedWithErrorTrace_ThenFilterOutTelemetry() 46 | { 47 | // Arrange 48 | var traceTelemetry = TraceTelemetryBuilder.AsFunctionCompletedFailed(); 49 | 50 | // Act 51 | _target.Process(traceTelemetry); 52 | 53 | // Assert 54 | Assert.False(_innerProcessor.WasProcessorCalled); 55 | } 56 | 57 | [Fact] 58 | public void GivenServiceBusBindingMessageProcessingErrorTrace_ThenFilterOutTelemetry() 59 | { 60 | // Arrange 61 | var traceTelemetry = TraceTelemetryBuilder.AsServiceBusBindingMessageProcessingError(); 62 | 63 | // Act 64 | _target.Process(traceTelemetry); 65 | 66 | // Assert 67 | Assert.False(_innerProcessor.WasProcessorCalled); 68 | } 69 | 70 | [Fact] 71 | public void GivenTelemetryIsNotTraceTelemetry_ThenKeepTelemetry() 72 | { 73 | // Arrange 74 | var exceptionTelemetry = new ExceptionTelemetry(); 75 | 76 | // Act 77 | _target.Process(exceptionTelemetry); 78 | 79 | // Assert 80 | Assert.True(_innerProcessor.WasProcessorCalled); 81 | } 82 | 83 | [Fact] 84 | public void GivenTraceTelemetryThatIsNeitherFunctionStartedOrCompleted_ThenKeepTelemetry() 85 | { 86 | // Arrange 87 | var traceTelemetry = new TraceTelemetry(); 88 | 89 | // Act 90 | _target.Process(traceTelemetry); 91 | 92 | // Assert 93 | Assert.True(_innerProcessor.WasProcessorCalled); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/ApplicationInsights/CustomApplicationInsightsConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetry.ApplicationInsights; 2 | 3 | /// 4 | /// Used to configure the Application Insights integration. Not intended to be used directly, it's recommended to 5 | /// rely on to construct it. 6 | /// 7 | public class CustomApplicationInsightsConfig 8 | { 9 | /// 10 | /// Will be used as the 'Cloud role name'. 11 | /// 12 | public string? ApplicationName { get; } 13 | /// 14 | /// The 'AssemblyInformationalVersion' of the assembly will be used as the 'Application version'. Falls back to 15 | /// 'unknown'. 16 | /// 17 | public Type TypeFromEntryAssembly { get; } 18 | /// 19 | /// The configuration section where we'll read the from. 20 | /// 21 | public string ConfigurationSectionName { get; } 22 | 23 | /// 24 | /// Even though you can call this constructor yourself, we provide 25 | /// to make it easier to configure the integration. 26 | /// 27 | /// Will be used as the 'Cloud role name'. If not provided, we won't set the 28 | /// 'Cloud role name' and we will not set the 'Application version'. 29 | /// The 'AssemblyInformationalVersion' of the assembly will be used as the 30 | /// 'Application version'. Falls back to 'unknown'. If applicationName is not provided, we will not set the 31 | /// 'Application version'. We also use this type to discover Service Bus triggered Functions so that we can apply 32 | /// special handling to them. 33 | /// The configuration section where we'll read the 34 | /// from. 35 | /// The configuration section name should not be empty or consist only 36 | /// of white-space characters. 37 | public CustomApplicationInsightsConfig( 38 | string? applicationName, 39 | Type typeFromEntryAssembly, 40 | string configurationSectionName) 41 | { 42 | if (string.IsNullOrWhiteSpace(configurationSectionName)) 43 | { 44 | throw new ArgumentOutOfRangeException( 45 | nameof(configurationSectionName), 46 | configurationSectionName, 47 | "The configuration section name should not be empty or consist only of white-space characters."); 48 | } 49 | 50 | ApplicationName = applicationName; 51 | TypeFromEntryAssembly = typeFromEntryAssembly; 52 | ConfigurationSectionName = configurationSectionName; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /scripts/testing/Deploy-Testing.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7.0 2 | #Requires -Modules Az 3 | 4 | <# 5 | 6 | .SYNOPSIS 7 | Deploys the testing infrastructure to Azure. 8 | 9 | .DESCRIPTION 10 | Provisions the below resources in Azure: 11 | 12 | - A Workspace based Application Insights instance 13 | - A Service Bus namespace 14 | 15 | The selected subscription is used to deploy. 16 | 17 | .PARAMETER Location 18 | An Azure region you can deploy to. 19 | 20 | .PARAMETER ResourceNamePrefix 21 | Used to create unique names. For example, with a 'hello' prefix and an Application Insights resource, the resource name will be 'hello-appi'. 22 | 23 | .EXAMPLE 24 | .\Deploy-Testing.ps1 -Location australiaeast -ResourceNamePrefix gabowtesting 25 | 26 | .NOTES 27 | You need: 28 | 29 | - PowerShell 7 (https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4) 30 | - Azure PowerShell (https://learn.microsoft.com/en-us/powershell/azure/install-azure-powershell) 31 | - Bicep CLI (https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install#install-manually) 32 | 33 | Select the subscription you want to deploy to before running this script. 34 | 35 | .LINK 36 | https://github.com/gabrielweyer/azure-functions-telemetry/blob/main/README.md 37 | 38 | #> 39 | 40 | [CmdletBinding()] 41 | param ( 42 | [Parameter(Mandatory=$true)] 43 | [string]$Location, 44 | 45 | [Parameter(Mandatory=$true)] 46 | [string]$ResourceNamePrefix 47 | ) 48 | 49 | $ErrorActionPreference = 'Stop' 50 | 51 | $resourceGroupName = "$ResourceNamePrefix-rg" 52 | 53 | Write-Host "Ensuring that you're signed-in into your Azure account" 54 | 55 | $selectedSubscription = Get-AzContext | Select-Object -ExpandProperty Subscription 56 | 57 | if ($null -eq $selectedSubscription) { 58 | throw 'You need to be signed-in into your Azure account to run this script' 59 | } else { 60 | Write-Verbose "Deploying to subscription '$($selectedSubscription.Id) ($($selectedSubscription.Name))'" 61 | } 62 | 63 | Write-Host 'Creating (or updating) resource group' 64 | 65 | $createResourceGroupParameters = @{ 66 | Name = $resourceGroupName 67 | Location = $Location 68 | Force = $true 69 | } 70 | 71 | New-AzResourceGroup @createResourceGroupParameters | Out-Null 72 | 73 | Write-Verbose "Created (or updated) resource group '$resourceGroupName'" 74 | 75 | Write-Host 'Deploying Bicep file (takes a while)' 76 | 77 | $bicepPath = Join-Path $PSScriptRoot 'testing.bicep' 78 | 79 | $bicepParameters = @{ 80 | location = $Location 81 | resourceNamePrefix = $ResourceNamePrefix 82 | } 83 | 84 | $deploymentName = "deploy-$((Get-Date).ToString('yyyyMMdd-HHmmss'))-$((New-Guid).Guid.Substring(0, 4))" 85 | 86 | $createEnvironmentDeploymentParameters = @{ 87 | Name = $deploymentName 88 | ResourceGroupName = $resourceGroupName 89 | TemplateFile = $bicepPath 90 | TemplateParameterObject = $bicepParameters 91 | SkipTemplateParameterPrompt = $true 92 | Force = $true 93 | } 94 | 95 | New-AzResourceGroupDeployment @createEnvironmentDeploymentParameters | Out-Null 96 | -------------------------------------------------------------------------------- /deploy/functionApp.bicep: -------------------------------------------------------------------------------- 1 | @description('Location for all resources.') 2 | param location string = resourceGroup().location 3 | 4 | @description('Function App storage account name.') 5 | param storageName string 6 | 7 | @description('Function App hosting plan name.') 8 | param hostingPlanName string 9 | 10 | @description('Function App name.') 11 | param functionAppName string 12 | 13 | @description('Function worker runtime version.') 14 | @allowed([ 15 | 'dotnet' 16 | 'dotnet-isolated' 17 | ]) 18 | @minLength(1) 19 | param functionWorkerRuntime string 20 | 21 | @description('Application Insights connection string.') 22 | @secure() 23 | param applicationInsightsConnectionString string 24 | 25 | @description('Service Bus connection string.') 26 | @secure() 27 | param serviceBusConnectionString string 28 | 29 | @description('Used to populate "Secret:ReallySecretValue".') 30 | @secure() 31 | param reallySecretValue string 32 | 33 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { 34 | name: storageName 35 | location: location 36 | kind: 'StorageV2' 37 | sku: { 38 | name: 'Standard_LRS' 39 | } 40 | properties: { 41 | accessTier: 'Hot' 42 | } 43 | } 44 | 45 | resource hostingPlan 'Microsoft.Web/serverfarms@2024-04-01' = { 46 | name: hostingPlanName 47 | location: location 48 | kind: '' 49 | sku: { 50 | name: 'Y1' 51 | tier: 'Dynamic' 52 | size: 'Y1' 53 | family: 'Y' 54 | capacity: 0 55 | } 56 | properties: {} 57 | } 58 | 59 | resource functionApp 'Microsoft.Web/sites@2024-04-01' = { 60 | name: functionAppName 61 | location: location 62 | identity: { 63 | type: 'SystemAssigned' 64 | } 65 | kind: 'functionapp' 66 | properties: { 67 | httpsOnly: true 68 | serverFarmId: hostingPlan.id 69 | } 70 | } 71 | 72 | var storageAccountConnectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' 73 | resource appSettings 'Microsoft.Web/sites/config@2024-04-01' = { 74 | parent: functionApp 75 | name: 'appsettings' 76 | properties: { 77 | AzureWebJobsStorage: storageAccountConnectionString 78 | APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsightsConnectionString 79 | FUNCTIONS_EXTENSION_VERSION: '~4' 80 | FUNCTIONS_WORKER_RUNTIME: functionWorkerRuntime 81 | FUNCTIONS_INPROC_NET8_ENABLED: '1' 82 | WEBSITE_CONTENTAZUREFILECONNECTIONSTRING: storageAccountConnectionString 83 | WEBSITE_CONTENTSHARE: functionAppName 84 | WEBSITE_RUN_FROM_PACKAGE: '1' 85 | 'Secret:ReallySecretValue': reallySecretValue 86 | ServiceBusConnection: serviceBusConnectionString 87 | 'Testing:IsEnabled': 'false' 88 | 'ApplicationInsights:DiscardServiceBusTrigger': 'true' 89 | 'ApplicationInsights:HealthCheckFunctionName': 'HealthFunction' 90 | } 91 | } 92 | 93 | resource webConfig 'Microsoft.Web/sites/config@2024-04-01' = { 94 | parent: functionApp 95 | name: 'web' 96 | properties: { 97 | netFrameworkVersion: 'v8.0' 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Infrastructure/Telemetry/TelemetryCounterInstanceInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | 5 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 6 | 7 | /// 8 | /// Even though this telemetry initializer enriches the telemetry, the main goal is to demonstrate that you need 9 | /// to be careful when registering an instance of a telemetry initializer. Despite what the documentation states at: 10 | /// https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core#add-telemetryinitializers 11 | /// 12 | /// 13 | /// You should not use: 14 | /// 15 | /// 16 | /// // This does not work, the telemetry initializer will not be registered 17 | /// .AddSingleton(new TelemetryCounterInstanceInitializer("NiceValue")) 18 | /// 19 | /// 20 | /// Instead you should use: 21 | /// 22 | /// 23 | /// .AddSingleton<ITelemetryInitializer>(new TelemetryCounterInstanceInitializer("NiceValue")) 24 | /// 25 | /// 26 | internal sealed class TelemetryCounterInstanceInitializer : ITelemetryInitializer 27 | { 28 | private readonly string _customPropertyValue; 29 | public long AvailabilityTelemetryCount; 30 | public long DependencyTelemetryCount; 31 | public long EventTelemetryCount; 32 | public long ExceptionTelemetryCount; 33 | public long MetricTelemetryCount; 34 | public long RequestTelemetryCount; 35 | public long TraceTelemetryCount; 36 | private const string CustomPropertyName = "NiceProp"; 37 | 38 | public TelemetryCounterInstanceInitializer(string customPropertyValue) 39 | { 40 | _customPropertyValue = customPropertyValue; 41 | } 42 | 43 | public void Initialize(ITelemetry telemetry) 44 | { 45 | if (telemetry is ISupportProperties itemProperties && 46 | !itemProperties.Properties.ContainsKey(CustomPropertyName)) 47 | { 48 | itemProperties.Properties[CustomPropertyName] = _customPropertyValue; 49 | } 50 | 51 | switch (telemetry) 52 | { 53 | case AvailabilityTelemetry: 54 | Interlocked.Increment(ref AvailabilityTelemetryCount); 55 | break; 56 | case DependencyTelemetry: 57 | Interlocked.Increment(ref DependencyTelemetryCount); 58 | break; 59 | case EventTelemetry: 60 | Interlocked.Increment(ref EventTelemetryCount); 61 | break; 62 | case ExceptionTelemetry: 63 | Interlocked.Increment(ref ExceptionTelemetryCount); 64 | break; 65 | case MetricTelemetry: 66 | Interlocked.Increment(ref MetricTelemetryCount); 67 | break; 68 | case RequestTelemetry: 69 | Interlocked.Increment(ref RequestTelemetryCount); 70 | break; 71 | case TraceTelemetry: 72 | Interlocked.Increment(ref TraceTelemetryCount); 73 | break; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /samples/CustomV4InProcessFunction/Functions/Telemetry/TelemetryFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Infrastructure.Telemetry; 3 | using Microsoft.ApplicationInsights.Channel; 4 | using Microsoft.ApplicationInsights.Extensibility.Implementation; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Azure.WebJobs; 8 | using Microsoft.Azure.WebJobs.Extensions.Http; 9 | using Microsoft.Extensions.Options; 10 | using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; 11 | 12 | namespace Gabo.AzureFunctionTelemetry.Samples.CustomV4InProcessFunction.Functions.Telemetry; 13 | 14 | #pragma warning disable CA1001 // The container will dispose the injected instances 15 | public class TelemetryFunction 16 | #pragma warning restore CA1001 17 | { 18 | private readonly TestingChannel _testingChannel; 19 | private readonly TestingOptions _testingOptions; 20 | 21 | public TelemetryFunction(ITelemetryChannel testingChannel, IOptions testingOptions) 22 | { 23 | _testingOptions = testingOptions.Value; 24 | 25 | if (_testingOptions.IsEnabled) 26 | { 27 | _testingChannel = (TestingChannel)testingChannel; 28 | } 29 | else 30 | { 31 | _testingChannel = new TestingChannel(); 32 | } 33 | } 34 | 35 | [FunctionName("GetTelemetry")] 36 | public IActionResult RunGetTelemetry( 37 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "telemetry")] 38 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 39 | HttpRequest request, 40 | ExecutionContext executionContext) 41 | { 42 | if (!_testingOptions.IsEnabled) 43 | { 44 | return GenerateTestingDisabledConflict(executionContext.InvocationId.ToString("D")); 45 | } 46 | 47 | var serialisedTelemetryItems = JsonSerializer.Serialize(_testingChannel.TelemetryItems, false); 48 | return new OkObjectResult(Encoding.UTF8.GetString(serialisedTelemetryItems)); 49 | } 50 | 51 | [FunctionName("DeleteTelemetry")] 52 | public IActionResult RunDeleteTelemetry( 53 | [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "telemetry")] 54 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 55 | HttpRequest request, 56 | ExecutionContext executionContext) 57 | { 58 | if (!_testingOptions.IsEnabled) 59 | { 60 | return GenerateTestingDisabledConflict(executionContext.InvocationId.ToString("D")); 61 | } 62 | 63 | _testingChannel.Clear(); 64 | return new AcceptedResult(); 65 | } 66 | 67 | private static ObjectResult GenerateTestingDisabledConflict(string invocationId) 68 | { 69 | var problemDetails = new ProblemDetails 70 | { 71 | Detail = "This endpoint is only available when testing is enabled.", 72 | Instance = invocationId, 73 | Status = StatusCodes.Status409Conflict, 74 | Title = "TestingDisabled" 75 | }; 76 | return new ObjectResult(problemDetails) 77 | { 78 | ContentTypes = { "application/problem+json" }, 79 | StatusCode = problemDetails.Status 80 | }; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /samples/DefaultV4InProcessFunction/Functions/Telemetry/TelemetryFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Infrastructure.Telemetry; 3 | using Microsoft.ApplicationInsights.Channel; 4 | using Microsoft.ApplicationInsights.Extensibility.Implementation; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Azure.WebJobs; 8 | using Microsoft.Azure.WebJobs.Extensions.Http; 9 | using Microsoft.Extensions.Options; 10 | using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; 11 | 12 | namespace Gabo.AzureFunctionTelemetry.Samples.DefaultV4InProcessFunction.Functions.Telemetry; 13 | 14 | #pragma warning disable CA1001 // The container is responsible for disposing the injected instances 15 | public class TelemetryFunction 16 | #pragma warning restore CA1001 17 | { 18 | private readonly TestingChannel _testingChannel; 19 | private readonly TestingOptions _testingOptions; 20 | 21 | public TelemetryFunction(ITelemetryChannel testingChannel, IOptions testingOptions) 22 | { 23 | _testingOptions = testingOptions.Value; 24 | 25 | if (_testingOptions.IsEnabled) 26 | { 27 | _testingChannel = (TestingChannel)testingChannel; 28 | } 29 | else 30 | { 31 | _testingChannel = new TestingChannel(); 32 | } 33 | } 34 | 35 | [FunctionName("GetTelemetry")] 36 | public IActionResult RunGetTelemetry( 37 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "telemetry")] 38 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 39 | HttpRequest request, 40 | ExecutionContext executionContext) 41 | { 42 | if (!_testingOptions.IsEnabled) 43 | { 44 | return GenerateTestingDisabledConflict(executionContext.InvocationId.ToString("D")); 45 | } 46 | 47 | var serialisedTelemetryItems = JsonSerializer.Serialize(_testingChannel.TelemetryItems, false); 48 | return new OkObjectResult(Encoding.UTF8.GetString(serialisedTelemetryItems)); 49 | } 50 | 51 | [FunctionName("DeleteTelemetry")] 52 | public IActionResult RunDeleteTelemetry( 53 | [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "telemetry")] 54 | [SuppressMessage("Style", "IDE0060", Justification = "Can't use discard as it breaks the binding")] 55 | HttpRequest request, 56 | ExecutionContext executionContext) 57 | { 58 | if (!_testingOptions.IsEnabled) 59 | { 60 | return GenerateTestingDisabledConflict(executionContext.InvocationId.ToString("D")); 61 | } 62 | 63 | _testingChannel.Clear(); 64 | return new AcceptedResult(); 65 | } 66 | 67 | private static ObjectResult GenerateTestingDisabledConflict(string invocationId) 68 | { 69 | var problemDetails = new ProblemDetails 70 | { 71 | Detail = "This endpoint is only available when testing is enabled.", 72 | Instance = invocationId, 73 | Status = StatusCodes.Status409Conflict, 74 | Title = "TestingDisabled" 75 | }; 76 | return new ObjectResult(problemDetails) 77 | { 78 | ContentTypes = { "application/problem+json" }, 79 | StatusCode = problemDetails.Status 80 | }; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /docs/img/noun-design-tools-155462.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/README.md: -------------------------------------------------------------------------------- 1 | # Better Azure Functions Application Insights integration 2 | 3 | :rotating_light: The customisation supports **in-process** Functions only. **Isolated Functions are not supported**. 4 | 5 | Beginning [10 November 2026, the in-process model for .NET apps in Azure Functions will no longer be supported][in-process-retirement-notice]. [Migrate to the isolated worked model][migrate-isolated-worker-model]. 6 | 7 | Automatically discards redundant telemetry items such as Functions execution traces and duplicate exceptions. Allows you to register your own telemetry processor(s) that will be invoked on every telemetry item type. 8 | 9 | More detailed documentation is available on [GitHub][documentation]. 10 | 11 | ## Usage 12 | 13 | In your `Startup` `class` add the below snippet: 14 | 15 | ```csharp 16 | var appInsightsOptions = new CustomApplicationInsightsOptionsBuilder( 17 | "{ApplicationName}", 18 | {TypeFromEntryAssembly}) 19 | .Build(); 20 | 21 | builder.Services 22 | .AddCustomApplicationInsights(appInsightsOptions); 23 | ``` 24 | 25 | Where 26 | 27 | - `{ApplicationName}` used to set Application Insights' _Cloud role name_ (optional). When not provided, the default behaviour is preserved (the _Cloud role name_ will be set to the Function App's name) 28 | - `{TypeFromEntryAssembly}` typically would be `typeof(Startup)`. When `{ApplicationName}` is provided, I read the [Assembly Informational Version][assembly-informational-version] of the entry assembly to set Application Insights' _Application version_ (I use _unknown_ as a fallback). When `{ApplicationName}` is not provided, _Application version_ will not be present on the telemetry items 29 | 30 | Additionally you can discard health requests and Service Bus trigger traces using application settings: 31 | 32 | ```json 33 | { 34 | "Values": { 35 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 36 | "FUNCTIONS_WORKER_RUNTIME": "dotnet", 37 | "ApplicationInsights:DiscardServiceBusTrigger": true, 38 | "ApplicationInsights:HealthCheckFunctionName": "HealthFunction" // The name of the Function, not the route 39 | } 40 | } 41 | ``` 42 | 43 | You can also add telemetry initializers and telemetry processors: 44 | 45 | ```csharp 46 | public override void Configure(IFunctionsHostBuilder builder) 47 | { 48 | var appInsightsOptions = new CustomApplicationInsightsOptionsBuilder( 49 | "customv4inprocess", // Will be used as Cloud role name 50 | typeof(Startup)) // Assembly Informational Version will be used as Application version 51 | .Build(); 52 | 53 | builder.Services 54 | // Telemetry processors are supported on all telemetry item types 55 | .AddApplicationInsightsTelemetryProcessor() 56 | .AddSingleton() 57 | /* 58 | * When adding an instance of a telemetry initializer, you need to provide the service Type otherwise 59 | * your initializer will not be used. 60 | * 61 | * 62 | * // Do not use: 63 | * .AddSingleton(new TelemetryCounterInstanceInitializer("NiceValue")) 64 | * 65 | */ 66 | .AddSingleton(new TelemetryCounterInstanceInitializer("NiceValue")) 67 | .AddCustomApplicationInsights(appInsightsOptions); 68 | } 69 | ``` 70 | 71 | ## Limitations 72 | 73 | Setting the cloud role name breaks the Function's Monitor blade. 74 | 75 | ## Migration guides 76 | 77 | - [Migrating from v1 to v2][migrating-from-v1-v2] 78 | 79 | ## Release notes 80 | 81 | Release notes can be found on [GitHub][release-notes]. 82 | 83 | ## Feedback 84 | 85 | Leave feedback by [opening an issue][open-issue] on GitHub. 86 | 87 | [assembly-informational-version]: https://learn.microsoft.com/en-us/dotnet/standard/assembly/versioning#assembly-informational-version 88 | [release-notes]: https://github.com/gabrielweyer/azure-functions-telemetry/releases 89 | [documentation]: https://github.com/gabrielweyer/azure-functions-telemetry/blob/main/README.md 90 | [open-issue]: https://github.com/gabrielweyer/azure-functions-telemetry/issues/new 91 | [migrating-from-v1-v2]: https://github.com/gabrielweyer/azure-functions-telemetry/blob/main/docs/Migrate-from-v1-to-v2.md 92 | [in-process-retirement-notice]: https://azure.microsoft.com/en-us/updates/retirement-support-for-the-inprocess-model-for-net-apps-in-azure-functions-ends-10-november-2026/ 93 | [migrate-isolated-worker-model]: https://learn.microsoft.com/en-au/azure/azure-functions/migrate-dotnet-to-isolated-model?tabs=net8 94 | --------------------------------------------------------------------------------