├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── build.yml │ ├── security-scanning.yml │ └── skipped-build.yml ├── .gitignore ├── .nuke ├── build.schema.json └── parameters.json ├── .vscode ├── extensions.json └── settings.json ├── AzureFunctionsTelemetry.sln ├── AzureFunctionsTelemetry.sln.DotSettings ├── CONTRIBUTING.md ├── Directory.Packages.props ├── LICENSE ├── README.md ├── build.ps1 ├── build.sh ├── build ├── .editorconfig ├── Build.cs ├── Configuration.cs ├── Directory.Build.props ├── Directory.Build.targets └── _build.csproj ├── deploy ├── Deploy.ps1 ├── functionApp.bicep └── main.bicep ├── docs ├── DEMO.md ├── Isolated.md ├── Migrate-from-v1-to-v2.md ├── img │ ├── broken-function-monitor-blade.png │ ├── demo │ │ ├── inprocess-v4-custom-cloud-role-name-application-version.png │ │ ├── inprocess-v4-custom-console-stack-trace-present.png │ │ ├── inprocess-v4-custom-dependency-function.png │ │ ├── inprocess-v4-custom-http-binding-exception-logged-once.png │ │ ├── inprocess-v4-custom-service-bus-binding-exception.png │ │ ├── inprocess-v4-custom-service-bus-binding-request-url-and-response-code.png │ │ ├── inprocess-v4-custom-system-trace-middleware-suppressed-logs.png │ │ ├── inprocess-v4-custom-telemetry-configuration-registered.png │ │ ├── inprocess-v4-custom-telemetry-processor-is-being-called.png │ │ ├── inprocess-v4-custom-trace-log-live-metrics.png │ │ ├── inprocess-v4-default-cloud-role-name-application-version.png │ │ ├── inprocess-v4-default-console-stack-trace-absent.png │ │ ├── inprocess-v4-default-dependency-function.png │ │ ├── inprocess-v4-default-http-binding-exception-logged-twice.png │ │ ├── inprocess-v4-default-service-bus-binding-exception.png │ │ ├── inprocess-v4-default-service-bus-binding-no-request-url-no-response-code.png │ │ ├── inprocess-v4-default-service-bus-binding.png │ │ ├── inprocess-v4-default-system-trace-middleware-local-two-logs-http-function-execution.png │ │ ├── inprocess-v4-default-telemetry-configuration-not-registered.png │ │ ├── inprocess-v4-default-telemetry-processor-not-being-called.png │ │ ├── isolated-v4-default-console-stack-trace-present.png │ │ ├── isolated-v4-default-dependency-function.png │ │ ├── isolated-v4-default-http-binding-exception-logged-twice-host.png │ │ ├── isolated-v4-default-service-bus-binding-exception.png │ │ ├── isolated-v4-default-service-bus-binding-no-request-url-no-response-code.png │ │ ├── isolated-v4-default-service-bus-binding.png │ │ └── isolated-v4-default-telemetry-processor-is-being-called-for-all-but-requests.png │ └── noun-design-tools-155462.svg └── postman │ ├── FunctionsTelemetry.postman_collection.json │ ├── FunctionsTelemetryAzure.postman_environment.json │ └── FunctionsTelemetryLocal.postman_environment.json ├── nuget.config ├── samples ├── CustomV4InProcessFunction │ ├── CustomV4InProcessFunction.csproj │ ├── Functions │ │ ├── Availability │ │ │ └── AvailabilityFunction.cs │ │ ├── CustomEvent │ │ │ └── CustomEventFunction.cs │ │ ├── Dependency │ │ │ └── DependencyFunction.cs │ │ ├── Health │ │ │ └── HealthFunction.cs │ │ ├── HttpExceptionThrowing │ │ │ └── HttpExceptionThrowingFunction.cs │ │ ├── Initializer │ │ │ └── InitializerFunction.cs │ │ ├── InstanceInitializer │ │ │ └── InstanceInitializerFunction.cs │ │ ├── Processor │ │ │ └── ProcessorFunction.cs │ │ ├── ServiceBus │ │ │ └── ServiceBusFunction.cs │ │ ├── ServiceBusExceptionThrowing │ │ │ └── ServiceBusExceptionThrowingFunction.cs │ │ ├── Telemetry │ │ │ └── TelemetryFunction.cs │ │ ├── TraceLog │ │ │ └── TraceLogFunction.cs │ │ ├── TriggerServiceBus │ │ │ └── TriggerServiceBusFunction.cs │ │ ├── TriggerServiceBusExceptionThrowing │ │ │ └── TriggerServiceBusExceptionThrowingFunction.cs │ │ └── UserSecret │ │ │ ├── SecretOptions.cs │ │ │ └── UserSecretFunction.cs │ ├── Infrastructure │ │ ├── ServiceCollectionExtension.cs │ │ └── Telemetry │ │ │ ├── CustomHttpDependencyFilter.cs │ │ │ ├── TelemetryCounterInitializer.cs │ │ │ ├── TelemetryCounterInstanceInitializer.cs │ │ │ ├── TelemetryCounterProcessor.cs │ │ │ ├── TestingChannel.cs │ │ │ └── TestingOptions.cs │ ├── Startup.cs │ ├── host.json │ └── local.settings.json ├── DefaultV4InProcessFunction │ ├── DefaultV4InProcessFunction.csproj │ ├── Functions │ │ ├── Availability │ │ │ └── AvailabilityFunction.cs │ │ ├── CustomEvent │ │ │ └── CustomEventFunction.cs │ │ ├── Dependency │ │ │ └── DependencyFunction.cs │ │ ├── Health │ │ │ └── HealthFunction.cs │ │ ├── HttpExceptionThrowing │ │ │ └── HttpExceptionThrowingFunction.cs │ │ ├── Initializer │ │ │ └── InitializerFunction.cs │ │ ├── Processor │ │ │ └── ProcessorFunction.cs │ │ ├── ServiceBus │ │ │ └── ServiceBusFunction.cs │ │ ├── ServiceBusExceptionThrowing │ │ │ └── ServiceBusExceptionThrowingFunction.cs │ │ ├── Telemetry │ │ │ └── TelemetryFunction.cs │ │ ├── TraceLog │ │ │ └── TraceLogFunction.cs │ │ ├── TriggerServiceBus │ │ │ └── TriggerServiceBusFunction.cs │ │ ├── TriggerServiceBusExceptionThrowing │ │ │ └── TriggerServiceBusExceptionThrowingFunction.cs │ │ └── UserSecret │ │ │ ├── SecretOptions.cs │ │ │ └── UserSecretFunction.cs │ ├── Infrastructure │ │ ├── ServiceCollectionExtension.cs │ │ └── Telemetry │ │ │ ├── TelemetryCounterInitializer.cs │ │ │ ├── TelemetryCounterProcessor.cs │ │ │ ├── TestingChannel.cs │ │ │ └── TestingOptions.cs │ ├── Startup.cs │ ├── host.json │ └── local.settings.json └── DefaultV4IsolatedFunction │ ├── DefaultV4IsolatedFunction.csproj │ ├── Functions │ ├── Availability │ │ └── AvailabilityFunction.cs │ ├── CustomEvent │ │ └── CustomEventFunction.cs │ ├── Dependency │ │ └── DependencyFunction.cs │ ├── Health │ │ └── HealthFunction.cs │ ├── HttpExceptionThrowing │ │ └── HttpExceptionThrowingFunction.cs │ ├── Initializer │ │ └── InitializerFunction.cs │ ├── Processor │ │ └── ProcessorFunction.cs │ ├── ServiceBus │ │ └── ServiceBusFunction.cs │ ├── ServiceBusExceptionThrowing │ │ └── ServiceBusExceptionThrowingFunction.cs │ ├── TraceLog │ │ └── TraceLogFunction.cs │ ├── TriggerServiceBus │ │ ├── TriggerServiceBusFunction.cs │ │ └── TriggerServiceBusFunctionOutput.cs │ ├── TriggerServiceBusExceptionThrowing │ │ ├── TriggerServiceBusExceptionThrowingFunction.cs │ │ └── TriggerServiceBusExceptionThrowingFunctionOutput.cs │ └── UserSecret │ │ ├── SecretOptions.cs │ │ └── UserSecretFunction.cs │ ├── Infrastructure │ ├── ServiceCollectionExtension.cs │ └── Telemetry │ │ ├── CustomHttpDependencyFilter.cs │ │ ├── HealthRequestFilter.cs │ │ ├── TelemetryCounterInitializer.cs │ │ └── TelemetryCounterProcessor.cs │ ├── Program.cs │ ├── host.json │ └── local.settings.json ├── scripts └── testing │ ├── Deploy-Testing.ps1 │ └── testing.bicep ├── src └── AzureFunctionsTelemetry │ ├── ApplicationInsights │ ├── ApplicationDescriptor.cs │ ├── ApplicationInitializer.cs │ ├── ApplicationInsightsServiceCollectionExtensions.cs │ ├── CustomApplicationInsightsConfig.cs │ ├── CustomApplicationInsightsConfigBuilder.cs │ ├── CustomApplicationInsightsOptions.cs │ ├── CustomApplicationInsightsOptionsBuilder.cs │ ├── DuplicateExceptionsFilter.cs │ ├── FunctionExecutionTracesFilter.cs │ ├── FunctionsFinder.cs │ ├── HealthRequestFilter.cs │ ├── ServiceBusRequestInitializer.cs │ ├── ServiceBusTriggerFilter.cs │ ├── StringHelper.cs │ └── TelemetryHelper.cs │ ├── AzureFunctionsTelemetry.csproj │ ├── FunctionRuntimeCategory.cs │ ├── FunctionRuntimeEventId.cs │ ├── FunctionRuntimeEventName.cs │ ├── Logging │ └── LoggingServiceCollectionExtensions.cs │ ├── README.md │ └── icon.png └── tests ├── AppInsightsConnectionStringIntegrationTests ├── AppInsightsConnectionStringIntegrationTests.csproj ├── CustomTelemetryClientResolutionTests.cs ├── DefaultTelemetryClientResolutionTests.cs └── TestInfrastructure │ ├── CustomTelemetryFunctionClient.cs │ ├── DefaultTelemetryFunctionClient.cs │ └── TelemetryFunctionClient.cs ├── AzureFunctionsTelemetryIntegrationTests ├── AzureFunctionsTelemetryIntegrationTests.csproj ├── CustomHttpTests.cs ├── CustomServiceBusTests.cs ├── CustomTelemetryTests.cs ├── DefaultHttpTests.cs ├── DefaultServiceBusTests.cs ├── DefaultTelemetryTests.cs └── TestInfrastructure │ ├── CustomTelemetryFunctionClient.cs │ ├── DefaultTelemetryFunctionClient.cs │ ├── DependencyItem.cs │ ├── ExceptionItem.cs │ ├── ListOfTelemetryItemExtensions.cs │ ├── MetricItem.cs │ ├── RequestBaseData.cs │ ├── RequestData.cs │ ├── RequestItem.cs │ ├── TelemetryFunctionClient.cs │ ├── TelemetryItem.cs │ ├── TelemetryItemConverter.cs │ ├── TelemetryItemTags.cs │ ├── TraceBaseData.cs │ ├── TraceData.cs │ └── TraceItem.cs └── AzureFunctionsTelemetryTests ├── ApplicationInsights ├── ApplicationInitializerTests.cs ├── ApplicationInsightsServiceCollectionExtensionsTests.cs ├── CustomApplicationInsightsConfigBuilderTests.cs ├── CustomApplicationInsightsConfigTests.cs ├── DuplicateExceptionsFilterHttpBindingTests.cs ├── DuplicateExceptionsFilterServiceBusBindingTests.cs ├── FunctionExecutionTracesFilterTests.cs ├── FunctionsFinderTests.cs ├── HealthRequestFilterTests.cs ├── ServiceBusRequestInitializerTests.cs └── ServiceBusTriggerFilterTests.cs ├── AzureFunctionsTelemetryTests.csproj └── TestInfrastructure ├── Builders ├── ExceptionTelemetryBuilder.cs ├── RequestTelemetryBuilder.cs └── TraceTelemetryBuilder.cs ├── Functions ├── HttpFunction.cs ├── InstanceServiceBusFunction.cs └── StaticServiceBusFunction.cs └── Mocks └── MockTelemetryProcessor.cs /.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 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @gabrielweyer 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.nuke/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./build.schema.json", 3 | "Solution": "AzureFunctionsTelemetry.sln" 4 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.schemas": { 3 | "https://json.schemastore.org/github-workflow.json": "/.github/workflows/*.yml" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 4 | [string[]]$BuildArguments 5 | ) 6 | 7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" 8 | 9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } 10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 11 | 12 | ########################################################################### 13 | # CONFIGURATION 14 | ########################################################################### 15 | 16 | $BuildProjectFile = "$PSScriptRoot\build\_build.csproj" 17 | $TempDirectory = "$PSScriptRoot\\.nuke\temp" 18 | 19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json" 20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" 21 | $DotNetChannel = "Current" 22 | 23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0 26 | 27 | ########################################################################### 28 | # EXECUTION 29 | ########################################################################### 30 | 31 | function ExecSafe([scriptblock] $cmd) { 32 | & $cmd 33 | if ($LASTEXITCODE) { exit $LASTEXITCODE } 34 | } 35 | 36 | # If dotnet CLI is installed globally and it matches requested version, use for execution 37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` 38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) { 39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path 40 | } 41 | else { 42 | # Download install script 43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" 44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null 45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) 47 | 48 | # If global.json exists, load expected version 49 | if (Test-Path $DotNetGlobalFile) { 50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) 51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { 52 | $DotNetVersion = $DotNetGlobal.sdk.version 53 | } 54 | } 55 | 56 | # Install by channel or version 57 | $DotNetDirectory = "$TempDirectory\dotnet-win" 58 | if (!(Test-Path variable:DotNetVersion)) { 59 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } 60 | } else { 61 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } 62 | } 63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" 64 | } 65 | 66 | Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)" 67 | 68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } 69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } 70 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | bash --version 2>&1 | head -n 1 4 | 5 | set -eo pipefail 6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 7 | 8 | ########################################################################### 9 | # CONFIGURATION 10 | ########################################################################### 11 | 12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj" 13 | TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp" 14 | 15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" 16 | DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" 17 | DOTNET_CHANNEL="Current" 18 | 19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 20 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 21 | export DOTNET_MULTILEVEL_LOOKUP=0 22 | 23 | ########################################################################### 24 | # EXECUTION 25 | ########################################################################### 26 | 27 | function FirstJsonValue { 28 | perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}" 29 | } 30 | 31 | # If dotnet CLI is installed globally and it matches requested version, use for execution 32 | if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then 33 | export DOTNET_EXE="$(command -v dotnet)" 34 | else 35 | # Download install script 36 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" 37 | mkdir -p "$TEMP_DIRECTORY" 38 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" 39 | chmod +x "$DOTNET_INSTALL_FILE" 40 | 41 | # If global.json exists, load expected version 42 | if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then 43 | DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")") 44 | if [[ "$DOTNET_VERSION" == "" ]]; then 45 | unset DOTNET_VERSION 46 | fi 47 | fi 48 | 49 | # Install by channel or version 50 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" 51 | if [[ -z ${DOTNET_VERSION+x} ]]; then 52 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path 53 | else 54 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path 55 | fi 56 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" 57 | fi 58 | 59 | echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)" 60 | 61 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet 62 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" 63 | -------------------------------------------------------------------------------- /build/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_style_qualification_for_field = false:warning 3 | dotnet_style_qualification_for_property = false:warning 4 | dotnet_style_qualification_for_method = false:warning 5 | dotnet_style_qualification_for_event = false:warning 6 | dotnet_style_require_accessibility_modifiers = never:warning 7 | 8 | csharp_style_expression_bodied_methods = true:silent 9 | csharp_style_expression_bodied_properties = true:warning 10 | csharp_style_expression_bodied_indexers = true:warning 11 | csharp_style_expression_bodied_accessors = true:warning 12 | 13 | # Ignore private instance fields naming rule 14 | dotnet_naming_rule.private_instance_fields_should_be_camel_case.severity = none 15 | -------------------------------------------------------------------------------- /build/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Nuke.Common.Tooling; 3 | 4 | namespace Gabo; 5 | 6 | [TypeConverter(typeof(TypeConverter))] 7 | sealed class Configuration : Enumeration 8 | { 9 | public static Configuration Debug = new() { Value = nameof(Debug) }; 10 | public static Configuration Release = new() { Value = nameof(Release) }; 11 | 12 | public static implicit operator string(Configuration configuration) 13 | { 14 | return configuration.Value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /build/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /build/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /build/_build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | Gabo 7 | CS0649;CS0169;CA1707 8 | .. 9 | .. 10 | 1 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/img/broken-function-monitor-blade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/broken-function-monitor-blade.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-cloud-role-name-application-version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-cloud-role-name-application-version.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-console-stack-trace-present.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-console-stack-trace-present.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-dependency-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-dependency-function.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-http-binding-exception-logged-once.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-http-binding-exception-logged-once.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-service-bus-binding-exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-service-bus-binding-exception.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-service-bus-binding-request-url-and-response-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-service-bus-binding-request-url-and-response-code.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-system-trace-middleware-suppressed-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-system-trace-middleware-suppressed-logs.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-telemetry-configuration-registered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-telemetry-configuration-registered.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-telemetry-processor-is-being-called.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-telemetry-processor-is-being-called.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-custom-trace-log-live-metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-custom-trace-log-live-metrics.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-cloud-role-name-application-version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-default-cloud-role-name-application-version.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-console-stack-trace-absent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-default-console-stack-trace-absent.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-dependency-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-default-dependency-function.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-http-binding-exception-logged-twice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-default-http-binding-exception-logged-twice.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-service-bus-binding-exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-default-service-bus-binding-exception.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/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-default-service-bus-binding-no-request-url-no-response-code.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-service-bus-binding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-default-service-bus-binding.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-system-trace-middleware-local-two-logs-http-function-execution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-default-system-trace-middleware-local-two-logs-http-function-execution.png -------------------------------------------------------------------------------- /docs/img/demo/inprocess-v4-default-telemetry-configuration-not-registered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/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/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/inprocess-v4-default-telemetry-processor-not-being-called.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-console-stack-trace-present.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/isolated-v4-default-console-stack-trace-present.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-dependency-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/isolated-v4-default-dependency-function.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-http-binding-exception-logged-twice-host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/isolated-v4-default-http-binding-exception-logged-twice-host.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-service-bus-binding-exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/isolated-v4-default-service-bus-binding-exception.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/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/isolated-v4-default-service-bus-binding-no-request-url-no-response-code.png -------------------------------------------------------------------------------- /docs/img/demo/isolated-v4-default-service-bus-binding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/isolated-v4-default-service-bus-binding.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/4b3635228855a1ce1da0ac2718a7833668357c89/docs/img/demo/isolated-v4-default-telemetry-processor-is-being-called-for-all-but-requests.png -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/AzureFunctionsTelemetry/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielweyer/azure-functions-telemetry/4b3635228855a1ce1da0ac2718a7833668357c89/src/AzureFunctionsTelemetry/icon.png -------------------------------------------------------------------------------- /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 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/DependencyItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class DependencyItem : TelemetryItem 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/ExceptionItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class ExceptionItem : TelemetryItem 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/AzureFunctionsTelemetryIntegrationTests/TestInfrastructure/MetricItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class MetricItem : TelemetryItem 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/RequestItem.cs: -------------------------------------------------------------------------------- 1 | namespace Gabo.AzureFunctionsTelemetryIntegrationTests.TestInfrastructure; 2 | 3 | internal sealed class RequestItem : TelemetryItem 4 | { 5 | public RequestData Data { get; set; } = new(); 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------