├── .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 |
--------------------------------------------------------------------------------