diff --git a/.github/workflows/all_solutions.yml b/.github/workflows/all_solutions.yml index f039a94c5..c1e126b3c 100644 --- a/.github/workflows/all_solutions.yml +++ b/.github/workflows/all_solutions.yml @@ -9,6 +9,12 @@ on: release: types: [published] workflow_dispatch: + inputs: + build-for-release: + description: 'This is a Release build. Use the "real" code signing certificate.' + required: true + type: boolean + default: false schedule: - cron: "0 9 * * *" @@ -41,7 +47,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -63,13 +69,6 @@ jobs: echo "version=$agentVersion" >> $env:GITHUB_OUTPUT shell: powershell - - name: Archive NewRelic.NuGetHelper - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - with: - name: NewRelic.NuGetHelper - path: ${{ github.workspace }}\build\NewRelic.NuGetHelper\bin - if-no-files-found: error - - name: Archive NewRelic.Agent.Extensions uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: @@ -91,7 +90,7 @@ jobs: if-no-files-found: error - name: Convert Code Signing Certificate Into File - if: ${{ github.event.release }} || github.event_name == 'workflow_dispatch' + if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }} id: write_cert run: | $filePath = '${{ github.workspace }}\newrelic_code_sign_cert.pfx' @@ -101,14 +100,14 @@ jobs: shell: powershell - name: Install Code Signing Certificate - if: ${{ github.event.release }} || github.event_name == 'workflow_dispatch' + if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }} run: | Write-Host "certutil.exe -f -user -p -importPFX ${{ steps.write_cert.outputs.filePath }} NoRoot" certutil.exe -f -user -p ${{ secrets.CERT_PASSPHRASE }} -importPFX ${{ steps.write_cert.outputs.filePath }} NoRoot shell: powershell - name: Create Self-signed code signing cert - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' + if: ${{ (github.event_name == 'pull_request') || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'false') || github.event_name == 'schedule' }} run: | Write-Host "New-SelfSignedCertificate -DnsName "Self-signed code signing cert" -Type CodeSigning -CertStoreLocation Cert:\CurrentUser\My -NotAfter (Get-Date).AddYears(100)" New-SelfSignedCertificate -DnsName "Self-signed code signing cert" -Type CodeSigning -CertStoreLocation Cert:\CurrentUser\My -NotAfter (Get-Date).AddYears(100) @@ -150,7 +149,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -192,7 +191,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -280,7 +279,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -390,12 +389,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -488,7 +487,7 @@ jobs: shell: powershell - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -585,19 +584,19 @@ jobs: create-package-rpm: needs: build-fullagent-msi - if: ${{ github.event.release }} || github.event_name == 'workflow_dispatch' + if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }} name: Create RPM Package runs-on: ubuntu-22.04 steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -653,19 +652,19 @@ jobs: create-package-deb: needs: build-fullagent-msi - if: ${{ github.event.release }} || github.event_name == 'workflow_dispatch' + if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }} name: Create Debian package runs-on: ubuntu-22.04 steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -707,13 +706,13 @@ jobs: run-artifactbuilder: needs: [create-package-rpm, create-package-deb] - if: ${{ github.event.release }} || github.event_name == 'workflow_dispatch' + if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }} name: Run ArtifactBuilder runs-on: windows-2022 steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -741,12 +740,6 @@ jobs: name: rpm-build-artifacts path: src/_build/CoreArtifacts - - name: Download NewRelic.NuGetHelper - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 - with: - name: NewRelic.NuGetHelper - path: build/NewRelic.NuGetHelper/bin - - name: Download NewRelic.Agent.Extensions uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: @@ -759,6 +752,14 @@ jobs: name: NewRelic.OpenTracing.AmazonLambda.Tracer path: src/AwsLambda/AwsLambdaOpenTracer/bin/Release/netstandard2.0-ILRepacked + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1 + + - name: Build NewRelic.NuGetHelper + run: | + MSBuild.exe -restore -m -p:Configuration=Release ${{ github.workspace }}\build\NewRelic.NuGetHelper\NewRelic.NuGetHelper.csproj + shell: powershell + - name: Run ArtifactBuilder run: | ${{ github.workspace }}\build\package.ps1 -configuration Release -IncludeDownloadSite @@ -778,7 +779,7 @@ jobs: contents: write name: Build and Publish Multiverse Testing Suite needs: build-fullagent-msi - if: ${{ github.event.release }} + if: ${{ github.event.release || (github.event_name == 'workflow_dispatch' && github.event.inputs.build-for-release == 'true') }} uses: newrelic/newrelic-dotnet-agent/.github/workflows/multiverse_run.yml@main with: agentVersion: ${{ needs.build-fullagent-msi.outputs.agentVersion }} diff --git a/.github/workflows/assignproj.yml b/.github/workflows/assignproj.yml index 0f0596de9..b07c79234 100644 --- a/.github/workflows/assignproj.yml +++ b/.github/workflows/assignproj.yml @@ -16,7 +16,7 @@ jobs: name: Assign to One Project steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit diff --git a/.github/workflows/awslambda_release.yml b/.github/workflows/awslambda_release.yml index d12e01d4c..c01101d90 100644 --- a/.github/workflows/awslambda_release.yml +++ b/.github/workflows/awslambda_release.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -109,7 +109,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 diff --git a/.github/workflows/build_download_site_index_files.yml b/.github/workflows/build_download_site_index_files.yml index 53602ddc7..37f209c97 100644 --- a/.github/workflows/build_download_site_index_files.yml +++ b/.github/workflows/build_download_site_index_files.yml @@ -55,7 +55,7 @@ jobs: image: ghcr.io/newrelic/s3indexer steps: - name: Login to AWS - uses: aws-actions/configure-aws-credentials@e1e17a757e536f70e52b5a12b2e8d1d1c60e04ef # v2.0.0 + uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 with: aws-region: ${{ inputs.aws-region }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} diff --git a/.github/workflows/build_profiler.yml b/.github/workflows/build_profiler.yml index 0c4863753..0b075940b 100644 --- a/.github/workflows/build_profiler.yml +++ b/.github/workflows/build_profiler.yml @@ -47,13 +47,13 @@ jobs: profiler_src: ${{ steps.filter.outputs.profiler_src }} steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -80,7 +80,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -136,7 +136,7 @@ jobs: # egress-policy: audit - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -183,12 +183,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -263,7 +263,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -325,7 +325,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit @@ -334,7 +334,7 @@ jobs: sudo apt-get install -y xmlstarlet - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -348,7 +348,7 @@ jobs: rm -f ${{ github.workspace }}/src/Agent/NewRelic/Home/_temp - name: Create Pull Request - uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 # v5.0.0 + uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2 with: commit-message: "chore: Update Profiler NuGet Package Reference to v${{ needs.package-and-deploy.outputs.package_version }}." title: "chore: Update Profiler NuGet Package Reference to v${{ needs.package-and-deploy.outputs.package_version }}" diff --git a/.github/workflows/deploy_agent.yml b/.github/workflows/deploy_agent.yml index 7e2611db1..066133a19 100644 --- a/.github/workflows/deploy_agent.yml +++ b/.github/workflows/deploy_agent.yml @@ -48,7 +48,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit @@ -247,7 +247,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit diff --git a/.github/workflows/deploy_awslambda.yml b/.github/workflows/deploy_awslambda.yml index 300a39858..ebc5e6b68 100644 --- a/.github/workflows/deploy_awslambda.yml +++ b/.github/workflows/deploy_awslambda.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit diff --git a/.github/workflows/deploy_siteextension.yml b/.github/workflows/deploy_siteextension.yml index 6df8dd02e..be2e72734 100644 --- a/.github/workflows/deploy_siteextension.yml +++ b/.github/workflows/deploy_siteextension.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit diff --git a/.github/workflows/get_release_checksums.yml b/.github/workflows/get_release_checksums.yml index 4d9396bf9..33b5a6b94 100644 --- a/.github/workflows/get_release_checksums.yml +++ b/.github/workflows/get_release_checksums.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit diff --git a/.github/workflows/markdowncheck.yml b/.github/workflows/markdowncheck.yml index 77f85a3e1..16959319d 100644 --- a/.github/workflows/markdowncheck.yml +++ b/.github/workflows/markdowncheck.yml @@ -16,11 +16,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit # Leave it audit mode - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1 with: diff --git a/.github/workflows/multiverse_run.yml b/.github/workflows/multiverse_run.yml index 0b71ed731..805ffb9dd 100644 --- a/.github/workflows/multiverse_run.yml +++ b/.github/workflows/multiverse_run.yml @@ -36,12 +36,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -62,7 +62,7 @@ jobs: shell: bash - name: Setup .NET Core 3.1.100 - uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3 + uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 with: dotnet-version: '3.1.100' @@ -102,17 +102,17 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit - name: Setup .NET Core 3.1.100 - uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3 + uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 with: dotnet-version: '3.1.100' - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: ref: 'gh-pages' fetch-depth: 0 @@ -131,7 +131,7 @@ jobs: shell: bash - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6 # 4.4.1 + uses: JamesIves/github-pages-deploy-action@a1ea191d508feb8485aceba848389d49f80ca2dc # 4.4.3 with: branch: gh-pages folder: . diff --git a/.github/workflows/nuget_slack_notifications.yml b/.github/workflows/nuget_slack_notifications.yml index f130251ea..a88bb66f3 100644 --- a/.github/workflows/nuget_slack_notifications.yml +++ b/.github/workflows/nuget_slack_notifications.yml @@ -10,7 +10,7 @@ on: default: "1" type: string testMode: - description: "If checked, no notification message will be sent to the team channel." + description: "If checked, no notification message will be sent to the team channel, nor will any Github issues be created." type: boolean default: false @@ -24,6 +24,8 @@ jobs: nuget-slack-notifications: name: Check for core technology package updates runs-on: ubuntu-latest + permissions: + issues: write continue-on-error: false env: @@ -31,12 +33,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit # Leave it audit mode - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -60,6 +62,7 @@ jobs: env: DOTTY_WEBHOOK: ${{ secrets.SLACK_NUGET_NOTIFICATIONS_WEBHOOK }} + DOTTY_TOKEN: ${{ secrets.GITHUB_TOKEN }} CORECLR_ENABLE_PROFILING: 1 CORECLR_NEWRELIC_HOME: ${{ env.scan-tool-path }}/bin/Debug/net6.0/newrelic CORECLR_PROFILER: "{36032161-FFC0-4B61-B559-F6C5D41BAE5A}" @@ -68,15 +71,20 @@ jobs: NEW_RELIC_HOST: staging-collector.newrelic.com NEW_RELIC_LICENSE_KEY: ${{ secrets.STAGING_LICENSE_KEY }} nugets: - "system.data.sqlclient + "elasticsearch.net + elastic.clients.elasticsearch + log4net + microsoft.extensions.logging microsoft.data.sqlclient - mongocsharpdriver + microsoft.net.http mongodb.driver mysql.data mysqlconnector - stackexchange.redis + nest + nlog rabbitmq.client - microsoft.net.http restsharp - serilog" + serilog + stackexchange.redis + system.data.sqlclient" \ No newline at end of file diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index bbbc12823..8494b8415 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -14,12 +14,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit - - uses: google-github-actions/release-please-action@c078ea33917ab8cfa5300e48f4b7e6b16606aede # v3.7.8 + - uses: google-github-actions/release-please-action@8016a6649226f2ec88ed05441c11bb5410a22d29 # v3.7.10 with: release-type: go changelog-path: src/Agent/CHANGELOG.md diff --git a/.github/workflows/repolinter.yml b/.github/workflows/repolinter.yml index cce1c1c9d..5a690900c 100644 --- a/.github/workflows/repolinter.yml +++ b/.github/workflows/repolinter.yml @@ -16,6 +16,7 @@ concurrency: permissions: contents: read + issues: write jobs: repolint: @@ -23,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit @@ -37,7 +38,7 @@ jobs: return data.data && data.data.default_branch === context.ref.split('/').slice(-1)[0] - name: Checkout Self if: ${{ steps.default-branch.outputs.result == 'true' }} - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Run Repolinter if: ${{ steps.default-branch.outputs.result == 'true' }} uses: newrelic/repolinter-action@3f4448f855c351e9695b24524a4111c7847b84cb # v1.7.0 diff --git a/.github/workflows/run_integration_tests.yml b/.github/workflows/run_integration_tests.yml index 1eef8bd9b..a57101a80 100644 --- a/.github/workflows/run_integration_tests.yml +++ b/.github/workflows/run_integration_tests.yml @@ -44,7 +44,7 @@ jobs: unbounded-tests-matrix: ${{ steps.configure_unbounded_tests_matrix.outputs.matrix }} steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: egress-policy: audit @@ -98,7 +98,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 @@ -226,7 +226,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 diff --git a/.github/workflows/run_unit_tests.yml b/.github/workflows/run_unit_tests.yml index e2d15937b..ed209ae83 100644 --- a/.github/workflows/run_unit_tests.yml +++ b/.github/workflows/run_unit_tests.yml @@ -30,12 +30,12 @@ jobs: test_results_path: tests\TestResults steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3 + uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 with: dotnet-version: 7.x dotnet-quality: 'ga' diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index c1af9f45c..c7d58ef19 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,18 +32,18 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit - name: "Checkout code" - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3 + uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0 with: results_file: results.sarif results_format: sarif @@ -73,6 +73,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3 + uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4 with: sarif_file: results.sarif diff --git a/.github/workflows/scripts/nugetSlackNotifications/Program.cs b/.github/workflows/scripts/nugetSlackNotifications/Program.cs index ed9594c97..c236d6e6f 100644 --- a/.github/workflows/scripts/nugetSlackNotifications/Program.cs +++ b/.github/workflows/scripts/nugetSlackNotifications/Program.cs @@ -1,6 +1,6 @@ using NewRelic.Api.Agent; +using Octokit; using Serilog; -using Serilog.Core; using System; using System.Collections.Generic; using System.Net; @@ -23,6 +23,7 @@ public class Program private static readonly int _daysToSearch = int.TryParse(Environment.GetEnvironmentVariable("DOTTY_DAYS_TO_SEARCH"), out var days) ? days : 1; // How many days of package release history to scan for changes private static readonly bool _testMode = bool.TryParse(Environment.GetEnvironmentVariable("DOTTY_TEST_MODE"), out var testMode) ? testMode : false; private static readonly string? _webhook = Environment.GetEnvironmentVariable("DOTTY_WEBHOOK"); + private static readonly string? _githubToken = Environment.GetEnvironmentVariable("DOTTY_TOKEN"); static async Task Main(string[] args) @@ -43,6 +44,7 @@ static async Task Main(string[] args) } await AlertOnNewVersions(); + await CreateGithubIssuesForNewVersions(); } @@ -117,6 +119,30 @@ static async Task AlertOnNewVersions() } } + [Transaction] + static async Task CreateGithubIssuesForNewVersions() + { + + if (_newVersions.Count > 0 && _githubToken != null && !_testMode) // only message channel if there's package updates to report AND we have a GH token from the environment AND we're not in test mode + { + var ghClient = new GitHubClient(new ProductHeaderValue("Dotty-Robot")); + var tokenAuth = new Credentials(_githubToken); + ghClient.Credentials = tokenAuth; + foreach (var versionData in _newVersions) + { + var newIssue = new NewIssue($"Dotty: update tests for {versionData.PackageName} from {versionData.OldVersion} to {versionData.NewVersion}"); + newIssue.Body = versionData.Url; + newIssue.Labels.Add("testing"); + newIssue.Labels.Add("Core Technologies"); + var issue = await ghClient.Issue.Create("newrelic", "newrelic-dotnet-agent", newIssue); + } + } + else + { + Log.Information($"Issues will not be created: # of new versions={_newVersions.Count}, token available={_webhook != null}, test mode={_testMode}"); + } + } + [Trace] static async Task SendSlackNotification(string msg) { diff --git a/.github/workflows/scripts/nugetSlackNotifications/nugetSlackNotifications.csproj b/.github/workflows/scripts/nugetSlackNotifications/nugetSlackNotifications.csproj index 8e73d5e0a..b4eaccd11 100644 --- a/.github/workflows/scripts/nugetSlackNotifications/nugetSlackNotifications.csproj +++ b/.github/workflows/scripts/nugetSlackNotifications/nugetSlackNotifications.csproj @@ -9,6 +9,7 @@ + diff --git a/.github/workflows/set_community_label.yml b/.github/workflows/set_community_label.yml index ddb502175..c45fb84ac 100644 --- a/.github/workflows/set_community_label.yml +++ b/.github/workflows/set_community_label.yml @@ -15,7 +15,7 @@ jobs: issues: write steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit diff --git a/.github/workflows/siteextension_release.yml b/.github/workflows/siteextension_release.yml index a456dd88a..dd5628ee2 100644 --- a/.github/workflows/siteextension_release.yml +++ b/.github/workflows/siteextension_release.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 diff --git a/.github/workflows/stale_issue.yml b/.github/workflows/stale_issue.yml index 549366a93..06a7de4ce 100644 --- a/.github/workflows/stale_issue.yml +++ b/.github/workflows/stale_issue.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit diff --git a/.github/workflows/stale_pr.yml b/.github/workflows/stale_pr.yml index ec86d5cbc..7d9c99cf9 100644 --- a/.github/workflows/stale_pr.yml +++ b/.github/workflows/stale_pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@55d479fb1c5bcad5a4f9099a5d9f37c8857b2845 # v2.4.1 with: disable-sudo: true egress-policy: audit diff --git a/FullAgent.sln b/FullAgent.sln index 82612dd1a..b7060abb1 100644 --- a/FullAgent.sln +++ b/FullAgent.sln @@ -206,8 +206,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLogLogging", "src\Agent\Ne EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackExchangeRedis2Plus", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\StackExchangeRedis2Plus\StackExchangeRedis2Plus.csproj", "{EC34F023-223D-432F-9401-9C3ED1B75DE4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewRelic.NuGetHelper", "build\NewRelic.NuGetHelper\NewRelic.NuGetHelper.csproj", "{94BF8D27-2122-4573-AA79-90B977B40EF3}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elasticsearch", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Elasticsearch\Elasticsearch.csproj", "{D9428449-3E4B-4723-A8AA-1191315C7AAD}" EndProject Global @@ -436,10 +434,6 @@ Global {EC34F023-223D-432F-9401-9C3ED1B75DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC34F023-223D-432F-9401-9C3ED1B75DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC34F023-223D-432F-9401-9C3ED1B75DE4}.Release|Any CPU.Build.0 = Release|Any CPU - {94BF8D27-2122-4573-AA79-90B977B40EF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94BF8D27-2122-4573-AA79-90B977B40EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94BF8D27-2122-4573-AA79-90B977B40EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94BF8D27-2122-4573-AA79-90B977B40EF3}.Release|Any CPU.Build.0 = Release|Any CPU {D9428449-3E4B-4723-A8AA-1191315C7AAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D9428449-3E4B-4723-A8AA-1191315C7AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU {D9428449-3E4B-4723-A8AA-1191315C7AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -512,7 +506,6 @@ Global {2E6CF650-CB50-453D-830A-D00F0540FC2C} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} {3D69B4C9-FD16-461F-95AF-6FCA6EAA914E} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} {EC34F023-223D-432F-9401-9C3ED1B75DE4} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} - {94BF8D27-2122-4573-AA79-90B977B40EF3} = {C0BB7A5D-6820-4058-AC47-0111ECC34015} {D9428449-3E4B-4723-A8AA-1191315C7AAD} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/README.md b/README.md index d37389222..6b9431a21 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ New Relic Open Source community plus project banner. +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![build_status](https://github.com/newrelic/newrelic-dotnet-agent/actions/workflows/all_solutions.yml/badge.svg?event=schedule)](https://github.com/newrelic/newrelic-dotnet-agent/actions/workflows/all_solutions.yml) +[![codecov](https://codecov.io/gh/newrelic/newrelic-dotnet-agent/branch/main/graph/badge.svg?token=VKV9XDVJ2U)](https://codecov.io/gh/newrelic/newrelic-dotnet-agent) +[![OpenSSF +Scorecard](https://api.securityscorecards.dev/projects/github.com/newrelic/newrelic-dotnet-agent/badge)](https://api.securityscorecards.dev/projects/github.com/newrelic/newrelic-dotnet-agent) # New Relic Monitoring for .NET #### .NET Agent @@ -38,7 +43,7 @@ If the issue has been confirmed as a bug or is a Feature request, please file a **Support Channels** * [New Relic Documentation](https://docs.newrelic.com/docs/agents/net-agent): Comprehensive guidance for using our agent -* [New Relic Community](https://discuss.newrelic.com/tags/c/full-stack-observability/agents/466/dotnetagent): The best place to engage in troubleshooting questions +* [New Relic Community](https://forum.newrelic.com/): The best place to engage in troubleshooting questions * [New Relic Developer](https://developer.newrelic.com/): Resources for building a custom observability applications * [New Relic University](https://learn.newrelic.com/): A range of online training for New Relic users of every level * [New Relic Technical Support](https://support.newrelic.com/) 24/7/365 ticketed support. Read more about our [Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/global-technical-support-offerings/). diff --git a/build/ArtifactBuilder/AgentComponents.cs b/build/ArtifactBuilder/AgentComponents.cs index 3712239df..b8d1cede7 100644 --- a/build/ArtifactBuilder/AgentComponents.cs +++ b/build/ArtifactBuilder/AgentComponents.cs @@ -100,6 +100,23 @@ public string SemanticVersion } } + // This will return the full four-component version string, unless the fourth component is zero, in which case it returns three components + public string MaybeSemanticVersion + { + get + { + var version = new Version(Version); + if (version.Revision == 0) + { + return version.ToString(3); + } + else + { + return version.ToString(4); + } + } + } + protected abstract string SourceHomeBuilderPath { get; } protected abstract List IgnoredHomeBuilderFiles { get; } protected abstract void CreateAgentComponents(); diff --git a/build/ArtifactBuilder/Artifacts/AzureSiteExtension.cs b/build/ArtifactBuilder/Artifacts/AzureSiteExtension.cs index 0ed6501ac..b6a6e1660 100644 --- a/build/ArtifactBuilder/Artifacts/AzureSiteExtension.cs +++ b/build/ArtifactBuilder/Artifacts/AzureSiteExtension.cs @@ -11,6 +11,7 @@ public class AzureSiteExtension : Artifact private const string NuGetHelperLibraryName = "NewRelic.NuGetHelper.dll"; private string _version; + private string _nuGetPackageName; public AzureSiteExtension() : base(nameof(AzureSiteExtension)) { @@ -26,7 +27,7 @@ protected override void InternalBuild() package.CopyToContent($@"{RepoRootDirectory}\build\NewRelic.NuGetHelper\bin\{NuGetLibraryName}"); package.CopyToContent($@"{RepoRootDirectory}\build\NewRelic.NuGetHelper\bin\{XmlLibraryName}"); package.SetVersion(_version); - package.Pack(); + _nuGetPackageName = package.Pack(); } private string ReadVersionFromFile() @@ -68,8 +69,11 @@ private void ValidateContent() private string Unpack() { + if (string.IsNullOrEmpty(_nuGetPackageName)) + throw new PackagingException("NuGet package name not found. Did you call InternalBuild()?"); + var unpackDir = Path.Join(OutputDirectory, "unpacked"); - var nugetFile = Path.Join(OutputDirectory, $"NewRelic.Azure.WebSites.Extension.{_version}.nupkg"); + var nugetFile = Path.Join(OutputDirectory, _nuGetPackageName); NuGetHelpers.Unpack(nugetFile, unpackDir); return unpackDir; } diff --git a/build/ArtifactBuilder/Artifacts/MsiInstaller.cs b/build/ArtifactBuilder/Artifacts/MsiInstaller.cs index 5b6a8e78c..2a3ed1dd3 100644 --- a/build/ArtifactBuilder/Artifacts/MsiInstaller.cs +++ b/build/ArtifactBuilder/Artifacts/MsiInstaller.cs @@ -50,6 +50,8 @@ protected override void InternalBuild() if (TryGetMsiPath(out var msiPath)) { + ValidateCodeSigningCertificate(msiPath); + FileHelpers.CopyFile(msiPath, OutputDirectory); File.WriteAllText($@"{OutputDirectory}\checksum.sha256", FileHelpers.GetSha256Checksum(msiPath)); } @@ -399,5 +401,10 @@ private SortedSet GetExpectedComponents(string installedFilesRoot) return expectedComponents; } + private void ValidateCodeSigningCertificate(string msiPath) + { + if (!SecurityHelpers.VerifyEmbeddedSignature(msiPath, out var errorMessage)) + throw new PackagingException($"Code signing certificate is not valid. {errorMessage}"); + } } } diff --git a/build/ArtifactBuilder/Artifacts/NugetAgent.cs b/build/ArtifactBuilder/Artifacts/NugetAgent.cs index 3713e9f2c..a6fee5c1e 100644 --- a/build/ArtifactBuilder/Artifacts/NugetAgent.cs +++ b/build/ArtifactBuilder/Artifacts/NugetAgent.cs @@ -11,6 +11,7 @@ public class NugetAgent : Artifact private readonly AgentComponents _coreAgentComponents; private readonly AgentComponents _coreAgentArm64Components; private readonly AgentComponents _coreAgentX86Components; + private string _nuGetPackageName; public NugetAgent(string configuration) : base(nameof(NugetAgent)) @@ -76,7 +77,7 @@ protected override void InternalBuild() package.SetVersion(_frameworkAgentComponents.Version); - package.Pack(); + _nuGetPackageName = package.Pack(); } private static void TransformNewRelicConfig(string newRelicConfigPath) @@ -120,8 +121,11 @@ private void ValidateContent() private string Unpack() { + if (string.IsNullOrEmpty(_nuGetPackageName)) + throw new PackagingException("NuGet package name not found. Did you call InternalBuild()?"); + var unpackDir = Path.Join(OutputDirectory, "unpacked"); - var nugetFile = Path.Join(OutputDirectory, $"NewRelic.Agent.{_frameworkAgentComponents.Version}.nupkg"); + var nugetFile = Path.Join(OutputDirectory, _nuGetPackageName); NuGetHelpers.Unpack(nugetFile, unpackDir); return unpackDir; } diff --git a/build/ArtifactBuilder/Artifacts/NugetAgentApi.cs b/build/ArtifactBuilder/Artifacts/NugetAgentApi.cs index 26883fc5f..bbdb6d1fc 100644 --- a/build/ArtifactBuilder/Artifacts/NugetAgentApi.cs +++ b/build/ArtifactBuilder/Artifacts/NugetAgentApi.cs @@ -9,6 +9,7 @@ public class NugetAgentApi : Artifact { private readonly AgentComponents _frameworkAgentComponents; private readonly AgentComponents _coreAgentComponents; + private string _nuGetPackageName; public NugetAgentApi(string configuration) : base(nameof(NugetAgentApi)) @@ -34,7 +35,7 @@ protected override void InternalBuild() package.CopyToRoot(_frameworkAgentComponents.NewRelicLicenseFile); package.CopyToRoot(_frameworkAgentComponents.NewRelicThirdPartyNoticesFile); package.SetVersion(_frameworkAgentComponents.Version); - package.Pack(); + _nuGetPackageName = package.Pack(); } private void ValidateContent() @@ -52,8 +53,11 @@ private void ValidateContent() private string Unpack() { + if (string.IsNullOrEmpty(_nuGetPackageName)) + throw new PackagingException("NuGet package name not found. Did you call InternalBuild()?"); + var unpackDir = Path.Join(OutputDirectory, "unpacked"); - var nugetFile = Path.Join(OutputDirectory, $"NewRelic.Agent.Api.{_frameworkAgentComponents.Version}.nupkg"); + var nugetFile = Path.Join(OutputDirectory, _nuGetPackageName); NuGetHelpers.Unpack(nugetFile, unpackDir); return unpackDir; } diff --git a/build/ArtifactBuilder/Artifacts/NugetAzureCloudServices.cs b/build/ArtifactBuilder/Artifacts/NugetAzureCloudServices.cs index d19863617..4c8d5587e 100644 --- a/build/ArtifactBuilder/Artifacts/NugetAzureCloudServices.cs +++ b/build/ArtifactBuilder/Artifacts/NugetAzureCloudServices.cs @@ -7,6 +7,7 @@ namespace ArtifactBuilder.Artifacts public class NugetAzureCloudServices : Artifact { private readonly AgentComponents _frameworkAgentComponents; + private string _nuGetPackageName; public NugetAzureCloudServices(string configuration) : base(nameof(NugetAzureCloudServices)) @@ -29,7 +30,7 @@ protected override void InternalBuild() package.CopyToLib(_frameworkAgentComponents.AgentApiDll); package.CopyToContent($@"{RepoRootDirectory}\src\_build\x64-{Configuration}\Installer\{GetMsiName()}"); package.SetVersion(_frameworkAgentComponents.Version); - package.Pack(); + _nuGetPackageName = package.Pack(); } private void DoInstallerReplacements(string agentInstaller) @@ -77,8 +78,11 @@ private void ValidateContent() private string Unpack() { + if (string.IsNullOrEmpty(_nuGetPackageName)) + throw new PackagingException("NuGet package name not found. Did you call InternalBuild()?"); + var unpackDir = Path.Join(OutputDirectory, "unpacked"); - var nugetFile = Path.Join(OutputDirectory, $"NewRelicWindowsAzure.{_frameworkAgentComponents.Version}.nupkg"); + var nugetFile = Path.Join(OutputDirectory, _nuGetPackageName); NuGetHelpers.Unpack(nugetFile, unpackDir); return unpackDir; } diff --git a/build/ArtifactBuilder/Artifacts/NugetAzureWebSites.cs b/build/ArtifactBuilder/Artifacts/NugetAzureWebSites.cs index 80170c405..09e938687 100644 --- a/build/ArtifactBuilder/Artifacts/NugetAzureWebSites.cs +++ b/build/ArtifactBuilder/Artifacts/NugetAzureWebSites.cs @@ -19,6 +19,7 @@ public NugetAzureWebSites(string platform, string configuration) public string Configuration { get; } public string Platform { get; } private readonly AgentComponents _frameworkAgentComponents; + private string _nuGetPackageName; private string RootDirectory => $@"{StagingDirectory}\content\newrelic"; @@ -38,7 +39,7 @@ protected override void InternalBuild() agentInfo.WriteToDisk(RootDirectory); package.SetVersion(_frameworkAgentComponents.Version); - package.Pack(); + _nuGetPackageName = package.Pack(); } private void TransformNewRelicConfig() @@ -85,13 +86,12 @@ private void ValidateContent() private string Unpack() { + if (string.IsNullOrEmpty(_nuGetPackageName)) + throw new PackagingException("NuGet package name not found. Did you call InternalBuild()?"); + var unpackDir = Path.Join(OutputDirectory, "unpacked"); - // The two packages have different naming conventions - var fileName = Platform == "x64" ? - $"NewRelic.Azure.WebSites.{Platform}.{_frameworkAgentComponents.Version}.nupkg" : - $"NewRelic.Azure.WebSites.{_frameworkAgentComponents.Version}.nupkg"; - var nugetFile = Path.Join(OutputDirectory, fileName); + var nugetFile = Path.Join(OutputDirectory, _nuGetPackageName); NuGetHelpers.Unpack(nugetFile, unpackDir); return unpackDir; diff --git a/build/ArtifactBuilder/NuGetHelpers.cs b/build/ArtifactBuilder/NuGetHelpers.cs index a78a0e91a..1b2879f75 100644 --- a/build/ArtifactBuilder/NuGetHelpers.cs +++ b/build/ArtifactBuilder/NuGetHelpers.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Text.RegularExpressions; namespace ArtifactBuilder { @@ -8,10 +9,20 @@ public static class NuGetHelpers { private static readonly string _nugetPath = Path.Combine(FileHelpers.GetRepoRootDirectory(), @"Build\Tools\nuget.exe"); - public static void Pack(string nuspecFilePath, string outputDirectory) + public static string Pack(string nuspecFilePath, string outputDirectory) { var parameters = $@"Pack -NoPackageAnalysis ""{nuspecFilePath}"" -OutputDirectory ""{outputDirectory}"""; - var process = Process.Start(_nugetPath, parameters); + var process = new Process(); + + var startInfo = new ProcessStartInfo + { + FileName = _nugetPath, + Arguments = parameters, + RedirectStandardOutput = true + }; + process.StartInfo = startInfo; + + process.Start(); process.WaitForExit(30000); if (!process.HasExited) { @@ -22,6 +33,30 @@ public static void Pack(string nuspecFilePath, string outputDirectory) { throw new Exception($"Nuget pack failed with exit code {process.ExitCode}."); } + + var stdOut = process.StandardOutput; + var output = stdOut.ReadToEnd(); + Console.WriteLine(output); + + // output is expected to look like: + // Attempting to build package from 'NewRelic.Azure.WebSites.x64.nuspec'. + // Successfully created package 'C:\Source\Repos\newrelic-dotnet-agent\Build\BuildArtifacts\NugetAzureWebSites-x64\NewRelic.Azure.WebSites.x64.10.11.0.nupkg'. + + // capture the full path + var regex = ".*Successfully created package '(.*)'.*"; + var matches = Regex.Match(output, regex); + if (matches.Success && matches.Groups.Count > 1) + { + var packagePath = matches.Groups[1].Value; + + // verify the file exists, then return the filename + if (File.Exists(packagePath)) + { + return Path.GetFileName(packagePath); + } + } + + throw new PackagingException("Failed to parse NuGet package filename."); } public static void Unpack(string nupkgFile, string outputDirectory) diff --git a/build/ArtifactBuilder/NugetPackage.cs b/build/ArtifactBuilder/NugetPackage.cs index 1d72eb335..dbd22c550 100644 --- a/build/ArtifactBuilder/NugetPackage.cs +++ b/build/ArtifactBuilder/NugetPackage.cs @@ -98,9 +98,9 @@ public void CopyToRoot(IEnumerable filePaths, string subDirectory = null FileHelpers.CopyFile(filePaths, $@"{StagingDirectory}\{subDirectory}"); } - public void Pack() + public string Pack() { - NuGetHelpers.Pack(NuspecFilePath, OutputDirectory); + return NuGetHelpers.Pack(NuspecFilePath, OutputDirectory); } } } diff --git a/build/ArtifactBuilder/SecurityHelpers.cs b/build/ArtifactBuilder/SecurityHelpers.cs new file mode 100644 index 000000000..1bb3682e4 --- /dev/null +++ b/build/ArtifactBuilder/SecurityHelpers.cs @@ -0,0 +1,226 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace ArtifactBuilder +{ + // based on http://www.pinvoke.net/default.aspx/wintrust.winverifytrust + + #region WinTrustData struct field enums + enum WinTrustDataUIChoice : uint + { + All = 1, + None = 2, + NoBad = 3, + NoGood = 4 + } + + enum WinTrustDataRevocationChecks : uint + { + None = 0x00000000, + WholeChain = 0x00000001 + } + + enum WinTrustDataChoice : uint + { + File = 1, + Catalog = 2, + Blob = 3, + Signer = 4, + Certificate = 5 + } + + enum WinTrustDataStateAction : uint + { + Ignore = 0x00000000, + Verify = 0x00000001, + Close = 0x00000002, + AutoCache = 0x00000003, + AutoCacheFlush = 0x00000004 + } + + [FlagsAttribute] + enum WinTrustDataProvFlags : uint + { + UseIe4TrustFlag = 0x00000001, + NoIe4ChainFlag = 0x00000002, + NoPolicyUsageFlag = 0x00000004, + RevocationCheckNone = 0x00000010, + RevocationCheckEndCert = 0x00000020, + RevocationCheckChain = 0x00000040, + RevocationCheckChainExcludeRoot = 0x00000080, + SaferFlag = 0x00000100, // Used by software restriction policies. Should not be used. + HashOnlyFlag = 0x00000200, + UseDefaultOsverCheck = 0x00000400, + LifetimeSigningFlag = 0x00000800, + CacheOnlyUrlRetrieval = 0x00001000, // affects CRL retrieval and AIA retrieval + DisableMD2andMD4 = 0x00002000 // Win7 SP1+: Disallows use of MD2 or MD4 in the chain except for the root + } + + enum WinTrustDataUIContext : uint + { + Execute = 0, + Install = 1 + } + #endregion + + #region WinTrust structures + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + class WinTrustFileInfo + { + UInt32 StructSize = (UInt32)Marshal.SizeOf(typeof(WinTrustFileInfo)); + IntPtr pszFilePath; // required, file name to be verified + IntPtr hFile = IntPtr.Zero; // optional, open handle to FilePath + IntPtr pgKnownSubject = IntPtr.Zero; // optional, subject type if it is known + + public WinTrustFileInfo(String _filePath) + { + pszFilePath = Marshal.StringToCoTaskMemAuto(_filePath); + } + public void Dispose() + { + if (pszFilePath != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(pszFilePath); + pszFilePath = IntPtr.Zero; + } + } + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + class WinTrustData + { + UInt32 StructSize = (UInt32)Marshal.SizeOf(typeof(WinTrustData)); + IntPtr PolicyCallbackData = IntPtr.Zero; + IntPtr SIPClientData = IntPtr.Zero; + // required: UI choice + WinTrustDataUIChoice UIChoice = WinTrustDataUIChoice.None; + // required: certificate revocation check options + WinTrustDataRevocationChecks RevocationChecks = WinTrustDataRevocationChecks.None; + // required: which structure is being passed in? + WinTrustDataChoice UnionChoice = WinTrustDataChoice.File; + // individual file + IntPtr FileInfoPtr; + WinTrustDataStateAction StateAction = WinTrustDataStateAction.Ignore; + IntPtr StateData = IntPtr.Zero; + String URLReference = null; + WinTrustDataProvFlags ProvFlags = WinTrustDataProvFlags.RevocationCheckChainExcludeRoot; + WinTrustDataUIContext UIContext = WinTrustDataUIContext.Execute; + + // constructor for silent WinTrustDataChoice.File check + public WinTrustData(WinTrustFileInfo _fileInfo) + { + // On Win7SP1+, don't allow MD2 or MD4 signatures + if ((Environment.OSVersion.Version.Major > 6) || + ((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor > 1)) || + ((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor == 1) && !String.IsNullOrEmpty(Environment.OSVersion.ServicePack))) + { + ProvFlags |= WinTrustDataProvFlags.DisableMD2andMD4; + } + + var wtfiData = _fileInfo; + FileInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(WinTrustFileInfo))); + Marshal.StructureToPtr(wtfiData, FileInfoPtr, false); + } + public void Dispose() + { + if (FileInfoPtr != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(FileInfoPtr); + FileInfoPtr = IntPtr.Zero; + } + } + } + #endregion + + enum WinVerifyTrustResult : uint + { + Success = 0, + ProviderUnknown = 0x800b0001, // Trust provider is not recognized on this system + ActionUnknown = 0x800b0002, // Trust provider does not support the specified action + SubjectFormUnknown = 0x800b0003, // Trust provider does not support the form specified for the subject + SubjectNotTrusted = 0x800b0004, // Subject failed the specified verification action + FileNotSigned = 0x800B0100, // TRUST_E_NOSIGNATURE - File was not signed + SubjectExplicitlyDistrusted = 0x800B0111, // Signer's certificate is in the Untrusted Publishers store + SignatureOrFileCorrupt = 0x80096010, // TRUST_E_BAD_DIGEST - file was probably corrupt + SubjectCertExpired = 0x800B0101, // CERT_E_EXPIRED - Signer's certificate was expired + SubjectCertificateRevoked = 0x800B010C, // CERT_E_REVOKED Subject's certificate was revoked + UntrustedRoot = 0x800B0109 // CERT_E_UNTRUSTEDROOT - A certification chain processed correctly but terminated in a root certificate that is not trusted by the trust provider. + } + + static class SecurityHelpers + { + private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + // GUID of the action to perform + private const string WINTRUST_ACTION_GENERIC_VERIFY_V2 = "{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}"; + + [DllImport("wintrust.dll", ExactSpelling = true, SetLastError = false, CharSet = CharSet.Unicode)] + static extern WinVerifyTrustResult WinVerifyTrust( + [In] IntPtr hwnd, + [In][MarshalAs(UnmanagedType.LPStruct)] Guid pgActionID, + [In] WinTrustData pWVTData + ); + + const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100; + const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; + const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; + + [DllImport("kernel32.dll", CharSet=CharSet.Unicode)] + static extern uint FormatMessage ( + uint dwFlags, IntPtr lpSource, + uint dwMessageId, uint dwLanguageId, + [Out] StringBuilder lpBuffer, + uint nSize, IntPtr lpArguments + ); + + public static bool VerifyEmbeddedSignature(string fileName, out string errorMessage) + { + WinTrustFileInfo winTrustFileInfo = null; + WinTrustData winTrustData = null; + + try + { + // specify the WinVerifyTrust function/action that we want + var action = new Guid(WINTRUST_ACTION_GENERIC_VERIFY_V2); + + // instantiate our WinTrustFileInfo and WinTrustData data structures + winTrustFileInfo = new WinTrustFileInfo(fileName); + winTrustData = new WinTrustData(winTrustFileInfo); + + var result = WinVerifyTrust(INVALID_HANDLE_VALUE, action, winTrustData); + if (result == WinVerifyTrustResult.Success) + { + errorMessage = null; + return true; + } + + var sb = new StringBuilder(1024); + var charCount = FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + IntPtr.Zero, (uint)result, 0, + sb, (uint)sb.Capacity, IntPtr.Zero); + + errorMessage = $"Error result: {result:X} - {sb.ToString(0, (int)charCount)}"; + + return false; + } + finally + { + // free the locally-held unmanaged memory in the data structures + winTrustFileInfo?.Dispose(); + winTrustData?.Dispose(); + } + } + + //public static bool CheckFile(string filename) + //{ + // // check digital signature + // var ret = WinTrust.VerifyEmbeddedSignature(filename); + // if (!ret) return false; + + // // do some other checks - for example verify the subject + // var cert = new X509Certificate2(filename); + // return cert.Verify() && cert.Subject == "foo"; + //} + } +} diff --git a/build/BuildTools.sln b/build/BuildTools.sln new file mode 100644 index 000000000..2789b312c --- /dev/null +++ b/build/BuildTools.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33723.286 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArtifactBuilder", "ArtifactBuilder\ArtifactBuilder.csproj", "{2FB32877-EF0C-4382-8D3C-F653F3C26A3A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewRelic.NuGetHelper", "NewRelic.NuGetHelper\NewRelic.NuGetHelper.csproj", "{94BF8D27-2122-4573-AA79-90B977B40EF3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2FB32877-EF0C-4382-8D3C-F653F3C26A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FB32877-EF0C-4382-8D3C-F653F3C26A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FB32877-EF0C-4382-8D3C-F653F3C26A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FB32877-EF0C-4382-8D3C-F653F3C26A3A}.Release|Any CPU.Build.0 = Release|Any CPU + {94BF8D27-2122-4573-AA79-90B977B40EF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94BF8D27-2122-4573-AA79-90B977B40EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94BF8D27-2122-4573-AA79-90B977B40EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94BF8D27-2122-4573-AA79-90B977B40EF3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BA603325-9FCE-400E-9852-C8D3E39A16C6} + EndGlobalSection +EndGlobal diff --git a/build/Packaging/NugetAzureCloudServices/content/newrelic.cmd b/build/Packaging/NugetAzureCloudServices/content/newrelic.cmd index 2a7cdfac7..9ade047d1 100644 --- a/build/Packaging/NugetAzureCloudServices/content/newrelic.cmd +++ b/build/Packaging/NugetAzureCloudServices/content/newrelic.cmd @@ -54,9 +54,9 @@ IF %NR_ERROR_LEVEL% EQU 0 ( :: if we are in a Worker Role then there is no need to restart W3SVC _or_ :: if we are emulating locally then do not restart W3SVC IF "%IsWorkerRole%" EQU "false" ( - ECHO Restarting IIS and W3SVC to pick up the new environment variables. >> "%RoleRoot%\nr-%NR_INSTALLID%.log" 2>&1 - IISRESET - NET START W3SVC + ECHO Restarting IIS to pick up the new environment variables. >> "%RoleRoot%\nr-%NR_INSTALLID%.log" 2>&1 + IISRESET /STOP + IISRESET /START ) IF %ERRORLEVEL% EQU 0 ( diff --git a/codecov.yml b/codecov.yml index 6b44a4e8a..3a022b7d0 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,23 +5,25 @@ # codecov: branch: main - require_ci_to_pass: no + require_ci_to_pass: false # codecov won't wait for all other CI status to pass before sending its status notify: - wait_for_ci: no + wait_for_ci: false coverage: + range: "80..100" # 80% or higher is green status: - project: + project: # project-level settings (i.e., main branch) default: - target: auto - threshold: 1% # code coverage can drop by 1% and still be successful - if_ci_failed: success - informational: true # if true, status will pass regardless regardless of other settings - patch: + target: auto # target coverage is based on current base commit + threshold: 0.5% # code coverage can drop by 0.5% below the base commit's coverage and still be successful + if_ci_failed: error + informational: false # if true, status will pass regardless regardless of other settings + patch: # pull request status default: target: auto - threshold: 1% # code coverage can drop by 1% and still be successful - if_ci_failed: success - informational: true # if true, status will pass regardless regardless of other settings + threshold: 80% # 80% of changed code in a PR must have code coverage for the CI to pass + if_ci_failed: error + informational: false # if true, status will pass regardless regardless of other settings + only_pulls: true # individual commits to a branch will not be considered, only pull requests comment: layout: "reach, diff, files" # change to "reach, diff, flags, files" if we start using flags behavior: default # new comment will be posted or existing comment will be edited diff --git a/deploy/linux/Dockerfile b/deploy/linux/Dockerfile index c009728ea..d61e61103 100644 --- a/deploy/linux/Dockerfile +++ b/deploy/linux/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:buster-20230202-slim +FROM debian:buster-20230703-slim@sha256:cddb688e1263b9752275b064171ef6ac9c70ae21a77c774339aecfb53690b9a1 RUN apt-get update && apt-get install -y \ apt-utils \ diff --git a/licenses/LICENSE.rtf b/licenses/LICENSE.rtf index 772933d7f..fbb3c971d 100644 --- a/licenses/LICENSE.rtf +++ b/licenses/LICENSE.rtf @@ -1,9 +1,9 @@ -{\rtf1\ansi\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Courier New;}} +{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}} {\colortbl ;\red0\green0\blue255;} -{\*\generator Riched20 10.0.19041}\viewkind4\uc1 -\pard\f0\fs22\lang1033 Apache License\par +{\*\generator Riched20 10.0.22621}\viewkind4\uc1 +\pard\fs16\lang1033 Apache License\par Version 2.0, January 2004\par - {{\field{\*\fldinst{HYPERLINK http://www.apache.org/licenses/ }}{\fldrslt{http://www.apache.org/licenses/\ul0\cf0}}}}\f0\fs22\par + {{\field{\*\fldinst{HYPERLINK http://www.apache.org/licenses/ }}{\fldrslt{http://www.apache.org/licenses/\ul0\cf0}}}}\f0\fs16\par \par TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\par \par @@ -195,7 +195,7 @@ you may not use this file except in compliance with the License.\par You may obtain a copy of the License at\par \par - {{\field{\*\fldinst{HYPERLINK http://www.apache.org/licenses/LICENSE-2.0 }}{\fldrslt{http://www.apache.org/licenses/LICENSE-2.0\ul0\cf0}}}}\f0\fs22\par + {{\field{\*\fldinst{HYPERLINK http://www.apache.org/licenses/LICENSE-2.0 }}{\fldrslt{http://www.apache.org/licenses/LICENSE-2.0\ul0\cf0}}}}\f0\fs16\par \par Unless required by applicable law or agreed to in writing, software\par distributed under the License is distributed on an "AS IS" BASIS,\par diff --git a/licenses/THIRD_PARTY_NOTICES.txt b/licenses/THIRD_PARTY_NOTICES.txt index c9bb0a468..145726033 100644 --- a/licenses/THIRD_PARTY_NOTICES.txt +++ b/licenses/THIRD_PARTY_NOTICES.txt @@ -96,213 +96,6 @@ SOFTWARE. ---------------------------------------------------------------- -This product includes 'Castle.Windsor' (http://www.castleproject.org/), which -is released under the following license(s): - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ----------------------------------------------------------------- - This product includes 'Dongle.Net' (https://github.com/webbers/dongle.net), which is released under the following license(s): diff --git a/src/Agent/CHANGELOG.md b/src/Agent/CHANGELOG.md index 6e7695801..460423bb5 100644 --- a/src/Agent/CHANGELOG.md +++ b/src/Agent/CHANGELOG.md @@ -4,6 +4,72 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [10.13.0](https://github.com/newrelic/newrelic-dotnet-agent/compare/v10.12.1...v10.13.0) (2023-07-14) + + +### Security + +* Update Grpc.Net.Client library to address Dependabot alerts. ([#1768](https://github.com/newrelic/newrelic-dotnet-agent/issues/1768)) ([#1769](https://github.com/newrelic/newrelic-dotnet-agent/issues/1769)) ([eee7564](https://github.com/newrelic/newrelic-dotnet-agent/commit/eee7564cbe79b653ad7909af36f09c9a64cdb731)) + + +### New Features + +* Add support for filtering log events based on a list of log levels so that they are not forwarded to New Relic. Also adds new logging metrics to count the total number of filtered log events (Logging/denied). Refer to our [application logging configuration](https://docs.newrelic.com/docs/apm/agents/net-agent/configuration/net-agent-configuration/#application_logging) documentation for more details. ([#1760](https://github.com/newrelic/newrelic-dotnet-agent/issues/1760)) ([#1761](https://github.com/newrelic/newrelic-dotnet-agent/issues/1761)) ([#1762](https://github.com/newrelic/newrelic-dotnet-agent/issues/1762)) ([#1766](https://github.com/newrelic/newrelic-dotnet-agent/issues/1766)) ([aadce3a](https://github.com/newrelic/newrelic-dotnet-agent/commit/aadce3a09f9fe3c77a93f557686f1ddc26fc6169)) +* Instrument OpenAsync() for SQL libraries. ([#1725](https://github.com/newrelic/newrelic-dotnet-agent/issues/1725)) ([a695ce6](https://github.com/newrelic/newrelic-dotnet-agent/commit/a695ce6de7e56bc3f803c9b9f6c8c09b30c106fd)) + + +### Fixes + +* Refactor StackExchange.Redis v2+ instrumentation to eliminate potential memory leaks. ([902b025](https://github.com/newrelic/newrelic-dotnet-agent/commit/902b025c8c420b8bc288b15d914b47aabc1bd426)) +* Remove invalid trailing comma added to W3C tracestate header. ([#1779](https://github.com/newrelic/newrelic-dotnet-agent/issues/1779)) ([790a3b7](https://github.com/newrelic/newrelic-dotnet-agent/commit/790a3b75dd7609d76638ea3625a9289f58b24378)) +* Update the MSI UI to clean up formatting and readability issues. ([#1748](https://github.com/newrelic/newrelic-dotnet-agent/issues/1748)) ([3fbc543](https://github.com/newrelic/newrelic-dotnet-agent/commit/3fbc54310ed3989f915e6f39b27ef8867ed573db)) + +## [10.12.1](https://github.com/newrelic/newrelic-dotnet-agent/compare/v10.12.0...v10.12.1) (2023-06-26) + + +### Fixes + +* Resolved an issue in the `all_solutions.yml` workflow where the MSI installers were built with a self-signed certificate rather than the production code signing certificate. ([386a277](https://github.com/newrelic/newrelic-dotnet-agent/commit/386a27705701a07d591a95f95830bda27898d255)) + +## [10.12.0](https://github.com/newrelic/newrelic-dotnet-agent/compare/v10.11.0...v10.12.0) (2023-06-23) + + +### New Features + +* add instrumentation for newer MongoDB.Client methods ([#1732](https://github.com/newrelic/newrelic-dotnet-agent/issues/1732)) ([1aa5680](https://github.com/newrelic/newrelic-dotnet-agent/commit/1aa5680a8f7f855895203a45b8dfcc5059d656e0)) +* add support for MySql.Data version 8.0.33+ ([#1708](https://github.com/newrelic/newrelic-dotnet-agent/issues/1708)) ([69d15df](https://github.com/newrelic/newrelic-dotnet-agent/commit/69d15dfbed178fb5698695253160ae12a4f7a410)) + + +### Fixes + +* Add more validation to msi installer. ([#1716](https://github.com/newrelic/newrelic-dotnet-agent/issues/1716)) ([d7bb7f2](https://github.com/newrelic/newrelic-dotnet-agent/commit/d7bb7f290beae8394599cee1ea9b3213cf2dc473)) +* Cache the AgentEnabled setting value. ([#1723](https://github.com/newrelic/newrelic-dotnet-agent/issues/1723)) ([1624938](https://github.com/newrelic/newrelic-dotnet-agent/commit/1624938ab48b63c1fa6e98037d74976dbc8186da)) +* Exclude WebResource.axd and ScriptResource.axd from browser instrumentation (via default config). ([#1711](https://github.com/newrelic/newrelic-dotnet-agent/issues/1711)) ([2fcce95](https://github.com/newrelic/newrelic-dotnet-agent/commit/2fcce95093ed4ef6d1efe67489c8d1ae6c9b29e6)) +* Format and log audit-level messages only when audit logging is enabled. ([#1734](https://github.com/newrelic/newrelic-dotnet-agent/issues/1734)) ([f71521f](https://github.com/newrelic/newrelic-dotnet-agent/commit/f71521f2540311e97d13646ff6d6524dfcc3965f)) +* Handle empty Request.Path values in AspNetCore middleware wrapper. ([#1704](https://github.com/newrelic/newrelic-dotnet-agent/issues/1704)) ([8b734a5](https://github.com/newrelic/newrelic-dotnet-agent/commit/8b734a59a53cfd218322d83acbe9d7eb4e7cc055)) +* Include config file path in the "Agent is disabled " message on all platforms. ([#1727](https://github.com/newrelic/newrelic-dotnet-agent/issues/1727)) ([1a56612](https://github.com/newrelic/newrelic-dotnet-agent/commit/1a5661243eaa84683694e022fe9806768b8af9f7)) +* Update install script to correctly stop and restart IIS. ([#1740](https://github.com/newrelic/newrelic-dotnet-agent/issues/1740)) ([3b91dff](https://github.com/newrelic/newrelic-dotnet-agent/commit/3b91dff0ad9aa2fc4218cd85d28fb6d0892cc7fb)) + +## [10.11.0](https://github.com/newrelic/newrelic-dotnet-agent/compare/v10.10.0...v10.11.0) (2023-06-03) + + +### Notice + +* The Dotnet VMs UI page is now available for .NET CLR performance metrics. There is a new New Relic APM UI page available called "Dotnet VMs" that displays the data the .NET agent collects about an application's CLR performance. See the [performance metrics documentaton](https://docs.newrelic.com/docs/apm/agents/net-agent/other-features/net-performance-metrics/) for more details. ([cc7cede](https://github.com/newrelic/newrelic-dotnet-agent/commit/cc7cedecc113812b5f7274e7a6bf1aa5a2511720)) + + +### Fixes + +* Clearing transaction context for held transactions and async WCF client instrumentation timing. ([#1608](https://github.com/newrelic/newrelic-dotnet-agent/issues/1608)) ([db9a48e](https://github.com/newrelic/newrelic-dotnet-agent/commit/db9a48e50b66c345fd53ff64b296025d03da77bb)) +* Stop double injecting headers with HttpClient on .NET Framework ([#1679](https://github.com/newrelic/newrelic-dotnet-agent/issues/1679)) ([e8bdc34](https://github.com/newrelic/newrelic-dotnet-agent/commit/e8bdc34072f044e7b056dd2ce773f184aed3bfe5)) + + +### New Features + +* Add detailed assembly reporting to enable Vulnerability Management support. ([#1685](https://github.com/newrelic/newrelic-dotnet-agent/issues/1685)) ([f249753](https://github.com/newrelic/newrelic-dotnet-agent/commit/f2497536dadb34caded7aa916b5f404ebf19e52a)) +* Adds minimal support for Devart Oracle client. ([181a628](https://github.com/newrelic/newrelic-dotnet-agent/commit/181a628ff1cb7a0f0b7a347378644782f085f3ab)) +* Use Serilog instead of log4net for internal logging. ([#1661](https://github.com/newrelic/newrelic-dotnet-agent/issues/1661)) ([51080df](https://github.com/newrelic/newrelic-dotnet-agent/commit/51080df3848e36e0b6aa29b6cb9a0e94a1638b6f)) + ## [10.10.0](https://github.com/newrelic/newrelic-dotnet-agent/compare/v10.9.1...v10.10.0) (2023-04-26) diff --git a/src/Agent/Configuration/newrelic.config b/src/Agent/Configuration/newrelic.config index 723e0dbc5..9d3c5c24d 100644 --- a/src/Agent/Configuration/newrelic.config +++ b/src/Agent/Configuration/newrelic.config @@ -33,7 +33,12 @@ 404 - + + + + + + System.Threading.WaitHandle:InternalWaitOne System.Threading.WaitHandle:WaitAny diff --git a/src/Agent/MsiInstaller/Installer/LicenseKeyDialog.wxs b/src/Agent/MsiInstaller/Installer/LicenseKeyDialog.wxs index 0a827316d..8e048ccc4 100644 --- a/src/Agent/MsiInstaller/Installer/LicenseKeyDialog.wxs +++ b/src/Agent/MsiInstaller/Installer/LicenseKeyDialog.wxs @@ -23,7 +23,7 @@ SPDX-License-Identifier: Apache-2.0 1 - " "]]> + " "]]> 1 diff --git a/src/Agent/MsiInstaller/Installer/WizardUI.wxs b/src/Agent/MsiInstaller/Installer/WizardUI.wxs index 08881a424..ae6cfb68a 100644 --- a/src/Agent/MsiInstaller/Installer/WizardUI.wxs +++ b/src/Agent/MsiInstaller/Installer/WizardUI.wxs @@ -14,13 +14,13 @@ SPDX-License-Identifier: Apache-2.0 1 - PREVLICENSEKEYFOUND AND LicenseAccepted = "1" + PREVLICENSEKEYFOUND AND LicenseAccepted = "1" NOT PREVLICENSEKEYFOUND AND LicenseAccepted = "1" - NOT Installed AND NOT PREVLICENSEKEYFOUND + NOT Installed AND NOT PREVLICENSEKEYFOUND - NOT Installed AND PREVLICENSEKEYFOUND + NOT Installed AND PREVLICENSEKEYFOUND diff --git a/src/Agent/MsiInstaller/InstallerActions/CustomActions.cs b/src/Agent/MsiInstaller/InstallerActions/CustomActions.cs index a8c3be98d..9f7a76edf 100644 --- a/src/Agent/MsiInstaller/InstallerActions/CustomActions.cs +++ b/src/Agent/MsiInstaller/InstallerActions/CustomActions.cs @@ -264,7 +264,7 @@ private void DeleteFolder(string path) { if (Directory.Exists(path)) { - Directory.Delete(path, true); + SaferFileUtils.DeleteDirectoryAndContents(path, this); LogSuccess("Folder deleted at '{0}'.", path); } else @@ -285,7 +285,7 @@ private void DeleteFile(string path) { if (File.Exists(path)) { - File.Delete(path); + SaferFileUtils.FileDelete(path, this); LogSuccess("File deleted at '{0}'.", path); } else @@ -480,7 +480,7 @@ private void MigrateNewRelicXml() session.Log("Attempting to move file from {0} to {1}.", sourcePath, destinationPath); try { - File.Copy(sourcePath, destinationPath); + SaferFileUtils.FileCopy(sourcePath, destinationPath, this); session.Log("Moved file from {0} to {1}.", sourcePath, destinationPath); } catch (IOException) @@ -543,7 +543,7 @@ private void MigrateCustomInstrumentation() } } - private static void CopyFolderContents(string source, string destination) + private void CopyFolderContents(string source, string destination) { if (source == null) return; if (destination == null) return; @@ -561,7 +561,7 @@ private static void CopyFolderContents(string source, string destination) // If the subfolder already exists don't try to copy. if (Directory.Exists(destinationPath)) continue; - Directory.Move(sourceDirectoryPath, destinationPath); + SaferFileUtils.DirectoryMove(sourceDirectoryPath, destinationPath, this); } // Copy all the files in the root of the directory. @@ -571,9 +571,156 @@ private static void CopyFolderContents(string source, string destination) string fileName = Path.GetFileName(sourceFilePath); string destinationPath = destination + fileName; - File.Copy(sourceFilePath, destinationPath, true); - File.Delete(sourceFilePath); + SaferFileUtils.FileCopy(sourceFilePath, destinationPath, true, this); + SaferFileUtils.FileDelete(sourceFilePath, this); } } } + + /// + /// This class is wrapper around the standard File and Directory classes + /// that will check the file and directory structure for symlinks and junctions + /// before attempting to copy or delete the file or directory. This code uses + /// the presence of reparse points to approximate the usage of junctions or + /// symlinks. No agent file or directory is expected to have a reparse point + /// defined. + /// + internal static class SaferFileUtils + { + internal static void FileCopy(string source, string destination, MySession logger) + { + if (!FileIsSafeToUse(source, logger)) + { + logger.Log("{0} was not copied.", source); + return; + } + + File.Copy(source, destination); + } + + internal static void FileCopy(string source, string destination, bool overwrite, MySession logger) + { + if (!FileIsSafeToUse(source, logger)) + { + logger.Log("{0} was not copied.", source); + return; + } + + File.Copy(source, destination, overwrite); + } + + internal static void FileDelete(string fileNameAndPath, MySession logger) + { + if (!FileIsSafeToUse(fileNameAndPath, logger)) + { + logger.Log("{0} was not deleted.", fileNameAndPath); + return; + } + + File.Delete(fileNameAndPath); + } + + internal static void DeleteDirectoryAndContents(string path, MySession logger) + { + if (!DirectoryIsSafeToUse(path, logger)) + { + logger.Log("{0} was not deleted.", path); + return; + } + + Directory.Delete(path, true); + } + + internal static void DirectoryMove(string source, string destination, MySession logger) + { + if (!DirectoryIsSafeToUse(source, logger)) + { + logger.Log("{0} was not moved.", source); + return; + } + + Directory.Move(source, destination); + } + + private static bool FileIsSafeToUse(string fileNameAndPath, MySession logger) + { + if (!File.Exists(fileNameAndPath)) + { + return false; + } + + var fileInfo = new FileInfo(fileNameAndPath); + + if (FileSystemReportsAReparsePoint(fileInfo, logger)) + { + return false; + } + + for (var directory = fileInfo.Directory; directory != null; directory = directory.Parent) + { + if (FileSystemReportsAReparsePoint(directory, logger)) + { + return false; + } + } + + return DirectoryAndParentsAreSafe(fileInfo.Directory, logger); + } + + private static bool DirectoryIsSafeToUse(string path, MySession logger) + { + if (!Directory.Exists(path)) + { + return false; + } + + var directory = new DirectoryInfo(path); + if (!DirectoryAndParentsAreSafe(directory, logger)) + { + return false; + } + + foreach (var childDirectory in directory.GetDirectories("*", SearchOption.AllDirectories)) + { + if (FileSystemReportsAReparsePoint(childDirectory, logger)) + { + return false; + } + } + + foreach (var childFile in directory.GetFiles("*", SearchOption.AllDirectories)) + { + if (FileSystemReportsAReparsePoint(childFile, logger)) + { + return false; + } + } + + return true; + } + + private static bool DirectoryAndParentsAreSafe(DirectoryInfo directoryToCheck, MySession logger) + { + for (var directory = directoryToCheck; directory != null; directory = directory.Parent) + { + if (FileSystemReportsAReparsePoint(directory, logger)) + { + return false; + } + } + + return true; + } + + private static bool FileSystemReportsAReparsePoint(FileSystemInfo fsInfo, MySession logger) + { + if((fsInfo.Attributes & System.IO.FileAttributes.ReparsePoint) != 0) + { + logger.Log("Reparse point detected for {0}.", fsInfo.FullName); + return true; + } + + return false; + } + } } diff --git a/src/Agent/NewRelic/Agent/Core/Agent.cs b/src/Agent/NewRelic/Agent/Core/Agent.cs index 37212fe9d..43050c5e4 100644 --- a/src/Agent/NewRelic/Agent/Core/Agent.cs +++ b/src/Agent/NewRelic/Agent/Core/Agent.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using NewRelic.Agent.Api; +using NewRelic.Agent.Api.Experimental; using NewRelic.Agent.Configuration; using NewRelic.Agent.Core.AgentHealth; using NewRelic.Agent.Core.Aggregators; @@ -64,6 +65,7 @@ public class Agent : IAgent // any changes to api, update the interface in exten private readonly ILogContextDataFilter _logContextDataFilter; private Extensions.Logging.ILogger _logger; private volatile IStackExchangeRedisCache _stackExchangeRedisCache; + private readonly ISimpleSchedulingService _simpleSchedulingService; public Agent(ITransactionService transactionService, ITransactionTransformer transactionTransformer, IThreadPoolStatic threadPoolStatic, ITransactionMetricNameMaker transactionMetricNameMaker, IPathHashMaker pathHashMaker, @@ -72,7 +74,7 @@ public Agent(ITransactionService transactionService, ITransactionTransformer tra IBrowserMonitoringPrereqChecker browserMonitoringPrereqChecker, IBrowserMonitoringScriptMaker browserMonitoringScriptMaker, IConfigurationService configurationService, IAgentHealthReporter agentHealthReporter, IAgentTimerService agentTimerService, IMetricNameService metricNameService, Api.ITraceMetadataFactory traceMetadataFactory, ICATSupportabilityMetricCounters catMetricCounters, - ILogEventAggregator logEventAggregator, ILogContextDataFilter logContextDataFilter) + ILogEventAggregator logEventAggregator, ILogContextDataFilter logContextDataFilter, ISimpleSchedulingService simpleSchedulingService) { _transactionService = transactionService; _transactionTransformer = transactionTransformer; @@ -93,6 +95,7 @@ public Agent(ITransactionService transactionService, ITransactionTransformer tra _catMetricCounters = catMetricCounters; _logEventAggregator = logEventAggregator; _logContextDataFilter = logContextDataFilter; + _simpleSchedulingService = simpleSchedulingService; Instance = this; } @@ -389,6 +392,11 @@ public Dictionary GetLinkingMetadata() #region ExperimentalApi + public ISimpleSchedulingService SimpleSchedulingService + { + get { return _simpleSchedulingService; } + } + public IStackExchangeRedisCache StackExchangeRedisCache { get { return _stackExchangeRedisCache; } @@ -400,7 +408,7 @@ public void RecordSupportabilityMetric(string metricName, int count) _agentHealthReporter.ReportSupportabilityCountMetric(metricName, count); } - public void RecordLogMessage(string frameworkName, object logEvent, Func getTimestamp, Func getLevel, Func getLogMessage, Func getLogException,Func> getContextData, string spanId, string traceId) + public void RecordLogMessage(string frameworkName, object logEvent, Func getTimestamp, Func getLevel, Func getLogMessage, Func getLogException, Func> getContextData, string spanId, string traceId) { _agentHealthReporter.ReportLogForwardingFramework(frameworkName); @@ -410,6 +418,15 @@ public void RecordLogMessage(string frameworkName, object logEvent, Func _recurringLogDatas = new ConcurrentList(); private readonly IDictionary _agentHealthEventCounters = new Dictionary(); private readonly ConcurrentDictionary _logLinesCountByLevel = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _logDeniedCountByLevel = new ConcurrentDictionary(); private PublishMetricDelegate _publishMetricDelegate; private InterlockedCounter _payloadCreateSuccessCounter; @@ -583,6 +584,22 @@ public void CollectLoggingMetrics() _loggingForwardingEnabledWithFrameworksReported[kvp.Key] = true; } } + + var totalDeniedCount = 0; + foreach (var logLinesDeniedCounter in _logDeniedCountByLevel) + { + if (TryGetCount(logLinesDeniedCounter.Value, out var linesCount)) + { + totalDeniedCount += linesCount; + TrySend(_metricBuilder.TryBuildLoggingMetricsDeniedCountBySeverityMetric(logLinesDeniedCounter.Key, linesCount)); + } + } + + if (totalDeniedCount > 0) + { + TrySend(_metricBuilder.TryBuildLoggingMetricsDeniedCountMetric(totalDeniedCount)); + } + } public void IncrementLogLinesCount(string level) @@ -591,6 +608,12 @@ public void IncrementLogLinesCount(string level) _logLinesCountByLevel[level].Increment(); } + public void IncrementLogDeniedCount(string level) + { + _logDeniedCountByLevel.TryAdd(level, new InterlockedCounter()); + _logDeniedCountByLevel[level].Increment(); + } + public void ReportLoggingEventCollected() => TrySend(_metricBuilder.TryBuildSupportabilityLoggingEventsCollectedMetric()); public void ReportLoggingEventsSent(int count) => TrySend(_metricBuilder.TryBuildSupportabilityLoggingEventsSentMetric(count)); diff --git a/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs b/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs index 83a9472a3..f1567dcef 100644 --- a/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs +++ b/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs @@ -142,6 +142,7 @@ public interface IAgentHealthReporter : IOutOfBandMetricSource void ReportSupportabilityDataUsage(string api, string apiArea, long dataSent, long dataReceived); void IncrementLogLinesCount(string logLevel); + void IncrementLogDeniedCount(string logLevel); void ReportLoggingEventCollected(); void ReportLoggingEventsSent(int count); void ReportLoggingEventsDropped(int droppedCount); diff --git a/src/Agent/NewRelic/Agent/Core/AgentInitializer.cs b/src/Agent/NewRelic/Agent/Core/AgentInitializer.cs index e58dceb31..609da830c 100644 --- a/src/Agent/NewRelic/Agent/Core/AgentInitializer.cs +++ b/src/Agent/NewRelic/Agent/Core/AgentInitializer.cs @@ -26,7 +26,7 @@ private static class CallOnce { static CallOnce() { - // we must ensure that we hook up to ProcessExit and DomainUnload *before* log4net. Otherwise we can't log anything during OnExit. + // we must ensure that we hook up to ProcessExit and DomainUnload *before* log initialization. Otherwise we can't log anything during OnExit. AppDomain.CurrentDomain.ProcessExit += (sender, args) => OnExit(sender, args); AppDomain.CurrentDomain.DomainUnload += (sender, args) => OnExit(sender, args); LoggerBootstrapper.Initialize(); diff --git a/src/Agent/NewRelic/Agent/Core/Aggregators/CustomEventAggregator.cs b/src/Agent/NewRelic/Agent/Core/Aggregators/CustomEventAggregator.cs index e59ba63f2..907585bfd 100644 --- a/src/Agent/NewRelic/Agent/Core/Aggregators/CustomEventAggregator.cs +++ b/src/Agent/NewRelic/Agent/Core/Aggregators/CustomEventAggregator.cs @@ -7,6 +7,7 @@ using NewRelic.Agent.Core.Time; using NewRelic.Agent.Core.WireModels; using NewRelic.Collections; +using NewRelic.Core.Logging; using NewRelic.SystemInterfaces; using System; using System.Collections.Generic; @@ -66,6 +67,8 @@ public override void Collect(CustomEventWireModel customEventWireModel) protected override void Harvest() { + Log.Finest("Custom Event harvest starting."); + ConcurrentPriorityQueue> originalCustomEvents; _readerWriterLockSlim.EnterWriteLock(); @@ -87,6 +90,8 @@ protected override void Harvest() var responseStatus = DataTransportService.Send(customEvents); HandleResponse(responseStatus, customEvents); + + Log.Finest("Custom Event harvest finished."); } protected override void OnConfigurationUpdated(ConfigurationUpdateSource configurationUpdateSource) diff --git a/src/Agent/NewRelic/Agent/Core/Aggregators/ErrorEventAggregator.cs b/src/Agent/NewRelic/Agent/Core/Aggregators/ErrorEventAggregator.cs index 010344d7a..250ed03de 100644 --- a/src/Agent/NewRelic/Agent/Core/Aggregators/ErrorEventAggregator.cs +++ b/src/Agent/NewRelic/Agent/Core/Aggregators/ErrorEventAggregator.cs @@ -8,6 +8,7 @@ using NewRelic.Agent.Core.Transactions; using NewRelic.Agent.Core.WireModels; using NewRelic.Collections; +using NewRelic.Core.Logging; using NewRelic.SystemInterfaces; using System; using System.Collections.Generic; @@ -70,6 +71,8 @@ public override void Collect(ErrorEventWireModel errorEventWireModel) protected override void Harvest() { + Log.Finest("Error Event harvest starting."); + ConcurrentPriorityQueue> originalErrorEvents; ConcurrentList originalSyntheticsErrorEvents; @@ -97,6 +100,8 @@ protected override void Harvest() var responseStatus = DataTransportService.Send(eventHarvestData, aggregatedEvents); HandleResponse(responseStatus, aggregatedEvents); + + Log.Finest("Error Event harvest finished."); } protected override void OnConfigurationUpdated(ConfigurationUpdateSource configurationUpdateSource) diff --git a/src/Agent/NewRelic/Agent/Core/Aggregators/ErrorTraceAggregator.cs b/src/Agent/NewRelic/Agent/Core/Aggregators/ErrorTraceAggregator.cs index 5d02fed96..9943e42e7 100644 --- a/src/Agent/NewRelic/Agent/Core/Aggregators/ErrorTraceAggregator.cs +++ b/src/Agent/NewRelic/Agent/Core/Aggregators/ErrorTraceAggregator.cs @@ -7,6 +7,7 @@ using NewRelic.Agent.Core.Time; using NewRelic.Agent.Core.WireModels; using NewRelic.Collections; +using NewRelic.Core.Logging; using NewRelic.SystemInterfaces; using System; using System.Collections.Generic; @@ -63,6 +64,8 @@ public override void Collect(ErrorTraceWireModel errorTraceWireModel) protected override void Harvest() { + Log.Finest("Error Trace harvest starting."); + ICollection errorTraceWireModels; _readerWriterLock.EnterWriteLock(); @@ -81,6 +84,8 @@ protected override void Harvest() var responseStatus = DataTransportService.Send(errorTraceWireModels); HandleResponse(responseStatus, errorTraceWireModels); + + Log.Finest("Error Trace harvest finished."); } protected override void OnConfigurationUpdated(ConfigurationUpdateSource configurationUpdateSource) diff --git a/src/Agent/NewRelic/Agent/Core/Aggregators/LogEventAggregator.cs b/src/Agent/NewRelic/Agent/Core/Aggregators/LogEventAggregator.cs index aa2bc9474..87a06b6c7 100644 --- a/src/Agent/NewRelic/Agent/Core/Aggregators/LogEventAggregator.cs +++ b/src/Agent/NewRelic/Agent/Core/Aggregators/LogEventAggregator.cs @@ -11,6 +11,7 @@ using NewRelic.Agent.Core.Time; using NewRelic.Agent.Core.WireModels; using NewRelic.Collections; +using NewRelic.Core.Logging; using NewRelic.SystemInterfaces; namespace NewRelic.Agent.Core.Aggregators @@ -57,6 +58,11 @@ public override void Collect(LogEventWireModel loggingEventWireModel) public void CollectWithPriority(IList logEventWireModels, float priority) { + if (logEventWireModels == null) + { + return; + } + for (int i = 0; i < logEventWireModels.Count; i++) { _agentHealthReporter.ReportLoggingEventCollected(); @@ -67,6 +73,8 @@ public void CollectWithPriority(IList logEventWireModels, flo protected override void Harvest() { + Log.Finest("Log Event harvest starting."); + var originalLogEvents = GetAndResetLogEvents(GetReservoirSize()); var aggregatedEvents = originalLogEvents.Where(node => node != null).Select(node => node.Data).ToList(); @@ -96,6 +104,8 @@ protected override void Harvest() var responseStatus = DataTransportService.Send(modelsCollection); HandleResponse(responseStatus, aggregatedEvents); + + Log.Finest("Log Event harvest finished."); } protected override void OnConfigurationUpdated(ConfigurationUpdateSource configurationUpdateSource) diff --git a/src/Agent/NewRelic/Agent/Core/Aggregators/SpanEventAggregator.cs b/src/Agent/NewRelic/Agent/Core/Aggregators/SpanEventAggregator.cs index 3ee55f891..bd2b46528 100644 --- a/src/Agent/NewRelic/Agent/Core/Aggregators/SpanEventAggregator.cs +++ b/src/Agent/NewRelic/Agent/Core/Aggregators/SpanEventAggregator.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using NewRelic.Core.Logging; namespace NewRelic.Agent.Core.Aggregators { @@ -102,6 +103,8 @@ public void Collect(IEnumerable wireModels) protected override void Harvest() { + Log.Finest("Span Event harvest starting."); + ConcurrentPriorityQueue> spanEventsPriorityQueue; _readerWriterLockSlim.EnterWriteLock(); @@ -124,6 +127,8 @@ protected override void Harvest() var responseStatus = DataTransportService.Send(eventHarvestData, wireModels); HandleResponse(responseStatus, wireModels); + + Log.Finest("Span Event harvest finished."); } private void ReduceReservoirSize(int newSize) diff --git a/src/Agent/NewRelic/Agent/Core/Aggregators/SqlTraceAggregator.cs b/src/Agent/NewRelic/Agent/Core/Aggregators/SqlTraceAggregator.cs index a2d8f63fe..2c68b6f15 100644 --- a/src/Agent/NewRelic/Agent/Core/Aggregators/SqlTraceAggregator.cs +++ b/src/Agent/NewRelic/Agent/Core/Aggregators/SqlTraceAggregator.cs @@ -6,6 +6,7 @@ using NewRelic.Agent.Core.Events; using NewRelic.Agent.Core.Time; using NewRelic.Agent.Core.WireModels; +using NewRelic.Core.Logging; using NewRelic.SystemInterfaces; using System; using System.Collections.Generic; @@ -46,6 +47,8 @@ public override void Collect(SqlTraceStatsCollection sqlTraceStats) protected override void Harvest() { + Log.Finest("SQL Trace harvest starting."); + IDictionary oldSqlTraces; lock (_sqlTraceLock) { @@ -65,6 +68,8 @@ protected override void Harvest() var responseStatus = DataTransportService.Send(slowestTraces); HandleResponse(responseStatus, slowestTraces); + + Log.Finest("SQL Trace harvest finished."); } private void HandleResponse(DataTransportResponseStatus responseStatus, ICollection traces) diff --git a/src/Agent/NewRelic/Agent/Core/Aggregators/TransactionEventAggregator.cs b/src/Agent/NewRelic/Agent/Core/Aggregators/TransactionEventAggregator.cs index b554f45ed..2ac83d6bc 100644 --- a/src/Agent/NewRelic/Agent/Core/Aggregators/TransactionEventAggregator.cs +++ b/src/Agent/NewRelic/Agent/Core/Aggregators/TransactionEventAggregator.cs @@ -8,6 +8,7 @@ using NewRelic.Agent.Core.Transactions; using NewRelic.Agent.Core.WireModels; using NewRelic.Collections; +using NewRelic.Core.Logging; using NewRelic.SystemInterfaces; using System; using System.Collections.Generic; @@ -69,6 +70,8 @@ public override void Collect(TransactionEventWireModel transactionEventWireModel protected override void Harvest() { + Log.Finest("Transaction Event harvest starting."); + IResizableCappedCollection> originalTransactionEvents; ConcurrentList originalSyntheticsTransactionEvents; _readerWriterLock.EnterWriteLock(); @@ -95,6 +98,8 @@ protected override void Harvest() var responseStatus = DataTransportService.Send(eventHarvestData, aggregatedEvents); HandleResponse(responseStatus, aggregatedEvents); + + Log.Finest("Transaction Event harvest finished."); } protected override void OnConfigurationUpdated(ConfigurationUpdateSource configurationUpdateSource) diff --git a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs index 2ad62e763..7f0227d26 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs @@ -9,12 +9,6 @@ // ------------------------------------------------------------------------------ namespace NewRelic.Agent.Core.Config { - using System; - using System.Diagnostics; - using System.Xml.Serialization; - using System.Collections; - using System.Xml.Schema; - using System.ComponentModel; using System.Collections.Generic; @@ -1301,8 +1295,6 @@ public partial class configurationLog private bool auditLogField; - private System.Nullable fileLockingModelField; - /// /// configurationLog class constructor /// @@ -1381,42 +1373,6 @@ public bool auditLog } } - [System.Xml.Serialization.XmlAttributeAttribute()] - public configurationLogFileLockingModel fileLockingModel - { - get - { - if (this.fileLockingModelField.HasValue) - { - return this.fileLockingModelField.Value; - } - else - { - return default(configurationLogFileLockingModel); - } - } - set - { - this.fileLockingModelField = value; - } - } - - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool fileLockingModelSpecified - { - get - { - return this.fileLockingModelField.HasValue; - } - set - { - if (value==false) - { - this.fileLockingModelField = null; - } - } - } - #region Clone method /// /// Create a clone of this configurationLog object @@ -1428,19 +1384,6 @@ public virtual configurationLog Clone() #endregion } - [System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="urn:newrelic-config")] - public enum configurationLogFileLockingModel - { - - /// - exclusive, - - /// - minimal, - } - [System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")] [System.SerializableAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5092,6 +5035,8 @@ public partial class configurationApplicationLoggingForwarding private int maxSamplesStoredField; + private string logLevelDenyListField; + /// /// configurationApplicationLoggingForwarding class constructor /// @@ -5142,6 +5087,19 @@ public int maxSamplesStored } } + [System.Xml.Serialization.XmlAttributeAttribute()] + public string logLevelDenyList + { + get + { + return this.logLevelDenyListField; + } + set + { + this.logLevelDenyListField = value; + } + } + #region Clone method /// /// Create a clone of this configurationApplicationLoggingForwarding object diff --git a/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd b/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd index 237943159..4b30c64a0 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd +++ b/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd @@ -347,19 +347,6 @@ - - - - Controls how agent-produced log files are to be locked. Experimental. - - - - - - - - - @@ -1668,6 +1655,15 @@ + + + + + A comma-separated, case-insensitive, list of log level names from the selected logging framework that should be ignored and not sent up to New Relic. + + + + diff --git a/src/Agent/NewRelic/Agent/Core/Config/ConfigurationLoader.cs b/src/Agent/NewRelic/Agent/Core/Config/ConfigurationLoader.cs index 324189c74..66c7c7f24 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/ConfigurationLoader.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/ConfigurationLoader.cs @@ -111,6 +111,14 @@ public static ValueWithProvenance GetConfigSetting(string key) value = new ValueWithProvenance(ConfigurationManager.AppSettings[key], "ConfigurationManager app setting"); } +#else + if (value?.Value == null) + { + var configMgrStatic = new ConfigurationManagerStatic(); + var configValue = configMgrStatic.GetAppSetting(key); + if (configValue != null) + value = new ValueWithProvenance(configValue, configMgrStatic.AppSettingsFilePath); + } #endif return value; } @@ -586,22 +594,6 @@ private string GetLogFileName() return "newrelic_agent_" + Strings.SafeFileName(name) + ".log"; } - public bool FileLockingModelSpecified - { - get - { - return fileLockingModelSpecified; - } - } - - public configurationLogFileLockingModel FileLockingModel - { - get - { - return fileLockingModel; - } - } - public bool Console { get diff --git a/src/Agent/NewRelic/Agent/Core/Config/ILogConfig.cs b/src/Agent/NewRelic/Agent/Core/Config/ILogConfig.cs index 63762c69b..8b072cbdd 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/ILogConfig.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/ILogConfig.cs @@ -9,9 +9,6 @@ public interface ILogConfig string GetFullLogFileName(); - bool FileLockingModelSpecified { get; } - configurationLogFileLockingModel FileLockingModel { get; } - bool Console { get; } bool IsAuditLogEnabled { get; } diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs b/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs index ed2abb61d..28fb62029 100644 --- a/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs +++ b/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs @@ -63,15 +63,8 @@ private void OnConfigurationDeserialized(ConfigurationDeserializedEvent configur private static void UpdateLogLevel(configuration localConfiguration) { - var hierarchy = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly()) as log4net.Repository.Hierarchy.Hierarchy; - var logger = hierarchy.Root; - - var logLevel = logger.Hierarchy.LevelMap[localConfiguration.LogConfig.LogLevel]; - if (logLevel != null && logLevel != logger.Level) - { - Log.InfoFormat("The log level was updated to {0}", logLevel); - logger.Level = logLevel; - } + Log.InfoFormat("The log level was updated to {0}", localConfiguration.LogConfig.LogLevel); + LoggerBootstrapper.UpdateLoggingLevel(localConfiguration.LogConfig.LogLevel); } private void OnServerConfigurationUpdated(ServerConfigurationUpdatedEvent serverConfigurationUpdatedEvent) diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs b/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs index b72509cca..9c3389930 100644 --- a/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs +++ b/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs @@ -183,17 +183,28 @@ private int TryGetAppSettingAsIntWithDefault(string key, int defaultValue) public object AgentRunId { get { return _serverConfiguration.AgentRunId; } } + // protected to allow unit test wrapper to manipulate + protected static bool? _agentEnabledAppSettingParsed; + protected static bool _appSettingAgentEnabled; + private static readonly object _lockObj = new object(); + + public virtual bool AgentEnabled { get { - var agentEnabledAsString = _configurationManagerStatic.GetAppSetting("NewRelic.AgentEnabled"); - - bool agentEnabled; - if (!bool.TryParse(agentEnabledAsString, out agentEnabled)) - return _localConfiguration.agentEnabled; + // read from app setting one time only and cache the result + if (!_agentEnabledAppSettingParsed.HasValue) + { + lock (_lockObj) + { + _agentEnabledAppSettingParsed ??= bool.TryParse(_configurationManagerStatic.GetAppSetting("NewRelic.AgentEnabled"), + out _appSettingAgentEnabled); + } + } - return agentEnabled; + // read from local config if we couldn't parse from app settings + return _agentEnabledAppSettingParsed.Value ? _appSettingAgentEnabled : _localConfiguration.agentEnabled; } } @@ -1979,6 +1990,31 @@ public virtual bool LogDecoratorEnabled } } + private HashSet _logLevelDenyList; + public virtual HashSet LogLevelDenyList + { + get + { + if (_logLevelDenyList == null) + { + _logLevelDenyList = new HashSet( + EnvironmentOverrides(_localConfiguration.applicationLogging.forwarding.logLevelDenyList, + "NEW_RELIC_APPLICATION_LOGGING_FORWARDING_LOG_LEVEL_DENYLIST") + ?.Split(new[] { StringSeparators.CommaChar, ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => s.ToUpper()) + ?? Enumerable.Empty()); + + if (_logLevelDenyList.Count > 0) + { + var logLevels = string.Join(",", _logLevelDenyList); + Log.Info($"Log Level Filtering is enabled for the following levels: {logLevels}"); + } + } + + return _logLevelDenyList; + } + } + #endregion public virtual bool AppDomainCachingDisabled @@ -2173,6 +2209,54 @@ public TimeSpan SqlTracesHarvestCycle } } + private TimeSpan? _updateLoadedModulesCycleOverride = null; + public TimeSpan UpdateLoadedModulesCycle + { + get + { + if (_updateLoadedModulesCycleOverride.HasValue) + { + return _updateLoadedModulesCycleOverride.Value; + } + + if (_newRelicAppSettings.TryGetValue("OverrideUpdateLoadedModulesCycle", out var harvestCycle)) + { + if (int.TryParse(harvestCycle, out var parsedHarvestCycle) && parsedHarvestCycle > 0) + { + Log.Info("Update loaded modules cycle overridden to " + parsedHarvestCycle + " seconds."); + _updateLoadedModulesCycleOverride = TimeSpan.FromSeconds(parsedHarvestCycle); + return _updateLoadedModulesCycleOverride.Value; + } + } + + return DefaultHarvestCycle; + } + } + + private TimeSpan? _stackExchangeRedisCleanupCycleOverride = null; + public TimeSpan StackExchangeRedisCleanupCycle + { + get + { + if (_stackExchangeRedisCleanupCycleOverride.HasValue) + { + return _stackExchangeRedisCleanupCycleOverride.Value; + } + + if (_newRelicAppSettings.TryGetValue("OverrideStackExchangeRedisCleanupCycle", out var harvestCycle)) + { + if (int.TryParse(harvestCycle, out var parsedHarvestCycle) && parsedHarvestCycle > 0) + { + Log.Info("StackExchange.Redis cleanup cycle overridden to " + parsedHarvestCycle + " seconds."); + _stackExchangeRedisCleanupCycleOverride = TimeSpan.FromSeconds(parsedHarvestCycle); + return _stackExchangeRedisCleanupCycleOverride.Value; + } + } + + return DefaultHarvestCycle; + } + } + #endregion #region Helpers @@ -2657,6 +2741,7 @@ private void LogDisabledPropertyUse(string disabledPropertyName, string newPrope TryGetAppSettingAsIntWithDefault("SqlStatementCacheCapacity", DefaultSqlStatementCacheCapacity)).Value; private bool? _codeLevelMetricsEnabled; + public bool CodeLevelMetricsEnabled { get diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs b/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs index e781d13a1..096bf8534 100644 --- a/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs +++ b/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs @@ -593,6 +593,9 @@ public ReportedConfiguration(IConfiguration configuration) [JsonProperty("application_logging.forwarding.max_samples_stored")] public int LogEventsMaxSamplesStored => _configuration.LogEventsMaxSamplesStored; + [JsonProperty("application_logging.forwarding.log_level_denylist")] + public HashSet LogLevelDenyList => _configuration.LogLevelDenyList; + [JsonProperty("application_logging.harvest_cycle")] public TimeSpan LogEventsHarvestCycle => _configuration.LogEventsHarvestCycle; @@ -638,6 +641,12 @@ public ReportedConfiguration(IConfiguration configuration) [JsonProperty("sql_traces.harvest_cycle")] public TimeSpan SqlTracesHarvestCycle => _configuration.SqlTracesHarvestCycle; + [JsonProperty("update_loaded_modules.cycle")] + public TimeSpan UpdateLoadedModulesCycle => _configuration.UpdateLoadedModulesCycle; + + [JsonProperty("stackexchangeredis_cleanup.cycle")] + public TimeSpan StackExchangeRedisCleanupCycle => _configuration.StackExchangeRedisCleanupCycle; + public IReadOnlyDictionary GetAppSettings() { return _configuration.GetAppSettings(); diff --git a/src/Agent/NewRelic/Agent/Core/Core.csproj b/src/Agent/NewRelic/Agent/Core/Core.csproj index cf61c9713..8e38d8be4 100644 --- a/src/Agent/NewRelic/Agent/Core/Core.csproj +++ b/src/Agent/NewRelic/Agent/Core/Core.csproj @@ -32,14 +32,20 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + - @@ -53,15 +59,13 @@ - - + - - + @@ -79,11 +83,6 @@ - - - Properties\SharedLog4NetRepository.cs - - @@ -98,15 +97,20 @@ + - - - + + + + + + + @@ -119,15 +123,19 @@ - - + + + + + + - 15 - 11 + 20 + 16 diff --git a/src/Agent/NewRelic/Agent/Core/DataTransport/DataTransportService.cs b/src/Agent/NewRelic/Agent/Core/DataTransport/DataTransportService.cs index a155edb5e..03b8ee65a 100644 --- a/src/Agent/NewRelic/Agent/Core/DataTransport/DataTransportService.cs +++ b/src/Agent/NewRelic/Agent/Core/DataTransport/DataTransportService.cs @@ -35,6 +35,7 @@ public interface IDataTransportService DataTransportResponseStatus Send(IEnumerable sqlTraceWireModels); DataTransportResponseStatus Send(IEnumerable customEvents); DataTransportResponseStatus Send(LogEventWireModelCollection loggingEvents); + DataTransportResponseStatus Send(LoadedModuleWireModelCollection loadedModules); } public class DataTransportService : ConfigurationBasedService, IDataTransportService @@ -139,6 +140,16 @@ public DataTransportResponseStatus Send(IEnumerable metrics) return status; } + public DataTransportResponseStatus Send(LoadedModuleWireModelCollection loadedModules) + { + if (loadedModules.LoadedModules.Count < 1) + { + return DataTransportResponseStatus.RequestSuccessful; + } + + return TrySendDataRequest("update_loaded_modules", loadedModules); + } + #endregion Public API #region Private helpers diff --git a/src/Agent/NewRelic/Agent/Core/DataTransport/GrpcWrapper.cs b/src/Agent/NewRelic/Agent/Core/DataTransport/GrpcWrapper.cs index 109678129..715797162 100644 --- a/src/Agent/NewRelic/Agent/Core/DataTransport/GrpcWrapper.cs +++ b/src/Agent/NewRelic/Agent/Core/DataTransport/GrpcWrapper.cs @@ -99,7 +99,7 @@ public bool CreateChannel(string host, int port, bool ssl, Metadata headers, int var uriBuilder = new UriBuilder { - Scheme = "https", + Scheme = ssl ? "https" : "http", Host = host, Port = port }; diff --git a/src/Agent/NewRelic/Agent/Core/DataTransport/HttpCollectorWire.cs b/src/Agent/NewRelic/Agent/Core/DataTransport/HttpCollectorWire.cs index 93f9ac210..e83401b2f 100644 --- a/src/Agent/NewRelic/Agent/Core/DataTransport/HttpCollectorWire.cs +++ b/src/Agent/NewRelic/Agent/Core/DataTransport/HttpCollectorWire.cs @@ -156,10 +156,14 @@ public string SendData(string method, ConnectionInfo connectionInfo, string seri } } - private static void AuditLog(Direction direction, Source source, string uri) + private void AuditLog(Direction direction, Source source, string uri) { - var message = string.Format(AuditLogFormat, direction, source, Strings.ObfuscateLicenseKeyInAuditLog(uri, LicenseKeyParameterName)); - Logging.AuditLog.Log(message); + if (Logging.AuditLog.IsAuditLogEnabled) + { + var message = string.Format(AuditLogFormat, direction, source, + Strings.ObfuscateLicenseKeyInAuditLog(uri, LicenseKeyParameterName)); + Logging.AuditLog.Log(message); + } } private Uri GetUri(string method, ConnectionInfo connectionInfo) diff --git a/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentContainer.cs b/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentContainer.cs new file mode 100644 index 000000000..fadb253e5 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentContainer.cs @@ -0,0 +1,127 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using Autofac; +using NewRelic.Core.Logging; + +namespace NewRelic.Agent.Core.DependencyInjection +{ + public class AgentContainer : IContainer + { + + private readonly ContainerBuilder _builder; + private Autofac.IContainer _container; + + // use the scope instead of the container to resolve instances. This allows us to replace registrations in a new scope for unit testing + private ILifetimeScope _scope; + private bool _disposedValue; + private readonly Dictionary _registrationsToReplace = new Dictionary(); + + public AgentContainer() + { + _builder = new ContainerBuilder(); + } + + public void Build() + { + _container = _builder.Build(); + _scope = _container.BeginLifetimeScope(); + } + + public void ReplaceRegistrations() + { + // create a new nested scope, registering the requested replacement instances. + _scope = _scope.BeginLifetimeScope(ReplaceRegistrations); + + _registrationsToReplace.Clear(); + } + + private void ReplaceRegistrations(ContainerBuilder builder) + { + foreach (var kvp in _registrationsToReplace) + { + builder.RegisterInstance(kvp.Value).As(kvp.Key); + } + } + + + public void Register() + where TInterface : class + where TConcrete : class, TInterface + { + _builder.RegisterType().As().InstancePerLifetimeScope(); + } + + public void Register() + where TInterface1 : class + where TInterface2 : class + where TConcrete : class, TInterface1, TInterface2 + { + _builder.RegisterType().As().InstancePerLifetimeScope(); + } + + public void RegisterInstance(TInterface instance) + where TInterface : class + { + _builder.RegisterInstance(instance).As().SingleInstance(); + } + + public void RegisterFactory(Func func) + where TInterface : class + { + _builder.Register(c => func.Invoke()).As(); + } + + public void ReplaceInstanceRegistration(TInterface instance) + where TInterface : class + { + // Add this replacement registration to a list, registration actually occurs in ReplaceRegistrations() + _registrationsToReplace.Add(typeof(TInterface), instance); + } + + public T Resolve() + { + return _scope.Resolve(); + } + + public IEnumerable ResolveAll() + { + try + { + return _scope.Resolve>(); + } + catch (Exception ex) + { + Log.Error($"Error during ResolveAll of {typeof(T)}: {ex}"); + throw; + } + } + + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _scope?.Dispose(); + _container?.Dispose(); + + _scope = null; + _container = null; + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs b/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs index 7220888a2..d87130831 100644 --- a/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs +++ b/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs @@ -52,11 +52,7 @@ public static class AgentServices { public static IContainer GetContainer() { -#if NETFRAMEWORK - return new WindsorContainer(); -#else - return new CoreContainer(); -#endif + return new AgentContainer(); } /// @@ -76,24 +72,24 @@ public static void RegisterServices(IContainer container) // Other container.Register(); - container.Register(AgentInstallConfiguration.GetIsWindows); + container.RegisterInstance(AgentInstallConfiguration.GetIsWindows); container.Register(); container.Register(); container.Register(); container.Register(); container.Register(); container.Register(); - container.Register>>(() => new ThreadEventsListener()); + container.RegisterInstance>>(() => new ThreadEventsListener()); container.Register(); container.Register(); #if NETFRAMEWORK - container.Register>(PerformanceCounterProxyFactory.DefaultCreatePerformanceCounterCategoryProxy); - container.Register>(PerformanceCounterProxyFactory.DefaultCreatePerformanceCounterProxy); + container.RegisterInstance>(PerformanceCounterProxyFactory.DefaultCreatePerformanceCounterCategoryProxy); + container.RegisterInstance>(PerformanceCounterProxyFactory.DefaultCreatePerformanceCounterProxy); container.Register(); container.Register(); #else - container.Register>>>(() => new GCEventsListener()); - container.Register>(GCSamplerNetCore.FXsamplerIsApplicableToFrameworkDefault); + container.RegisterInstance>>>(() => new GCEventsListener()); + container.RegisterInstance>(GCSamplerNetCore.FXsamplerIsApplicableToFrameworkDefault); container.Register(); #endif @@ -116,7 +112,7 @@ public static void RegisterServices(IContainer container) container.Register(); container.Register(); container.Register(); - container.Register>(MetricWireModel.Merge); + container.RegisterInstance>(MetricWireModel.Merge); container.Register(); container.Register(); container.Register(); @@ -136,9 +132,6 @@ public static void RegisterServices(IContainer container) container.Register(); container.Register(); container.Register(); -#if NETFRAMEWORK - container.RegisterFactory>(container.ResolveAll); -#endif container.Register(); container.Register(); container.Register(); @@ -157,7 +150,7 @@ public static void RegisterServices(IContainer container) container.Register(); container.Register(); container.Register(); - container.Register>(transactionCollectors); + container.RegisterInstance>(transactionCollectors); container.Register(); container.Register(); @@ -180,7 +173,7 @@ public static void RegisterServices(IContainer container) container.Register(); container.Register(); - container.Register>((filter) => new AttributeDefinitions(filter)); + container.RegisterInstance>((filter) => new AttributeDefinitions(filter)); container.Register(); container.Register(); container.Register(); @@ -203,6 +196,9 @@ public static void RegisterServices(IContainer container) container.Register(); } container.Register(); + container.Register(); + + container.Register(); container.Build(); } @@ -231,6 +227,7 @@ public static void StartServices(IContainer container) container.Resolve().Start(); container.Resolve(); container.Resolve(); + container.Resolve(); } } } diff --git a/src/Agent/NewRelic/Agent/Core/DependencyInjection/CoreContainer.cs b/src/Agent/NewRelic/Agent/Core/DependencyInjection/CoreContainer.cs deleted file mode 100644 index 899595428..000000000 --- a/src/Agent/NewRelic/Agent/Core/DependencyInjection/CoreContainer.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -#if NETSTANDARD2_0 -using System; -using System.Collections.Generic; -using NewRelic.Agent.Core.Logging; - -using System.Linq; -using System.Text; -using Autofac; -using NewRelic.Core.Logging; - -namespace NewRelic.Agent.Core.DependencyInjection -{ - public class CoreContainer : IContainer - { - - private readonly ContainerBuilder builder; - private Autofac.IContainer container; - - public CoreContainer() - { - this.builder = new ContainerBuilder(); - } - - public void Build() - { - this.container = builder.Build(); - } - - public void Dispose() - { - } - - public void Register() - where TInterface : class - where TConcrete : class, TInterface - { - builder.RegisterType().As().SingleInstance(); - } - - public void Register() - where TInterface1 : class - where TInterface2 : class - where TConcrete : class, TInterface1, TInterface2 - { - builder.RegisterType().As().SingleInstance(); - } - - public void Register(TInterface instance) - where TInterface : class - { - builder.RegisterInstance(instance).As().SingleInstance(); - } - - public void RegisterFactory(Func func) - where TInterface : class - { - builder.Register(c => func.Invoke()).As(); - } - - public void ReplaceRegistration(TInterface instance) - where TInterface : class - { - throw new NotImplementedException(); - } - - public T Resolve() - { - Check(typeof(T)); - return container.Resolve(); - } - - public IEnumerable ResolveAll() - { - Check(typeof(T)); - try - { - return container.Resolve>(); - } catch (Exception ex) - { - Log.Error($"Error during ResolveAll of {typeof(T)}"); - throw ex; - } - } - private void Check(Type type) - { - if (container == null) - { - throw new Exception("Resolve invoked with uninitialized container for " + type); - } - } - } -} -#endif diff --git a/src/Agent/NewRelic/Agent/Core/DependencyInjection/IContainer.cs b/src/Agent/NewRelic/Agent/Core/DependencyInjection/IContainer.cs index 9e99741b2..0ecb58318 100644 --- a/src/Agent/NewRelic/Agent/Core/DependencyInjection/IContainer.cs +++ b/src/Agent/NewRelic/Agent/Core/DependencyInjection/IContainer.cs @@ -17,13 +17,13 @@ void Register() where TInterface2 : class where TConcrete : class, TInterface1, TInterface2; - void Register(TInterface instance) + void RegisterInstance(TInterface instance) where TInterface : class; void RegisterFactory(Func func) where TInterface : class; - void ReplaceRegistration(TInterface instance) + void ReplaceInstanceRegistration(TInterface instance) where TInterface : class; T Resolve(); @@ -31,5 +31,7 @@ void ReplaceRegistration(TInterface instance) IEnumerable ResolveAll(); void Build(); + + void ReplaceRegistrations(); } } diff --git a/src/Agent/NewRelic/Agent/Core/DependencyInjection/WindsorContainer.cs b/src/Agent/NewRelic/Agent/Core/DependencyInjection/WindsorContainer.cs deleted file mode 100644 index e4293cf02..000000000 --- a/src/Agent/NewRelic/Agent/Core/DependencyInjection/WindsorContainer.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -#if NETFRAMEWORK -using System; -using System.Collections.Generic; -using System.Linq; -using Castle.MicroKernel.ModelBuilder.Inspectors; -using Castle.MicroKernel.Registration; - -namespace NewRelic.Agent.Core.DependencyInjection -{ - public class WindsorContainer : IContainer - { - private readonly Castle.Windsor.WindsorContainer _windsorContainer; - - public WindsorContainer() - { - _windsorContainer = new Castle.Windsor.WindsorContainer(); - - // Disable property injection - var propInjector = _windsorContainer.Kernel.ComponentModelBuilder - .Contributors - .OfType() - .Single(); - _windsorContainer.Kernel.ComponentModelBuilder.RemoveContributor(propInjector); - } - - public void Build() - { - // no op - } - - public void Dispose() - { - _windsorContainer.Dispose(); - } - - public void Register() - where TInterface : class - where TConcrete : class, TInterface - { - _windsorContainer.Register( - Component - .For() - .ImplementedBy() - .Named(typeof(TInterface).FullName + "-" + typeof(TConcrete).FullName)); - } - - - public void Register() - where TInterface1 : class - where TInterface2 : class - where TConcrete : class, TInterface1, TInterface2 - { - _windsorContainer.Register( - Component - .For() - .ImplementedBy() - .Named(typeof(TInterface1).FullName + "," + typeof(TInterface2).FullName + "-" + typeof(TConcrete).FullName)); - } - - public void Register(TInterface instance) where TInterface : class - { - _windsorContainer.Register(Component.For().Instance(instance)); - } - - public void RegisterFactory(Func func) - where TInterface : class - { - _windsorContainer.Register(Component.For().UsingFactoryMethod(func)); - } - - public void ReplaceRegistration(TInterface instance) - where TInterface : class - { - var guid = Guid.NewGuid().ToString(); - _windsorContainer.Register(Component.For().Instance(instance).Named(guid).IsDefault()); - } - - public T Resolve() - { - return _windsorContainer.Resolve(); - } - - public IEnumerable ResolveAll() - { - return _windsorContainer.ResolveAll(); - } - } -} -#endif diff --git a/src/Agent/NewRelic/Agent/Core/DistributedTracing/DistributedTracePayloadHandler.cs b/src/Agent/NewRelic/Agent/Core/DistributedTracing/DistributedTracePayloadHandler.cs index d6e93a7fd..b9e44515d 100644 --- a/src/Agent/NewRelic/Agent/Core/DistributedTracing/DistributedTracePayloadHandler.cs +++ b/src/Agent/NewRelic/Agent/Core/DistributedTracing/DistributedTracePayloadHandler.cs @@ -12,6 +12,7 @@ using NewRelic.Core.Logging; using System; using System.Collections.Generic; +using System.Linq; namespace NewRelic.Agent.Core.DistributedTracing { @@ -162,12 +163,15 @@ private string BuildTracestate(IInternalTransaction transaction, DateTime timest var newRelicTracestate = $"{accountKey}@nr={version}-{parentType}-{parentAccountId}-{appId}-{spanId}-{transactionId}-{sampled}-{priority}-{timestampInMillis}"; var otherVendorTracestates = string.Empty; - if (transaction.TracingState != null) + if (transaction.TracingState?.VendorStateEntries != null && transaction.TracingState.VendorStateEntries.Any()) { - if (transaction.TracingState.VendorStateEntries != null) - { - otherVendorTracestates = string.Join(",", transaction.TracingState.VendorStateEntries); - } + otherVendorTracestates = string.Join(",", transaction.TracingState.VendorStateEntries); + } + + // If otherVendorTracestates is null/empty we get a trailing comma. + if (string.IsNullOrWhiteSpace(otherVendorTracestates)) + { + return newRelicTracestate; } return string.Join(",", newRelicTracestate, otherVendorTracestates); diff --git a/src/Agent/NewRelic/Agent/Core/Errors/ExceptionFormatter.cs b/src/Agent/NewRelic/Agent/Core/Errors/ExceptionFormatter.cs index 73a62ab07..99ad09836 100644 --- a/src/Agent/NewRelic/Agent/Core/Errors/ExceptionFormatter.cs +++ b/src/Agent/NewRelic/Agent/Core/Errors/ExceptionFormatter.cs @@ -13,7 +13,10 @@ public static string FormatStackTrace(Exception exception, bool stripErrorMessag var message = stripErrorMessage ? ErrorData.StripExceptionMessagesMessage : exception.Message; var formattedInnerException = FormatInnerStackTrace(exception.InnerException, stripErrorMessage); var formattedStackTrace = exception.StackTrace != null ? System.Environment.NewLine + exception.StackTrace : null; - +#if NETSTANDARD2_0 + if (!string.IsNullOrEmpty(formattedInnerException)) + formattedInnerException = System.Environment.NewLine + formattedInnerException; +#endif var result = $"{type}: {message}{formattedInnerException}{formattedStackTrace}"; return result; diff --git a/src/Agent/NewRelic/Agent/Core/JsonConverters/LoadedModuleWireModelCollectionJsonConverter.cs b/src/Agent/NewRelic/Agent/Core/JsonConverters/LoadedModuleWireModelCollectionJsonConverter.cs new file mode 100644 index 000000000..7cbb58d47 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/JsonConverters/LoadedModuleWireModelCollectionJsonConverter.cs @@ -0,0 +1,55 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using NewRelic.Agent.Core.WireModels; +using Newtonsoft.Json; + +namespace NewRelic.Agent.Core.JsonConverters +{ + public class LoadedModuleWireModelCollectionJsonConverter : JsonConverter + { + // The payload is labeled "Jars" since the collector method was originally meant for and used by Java. + private const string JarsName = "Jars"; + + public override LoadedModuleWireModelCollection ReadJson(JsonReader reader, Type objectType, LoadedModuleWireModelCollection existingValue, bool hasExistingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override void WriteJson(JsonWriter jsonWriter, LoadedModuleWireModelCollection value, JsonSerializer serializer) + { + WriteJsonImpl(jsonWriter, value); + } + + private static void WriteJsonImpl(JsonWriter jsonWriter, LoadedModuleWireModelCollection value) + { + jsonWriter.WriteValue(JarsName); + + jsonWriter.WriteStartArray(); + + foreach (var loadedModule in value.LoadedModules) + { + // MODULE + jsonWriter.WriteStartArray(); + + jsonWriter.WriteValue(loadedModule.AssemblyName); + jsonWriter.WriteValue(loadedModule.Version ?? " "); + + // DATA DICTIONARY + jsonWriter.WriteStartObject(); + foreach (var item in loadedModule.Data) + { + jsonWriter.WritePropertyName(item.Key); + jsonWriter.WriteValue(item.Value.ToString()); + } + + jsonWriter.WriteEndObject(); + + jsonWriter.WriteEndArray(); + } + + jsonWriter.WriteEndArray(); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Labels/LabelsService.cs b/src/Agent/NewRelic/Agent/Core/Labels/LabelsService.cs index f7e91f3c7..07345ad0a 100644 --- a/src/Agent/NewRelic/Agent/Core/Labels/LabelsService.cs +++ b/src/Agent/NewRelic/Agent/Core/Labels/LabelsService.cs @@ -12,7 +12,7 @@ namespace NewRelic.Agent.Core.Labels { public class LabelsService : ILabelsService { - private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(LabelsService)); + private readonly Serilog.ILogger Log = Serilog.Log.Logger; private const int MaxLabels = 64; private const int MaxLength = 255; @@ -45,18 +45,18 @@ private IEnumerable public void Error(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Error(exception.ToString()); + _logger.Error(exception, ""); } /// @@ -95,10 +85,7 @@ public void Error(Exception exception) public void ErrorFormat(string format, params object[] args) { if (IsErrorEnabled) - { - EnsureThreadIdPropertyExistsInContext(); - _logger.Error(string.Format(format, args)); - } + _logger.Error(format, args); } #endregion Error @@ -108,15 +95,14 @@ public void ErrorFormat(string format, params object[] args) /// /// True iff logging has been configured to include WARN level logs. /// - public bool IsWarnEnabled => _logger.IsWarnEnabled; + public bool IsWarnEnabled => _logger.IsEnabled(LogEventLevel.Warning); /// /// Logs at the WARN level. This log level should be used for information regarding *possible* problems in the agent that *might* adversely affect the user in some way (data loss, performance problems, reduced agent functionality, etc). Do not use if logging that information will create a performance problem (say, due to excessive logging). /// public void Warn(string message) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Warn(message); + _logger.Warning(message); } /// @@ -124,8 +110,7 @@ public void Warn(string message) /// public void Warn(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Warn(exception.ToString()); + _logger.Warning(exception, ""); } /// @@ -134,10 +119,7 @@ public void Warn(Exception exception) public void WarnFormat(string format, params object[] args) { if (IsWarnEnabled) - { - EnsureThreadIdPropertyExistsInContext(); - _logger.Warn(string.Format(format, args)); - } + _logger.Warning(format, args); } #endregion Warn @@ -147,15 +129,14 @@ public void WarnFormat(string format, params object[] args) /// /// True iff logging has been configured to include INFO level logs. /// - public bool IsInfoEnabled => _logger.IsInfoEnabled; + public bool IsInfoEnabled => _logger.IsEnabled(LogEventLevel.Information); /// /// Logs at the INFO level. This log level should be used for information for non-error information that may be of interest to the user, such as a the agent noticing a configuration change, or an infrequent "heartbeat". Do not use if logging that information will create a performance problem (say, due to excessive logging). /// public void Info(string message) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Info(message); + _logger.Information(message); } /// @@ -163,8 +144,7 @@ public void Info(string message) /// public void Info(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Info(exception.ToString()); + _logger.Information(exception, ""); } /// @@ -173,10 +153,7 @@ public void Info(Exception exception) public void InfoFormat(string format, params object[] args) { if (IsInfoEnabled) - { - EnsureThreadIdPropertyExistsInContext(); - _logger.Info(string.Format(format, args)); - } + _logger.Information(format, args); } #endregion Info @@ -186,14 +163,13 @@ public void InfoFormat(string format, params object[] args) /// /// True iff logging has been configured to include DEBUG level logs. /// - public bool IsDebugEnabled => _logger.IsDebugEnabled; + public bool IsDebugEnabled => _logger.IsEnabled(LogEventLevel.Debug); /// /// Logs at the DEBUG level. This log level should be used for information that is non-critical and used mainly for troubleshooting common problems such as RUM injection or SQL explain plans. This level is not enabled by default so there is less concern about performance, but this level still should not be used for any logging that would cause significant performance, such as logging every transaction name for every transaction. /// public void Debug(string message) { - EnsureThreadIdPropertyExistsInContext(); _logger.Debug(message); } @@ -202,8 +178,7 @@ public void Debug(string message) /// public void Debug(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Debug(exception.ToString()); + _logger.Debug(exception, ""); } /// @@ -211,11 +186,8 @@ public void Debug(Exception exception) /// public void DebugFormat(string format, params object[] args) { - if (_logger.IsDebugEnabled) - { - EnsureThreadIdPropertyExistsInContext(); - _logger.Debug(string.Format(format, args)); - } + if (IsDebugEnabled) + _logger.Debug(format, args); } #endregion Debug @@ -225,15 +197,14 @@ public void DebugFormat(string format, params object[] args) /// /// True iff logging has been configured to include FINEST level logs. /// - public bool IsFinestEnabled => _logger.Logger.IsEnabledFor(log4net.Core.Level.Finest); + public bool IsFinestEnabled => _logger.IsEnabled(LogEventLevel.Verbose); /// /// Logs at the FINEST level. This log level should be used as a last resort for information that would otherwise be too expensive or too noisy to log at DEBUG level, such as logging every transaction name for every transaction. Useful for troubleshooting subtle problems like WCF's dual transactions. /// public void Finest(string message) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Logger.Log(typeof(Logger), log4net.Core.Level.Finest, message, null); + _logger.Verbose(message); } /// @@ -241,8 +212,7 @@ public void Finest(string message) /// public void Finest(Exception exception) { - EnsureThreadIdPropertyExistsInContext(); - _logger.Logger.Log(typeof(Logger), log4net.Core.Level.Finest, exception.ToString(), null); + _logger.Verbose(exception, ""); } /// @@ -251,11 +221,7 @@ public void Finest(Exception exception) public void FinestFormat(string format, params object[] args) { if (IsFinestEnabled) - { - EnsureThreadIdPropertyExistsInContext(); - var formattedMessage = string.Format(format, args); - _logger.Logger.Log(typeof(Logger), log4net.Core.Level.Finest, formattedMessage, null); - } + _logger.Verbose(format, args); } #endregion Finest diff --git a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs index 5c5000e1c..50fcab343 100644 --- a/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs +++ b/src/Agent/NewRelic/Agent/Core/Logging/LoggerBootstrapper.cs @@ -1,393 +1,234 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -using log4net; -using log4net.Appender; -using log4net.Core; -using log4net.Filter; -using log4net.Layout; using NewRelic.Agent.Core.Config; -using NewRelic.Agent.Core.Logging; -using NewRelic.Core.Logging; -using NewRelic.SystemInterfaces; using System; -using System.Collections.Generic; using System.IO; -using System.Reflection; -using log4netLogger = log4net.Repository.Hierarchy.Logger; +using System.Text; +using Serilog; +using Serilog.Core; +using Serilog.Formatting; +using Logger = NewRelic.Agent.Core.Logging.Logger; +using NewRelic.Agent.Core.Logging; +using Serilog.Templates; +#if NETFRAMEWORK +using Serilog.Events; +#endif namespace NewRelic.Agent.Core { public static class LoggerBootstrapper { - /// - /// The name of the Audit log appender. - /// - private static readonly string AuditLogAppenderName = "AuditLog"; - - /// - /// The name of the Console log appender. - /// - private static readonly string ConsoleLogAppenderName = "ConsoleLog"; - - /// - /// The name of the temporary event log appender. - /// - private static readonly string TemporaryEventLogAppenderName = "TemporaryEventLog"; - -#if NETFRAMEWORK - /// - /// The name of the event log appender. - /// - private static readonly string EventLogAppenderName = "EventLog"; - - /// - /// The name of the event log to log to. - /// - private static readonly string EventLogName = "Application"; - - /// - /// The event source name. - /// - private static readonly string EventLogSourceName = "New Relic .NET Agent"; -#endif - - /// - /// The numeric level of the Audit log. - /// - private static int AuditLogLevel = 150000; - - /// - /// The string name of the Audit log. - /// - private static string AuditLogName = "Audit"; - // Watch out! If you change the time format that the agent puts into its log files, other log parsers may fail. - private static ILayout AuditLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %level: %message\r\n"); - private static ILayout FileLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %6level: [pid: %property{pid}, tid: %property{threadid}] %message\r\n"); + //private static ILayout AuditLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %level: %message\r\n"); + //private static ILayout FileLogLayout = new PatternLayout("%utcdate{yyyy-MM-dd HH:mm:ss,fff} NewRelic %6level: [pid: %property{pid}, tid: %property{threadid}] %message\r\n"); - private static ILayout eventLoggerLayout = new PatternLayout("%level: %message"); + private static ExpressionTemplate AuditLogLayout = new ExpressionTemplate("{UtcDateTime(@t):yyyy-MM-dd HH:mm:ss,fff} NewRelic Audit: {@m}\n"); + private static ExpressionTemplate FileLogLayout = new ExpressionTemplate("{UtcDateTime(@t):yyyy-MM-dd HH:mm:ss,fff} NewRelic {NRLogLevel,6}: [pid: {pid}, tid: {tid}] {@m}\n{@x}"); - private static string STARTUP_APPENDER_NAME = "NEWRELIC_DOTNET_AGENT_STARTUP_APPENDER"; + private static LoggingLevelSwitch _loggingLevelSwitch = new LoggingLevelSwitch(); - private static List DeprecatedLogLevels = new List() { Level.Alert, Level.Critical, Level.Emergency, Level.Fatal, Level.Finer, Level.Trace, Level.Notice, Level.Severe, Level.Verbose, Level.Fine }; + private static InMemorySink _inMemorySink = new InMemorySink(); - public static void Initialize() + public static void UpdateLoggingLevel(string newLogLevel) { - CreateAuditLogLevel(); - var hierarchy = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly()) as log4net.Repository.Hierarchy.Hierarchy; - var logger = hierarchy.Root; - - // initially we will log to console and event log so it should only log items that need action - logger.Level = Level.Info; - - GlobalContext.Properties["pid"] = new ProcessStatic().GetCurrentProcess().Id; + _loggingLevelSwitch.MinimumLevel = newLogLevel.MapToSerilogLogLevel(); + } - SetupStartupLogAppender(logger); - SetupConsoleLogAppender(logger); - SetupTemporaryEventLogAppender(logger); + public static void Initialize() + { + var startupLoggerConfig = new LoggerConfiguration() + .Enrich.With(new ThreadIdEnricher(), new ProcessIdEnricher(), new NrLogLevelEnricher()) + .MinimumLevel.Information() + .ConfigureInMemoryLogSink() + .ConfigureEventLogSink(); + + // set the global Serilog logger to our startup logger instance, this gets replaced when ConfigureLogger() is called + Log.Logger = startupLoggerConfig.CreateLogger(); } /// /// Configures the agent logger. /// - /// - /// A - /// - /// - /// A - /// - /// - /// A - /// /// This should only be called once, as soon as you have a valid config. public static void ConfigureLogger(ILogConfig config) { - var hierarchy = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly()) as log4net.Repository.Hierarchy.Hierarchy; - var logger = hierarchy.Root; - - SetupLogLevel(logger, config); + SetupLogLevel(config); - SetupFileLogAppender(logger, config); - SetupAuditLogger(logger, config); - SetupDebugLogAppender(logger); - logger.RemoveAppender(TemporaryEventLogAppenderName); + var loggerConfig = new LoggerConfiguration() + .MinimumLevel.ControlledBy(_loggingLevelSwitch) + .ConfigureAuditLogSink(config) + .Enrich.With(new ThreadIdEnricher(), new ProcessIdEnricher(), new NrLogLevelEnricher()) + .ConfigureFileSink(config) + .ConfigureDebugSink(); - if (!config.Console) + if (config.Console) { - logger.RemoveAppender(ConsoleLogAppenderName); + loggerConfig = loggerConfig.ConfigureConsoleSink(); } - logger.Repository.Configured = true; + // configure the global singleton logger instance (which remains specific to the Agent by way of ILRepack) + var configuredLogger = loggerConfig.CreateLogger(); - // We have now bootstrapped the agent logger, so - // remove the startup appender, then send its messages - // to the agent logger, which will get picked up by - // the other appenders. - ShutdownStartupLogAppender(logger); + EchoInMemoryLogsToConfiguredLogger(configuredLogger); - Log.Initialize(new Logger()); - } + Log.Logger = configuredLogger; - /// - /// Gets the log4net Level of the "Audit" log level. - /// - /// The "Audit" log4net Level. - public static Level GetAuditLevel() - { - return LogManager.GetRepository(Assembly.GetCallingAssembly()).LevelMap[AuditLogName]; + NewRelic.Core.Logging.Log.Initialize(new Logger()); } - private static void ShutdownStartupLogAppender(log4netLogger logger) + private static void EchoInMemoryLogsToConfiguredLogger(ILogger configuredLogger) { - var startupAppender = logger.GetAppender(STARTUP_APPENDER_NAME) as MemoryAppender; - if (startupAppender != null) + foreach (var logEvent in _inMemorySink.LogEvents) { - LoggingEvent[] events = startupAppender.GetEvents(); - logger.RemoveAppender(startupAppender); - - if (events != null) - { - foreach (LoggingEvent logEvent in events) - { - logger.Log(logEvent.Level, logEvent.MessageObject, null); - } - } + configuredLogger.Write(logEvent); } - } - private static FileAppender.LockingModelBase GetFileLockingModel(ILogConfig config) - { - if (config.FileLockingModelSpecified) - { - if (config.FileLockingModel.Equals(configurationLogFileLockingModel.minimal)) - { - return (FileAppender.LockingModelBase)new FileAppender.MinimalLock(); - } - else - { - return new FileAppender.ExclusiveLock(); - } - - } - return new FileAppender.MinimalLock(); - } - - /// - /// Creates a new AuditLogName log level at level AuditLogLevel (higher than Emergency log level) and registers it as a log4net level. - /// - private static void CreateAuditLogLevel() - { - Level auditLevel = new Level(AuditLogLevel, AuditLogName); - LogManager.GetRepository(Assembly.GetCallingAssembly()).LevelMap.Add(auditLevel); - } - - /// - /// Returns a filter set to immediately deny any log events that are "Audit" level. - /// - /// A filter set to imediately deny any log events that are "Audit" level. - private static IFilter GetNoAuditFilter() - { - LevelMatchFilter filter = new LevelMatchFilter(); - filter.LevelToMatch = GetAuditLevel(); - filter.AcceptOnMatch = false; - return filter; - } - - /// - /// Returns a filter set to immediately accept any log events that are "Audit" level. - /// - /// A filter set to immediately accept any log events that are "Audit" level. - private static IFilter GetAuditFilter() - { - LevelMatchFilter filter = new LevelMatchFilter(); - filter.LevelToMatch = GetAuditLevel(); - filter.AcceptOnMatch = true; - return filter; + _inMemorySink.Dispose(); } /// /// Sets the log level for logger to either the level provided by the config or an public default. /// - /// The logger to set the level of. /// The LogConfig to look for the level setting in. - private static void SetupLogLevel(log4netLogger logger, ILogConfig config) - { - logger.Level = logger.Hierarchy.LevelMap[config.LogLevel]; - - if (logger.Level == null) - { - logger.Level = log4net.Core.Level.Info; - } + private static void SetupLogLevel(ILogConfig config) => _loggingLevelSwitch.MinimumLevel = config.LogLevel.MapToSerilogLogLevel(); - if (logger.Level == GetAuditLevel()) - { - logger.Level = Level.Info; - logger.Log(Level.Warn, $"Log level was set to {AuditLogName} which is not a valid log level. To enable audit logging, set the auditLog configuration option to true. Log level will be treated as INFO for this run.", null); - } - - if (IsLogLevelDeprecated(logger.Level)) - { - logger.Log(Level.Warn, string.Format( - "The log level, {0}, set in your configuration file has been deprecated. The agent will still log correctly, but you should change to a supported logging level as described in newrelic.config or the online documentation.", - logger.Level.ToString()), null); - } - } - - private static bool IsLogLevelDeprecated(Level level) + private static LoggerConfiguration ConfigureInMemoryLogSink(this LoggerConfiguration loggerConfiguration) { - foreach (var l in DeprecatedLogLevels) - { - if (l.Name.Equals(level.Name, StringComparison.InvariantCultureIgnoreCase)) return true; - } - return false; - } - - /// - /// A memory appender for logging to memory during startup. Log messages will be re-logged after configuration is loaded. - /// - /// - private static void SetupStartupLogAppender(log4netLogger logger) - { - var startupAppender = new MemoryAppender(); - startupAppender.Name = STARTUP_APPENDER_NAME; - startupAppender.Layout = LoggerBootstrapper.FileLogLayout; - startupAppender.ActivateOptions(); - - logger.AddAppender(startupAppender); - logger.Repository.Configured = true; + // formatter not needed since this will be pushed to other sinks for output. + return loggerConfiguration + .WriteTo.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .WriteTo.Sink(_inMemorySink); + }); } /// - /// A temporary event log appender for logging during startup (before config is loaded) + /// Add the Event Log sink if running on .NET Framework /// - /// - private static void SetupTemporaryEventLogAppender(log4netLogger logger) + /// + private static LoggerConfiguration ConfigureEventLogSink(this LoggerConfiguration loggerConfiguration) { #if NETFRAMEWORK - var appender = new EventLogAppender(); - appender.Layout = eventLoggerLayout; - appender.Name = TemporaryEventLogAppenderName; - appender.LogName = EventLogName; - appender.ApplicationName = EventLogSourceName; - appender.Threshold = Level.Warn; - appender.AddFilter(GetNoAuditFilter()); - appender.ActivateOptions(); - - logger.AddAppender(appender); -#endif - } + const string eventLogName = "Application"; + const string eventLogSourceName = "New Relic .NET Agent"; - /// - /// Setup the event log appender and attach it to a logger. - /// - /// The logger you want to attach the event log appender to. - /// The configuration for the appender. - private static void SetupEventLogAppender(log4netLogger logger, ILogConfig config) - { -#if NETFRAMEWORK - var appender = new EventLogAppender(); - appender.Layout = eventLoggerLayout; - appender.Threshold = Level.Warn; - appender.Name = EventLogAppenderName; - appender.LogName = EventLogName; - appender.ApplicationName = EventLogSourceName; - appender.AddFilter(GetNoAuditFilter()); - appender.ActivateOptions(); - - logger.AddAppender(appender); + loggerConfiguration + .WriteTo.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .WriteTo.EventLog( + source: eventLogSourceName, + logName: eventLogName, + restrictedToMinimumLevel: LogEventLevel.Warning, + outputTemplate: "{Level}: {Message}{NewLine}{Exception}" + ); + }); #endif + return loggerConfiguration; } /// - /// Setup the debug log appender and attach it to a logger. + /// Configure the debug sink /// - /// The logger you want to attach the event log appender to. - private static void SetupDebugLogAppender(log4netLogger logger) + private static LoggerConfiguration ConfigureDebugSink(this LoggerConfiguration loggerConfiguration) { #if DEBUG - // Create the debug appender and connect it to our logger. - var debugAppender = new DebugAppender(); - debugAppender.Layout = FileLogLayout; - debugAppender.AddFilter(GetNoAuditFilter()); - logger.AddAppender(debugAppender); + loggerConfiguration + .WriteTo.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .WriteTo.Debug(FileLogLayout); + }); #endif + return loggerConfiguration; } /// - /// Setup the console log appender and attach it to a logger. + /// Configure the console sink /// - /// The logger you want to attach the console log appender to. - private static void SetupConsoleLogAppender(log4netLogger logger) + private static LoggerConfiguration ConfigureConsoleSink(this LoggerConfiguration loggerConfiguration) { - var appender = new ConsoleAppender(); - appender.Name = ConsoleLogAppenderName; - appender.Layout = FileLogLayout; - appender.AddFilter(GetNoAuditFilter()); - appender.ActivateOptions(); - logger.AddAppender(appender); + return loggerConfiguration + .WriteTo.Async(a => + a.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .WriteTo.Console(FileLogLayout); + }) + ); } /// - /// Setup the file log appender and attach it to a logger. + /// Configure the file log sink /// - /// The logger you want to attach the file appender to. + /// /// The configuration for the appender. - /// If an exception occurs, the Event Log Appender is setup - /// to handle output. - private static void SetupFileLogAppender(log4netLogger logger, ILogConfig config) + private static LoggerConfiguration ConfigureFileSink(this LoggerConfiguration loggerConfiguration, ILogConfig config) { string logFileName = config.GetFullLogFileName(); try { - var appender = SetupRollingFileAppender(config, logFileName, "FileLog", FileLogLayout); - appender.AddFilter(GetNoAuditFilter()); - appender.ActivateOptions(); - logger.AddAppender(appender); + loggerConfiguration + .WriteTo + .Async(a => + a.Logger(configuration => + { + configuration + .ExcludeAuditLog() + .ConfigureRollingLogSink(logFileName, FileLogLayout); + }) + ); } - catch (Exception) + catch (Exception ex) { - // Fallback to the event logger if we cannot setup a file logger. - SetupEventLogAppender(logger, config); + Log.Logger.Warning(ex, "Unexpected exception when configuring file sink."); +#if NETFRAMEWORK + // Fallback to the event log sink if we cannot setup a file logger. + Log.Logger.Warning("Falling back to EventLog sink."); + loggerConfiguration.ConfigureEventLogSink(); +#endif } + + return loggerConfiguration; } /// /// Setup the audit log file appender and attach it to a logger. /// - /// The logger you want to attach the audit log appender to. - /// The configuration for the appender. - private static void SetupAuditLogger(log4netLogger logger, ILogConfig config) + private static LoggerConfiguration ConfigureAuditLogSink(this LoggerConfiguration loggerConfiguration, ILogConfig config) { - if (!config.IsAuditLogEnabled) return; + if (!config.IsAuditLogEnabled) return loggerConfiguration; string logFileName = config.GetFullLogFileName().Replace(".log", "_audit.log"); - try - { - var appender = SetupRollingFileAppender(config, logFileName, AuditLogAppenderName, AuditLogLayout); - appender.AddFilter(GetAuditFilter()); - appender.AddFilter(new DenyAllFilter()); - appender.ActivateOptions(); - logger.AddAppender(appender); - } - catch (Exception) - { } + return loggerConfiguration + .WriteTo + .Logger(configuration => + { + configuration + .MinimumLevel.Fatal() // We've hijacked Fatal log level as the level to use when writing an audit log + .IncludeOnlyAuditLog() + .ConfigureRollingLogSink(logFileName, AuditLogLayout); + }); } /// /// Sets up a rolling file appender using defaults shared for all our rolling file appenders. /// - /// The configuration for the appender. + /// /// The name of the file this appender will write to. - /// The name of this appender. + /// /// This does not call appender.ActivateOptions or add the appender to the logger. - private static RollingFileAppender SetupRollingFileAppender(ILogConfig config, string fileName, string appenderName, ILayout layout) + private static LoggerConfiguration ConfigureRollingLogSink(this LoggerConfiguration loggerConfiguration, string fileName, ITextFormatter textFormatter) { - var log = log4net.LogManager.GetLogger(typeof(AgentManager)); - // check that the log file is accessible try { @@ -395,35 +236,31 @@ private static RollingFileAppender SetupRollingFileAppender(ILogConfig config, s var directory = Path.GetDirectoryName(fileName); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); - using (File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write)) { } } catch (Exception exception) { - log.ErrorFormat("Unable to write the {0} log to \"{1}\": {2}", appenderName, fileName, exception.Message); + Log.Logger.Warning(exception, $"Unable to write logfile at \"{fileName}\""); throw; } try { - var appender = new RollingFileAppender(); - - appender.LockingModel = GetFileLockingModel(config); - appender.Layout = layout; - appender.File = fileName; - appender.Encoding = System.Text.Encoding.UTF8; - appender.AppendToFile = true; - appender.RollingStyle = RollingFileAppender.RollingMode.Size; - appender.MaxSizeRollBackups = 4; - appender.MaxFileSize = 50 * 1024 * 1024; // 50MB - appender.StaticLogFileName = true; - appender.ImmediateFlush = true; - - return appender; + return loggerConfiguration + .WriteTo + .File(path: fileName, + formatter: textFormatter, + fileSizeLimitBytes: 50 * 1024 * 1024, + encoding: Encoding.UTF8, + rollOnFileSizeLimit: true, + retainedFileCountLimit: 4, + shared: true, + buffered: false + ); } catch (Exception exception) { - log.ErrorFormat("Unable to configure the {0} log file appender for \"{1}\": {2}", appenderName, fileName, exception.Message); + Log.Logger.Warning(exception, $"Unexpected exception while configuring file logging for \"{fileName}\""); throw; } } diff --git a/src/Agent/NewRelic/Agent/Core/Logging/NrLogLevelEnricher.cs b/src/Agent/NewRelic/Agent/Core/Logging/NrLogLevelEnricher.cs new file mode 100644 index 000000000..ae48b788f --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Logging/NrLogLevelEnricher.cs @@ -0,0 +1,21 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using NewRelic.Core.CodeAttributes; +using Serilog.Core; +using Serilog.Events; + +namespace NewRelic.Agent.Core +{ + /// + /// Maps serilog log level to corresponding "legacy" log4net loglevel and adds the mapped value as a property named NRLogLevel + /// + internal class NrLogLevelEnricher : ILogEventEnricher + { + [NrExcludeFromCodeCoverage] + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("NRLogLevel", logEvent.Level.TranslateLogLevel())); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs new file mode 100644 index 000000000..c5171e9c7 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs @@ -0,0 +1,21 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using NewRelic.Core.CodeAttributes; +using NewRelic.SystemInterfaces; +using Serilog.Core; +using Serilog.Events; + +namespace NewRelic.Agent.Core +{ + [NrExcludeFromCodeCoverage] + internal class ProcessIdEnricher : ILogEventEnricher + { + private static int _pid = new ProcessStatic().GetCurrentProcess().Id; + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("pid", _pid)); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs b/src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs new file mode 100644 index 000000000..5fbc34b31 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs @@ -0,0 +1,20 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Threading; +using NewRelic.Core.CodeAttributes; +using Serilog.Core; +using Serilog.Events; + +namespace NewRelic.Agent.Core +{ + [NrExcludeFromCodeCoverage] + internal class ThreadIdEnricher : ILogEventEnricher + { + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty( + "tid", Thread.CurrentThread.ManagedThreadId)); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Metrics/MetricNameService.cs b/src/Agent/NewRelic/Agent/Core/Metrics/MetricNameService.cs index 93b9cced2..8826d2158 100644 --- a/src/Agent/NewRelic/Agent/Core/Metrics/MetricNameService.cs +++ b/src/Agent/NewRelic/Agent/Core/Metrics/MetricNameService.cs @@ -43,7 +43,7 @@ public string NormalizeUrl(string url) { if (_configuration.WebTransactionsApdex.TryGetValue(transactionName, out double apdexT)) { - return TimeSpan.FromSeconds(Convert.ToSingle(apdexT)); + return TimeSpan.FromSeconds(apdexT); } return null; } diff --git a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core.Metric/MetricNames.cs b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core.Metric/MetricNames.cs index 882d7f27b..96cbd2661 100644 --- a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core.Metric/MetricNames.cs +++ b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core.Metric/MetricNames.cs @@ -1040,6 +1040,7 @@ public static string GetPerDestinationAreaDataUsageMetricName(string destination private const string LoggingMetrics = "Logging"; private const string LoggingMetricsDotnetLines = LoggingMetrics + PathSeparator + "lines"; + private const string LoggingMetricsDotnetDenied = LoggingMetrics + PathSeparator + "denied"; private const string SupportabilityLoggingEventsPs = SupportabilityPs + "Logging" + PathSeparator; public const string SupportabilityLoggingEventsSent = SupportabilityLoggingEventsPs + Forwarding + PathSeparator + "Sent"; public const string SupportabilityLoggingEventsCollected = SupportabilityLoggingEventsPs + Forwarding + PathSeparator + "Seen"; @@ -1055,6 +1056,16 @@ public static string GetLoggingMetricsLinesName() return LoggingMetricsDotnetLines; } + public static string GetLoggingMetricsDeniedBySeverityName(string logLevel) + { + return LoggingMetricsDotnetDenied + PathSeparator + logLevel; + } + + public static string GetLoggingMetricsDeniedName() + { + return LoggingMetricsDotnetDenied; + } + private const string Enabled = "enabled"; private const string Disabled = "disabled"; private const string Metrics = "Metrics"; diff --git a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentManager.cs b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentManager.cs index e4bff496a..b280ad896 100644 --- a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentManager.cs +++ b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentManager.cs @@ -152,7 +152,7 @@ private AgentManager() private void AssertAgentEnabled(configuration config) { if (!Configuration.AgentEnabled) - throw new Exception(string.Format("The New Relic agent is disabled. Update {0} to re-enable it.", config.AgentEnabledAt)); + throw new Exception(string.Format("The New Relic agent is disabled. Update {0} to re-enable it.", config.AgentEnabledAt ?? config.ConfigurationFileName)); if ("REPLACE_WITH_LICENSE_KEY".Equals(Configuration.AgentLicenseKey)) throw new Exception("Please set your license key."); @@ -373,7 +373,7 @@ private void Shutdown(bool cleanShutdown) finally { Dispose(); - log4net.LogManager.Shutdown(); + Serilog.Log.CloseAndFlush(); } } diff --git a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentShim.cs b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentShim.cs index 950fe1310..eb6ff4c61 100644 --- a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentShim.cs +++ b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/AgentShim.cs @@ -15,12 +15,9 @@ namespace NewRelic.Agent.Core /// public class AgentShim { - private static log4net.ILog Log; - static void Initialize() { AgentInitializer.InitializeAgent(); - Log = log4net.LogManager.GetLogger(typeof(AgentShim)); } #if NETSTANDARD2_0 @@ -229,7 +226,7 @@ public static ITracer GetTracer( { try { - Log.Debug("Exception occurred in AgentShim.GetTracer", exception); + Serilog.Log.Logger.Debug(exception, "Exception occurred in AgentShim.GetTracer"); } catch { @@ -263,7 +260,7 @@ public static void FinishTracer(object tracerObject, object returnValue, object ITracer tracer = tracerObject as ITracer; if (tracer == null) { - Log.ErrorFormat("AgentShim.FinishTracer received a tracer object but it was not an ITracer. {0}", tracerObject); + Serilog.Log.Logger.Error($"AgentShim.FinishTracer received a tracer object but it was not an ITracer. {tracerObject}"); return; } @@ -271,7 +268,7 @@ public static void FinishTracer(object tracerObject, object returnValue, object Exception exception = exceptionObject as Exception; if (exception == null && exceptionObject != null) { - Log.ErrorFormat("AgentShim.FinishTracer received an exception object but it was not an Exception. {0}", exceptionObject); + Serilog.Log.Logger.Error($"AgentShim.FinishTracer received an exception object but it was not an Exception. {exceptionObject}"); return; } @@ -291,7 +288,7 @@ public static void FinishTracer(object tracerObject, object returnValue, object { try { - Log.Debug("Exception occurred in AgentShim.FinishTracer", exception); + Serilog.Log.Logger.Debug(exception,"Exception occurred in AgentShim.FinishTracer"); } catch { diff --git a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/RuntimeEnvironmentInfo.cs b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/RuntimeEnvironmentInfo.cs index 067e36c89..b3fa14f30 100644 --- a/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/RuntimeEnvironmentInfo.cs +++ b/src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core/RuntimeEnvironmentInfo.cs @@ -4,6 +4,7 @@ using System; using System.IO; using NewRelic.Core.CodeAttributes; +using NewRelic.Core.Logging; namespace NewRelic.Agent.Core { @@ -88,8 +89,7 @@ private static string GetFreeBSDVersion() } catch (Exception ex) { - log4net.ILog logger = log4net.LogManager.GetLogger(typeof(RuntimeEnvironmentInfo)); - logger.Debug($"Unable to report Operating System: Unexpected exception in GetFreeBSDVersion: {ex}"); + Serilog.Log.Logger.Debug(ex, "Unable to report Operating System: Unexpected exception in GetFreeBSDVersion."); } #endif return string.Empty; @@ -152,8 +152,7 @@ private static DistroInfo LoadDistroInfo() } catch (Exception ex) { - log4net.ILog logger = log4net.LogManager.GetLogger(typeof(RuntimeEnvironmentInfo)); - logger.Debug($"Unable to report Operating System: Unexpected exception in LoadDistroInfo: {ex}"); + Serilog.Log.Logger.Debug(ex, $"Unable to report Operating System: Unexpected exception in LoadDistroInfo."); } return result; diff --git a/src/Agent/NewRelic/Agent/Core/Samplers/ThreadStatsSampler.cs b/src/Agent/NewRelic/Agent/Core/Samplers/ThreadStatsSampler.cs index 68b4a8321..fcc769131 100644 --- a/src/Agent/NewRelic/Agent/Core/Samplers/ThreadStatsSampler.cs +++ b/src/Agent/NewRelic/Agent/Core/Samplers/ThreadStatsSampler.cs @@ -79,7 +79,9 @@ public override void Dispose() { base.Dispose(); _listener?.StopListening(); - _listener?.Dispose(); +#if NETFRAMEWORK // calling .Dispose() in .NET 7 explodes. No idea why. + _listener?.Dispose(); +#endif _listener = null; } } diff --git a/src/Agent/NewRelic/Agent/Core/Segments/NoOpSegment.cs b/src/Agent/NewRelic/Agent/Core/Segments/NoOpSegment.cs index ab5c5fcbd..4a2892d3b 100644 --- a/src/Agent/NewRelic/Agent/Core/Segments/NoOpSegment.cs +++ b/src/Agent/NewRelic/Agent/Core/Segments/NoOpSegment.cs @@ -18,6 +18,7 @@ public class NoOpSegment : ISegment, ISegmentExperimental, ISegmentDataState private readonly IAttributeDefinitions _attribDefs = new AttributeDefinitions(new AttributeFilter(new AttributeFilter.Settings())); + public bool IsDone => true; // the segment is technically done since it is does nothing. public bool IsValid => false; public bool DurationShouldBeDeductedFromParent { get; set; } = false; public bool AlwaysDeductChildDuration { private get; set; } = false; @@ -64,5 +65,10 @@ public ISpan SetName(string name) { return this; } + + public string GetCategory() + { + return string.Empty; + } } } diff --git a/src/Agent/NewRelic/Agent/Core/Segments/Segment.cs b/src/Agent/NewRelic/Agent/Core/Segments/Segment.cs index c3667805b..938e1e939 100644 --- a/src/Agent/NewRelic/Agent/Core/Segments/Segment.cs +++ b/src/Agent/NewRelic/Agent/Core/Segments/Segment.cs @@ -17,6 +17,7 @@ using System.Diagnostics; using NewRelic.Agent.Core.Configuration; using NewRelic.Agent.Core.Utils; +using NewRelic.Agent.Extensions.Providers.Wrapper; namespace NewRelic.Agent.Core.Segments { @@ -106,7 +107,13 @@ public Segment(TimeSpan relativeStartTime, TimeSpan? duration, Segment segment, SpanId = segment.SpanId; } + public bool IsDone + { + get { return RelativeEndTime.HasValue; } + } + public bool IsValid => true; + public bool DurationShouldBeDeductedFromParent { get; set; } = false; public bool AlwaysDeductChildDuration { private get; set; } = false; @@ -138,14 +145,11 @@ public void End() // this segment may have already been forced to end if (RelativeEndTime.HasValue == false) { + // This order is to ensure the segment end time is correct, but also not mark the segment as IsDone so that CleanUp ignores it. var endTime = _transactionSegmentState.GetRelativeTime(); + Agent.Instance?.StackExchangeRedisCache?.Harvest(this); RelativeEndTime = endTime; - if (Agent.Instance.StackExchangeRedisCache != null) - { - Agent.Instance.StackExchangeRedisCache.Harvest(this); - } - Finish(); _transactionSegmentState.CallStackPop(this, true); @@ -432,5 +436,10 @@ public ISpan SetName(string name) SegmentNameOverride = name; return this; } + + public string GetCategory() + { + return EnumNameCache.GetName(Data.SpanCategory); + } } } diff --git a/src/Agent/NewRelic/Agent/Core/Time/SimpleSchedulingService.cs b/src/Agent/NewRelic/Agent/Core/Time/SimpleSchedulingService.cs new file mode 100644 index 000000000..b7faf8d1d --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Time/SimpleSchedulingService.cs @@ -0,0 +1,46 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using NewRelic.Agent.Api.Experimental; +using NewRelic.Agent.Core.Utilities; + +namespace NewRelic.Agent.Core.Time +{ + public class SimpleSchedulingService : DisposableService, ISimpleSchedulingService + { + private readonly IScheduler _scheduler; + private readonly List _executingActions; + + public SimpleSchedulingService(IScheduler scheduler) + { + _scheduler = scheduler; + _executingActions = new List(); + } + + public void StartExecuteEvery(Action action, TimeSpan timeBetweenExecutions, TimeSpan? optionalInitialDelay = null) + { + _scheduler.ExecuteEvery(action, timeBetweenExecutions, optionalInitialDelay); + _executingActions.Add(action); + } + + public void StopExecuting(Action action) + { + _scheduler.StopExecuting(action); + _executingActions.Remove(action); + } + + public override void Dispose() + { + foreach (var executingAction in _executingActions) + { + _scheduler.StopExecuting(executingAction); + } + + _executingActions.Clear(); + + base.Dispose(); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs b/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs index 71c3e16e1..6af2f98fb 100644 --- a/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs +++ b/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs @@ -1020,7 +1020,7 @@ public void Ignore() if (Log.IsFinestEnabled) { var transactionName = CandidateTransactionName.CurrentTransactionName; - var transactionMetricName = Agent._transactionMetricNameMaker.GetTransactionMetricName(transactionName); + var transactionMetricName = Agent?._transactionMetricNameMaker?.GetTransactionMetricName(transactionName); var stackTrace = new StackTrace(); Log.Finest($"Transaction \"{transactionMetricName}\" is being ignored from {stackTrace}"); } diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/EventBus.cs b/src/Agent/NewRelic/Agent/Core/Utilities/EventBus.cs index c2ca77c9e..bddb6a954 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilities/EventBus.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilities/EventBus.cs @@ -8,7 +8,6 @@ namespace NewRelic.Agent.Core.Utilities { public static class EventBus { - private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(EventBus)); private static event Action Events = T => { }; private static readonly ReaderWriterLock Lock = new ReaderWriterLock(); private static readonly ReaderLockGuard ReaderLockGuard = new ReaderLockGuard(Lock); @@ -63,7 +62,7 @@ public static void Publish(T message) } catch (Exception exception) { - Log.Error($"Exception thrown from event handler. Event handlers should not let exceptions bubble out of them: {exception}"); + Serilog.Log.Logger.Error(exception, "Exception thrown from event handler. Event handlers should not let exceptions bubble out of them."); } } } diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs b/src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs index f5e10b9a1..fffc8bfa6 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs @@ -62,7 +62,9 @@ public static void Initialize(string installPathExtensionsDirectory) { "DataReaderWrapperAsync", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") }, { "OpenConnectionTracer", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") }, + { "OpenConnectionTracerAsync", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") }, { "OpenConnectionWrapper", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") }, + { "OpenConnectionWrapperAsync", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") }, //The NewRelic.Providers.Wrapper.SerilogLogging.dll depends on the Serilog.dll; therefore, it should //only be loaded by the agent when Serilog is used otherwise assembly load exception will occur. diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/RequestBus.cs b/src/Agent/NewRelic/Agent/Core/Utilities/RequestBus.cs index 7dad1e56f..1cf3d0371 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilities/RequestBus.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilities/RequestBus.cs @@ -15,8 +15,6 @@ namespace NewRelic.Agent.Core.Utilities /// Responders are not required to answer and there may not be a responder setup for any given request so you must be prepared to handle either no callback, an empty enumeration or default(TResponse), depending on which Post overload you use. public static class RequestBus { - private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(RequestBus)); - public delegate void ResponsesCallback(IEnumerable responses); public delegate void ResponseCallback(TResponse response); @@ -73,7 +71,7 @@ public static void Post(TRequest request, ResponsesCallback responsesCallback) } catch (Exception exception) { - Log.Error($"Exception thrown from request handler. Request handlers should not let exceptions bubble out of them: {exception}"); + Serilog.Log.Logger.Error(exception, "Exception thrown from request handler. Request handlers should not let exceptions bubble out of them."); } } diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/SignalableAction.cs b/src/Agent/NewRelic/Agent/Core/Utilities/SignalableAction.cs index 72dd2a03e..9c681666c 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilities/SignalableAction.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilities/SignalableAction.cs @@ -12,11 +12,13 @@ public class SignalableAction : IDisposable private readonly object _lock = new object(); private bool _signaled; + private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + public SignalableAction(Action action, int delay) { void Action() { - while (true) + while (!_cancellationTokenSource.IsCancellationRequested) { lock (_lock) { @@ -49,7 +51,7 @@ public void Signal() public void Dispose() { - _worker.Abort(); + _cancellationTokenSource.Cancel(); } } } diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/UpdatedLoadedModulesService.cs b/src/Agent/NewRelic/Agent/Core/Utilities/UpdatedLoadedModulesService.cs new file mode 100644 index 000000000..63a1bfb1b --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Utilities/UpdatedLoadedModulesService.cs @@ -0,0 +1,65 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using NewRelic.Agent.Configuration; +using NewRelic.Agent.Core.DataTransport; +using NewRelic.Agent.Core.Time; +using NewRelic.Agent.Core.WireModels; + +namespace NewRelic.Agent.Core.Utilities +{ + public class UpdatedLoadedModulesService : DisposableService + { + private readonly IList _loadedModulesSeen = new List(); + private readonly IScheduler _scheduler; + private readonly IDataTransportService _dataTransportService; + private readonly IConfigurationService _configurationService; + private IConfiguration _configuration => _configurationService?.Configuration; + + public UpdatedLoadedModulesService(IScheduler scheduler, IDataTransportService dataTransportService, IConfigurationService configurationService) + { + _configurationService = configurationService; + _dataTransportService = dataTransportService; + _scheduler = scheduler; + _scheduler.ExecuteEvery(GetLoadedModules, _configuration.UpdateLoadedModulesCycle); + } + + private void GetLoadedModules() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies() + .Where(assembly => assembly != null) + .Where(assembly => !_loadedModulesSeen.Contains(assembly.GetName().Name)) +#if NETFRAMEWORK + .Where(assembly => !(assembly is System.Reflection.Emit.AssemblyBuilder)) +#endif + .ToList(); + + if (assemblies.Count < 1) + { + return; + } + + var loadedModulesCollection = LoadedModuleWireModelCollection.Build(assemblies); + + SendUpdatedLoadedModules(loadedModulesCollection); + } + + private void SendUpdatedLoadedModules(LoadedModuleWireModelCollection loadedModulesCollection) + { + var responseStatus = _dataTransportService.Send(loadedModulesCollection); + if (responseStatus != DataTransportResponseStatus.RequestSuccessful) + { + // Try again next time + return; + } + + foreach (var module in loadedModulesCollection.LoadedModules) + { + _loadedModulesSeen.Add(module.Data["namespace"].ToString()); + } + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs b/src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs index 9c65cd82c..1223811a7 100644 --- a/src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs +++ b/src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs @@ -196,6 +196,10 @@ public interface IMetricBuilder MetricWireModel TryBuildLoggingMetricsLinesCountMetric(int count); + MetricWireModel TryBuildLoggingMetricsDeniedCountBySeverityMetric(string logLevel, int count); + + MetricWireModel TryBuildLoggingMetricsDeniedCountMetric(int count); + MetricWireModel TryBuildSupportabilityLoggingEventsCollectedMetric(); MetricWireModel TryBuildSupportabilityLoggingEventsSentMetric(int loggingEventCount); diff --git a/src/Agent/NewRelic/Agent/Core/WireModels/LoadedModuleWireModel.cs b/src/Agent/NewRelic/Agent/Core/WireModels/LoadedModuleWireModel.cs new file mode 100644 index 000000000..63ec4a6da --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/WireModels/LoadedModuleWireModel.cs @@ -0,0 +1,23 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Generic; + +namespace NewRelic.Agent.Core.WireModels +{ + public class LoadedModuleWireModel + { + public string AssemblyName { get; } + + public string Version { get; } + + public Dictionary Data { get; } + + public LoadedModuleWireModel(string assemblyName, string version) + { + AssemblyName = assemblyName; + Version = version; + Data = new Dictionary(); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/WireModels/LoadedModuleWireModelCollection.cs b/src/Agent/NewRelic/Agent/Core/WireModels/LoadedModuleWireModelCollection.cs new file mode 100644 index 000000000..f03c6d504 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/WireModels/LoadedModuleWireModelCollection.cs @@ -0,0 +1,223 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Security.Cryptography; +using NewRelic.Agent.Core.JsonConverters; +using Newtonsoft.Json; + +namespace NewRelic.Agent.Core.WireModels +{ + [JsonConverter(typeof(LoadedModuleWireModelCollectionJsonConverter))] + public class LoadedModuleWireModelCollection + { + public List LoadedModules { get; } + + private LoadedModuleWireModelCollection() + { + LoadedModules = new List(); + } + + public static LoadedModuleWireModelCollection Build(IList assemblies) + { + var loadedModulesCollection = new LoadedModuleWireModelCollection(); + foreach (var assembly in assemblies) + { + if (!TryGetAssemblyName(assembly, out var assemblyName)) + { + // no way to properly track this assembly + continue; + } + + var assemblyDetails = assembly.GetName(); + + var loadedModule = new LoadedModuleWireModel(assemblyName, assemblyDetails.Version.ToString()); + + loadedModule.Data.Add("namespace", assemblyDetails.Name); + + if (TryGetPublicKeyToken(assemblyDetails, out var publicKey)) + { + loadedModule.Data.Add("publicKeyToken", publicKey); + } + + if (TryGetShaFileHashes(assembly, out var sha1FileHash, out var sha512FileHash)) + { + loadedModule.Data.Add("sha1Checksum", sha1FileHash); + loadedModule.Data.Add("sha512Checksum", sha512FileHash); + } + + if (TryGetAssemblyHashCode(assembly, out var assemblyHashCode)) + { + loadedModule.Data.Add("assemblyHashCode", assemblyHashCode); + } + if (TryGetCompanyName(assembly, out var companyName)) + { + loadedModule.Data.Add("Implementation-Vendor", companyName); + } + if (TryGetCopyright(assembly, out var copyright)) + { + loadedModule.Data.Add("copyright", copyright); + } + + // Use the .Name here and in GetLoadedModules + loadedModulesCollection.LoadedModules.Add(loadedModule); + } + + return loadedModulesCollection; + } + + private static bool TryGetAssemblyName(Assembly assembly, out string assemblyName) + { + try + { + if (assembly.IsDynamic) + { + assemblyName = assembly.GetName().Name; + } + else + { + assemblyName = Path.GetFileName(assembly.Location); + } + + if (string.IsNullOrWhiteSpace(assemblyName)) + { + return false; + } + + return true; + } + catch + { + assemblyName = null; + return false; + } + } + + private static bool TryGetPublicKeyToken(AssemblyName assemblyDetails, out string publicKey) + { + try + { + publicKey = BitConverter.ToString(assemblyDetails.GetPublicKeyToken()).Replace("-", ""); + if (string.IsNullOrWhiteSpace(publicKey)) + { + return false; + } + + return true; + } + catch + { + publicKey = null; + return false; + } + } + + private static bool TryGetShaFileHashes(Assembly assembly, out string sha1FileHash, out string sha512FileHash) + { + try + { + var location = assembly.Location; + if (string.IsNullOrEmpty(location)) + { + sha1FileHash = null; + sha512FileHash = null; + return false; + } + + if (!File.Exists(location)) + { + sha1FileHash = null; + sha512FileHash = null; + return false; + } + + using (var fs = new FileStream(location, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var sha1 = SHA1.Create(); + var sha512 = SHA512.Create(); + var buffer = new byte[4096]; // 4KB + int bytesRead; + while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) + { + sha1.TransformBlock(buffer, 0, bytesRead, null, 0); + sha512.TransformBlock(buffer, 0, bytesRead, null, 0); + } + + sha1.TransformFinalBlock(buffer, 0, 0); + sha512.TransformFinalBlock(buffer, 0, 0); + sha1FileHash = BitConverter.ToString(sha1.Hash).Replace("-", "").ToLowerInvariant(); + sha512FileHash = BitConverter.ToString(sha512.Hash).Replace("-", "").ToLowerInvariant(); + sha1.Dispose(); + sha512.Dispose(); + } + + return true; + } + catch + { + sha1FileHash = null; + sha512FileHash = null; + return false; + } + } + + private static bool TryGetAssemblyHashCode(Assembly assembly, out string assemblyHashCode) + { + try + { + assemblyHashCode = assembly.GetHashCode().ToString(); + return true; + } + catch + { + assemblyHashCode = null; + return false; + } + } + + private static bool TryGetCompanyName(Assembly assembly, out string companyName) + { + try + { + var attributes = assembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), true); + if (attributes.Length < 1) + { + companyName = null; + return false; + } + + companyName = ((AssemblyCompanyAttribute)attributes[0]).Company; + return true; + } + catch + { + companyName = null; + return false; + } + } + + private static bool TryGetCopyright(Assembly assembly, out string copyright) + { + try + { + var attributes = assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), true); + if (attributes.Length < 1) + { + copyright = null; + return false; + } + + copyright = ((AssemblyCopyrightAttribute)attributes[0]).Copyright; + return true; + } + catch + { + copyright = null; + return false; + } + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs b/src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs index c3e75fde5..61d5ce221 100644 --- a/src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs +++ b/src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs @@ -955,6 +955,18 @@ public MetricWireModel TryBuildLoggingMetricsLinesCountMetric(int count) return BuildMetric(_metricNameService, proposedName, null, MetricDataWireModel.BuildCountData(count)); } + public MetricWireModel TryBuildLoggingMetricsDeniedCountBySeverityMetric(string logLevel, int count) + { + var proposedName = MetricNames.GetLoggingMetricsDeniedBySeverityName(logLevel); + return BuildMetric(_metricNameService, proposedName, null, MetricDataWireModel.BuildCountData(count)); + } + + public MetricWireModel TryBuildLoggingMetricsDeniedCountMetric(int count) + { + var proposedName = MetricNames.GetLoggingMetricsDeniedName(); + return BuildMetric(_metricNameService, proposedName, null, MetricDataWireModel.BuildCountData(count)); + } + public MetricWireModel TryBuildSupportabilityLoggingEventsCollectedMetric() { const string proposedName = MetricNames.SupportabilityLoggingEventsCollected; diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/IAgentExperimental.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/IAgentExperimental.cs index d54a198b1..5040635d7 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/IAgentExperimental.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/IAgentExperimental.cs @@ -33,5 +33,7 @@ public interface IAgentExperimental void RecordLogMessage(string frameworkName, object logEvent, Func getTimestamp, Func getLogLevel, Func getLogMessage, Func getLogException, Func> getContextData, string spanId, string traceId); Extensions.Helpers.IStackExchangeRedisCache StackExchangeRedisCache { get; set; } + + ISimpleSchedulingService SimpleSchedulingService { get; } } } diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/ISegmentExperimental.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/ISegmentExperimental.cs index 2bf257e31..9f3ab34bf 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/ISegmentExperimental.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/ISegmentExperimental.cs @@ -44,5 +44,15 @@ public interface ISegmentExperimental /// string UserCodeNamespace { get; set; } + /// + /// Returns the category of the segment. + /// + /// Category of the segment. + string GetCategory(); + + /// + /// Will be true if a relative end time has been set on the segment. In most situations, this is only set when a segment is ended. + /// + bool IsDone { get; } } } diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/ISimpleSchedulingService.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/ISimpleSchedulingService.cs new file mode 100644 index 000000000..836497f5a --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/ISimpleSchedulingService.cs @@ -0,0 +1,14 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; + +namespace NewRelic.Agent.Api.Experimental +{ + public interface ISimpleSchedulingService + { + void StartExecuteEvery(Action action, TimeSpan timeBetweenExecutions, TimeSpan? optionalInitialDelay = null); + + void StopExecuting(Action action); + } +} diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs index f6c865ea2..65dabe615 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs @@ -190,6 +190,7 @@ public interface IConfiguration int LogEventsMaxSamplesStored { get; } TimeSpan LogEventsHarvestCycle { get; } bool LogDecoratorEnabled { get; } + HashSet LogLevelDenyList { get; } bool ContextDataEnabled { get; } IEnumerable ContextDataInclude { get; } IEnumerable ContextDataExclude { get; } @@ -202,5 +203,7 @@ public interface IConfiguration TimeSpan GetAgentCommandsCycle { get; } TimeSpan DefaultHarvestCycle { get; } TimeSpan SqlTracesHarvestCycle { get; } + TimeSpan UpdateLoadedModulesCycle { get; } + TimeSpan StackExchangeRedisCleanupCycle { get; } } } diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore/WrapPipelineMiddleware.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore/WrapPipelineMiddleware.cs index 57349ac05..3b3c223a5 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore/WrapPipelineMiddleware.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore/WrapPipelineMiddleware.cs @@ -157,13 +157,15 @@ private ISegment SetupSegment(ITransaction transaction, HttpContext context) private ITransaction SetupTransaction(HttpRequest request) { var path = request.Path.Value; - path = "/".Equals(path) ? "ROOT" : path.Substring(1); + + // if path is empty, consider it the same as / + path = request.Path == PathString.Empty || path.Equals("/") ? "ROOT" : path.Substring(1); var transaction = _agent.CreateTransaction( - isWeb: true, - category: EnumNameCache.GetName(WebTransactionType.ASP), - transactionDisplayName: path, - doNotTrackAsUnitOfWork: true); + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: path, + doNotTrackAsUnitOfWork: true); transaction.SetRequestMethod(request.Method); transaction.SetUri(request.Path); diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/HttpClient/SendAsync.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/HttpClient/SendAsync.cs index 4407074b4..944469389 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/HttpClient/SendAsync.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/HttpClient/SendAsync.cs @@ -61,7 +61,9 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins var externalSegmentData = transactionExperimental.CreateExternalSegmentData(uri, method); var segment = transactionExperimental.StartSegment(instrumentedMethodCall.MethodCall); - segment.GetExperimentalApi().SetSegmentData(externalSegmentData); + segment.GetExperimentalApi() + .SetSegmentData(externalSegmentData) + .MakeLeaf(); if (agent.Configuration.ForceSynchronousTimingCalculationHttpClient) { diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/HttpWebRequest/SerializeHeadersWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/HttpWebRequest/SerializeHeadersWrapper.cs index 8364f635a..b31d7d977 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/HttpWebRequest/SerializeHeadersWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/HttpWebRequest/SerializeHeadersWrapper.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 using System; -using System.Linq; using NewRelic.Agent.Api; using NewRelic.Agent.Extensions.Providers.Wrapper; @@ -33,6 +32,16 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins throw new NullReferenceException("request.Headers"); } + // We should only inject headers with this instrumentation if HttpWebRequest created an external segment. + // Both HttpClient and RestSharp instrumentation have their own header injection support and do not rely + // on this instrumentation to inject the necessary headers. Other instrumentation rely on leaf segments + // to prevent this instrumentation from running. + if (!transaction.CurrentSegment.IsExternal) + { + transaction.LogFinest("Skipping HttpWebRequest header injection because the current segment was not created by HttpWebRequest."); + return Delegates.NoOp; + } + var setHeaders = new Action((carrier, key, value) => { carrier.Headers?.Set(key, value); diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MongoDb26/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MongoDb26/Instrumentation.xml index 9ed3a14fe..65d9f483a 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MongoDb26/Instrumentation.xml +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MongoDb26/Instrumentation.xml @@ -4,105 +4,119 @@ Copyright 2020 New Relic Corporation. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + - - - - - - - - + + + + + + - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml index e4c18395c..386c2adcf 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml @@ -25,6 +25,7 @@ SPDX-License-Identifier: Apache-2.0 + @@ -83,8 +84,9 @@ SPDX-License-Identifier: Apache-2.0 - - + + + @@ -130,6 +132,7 @@ SPDX-License-Identifier: Apache-2.0 + @@ -159,6 +162,13 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + @@ -189,6 +199,7 @@ SPDX-License-Identifier: Apache-2.0 + @@ -227,7 +238,8 @@ SPDX-License-Identifier: Apache-2.0 - + + @@ -266,6 +278,7 @@ SPDX-License-Identifier: Apache-2.0 + @@ -284,6 +297,12 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + @@ -349,7 +368,11 @@ SPDX-License-Identifier: Apache-2.0 - + + + + + @@ -371,5 +394,45 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/OpenConnectionWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/OpenConnectionWrapper.cs index 033994afb..c7c7bd3c5 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/OpenConnectionWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/OpenConnectionWrapper.cs @@ -5,30 +5,67 @@ using NewRelic.Agent.Extensions.Providers.Wrapper; using System; using System.Linq; +using System.Threading.Tasks; namespace NewRelic.Providers.Wrapper.Sql { - public class OpenConnectionWrapper : IWrapper + public class OpenConnectionWrapper : OpenConnectionWrapperBase { - public static readonly string[] WrapperNames = + private static readonly string[] _tracerNames = { "OpenConnectionTracer", - "OpenConnectionWrapper" + "OpenConnectionWrapper", }; + public override string[] WrapperNames => _tracerNames; + public override bool ExecuteAsAsync => false; + } + + public class OpenConnectionAsyncWrapper : OpenConnectionWrapperBase + { + private static readonly string[] _tracerNames = + { + "OpenConnectionTracerAsync", + "OpenConnectionWrapperAsync" + }; + public override string[] WrapperNames => _tracerNames; + public override bool ExecuteAsAsync => true; + } + + + public abstract class OpenConnectionWrapperBase : IWrapper + { + public abstract string[] WrapperNames { get; } + + public abstract bool ExecuteAsAsync { get; } + public bool IsTransactionRequired => true; public CanWrapResponse CanWrap(InstrumentedMethodInfo methodInfo) { - return new CanWrapResponse(WrapperNames.Contains(methodInfo.RequestedWrapperName, StringComparer.OrdinalIgnoreCase)); + var canWrap = WrapperNames.Contains(methodInfo.RequestedWrapperName, StringComparer.OrdinalIgnoreCase); + if (canWrap && ExecuteAsAsync) + { + var method = methodInfo.Method; + return TaskFriendlySyncContextValidator.CanWrapAsyncMethod(method.Type.Assembly.GetName().Name, method.Type.FullName, method.MethodName); + } + + return new CanWrapResponse(canWrap); } public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) { + if (instrumentedMethodCall.IsAsync) + { + transaction.AttachToAsync(); + } + var typeName = instrumentedMethodCall.MethodCall.Method.Type.FullName ?? "unknown"; var segment = transaction.StartMethodSegment(instrumentedMethodCall.MethodCall, typeName, instrumentedMethodCall.MethodCall.Method.MethodName, isLeaf: true); - return Delegates.GetDelegateFor(segment); + return ExecuteAsAsync + ? Delegates.GetAsyncDelegateFor(agent, segment) + : Delegates.GetDelegateFor(segment); } } } diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis2Plus/SessionCache.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis2Plus/SessionCache.cs index 1b2b1eea0..0d0979220 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis2Plus/SessionCache.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis2Plus/SessionCache.cs @@ -17,9 +17,11 @@ namespace NewRelic.Providers.Wrapper.StackExchangeRedis2Plus { public class SessionCache : IStackExchangeRedisCache { + private const string SessionCacheCleanupSupportabilityMetricName = "Supportability/Dotnet/RedisSessionCacheCleanup/Count"; + private readonly EventWaitHandle _stopHandle = new EventWaitHandle(false, EventResetMode.ManualReset); - private readonly ConcurrentDictionary _sessionCache = new ConcurrentDictionary(); + private readonly ConcurrentDictionary transaction, ProfilingSession session)> _sessionCache = new ConcurrentDictionary transaction, ProfilingSession session)>(); private readonly IAgent _agent; @@ -31,6 +33,8 @@ public SessionCache(IAgent agent, int invocationTargetHashCode) // Since the methodcall will not change, it is passed in from the instrumentation for reuse later. _invocationTargetHashCode = invocationTargetHashCode; + + _agent.SimpleSchedulingService.StartExecuteEvery(CleanUp, _agent.Configuration.StackExchangeRedisCleanupCycle, _agent.Configuration.StackExchangeRedisCleanupCycle); } /// @@ -39,21 +43,24 @@ public SessionCache(IAgent agent, int invocationTargetHashCode) /// Segment being finalized. public void Harvest(ISegment hostSegment) { - // If we can't remove the session, it doesn't exist, so do nothing and return. - if (!_sessionCache.TryRemove(hostSegment, out var sessionData)) + (WeakReference transaction, ProfilingSession session) sessionData; + lock (hostSegment) { - return; + // If we can't remove the session, it doesn't exist, so do nothing and return. + if (!_sessionCache.TryRemove(hostSegment, out sessionData)) + { + return; + } } - - // Get the transaction from the session - var weakTransaction = sessionData.UserToken as WeakReference; - if (!(weakTransaction?.TryGetTarget(out var transaction) ?? false) || transaction.IsFinished) + + // Get the transaction from the data. + if (!(sessionData.transaction?.TryGetTarget(out var transaction) ?? false)) { return; } var xTransaction = (ITransactionExperimental)transaction; - var commands = sessionData.FinishProfiling(); + var commands = sessionData.session.FinishProfiling(); foreach (var command in commands) { // We need to build the relative start and stop time based on the transaction start time. @@ -62,7 +69,8 @@ public void Harvest(ISegment hostSegment) // This new segment maker accepts relative start and stop times since we will be starting and ending(RemoveSegmentFromCallStack) the segment immediately. // This also sets the segment as a Leaf. - var segment = xTransaction.StartStackExchangeRedisSegment(_invocationTargetHashCode, ParsedSqlStatement.FromOperation(DatastoreVendor.Redis, command.Command), + var segment = xTransaction.StartStackExchangeRedisSegment(_invocationTargetHashCode, + ParsedSqlStatement.FromOperation(DatastoreVendor.Redis, command.Command), GetConnectionInfo(command.EndPoint), relativeStartTime, relativeEndTime); // This version of End does not set the end time or check for redis Harvests @@ -71,6 +79,40 @@ public void Harvest(ISegment hostSegment) } } + private void CleanUp() + { + var cleanedSessions = 0; + + try + { + foreach (var pair in _sessionCache) + { + // This can happen outside the lock since the object transaction was garbage collected. + if (!(pair.Value.transaction?.TryGetTarget(out _) ?? false)) + { + if (_sessionCache.TryRemove(pair.Key, out _)) + { + cleanedSessions++; + } + } + + lock (pair.Key) + { + if (((ISegmentExperimental)pair.Key).IsDone) + { + if (_sessionCache.TryRemove(pair.Key, out _)) + { + cleanedSessions++; + } + } + } + } + } + catch { } // Don't want to log here, just want to prevent collection problems from breaking things. + + _agent.RecordSupportabilityMetric(SessionCacheCleanupSupportabilityMetricName, cleanedSessions); + } + private ConnectionInfo GetConnectionInfo(EndPoint endpoint) { if (endpoint is DnsEndPoint dnsEndpoint) @@ -111,25 +153,34 @@ public Func GetProfilingSession() } // Don't want to save data to a session to a NoOp - no way to clean it up easily or reliably. + // Don't want to save to a Datastore segment - could be another Redis segment or something else. var segment = transaction.CurrentSegment; - if (!segment.IsValid) + + // These don't change over time so they don't need to be in the lock. + if (!segment.IsValid || ((ISegmentExperimental)segment).GetCategory() == "Datastore") { return null; } - return _sessionCache.GetOrAdd(segment, GetProfilingSession); - }; - } + ProfilingSession session = null; + lock (segment) + { + if (!((ISegmentExperimental)segment).IsDone) + { + var sessiontoken = _sessionCache.GetOrAdd(segment, (s) => (new WeakReference(transaction), new ProfilingSession())); + session = sessiontoken.session; + } + } - private ProfilingSession GetProfilingSession(ISegment segment) - { - return new ProfilingSession(new WeakReference(_agent.CurrentTransaction, false)); + return session; + }; } // Clean up the handles, sessions, and wipe the dictionary. public void Dispose() { _stopHandle.Set(); + _agent.SimpleSchedulingService.StopExecuting(CleanUp); _sessionCache.Clear(); _stopHandle.Dispose(); } diff --git a/src/Agent/NewRelic/Profiler/ProfiledMethods/newrelic.config b/src/Agent/NewRelic/Profiler/ProfiledMethods/newrelic.config index 97d43aebb..83bfaa80f 100644 --- a/src/Agent/NewRelic/Profiler/ProfiledMethods/newrelic.config +++ b/src/Agent/NewRelic/Profiler/ProfiledMethods/newrelic.config @@ -2,7 +2,7 @@ - + My Application diff --git a/src/Agent/NewRelic/Profiler/docker-compose.yml b/src/Agent/NewRelic/Profiler/docker-compose.yml index fcf733921..cc9e47a69 100644 --- a/src/Agent/NewRelic/Profiler/docker-compose.yml +++ b/src/Agent/NewRelic/Profiler/docker-compose.yml @@ -1,4 +1,4 @@ -version: '2.1' +version: '3.4' services: @@ -19,3 +19,12 @@ services: - .:/profiler - $CORECLR_NEWRELIC_HOME:/agent working_dir: /profiler/linux/ + build_arm64: + platform: linux/arm64/v8 + build: + context: linux/. + dockerfile: Arm64Dockerfile + command: bash -c "dos2unix ./build_profiler.sh && chmod 777 build_profiler.sh && ./build_profiler.sh" + volumes: + - .:/profiler + working_dir: /profiler/linux diff --git a/src/Agent/NewRelic/Profiler/linux/Arm64Dockerfile b/src/Agent/NewRelic/Profiler/linux/Arm64Dockerfile new file mode 100644 index 000000000..de9619216 --- /dev/null +++ b/src/Agent/NewRelic/Profiler/linux/Arm64Dockerfile @@ -0,0 +1,38 @@ +# This builds an Ubuntu image, clones the coreclr github repo and builds it. +# It then sets up the environment for compiling the New Relic .NET profiler. + +# ubuntu:18.04 - multi-platform image +FROM ubuntu@sha256:152dc042452c496007f07ca9127571cb9c29697f42acbfad72324b2bb2e43c98 + +RUN apt-get update -q -y +RUN apt-get install -q -y \ + wget \ + curl \ + git \ + dos2unix \ + software-properties-common \ + make \ + binutils \ + libc++-dev \ + clang-3.9 \ + lldb-3.9 \ + build-essential + +RUN echo "deb https://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main" | tee /etc/apt/sources.list.d/llvm.list +RUN wget --no-cache --no-cookies -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - + +# The CoreCLR build notes say their repos should be pulled into a `git` directory. +# Not sure how necessary that is. +RUN mkdir /root/git +WORKDIR /root/git + +RUN git clone --branch release/3.1 https://github.com/dotnet/coreclr.git + +RUN curl -sSL https://virtuoso-testing.s3.us-west-2.amazonaws.com/cmake-3.9.0-rc3-aarch64.tar.gz | tar -xzC ~ +RUN chmod 777 ~/cmake-3.9.0-rc3-aarch64/bin/cmake + +RUN ln -s ~/cmake-3.9.0-rc3-aarch64/bin/cmake /usr/bin/cmake || true +RUN rm /usr/bin/cc || true +RUN ln -s /usr/bin/clang-3.9 /usr/bin/cc +RUN rm /usr/bin/c++ || true +RUN ln -s /usr/bin/clang++-3.9 /usr/bin/c++ diff --git a/src/Agent/Shared/SharedLog4NetRepository.cs b/src/Agent/Shared/SharedLog4NetRepository.cs deleted file mode 100644 index a1d12a3e1..000000000 --- a/src/Agent/Shared/SharedLog4NetRepository.cs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// This ensures that we use a different logging repository from the rest of the process that we end up in. -[assembly: log4net.Config.Repository("NewRelic Log4Net Repository")] diff --git a/src/NewRelic.Core/Logging/Log.cs b/src/NewRelic.Core/Logging/Log.cs index 90502d279..d7b0a9418 100644 --- a/src/NewRelic.Core/Logging/Log.cs +++ b/src/NewRelic.Core/Logging/Log.cs @@ -51,10 +51,7 @@ public static void Error(Exception exception) /// public static void ErrorFormat(string format, params object[] args) { - if (IsErrorEnabled) - { - Logger.Error(string.Format(format, args)); - } + Logger.ErrorFormat(format, args); } #endregion Error @@ -87,10 +84,7 @@ public static void Warn(Exception exception) /// public static void WarnFormat(string format, params object[] args) { - if (IsWarnEnabled) - { - Logger.Warn(string.Format(format, args)); - } + Logger.WarnFormat(format, args); } #endregion Warn @@ -123,10 +117,7 @@ public static void Info(Exception exception) /// public static void InfoFormat(string format, params object[] args) { - if (IsInfoEnabled) - { - Logger.Info(string.Format(format, args)); - } + Logger.InfoFormat(format, args); } #endregion Info @@ -151,7 +142,7 @@ public static void Debug(string message) /// public static void Debug(Exception exception) { - Logger.Debug(exception.ToString()); + Logger.Debug(exception); } /// @@ -159,10 +150,7 @@ public static void Debug(Exception exception) /// public static void DebugFormat(string format, params object[] args) { - if (Logger.IsDebugEnabled) - { - Logger.Debug(string.Format(format, args)); - } + Logger.DebugFormat(format, args); } #endregion Debug @@ -195,10 +183,7 @@ public static void Finest(Exception exception) /// public static void FinestFormat(string format, params object[] args) { - if (IsFinestEnabled) - { - Logger.FinestFormat(format, args); - } + Logger.FinestFormat(format, args); } #endregion Finest @@ -251,10 +236,5 @@ public static void LogMessage(LogLevel level, string message) break; } } - - public static void LogException(LogLevel level, Exception ex) - { - LogMessage(level, ex.ToString()); - } } } diff --git a/tests/Agent/Benchmarking/BenchmarkingTests/BenchmarkingTests.csproj b/tests/Agent/Benchmarking/BenchmarkingTests/BenchmarkingTests.csproj index dd9463e62..15b370d52 100644 --- a/tests/Agent/Benchmarking/BenchmarkingTests/BenchmarkingTests.csproj +++ b/tests/Agent/Benchmarking/BenchmarkingTests/BenchmarkingTests.csproj @@ -9,18 +9,16 @@ - - all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Agent/IntegrationTests/Applications/OpenRastaWebApplication/OpenRastaWebApplication.csproj b/tests/Agent/IntegrationTests/Applications/OpenRastaWebApplication/OpenRastaWebApplication.csproj index b90b9f51a..c8f7df44f 100644 --- a/tests/Agent/IntegrationTests/Applications/OpenRastaWebApplication/OpenRastaWebApplication.csproj +++ b/tests/Agent/IntegrationTests/Applications/OpenRastaWebApplication/OpenRastaWebApplication.csproj @@ -91,12 +91,6 @@ - - 3.3.0 - - - 3.3.0 - 2.0.30506 @@ -111,9 +105,6 @@ 2.5.63 - - 2.5.16 - 2.5.25 diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogBase.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogBase.cs index 3d64eeb64..ccd6ff983 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogBase.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogBase.cs @@ -11,6 +11,7 @@ using System.Text.RegularExpressions; using System.Threading; using NewRelic.Agent.IntegrationTestHelpers.Models; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; @@ -41,6 +42,7 @@ public abstract class AgentLogBase public const string SpanEventDataLogLineRegex = DebugLogLinePrefixRegex + @"Request\(.{36}\): Invoked ""span_event_data"" with : (.*)"; public const string ErrorEventDataLogLineRegex = DebugLogLinePrefixRegex + @"Request\(.{36}\): Invoked ""error_event_data"" with : (.*)"; public const string ThreadProfileDataLogLineRegex = DebugLogLinePrefixRegex + @"Request\(.{36}\): Invoked ""profile_data"" with : (.*)"; + public const string UpdateLoadedModulesLogLineRegex = DebugLogLinePrefixRegex + @"Request\(.{36}\): Invoked ""update_loaded_modules"" with : (.*)"; // Collector responses public const string ConnectResponseLogLineRegex = DebugLogLinePrefixRegex + @"Request\(.{36}\): Invocation of ""connect"" yielded response : {""return_value"":{""agent_run_id""(.*)"; @@ -70,6 +72,13 @@ public abstract class AgentLogBase // ContextData related messages public const string ContextDataNotSupportedLogLineRegex = WarnLogLinePrefixRegex + @".* Context data is not supported for this logging framework."; + public AgentLogBase(RemoteApplication remoteApplication) + { + _remoteApplication = remoteApplication; + } + + private RemoteApplication _remoteApplication; + public abstract IEnumerable GetFileLines(); public string GetAccountId(TimeSpan? timeoutOrZero = null) @@ -148,19 +157,23 @@ public IEnumerable WaitForLogLines(string regularExpression, TimeSpan? ti var timeout = timeoutOrZero ?? TimeSpan.Zero; + _remoteApplication.TestLogger?.WriteLine($"{Timestamp} WaitForLogLines Waiting for expression: {regularExpression}. Duration: {timeout.TotalSeconds} seconds. Minimum count: {minimumCount}"); + var timeTaken = Stopwatch.StartNew(); do { var matches = TryGetLogLines(regularExpression).ToList(); if (matches.Count >= minimumCount) { + _remoteApplication.TestLogger?.WriteLine($"{Timestamp} WaitForLogLines Matched expression: {regularExpression}."); return matches; } Thread.Sleep(TimeSpan.FromMilliseconds(100)); } while (timeTaken.Elapsed < timeout); - var message = $"Log line did not appear a minimum of {minimumCount} times within {timeout.TotalSeconds} seconds. Expected line expression: {regularExpression}"; + var message = $"{Timestamp} Log line did not appear a minimum of {minimumCount} times within {timeout.TotalSeconds} seconds. Expected line expression: {regularExpression}"; + _remoteApplication.TestLogger?.WriteLine(message); throw new Exception(message); } @@ -440,6 +453,8 @@ public bool GetRejitRequesting(int timeOut = 80) #endregion + #region Connect Data + public ConnectData GetConnectData() { var json = TryGetLogLines(ConnectLogLineRegex) @@ -479,6 +494,8 @@ public IEnumerable GetConnectResponseDatas() return result; } + #endregion + #region Metrics public IEnumerable GetMetrics() @@ -521,5 +538,31 @@ public IEnumerable GetLogEventDataLogLines() } #endregion LogData + + #region UpdateLoadedModules + + public IEnumerable GetUpdateLoadedModulesPayloads() + { + return TryGetLogLines(UpdateLoadedModulesLogLineRegex) + .Select(match => TryExtractJson(match, 1)) + .Select(json => JsonConvert.DeserializeObject(json)) + .Where(errorEvent => errorEvent != null); + } + + public IEnumerable GetUpdateLoadedModulesAeemblies() + { + return GetUpdateLoadedModulesPayloads().SelectMany(payload => payload.Assemblies); + } + + #endregion + + private string Timestamp + { + get + { + // Matches agent log date-time format + return DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss,fff"); + } + } } } diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogFile.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogFile.cs index 6df131258..087d155a4 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogFile.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogFile.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Threading; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; namespace NewRelic.Agent.IntegrationTestHelpers { @@ -19,7 +20,8 @@ public class AgentLogFile : AgentLogBase public bool Found => File.Exists(_filePath); - public AgentLogFile(string logDirectoryPath, string fileName = "", TimeSpan? timeoutOrZero = null, bool throwIfNotFound = true) + public AgentLogFile(string logDirectoryPath, RemoteApplication remoteApplication, string fileName = "", TimeSpan? timeoutOrZero = null, bool throwIfNotFound = true) + : base(remoteApplication) { Contract.Assert(logDirectoryPath != null); diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogString.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogString.cs deleted file mode 100644 index 437d908ea..000000000 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogString.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - - -using System.Collections.Generic; -using System.IO; - -namespace NewRelic.Agent.IntegrationTestHelpers -{ - public class AgentLogString : AgentLogBase - { - private readonly string _log; - - public AgentLogString(string log) - { - _log = log; - } - - public override IEnumerable GetFileLines() - { - string line; - using (var stringReader = new StringReader(_log)) - while ((line = stringReader.ReadLine()) != null) - { - yield return line; - } - } - } -} diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/Models/UpdateLoadedModulesPayload.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/Models/UpdateLoadedModulesPayload.cs new file mode 100644 index 000000000..8c62dfc85 --- /dev/null +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/Models/UpdateLoadedModulesPayload.cs @@ -0,0 +1,48 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + + +using System.Collections.Generic; +using NewRelic.Agent.IntegrationTestHelpers.JsonConverters; +using Newtonsoft.Json; + +namespace NewRelic.Agent.IntegrationTestHelpers.Models +{ + [JsonConverter(typeof(JsonArrayConverter))] + public class UpdateLoadedModulesPayload + { + [JsonArrayIndex(Index = 0)] public const string Jars = "Jars"; + + [JsonArrayIndex(Index = 1)] public readonly IList Assemblies; + + public UpdateLoadedModulesPayload() : this(new List()) + { } + + public UpdateLoadedModulesPayload(IList assemblies) + { + Assemblies = assemblies; + } + } + + [JsonConverter(typeof(JsonArrayConverter))] + public class UpdateLoadedModulesAssembly + { + [JsonArrayIndex(Index = 0)] public readonly string AssemblyName; + + [JsonArrayIndex(Index = 1)] public readonly string AssemblyVersion; + + [JsonArrayIndex(Index = 2)] public readonly IDictionary Data; + + public UpdateLoadedModulesAssembly() + { + Data = new Dictionary(); + } + + public UpdateLoadedModulesAssembly(string assemblyName, string assemblyVersion, IDictionary data) + { + AssemblyName = assemblyName; + AssemblyVersion = assemblyVersion; + Data = data; + } + } +} diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs index 2ffedc826..bd2ded841 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs @@ -317,6 +317,13 @@ public NewRelicConfigModifier SetLogForwardingMaxSamplesStored(int samples) return this; } + public NewRelicConfigModifier SetLogForwardingLogLevelDenyList(string logLevelDenyList) + { + CommonUtils.ModifyOrCreateXmlNodeInNewRelicConfig(_configFilePath, new[] { "configuration", "applicationLogging" }, "forwarding", string.Empty); + CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(_configFilePath, new[] { "configuration", "applicationLogging", "forwarding" }, "logLevelDenyList", logLevelDenyList); + return this; + } + public NewRelicConfigModifier SetCodeLevelMetricsEnabled(bool enabled = true) { CommonUtils.ModifyOrCreateXmlNodeInNewRelicConfig(_configFilePath, new[] { "configuration" }, "codeLevelMetrics", string.Empty); @@ -380,5 +387,11 @@ public NewRelicConfigModifier ConfigureFasterSqlTracesHarvestCycle(int seconds) CommonUtils.SetConfigAppSetting(_configFilePath, "OverrideSqlTracesHarvestCycle", seconds.ToString(), "urn:newrelic-config"); return this; } + + public NewRelicConfigModifier ConfigureFasterUpdateLoadedModulesCycle(int seconds) + { + CommonUtils.SetConfigAppSetting(_configFilePath, "OverrideUpdateLoadedModulesCycle", seconds.ToString(), "urn:newrelic-config"); + return this; + } } } diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplication.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplication.cs index 0260748a0..8d2c06487 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplication.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplication.cs @@ -215,7 +215,7 @@ public string UniqueFolderName private AgentLogFile _agentLogFile; - public AgentLogFile AgentLog => _agentLogFile ?? (_agentLogFile = new AgentLogFile(DestinationNewRelicLogFileDirectoryPath, AgentLogFileName, Timing.TimeToWaitForLog)); + public AgentLogFile AgentLog => _agentLogFile ?? (_agentLogFile = new AgentLogFile(DestinationNewRelicLogFileDirectoryPath, this, AgentLogFileName, Timing.TimeToWaitForLog)); public ProfilerLogFile ProfilerLog { get { return new ProfilerLogFile(DefaultLogFileDirectoryPath, Timing.TimeToConnect); } } diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplicationFixture.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplicationFixture.cs index 023f2ca4b..8e8820310 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplicationFixture.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplicationFixture.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Threading; @@ -21,6 +22,12 @@ public abstract class RemoteApplicationFixture : IDisposable private Action _setupConfiguration; private Action _exerciseApplication; + private HashSet _errorsToRetryOn = new HashSet + { + 0xC000_0005 // System.AccessViolationException. This is a .NET bug that + // is supposed to be fixed but we're still seeing + // https://github.com/dotnet/runtime/issues/62145 + }; public Dictionary EnvironmentVariables; @@ -88,8 +95,8 @@ public bool KeepWorkingDirectory set { RemoteApplication.KeepWorkingDirectory = value; } } - // We think that the test retry loop may be masking real problems and/or actually causing them, so we've disabled it for now - protected virtual int MaxTries => 1; + // Tests are only retried if they return a known error not related to the test + protected virtual int MaxTries => 3; public void DisableAsyncLocalCallStack() { @@ -190,11 +197,23 @@ public RemoteApplicationFixture SetAdditionalEnvironmentVariable(string key, str return this; } + public void AddErrorToRetryOn(uint error) + { + _errorsToRetryOn.Add(error); + } + private void ExerciseApplication() { _exerciseApplication?.Invoke(); } + private string FormatExitCode(int? exitCode) + { + if (exitCode == null) return "[null]"; + if (Math.Abs(exitCode.Value) < 10) return exitCode.Value.ToString(); + return exitCode.Value.ToString("X8"); + } + public virtual void Initialize() { lock (_initializeLock) @@ -214,16 +233,14 @@ public virtual void Initialize() try { var retryTest = false; - var exceptionInExerciseApplication = false; + var retryMessage = ""; var applicationHadNonZeroExitCode = false; - do { TestLogger?.WriteLine("Test Home" + RemoteApplication.DestinationNewRelicHomeDirectoryPath); // reset these for each loop iteration - exceptionInExerciseApplication = false; applicationHadNonZeroExitCode = false; retryTest = false; @@ -245,8 +262,9 @@ public virtual void Initialize() } catch (Exception ex) { - exceptionInExerciseApplication = true; + retryTest = true; TestLogger?.WriteLine("Exception occurred in try number " + (numberOfTries + 1) + " : " + ex.ToString()); + retryMessage = "Exception thrown."; } finally { @@ -278,15 +296,19 @@ public virtual void Initialize() RemoteApplication.WaitForExit(); applicationHadNonZeroExitCode = RemoteApplication.ExitCode != 0; + var formattedExitCode = FormatExitCode(RemoteApplication.ExitCode); - TestLogger?.WriteLine($"Remote application exited with a {(applicationHadNonZeroExitCode ? "failure" : "success")} exit code of {RemoteApplication.ExitCode}."); + TestLogger?.WriteLine($"Remote application exited with a {(applicationHadNonZeroExitCode ? "failure" : "success")} exit code of {formattedExitCode}."); - retryTest = exceptionInExerciseApplication || applicationHadNonZeroExitCode; + if (applicationHadNonZeroExitCode && _errorsToRetryOn.Contains((uint)RemoteApplication.ExitCode.Value)) + { + retryMessage = $"{formattedExitCode} is a known error."; + retryTest = true; + } - if (retryTest) + if (retryTest && (numberOfTries < MaxTries)) { - var message = $"Retrying test. Exception caught when exercising test app = {exceptionInExerciseApplication}, application had non-zero exit code = {applicationHadNonZeroExitCode}."; - TestLogger?.WriteLine(message); + TestLogger?.WriteLine(retryMessage + " Retrying test."); Thread.Sleep(1000); numberOfTries++; } diff --git a/tests/Agent/IntegrationTests/IntegrationTests/AgentFeatures/VulnerabilityManagementTests.cs b/tests/Agent/IntegrationTests/IntegrationTests/AgentFeatures/VulnerabilityManagementTests.cs new file mode 100644 index 000000000..c26925fd2 --- /dev/null +++ b/tests/Agent/IntegrationTests/IntegrationTests/AgentFeatures/VulnerabilityManagementTests.cs @@ -0,0 +1,117 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + + +using System; +using System.Linq; +using MultiFunctionApplicationHelpers; +using NewRelic.Agent.IntegrationTestHelpers; +using NewRelic.Testing.Assertions; +using Xunit; +using Xunit.Abstractions; + + +namespace NewRelic.Agent.IntegrationTests.AgentFeatures +{ + public abstract class VulnerabilityManagementTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture + { + private const string Namespace = "namespace"; + private const string PublicKeyToken = "publicKeyToken"; + private const string Sha1Checksum = "sha1Checksum"; + private const string Sha512Checksum = "sha512Checksum"; + private const string AssemblyHashCode = "assemblyHashCode"; + private const string ImplementationVendor = "Implementation-Vendor"; + private const string Copyright = "copyright"; + private const string MicrosoftName = "Microsoft"; + + protected readonly TFixture _fixture; + + public VulnerabilityManagementTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) + { + _fixture = fixture; + _fixture.TestLogger = output; + _fixture.SetTimeout(TimeSpan.FromMinutes(3)); + + // the commands doesn't really matter as long as the agent starts and runs correctly. + _fixture.AddCommand($"ApiCalls TestGetLinkingMetadata"); + + _fixture.AddActions + ( + setupConfiguration: () => + { + var configModifier = new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath); + configModifier.SetLogLevel("debug"); + configModifier.ConfigureFasterUpdateLoadedModulesCycle(15); + }, + exerciseApplication: () => + { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(2)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.UpdateLoadedModulesLogLineRegex, TimeSpan.FromMinutes(2)); + } + ); + + _fixture.Initialize(); + } + + [Fact] + public void Test() + { + var assemblies = _fixture.AgentLog.GetUpdateLoadedModulesAeemblies(); + + // This assembly is always present and has all the items we send so we can verify that we capture everything. + var mscorlib = assemblies.First(a => a.AssemblyName == "mscorlib.dll"); + + // We can be only certain of a few bits of information, like the AssemblyName and Namespace. + // We can check the contents of those items, but for the others we just want to make sure there is data. + NrAssert.Multiple + ( + () => Assert.NotEmpty(assemblies), + () => Assert.Equal("mscorlib.dll", mscorlib.AssemblyName), + () => Assert.False(string.IsNullOrWhiteSpace(mscorlib.AssemblyVersion)), + () => Assert.Equal(7, mscorlib.Data.Count), + + () => Assert.True(mscorlib.Data.ContainsKey(Namespace)), + () => Assert.Equal("mscorlib", mscorlib.Data[Namespace]), + + () => Assert.True(mscorlib.Data.ContainsKey(PublicKeyToken)), + () => Assert.False(string.IsNullOrWhiteSpace(mscorlib.Data[PublicKeyToken])), + + () => Assert.True(mscorlib.Data.ContainsKey(Sha1Checksum)), + () => Assert.False(string.IsNullOrWhiteSpace(mscorlib.Data[Sha1Checksum])), + + () => Assert.True(mscorlib.Data.ContainsKey(Sha512Checksum)), + () => Assert.False(string.IsNullOrWhiteSpace(mscorlib.Data[Sha512Checksum])), + + () => Assert.True(mscorlib.Data.ContainsKey(AssemblyHashCode)), + () => Assert.False(string.IsNullOrWhiteSpace(mscorlib.Data[AssemblyHashCode])), + + () => Assert.True(mscorlib.Data.ContainsKey(ImplementationVendor)), + () => Assert.False(string.IsNullOrWhiteSpace(mscorlib.Data[ImplementationVendor])), + () => Assert.Contains(MicrosoftName, mscorlib.Data[ImplementationVendor]), + + () => Assert.True(mscorlib.Data.ContainsKey(Copyright)), + () => Assert.False(string.IsNullOrWhiteSpace(mscorlib.Data[Copyright])), + () => Assert.Contains(MicrosoftName, mscorlib.Data[Copyright]) + ); + } + } + + [NetFrameworkTest] + public class VulnerabilityManagementTestsFWLatest : VulnerabilityManagementTestsBase + { + public VulnerabilityManagementTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) + : base(fixture, output) + { + } + } + + [NetCoreTest] + public class VulnerabilityManagementTestsCoreLatest : VulnerabilityManagementTestsBase + { + public VulnerabilityManagementTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) + : base(fixture, output) + { + } + } +} diff --git a/tests/Agent/IntegrationTests/IntegrationTests/AgentLogs/LogLevelAndDirectoryEnvironmentTests.cs b/tests/Agent/IntegrationTests/IntegrationTests/AgentLogs/LogLevelAndDirectoryEnvironmentTests.cs index 8b56aad1c..31a20c1e1 100644 --- a/tests/Agent/IntegrationTests/IntegrationTests/AgentLogs/LogLevelAndDirectoryEnvironmentTests.cs +++ b/tests/Agent/IntegrationTests/IntegrationTests/AgentLogs/LogLevelAndDirectoryEnvironmentTests.cs @@ -46,9 +46,9 @@ public LogLevelAndDirectoryEnvironmentTests(T fixture, ITestOutputHelper output) [Fact] public void AgentLog() { - var configLocation = new AgentLogFile(_configLogDirectory, throwIfNotFound: false); - var generalEnvLocation = new AgentLogFile(_generalEnvLogDirectory, throwIfNotFound: false); - var profilerEnvLocation = new AgentLogFile(_profilerEnvLogDirectory, throwIfNotFound: false); + var configLocation = new AgentLogFile(_configLogDirectory, _fixture.RemoteApplication, throwIfNotFound: false); + var generalEnvLocation = new AgentLogFile(_generalEnvLogDirectory, _fixture.RemoteApplication, throwIfNotFound: false); + var profilerEnvLocation = new AgentLogFile(_profilerEnvLogDirectory, _fixture.RemoteApplication, throwIfNotFound: false); Assert.False(configLocation.Found); Assert.True(generalEnvLocation.Found); diff --git a/tests/Agent/IntegrationTests/IntegrationTests/Logging/LogLevelDenyListTests.cs b/tests/Agent/IntegrationTests/IntegrationTests/Logging/LogLevelDenyListTests.cs new file mode 100644 index 000000000..5ca7976da --- /dev/null +++ b/tests/Agent/IntegrationTests/IntegrationTests/Logging/LogLevelDenyListTests.cs @@ -0,0 +1,297 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using MultiFunctionApplicationHelpers; +using NewRelic.Agent.IntegrationTestHelpers; +using Xunit; +using Xunit.Abstractions; + +namespace NewRelic.Agent.IntegrationTests.Logging.MetricsAndForwarding +{ + public abstract class LogLevelDenyListTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture + { + private readonly TFixture _fixture; + private LoggingFramework _loggingFramework; + + + public LogLevelDenyListTestsBase(TFixture fixture, ITestOutputHelper output, + LoggingFramework loggingFramework) : base(fixture) + { + _fixture = fixture; + _loggingFramework = loggingFramework; + _fixture.SetTimeout(TimeSpan.FromMinutes(2)); + _fixture.TestLogger = output; + + //_fixture.AddCommand("RootCommands LaunchDebugger"); + _fixture.AddCommand($"LoggingTester SetFramework {_loggingFramework}"); + _fixture.AddCommand("LoggingTester Configure"); + + _fixture.AddCommand("LoggingTester CreateSingleLogMessage DebugMessage DEBUG"); + _fixture.AddCommand("LoggingTester CreateSingleLogMessage InfoMessage INFO"); + _fixture.AddCommand("LoggingTester CreateSingleLogMessage WarningMessage WARNING"); + _fixture.AddCommand("LoggingTester CreateSingleLogMessage ErrorMessage ERROR"); + + _fixture.AddActions + ( + setupConfiguration: () => + { + var configModifier = new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath); + + configModifier + .EnableApplicationLogging() + .EnableLogForwarding() + .SetLogForwardingLogLevelDenyList($"{LogUtils.GetLevelName(_loggingFramework, "DEBUG")},{LogUtils.GetLevelName(_loggingFramework, "INFO")}") + .SetLogLevel("debug"); + }, + exerciseApplication: () => + { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.LogDataLogLineRegex, TimeSpan.FromSeconds(30)); + } + ); + + _fixture.Initialize(); + } + + [Fact] + public void LoggingMetricsExist() + { + var expectedMetrics = new List + { + new Assertions.ExpectedMetric { metricName = "Logging/lines/" + LogUtils.GetLevelName(_loggingFramework, "WARN"), callCount = 1 }, + new Assertions.ExpectedMetric { metricName = "Logging/lines/" + LogUtils.GetLevelName(_loggingFramework, "ERROR"), callCount = 1 }, + + new Assertions.ExpectedMetric { metricName = "Logging/lines", callCount = 2 }, + + new Assertions.ExpectedMetric { metricName = "Logging/denied/" + LogUtils.GetLevelName(_loggingFramework, "DEBUG"), callCount = 1 }, + new Assertions.ExpectedMetric { metricName = "Logging/denied/" + LogUtils.GetLevelName(_loggingFramework, "INFO"), callCount = 1 }, + + new Assertions.ExpectedMetric { metricName = "Logging/denied", callCount = 2 }, + }; + var notExpectedMetrics = new List + { + new Assertions.ExpectedMetric { metricName = "Logging/lines/" + LogUtils.GetLevelName(_loggingFramework, "DEBUG") }, + new Assertions.ExpectedMetric { metricName = "Logging/lines/" + LogUtils.GetLevelName(_loggingFramework, "INFO") } + }; + + var actualMetrics = _fixture.AgentLog.GetMetrics().ToList(); + Assertions.MetricsExist(expectedMetrics, actualMetrics); + Assertions.MetricsDoNotExist(notExpectedMetrics, actualMetrics); + } + + } + #region log4net + + [NetFrameworkTest] + public class Log4NetLogLevelDenyListTestsFWLatestTests : LogLevelDenyListTestsBase + { + public Log4NetLogLevelDenyListTestsFWLatestTests(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Log4net) + { + } + } + + [NetFrameworkTest] + public class Log4NetLogLevelDenyListTestsFW471Tests : LogLevelDenyListTestsBase + { + public Log4NetLogLevelDenyListTestsFW471Tests(ConsoleDynamicMethodFixtureFW471 fixture, ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Log4net) + { + } + } + + [NetFrameworkTest] + public class Log4NetLogLevelDenyListTestsFW462Tests : LogLevelDenyListTestsBase + { + public Log4NetLogLevelDenyListTestsFW462Tests(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Log4net) + { + } + } + + [NetCoreTest] + public class Log4NetLogLevelDenyListTestsNetCoreLatestTests : LogLevelDenyListTestsBase + { + public Log4NetLogLevelDenyListTestsNetCoreLatestTests(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Log4net) + { + } + } + + [NetCoreTest] + public class Log4NetLogLevelDenyListTestsNetCoreOldestTests : LogLevelDenyListTestsBase + { + public Log4NetLogLevelDenyListTestsNetCoreOldestTests(ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Log4net) + { + } + } + #endregion + + #region MEL + + [NetCoreTest] + public class MELLogLevelDenyListTestsNetCoreLatestTests : LogLevelDenyListTestsBase + { + public MELLogLevelDenyListTestsNetCoreLatestTests( + ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) + : base(fixture, output, LoggingFramework.MicrosoftLogging) + { + } + } + + [NetCoreTest] + public class + MELLogLevelDenyListTestsNetCoreOldestTests : LogLevelDenyListTestsBase + { + public MELLogLevelDenyListTestsNetCoreOldestTests( + ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) + : base(fixture, output, LoggingFramework.MicrosoftLogging) + { + } + } + + [NetFrameworkTest] + public class + MELLogLevelDenyListTestsFWLatestTests : LogLevelDenyListTestsBase + { + public MELLogLevelDenyListTestsFWLatestTests( + ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) + : base(fixture, output, LoggingFramework.MicrosoftLogging) + { + } + } + + #endregion + + #region Serilog + + [NetFrameworkTest] + public class + SerilogLogLevelDenyListTestsFWLatestTests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureFWLatest> + { + public SerilogLogLevelDenyListTestsFWLatestTests(ConsoleDynamicMethodFixtureFWLatest fixture, + ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Serilog) + { + } + } + + [NetFrameworkTest] + public class + SerilogLogLevelDenyListTestsFW471Tests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureFW471> + { + public SerilogLogLevelDenyListTestsFW471Tests(ConsoleDynamicMethodFixtureFW471 fixture, + ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Serilog) + { + } + } + + [NetFrameworkTest] + public class + SerilogLogLevelDenyListTestsFW462Tests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureFW462> + { + public SerilogLogLevelDenyListTestsFW462Tests(ConsoleDynamicMethodFixtureFW462 fixture, + ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Serilog) + { + } + } + + [NetCoreTest] + public class + SerilogLogLevelDenyListTestsNetCoreLatestTests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureCoreLatest> + { + public SerilogLogLevelDenyListTestsNetCoreLatestTests( + ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Serilog) + { + } + } + + [NetCoreTest] + public class + SerilogLogLevelDenyListTestsNetCoreOldestTests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureCoreOldest> + { + public SerilogLogLevelDenyListTestsNetCoreOldestTests(ConsoleDynamicMethodFixtureCoreOldest fixture, + ITestOutputHelper output) + : base(fixture, output, LoggingFramework.Serilog) + { + } + } + + #endregion + + #region NLog + + [NetFrameworkTest] + public class + NLogLogLevelDenyListTestsFWLatestTests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureFWLatest> + { + public NLogLogLevelDenyListTestsFWLatestTests(ConsoleDynamicMethodFixtureFWLatest fixture, + ITestOutputHelper output) + : base(fixture, output, LoggingFramework.NLog) + { + } + } + + [NetFrameworkTest] + public class + NLogLogLevelDenyListTestsFW471Tests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureFW471> + { + public NLogLogLevelDenyListTestsFW471Tests(ConsoleDynamicMethodFixtureFW471 fixture, + ITestOutputHelper output) + : base(fixture, output, LoggingFramework.NLog) + { + } + } + + [NetFrameworkTest] + public class + NLogLogLevelDenyListTestsFW462Tests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureFW462> + { + public NLogLogLevelDenyListTestsFW462Tests(ConsoleDynamicMethodFixtureFW462 fixture, + ITestOutputHelper output) + : base(fixture, output, LoggingFramework.NLog) + { + } + } + + [NetCoreTest] + public class + NLogLogLevelDenyListTestsNetCoreLatestTests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureCoreLatest> + { + public NLogLogLevelDenyListTestsNetCoreLatestTests(ConsoleDynamicMethodFixtureCoreLatest fixture, + ITestOutputHelper output) + : base(fixture, output, LoggingFramework.NLog) + { + } + } + + [NetCoreTest] + public class + NLogLogLevelDenyListTestsNetCoreOldestTests : LogLevelDenyListTestsBase< + ConsoleDynamicMethodFixtureCoreOldest> + { + public NLogLogLevelDenyListTestsNetCoreOldestTests(ConsoleDynamicMethodFixtureCoreOldest fixture, + ITestOutputHelper output) + : base(fixture, output, LoggingFramework.NLog) + { + } + } + + #endregion +} diff --git a/tests/Agent/IntegrationTests/IntegrationTests/WCF/WCFLogHelpers.cs b/tests/Agent/IntegrationTests/IntegrationTests/WCF/WCFLogHelpers.cs index cfd2c2337..3bfe561b3 100644 --- a/tests/Agent/IntegrationTests/IntegrationTests/WCF/WCFLogHelpers.cs +++ b/tests/Agent/IntegrationTests/IntegrationTests/WCF/WCFLogHelpers.cs @@ -65,7 +65,7 @@ public AgentLogFile AgentLog_Client .Where(f => !f.Name.StartsWith("NewRelic.Profiler.", StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(f => f.Name.EndsWith("ConsoleMultiFunctionApplicationFW.log", StringComparison.OrdinalIgnoreCase)); - _agentLog_Client = new AgentLogFile(_fixture.DestinationNewRelicLogFileDirectoryPath, logFile.Name); + _agentLog_Client = new AgentLogFile(_fixture.DestinationNewRelicLogFileDirectoryPath, _fixture.RemoteApplication, logFile.Name); } return _agentLog_Client; @@ -85,7 +85,7 @@ public AgentLogFile AgentLog_Service .Where(f => !f.Name.StartsWith("NewRelic.Profiler.", StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(f => !f.Name.EndsWith("ConsoleMultiFunctionApplicationFW.log", StringComparison.OrdinalIgnoreCase)); - _agentLog_Service = new AgentLogFile(_fixture.DestinationNewRelicLogFileDirectoryPath, logFile.Name); + _agentLog_Service = new AgentLogFile(_fixture.DestinationNewRelicLogFileDirectoryPath, _fixture.RemoteApplication, logFile.Name); } return _agentLog_Service; diff --git a/tests/Agent/IntegrationTests/Shared/ElasticSearchConfiguration.cs b/tests/Agent/IntegrationTests/Shared/ElasticSearchConfiguration.cs index 342a2532b..408facb8a 100644 --- a/tests/Agent/IntegrationTests/Shared/ElasticSearchConfiguration.cs +++ b/tests/Agent/IntegrationTests/Shared/ElasticSearchConfiguration.cs @@ -33,7 +33,8 @@ public static string ElasticServer } } - public static string ElasticUserName { + public static string ElasticUserName + { get { if (_elasticUserName == null) @@ -52,7 +53,8 @@ public static string ElasticUserName { return _elasticUserName; } } - public static string ElasticPassword { + public static string ElasticPassword + { get { if (_elasticPassword == null) @@ -72,4 +74,73 @@ public static string ElasticPassword { } } } + public class ElasticSearch7Configuration + { + private static string _elasticServer; + private static string _elasticUserName; + private static string _elasticPassword; + + public static string ElasticServer + { + get + { + if (_elasticServer == null) + { + try + { + var testConfiguration = + IntegrationTestConfiguration.GetIntegrationTestConfiguration("ElasticSearch7Tests"); + _elasticServer = testConfiguration["Server"]; + } + catch (Exception ex) + { + throw new Exception("ElasticServer configuration is invalid.", ex); + } + } + + return _elasticServer; + } + } + + public static string ElasticUserName + { + get + { + if (_elasticUserName == null) + { + try + { + var testConfiguration = + IntegrationTestConfiguration.GetIntegrationTestConfiguration("ElasticSearch7Tests"); + _elasticUserName = testConfiguration["UserName"]; + } + catch (Exception ex) + { + throw new Exception("ElasticServer configuration is invalid.", ex); + } + } + return _elasticUserName; + } + } + public static string ElasticPassword + { + get + { + if (_elasticPassword == null) + { + try + { + var testConfiguration = + IntegrationTestConfiguration.GetIntegrationTestConfiguration("ElasticSearch7Tests"); + _elasticPassword = testConfiguration["Password"]; + } + catch (Exception ex) + { + throw new Exception("ElasticServer configuration is invalid.", ex); + } + } + return _elasticPassword; + } + } + } } diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/ConsoleDynamicMethodFixture.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/ConsoleDynamicMethodFixture.cs index cbe9fc328..56a044375 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/ConsoleDynamicMethodFixture.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/ConsoleDynamicMethodFixture.cs @@ -189,8 +189,6 @@ public abstract class ConsoleDynamicMethodFixture : RemoteApplicationFixture { protected static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30); - protected override int MaxTries => 1; - private List _commands = new List(); public new RemoteConsoleApplication RemoteApplication => base.RemoteApplication as RemoteConsoleApplication; diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj index e3071db4d..4b66db804 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj @@ -51,32 +51,34 @@ - - + + - + - + + - + + - + - + @@ -92,41 +94,41 @@ - + - + - - - - + + + + - - + + - - - - + + + + - - + + - + - + @@ -139,7 +141,7 @@ - + @@ -155,7 +157,7 @@ - + @@ -193,9 +195,9 @@ - + - + @@ -217,30 +219,30 @@ - - - - + + + + - - - - + + + + - + - + diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchElasticClient.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchElasticClient.cs index c474788af..fd7d51497 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchElasticClient.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchElasticClient.cs @@ -1,6 +1,7 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Elastic.Clients.Elasticsearch; @@ -13,13 +14,33 @@ namespace MultiFunctionApplicationHelpers.NetStandardLibraries.Elasticsearch internal class ElasticsearchElasticClient : ElasticsearchTestClient { private ElasticsearchClient _client; + protected override Uri Address + { + get + { + return new Uri(ElasticSearchConfiguration.ElasticServer); + } + } + protected override string Username + { + get + { + return ElasticSearchConfiguration.ElasticUserName; + } + } + protected override string Password + { + get + { + return ElasticSearchConfiguration.ElasticPassword; + } + } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public override void Connect() { var settings = new ElasticsearchClientSettings(Address) - .Authentication(new BasicAuthentication(ElasticSearchConfiguration.ElasticUserName, - ElasticSearchConfiguration.ElasticPassword)). + .Authentication(new BasicAuthentication(Username, Password)). DefaultIndex(IndexName); _client = new ElasticsearchClient(settings); diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchNestClient.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchNestClient.cs index 1b99d63bd..f05bfcc0e 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchNestClient.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchNestClient.cs @@ -1,6 +1,7 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Elastic.Clients.Elasticsearch; @@ -13,13 +14,33 @@ namespace MultiFunctionApplicationHelpers.NetStandardLibraries.Elasticsearch internal class ElasticsearchNestClient : ElasticsearchTestClient { private ElasticClient _client; + protected override Uri Address + { + get + { + return new Uri(ElasticSearch7Configuration.ElasticServer); + } + } + protected override string Username + { + get + { + return ElasticSearch7Configuration.ElasticUserName; + } + } + protected override string Password + { + get + { + return ElasticSearch7Configuration.ElasticPassword; + } + } - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public override void Connect() { var settings = new ConnectionSettings(Address). - BasicAuthentication(ElasticSearchConfiguration.ElasticUserName, - ElasticSearchConfiguration.ElasticPassword). + BasicAuthentication(Username,Password). DefaultIndex(IndexName); _client = new ElasticClient(settings); diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchNetClient.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchNetClient.cs index ce09d731c..bbd6a68f5 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchNetClient.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchNetClient.cs @@ -14,13 +14,33 @@ namespace MultiFunctionApplicationHelpers.NetStandardLibraries.Elasticsearch internal class ElasticsearchNetClient : ElasticsearchTestClient { private ElasticLowLevelClient _client; + protected override Uri Address + { + get + { + return new Uri(ElasticSearch7Configuration.ElasticServer); + } + } + protected override string Username + { + get + { + return ElasticSearch7Configuration.ElasticUserName; + } + } + protected override string Password + { + get + { + return ElasticSearch7Configuration.ElasticPassword; + } + } [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public override void Connect() { var settings = new ConnectionConfiguration(Address) - .BasicAuthentication(ElasticSearchConfiguration.ElasticUserName, - ElasticSearchConfiguration.ElasticPassword) + .BasicAuthentication(Username, Password) .RequestTimeout(TimeSpan.FromMinutes(2)); _client = new ElasticLowLevelClient(settings); diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchTestClient.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchTestClient.cs index 6d1e7ba8f..f199d35f3 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchTestClient.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Elasticsearch/ElasticsearchTestClient.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using NewRelic.Agent.IntegrationTests.Shared; namespace MultiFunctionApplicationHelpers.NetStandardLibraries.Elasticsearch { @@ -14,7 +13,18 @@ internal abstract class ElasticsearchTestClient protected const string IndexName = "flights"; // Must be lowercase! protected const string BadIndexName = "_ILLEGAL"; - protected Uri Address = new Uri(ElasticSearchConfiguration.ElasticServer); + protected abstract Uri Address + { + get; + } + protected abstract string Username + { + get; + } + protected abstract string Password + { + get; + } public ElasticsearchTestClient() { } diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MongoDB/MongoDBDriverExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MongoDB/MongoDBDriverExerciser.cs index e9c16cb21..3859811e5 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MongoDB/MongoDBDriverExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MongoDB/MongoDBDriverExerciser.cs @@ -1,6 +1,15 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +// Several methods exercised here do not exist in MongoDB.Driver version 2.3 which is the oldest we support on .NET Framework. It is bound to the net462 TFM in MultiFunctionApplicationHelpers.csproj +#if NET462 +#define MONGODRIVER2_3 +#endif + +// Some methods exercised here do not exist in MongoDB.Driver version 2.8.1 which is the oldest we support on .NET Core. It is bound to the net6.0 TFM in MultiFunctionApplicationHelpers.csproj +#if NET6_0 +#define MONGODRIVER2_8_1 +#endif using System.Collections.Generic; using System.Linq; @@ -8,11 +17,11 @@ using MongoDB.Bson; using MongoDB.Driver.Linq; using System.Threading.Tasks; -using NewRelic.Agent.IntegrationTests.Shared; using NewRelic.Agent.IntegrationTests.Shared.ReflectionHelpers; using System.Runtime.CompilerServices; using NewRelic.Api.Agent; using System; +using System.Web.Http.Results; namespace MultiFunctionApplicationHelpers.NetStandardLibraries.MongoDB { @@ -43,7 +52,7 @@ public void SetMongoUrl(string mongoUrl) } - #region Drop +#region Drop public void DropDatabase(string databaseName) { @@ -52,7 +61,6 @@ public void DropDatabase(string databaseName) #endregion Drop - #region Insert [LibraryMethod] @@ -423,6 +431,105 @@ public IAsyncCursor Aggregate() return result; } + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task> AggregateAsync() + { + + var document = new CustomMongoDbEntity { Id = new ObjectId(), Name = "Fred Flintstone" }; + await Collection.InsertOneAsync(document); + + var match = new BsonDocument + { + { + "$match", + new BsonDocument + { + { "Name", "Fred Flintstone" } + } + } + }; + + var pipeline = new[] { match }; + var result = Collection.AggregateAsync(pipeline); + return await result; + } + +#if !MONGODRIVER2_3 && !MONGODRIVER2_8_1 + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void AggregateToCollection() + { + + var document = new CustomMongoDbEntity { Id = new ObjectId(), Name = "Fred Flintstone" }; + Collection.InsertOne(document); + + var matchStage = new BsonDocument + { + { + "$match", + new BsonDocument + { + { "Name", "Fred Flintstone" } + } + } + }; + + var outStage = new BsonDocument + { + { + "$out", + new BsonDocument + { + { "db", _dbName }, + { "coll", _defaultCollectionName } + } + } + }; + + var pipeline = new[] { matchStage, outStage }; + Collection.AggregateToCollection(pipeline); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task AggregateToCollectionAsync() + { + + var document = new CustomMongoDbEntity { Id = new ObjectId(), Name = "Fred Flintstone" }; + await Collection.InsertOneAsync(document); + + var matchStage = new BsonDocument + { + { + "$match", + new BsonDocument + { + { "Name", "Fred Flintstone" } + } + } + }; + + var outStage = new BsonDocument + { + { + "$out", + new BsonDocument + { + { "db", _dbName }, + { "coll", _defaultCollectionName } + } + } + }; + + var pipeline = new[] { matchStage, outStage }; + await Collection.AggregateToCollectionAsync(pipeline); + } +#endif + [LibraryMethod] [Transaction] [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] @@ -445,6 +552,31 @@ public async Task CountAsync() return await Collection.CountAsync(filter); } +// CountDocuments{Async} did not exist in driver version 2.3 which is bound to net462 in MultiFunctionApplicationHelpers.csproj +#if !MONGODRIVER2_3 + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public long CountDocuments() + { + var document = new CustomMongoDbEntity { Id = new ObjectId(), Name = "Fred Flintstone" }; + Collection.InsertOne(document); + var filter = Builders.Filter.Eq("Name", "Fred Flintstone"); + return Collection.CountDocuments(filter); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task CountDocumentsAsync() + { + var document = new CustomMongoDbEntity { Id = new ObjectId(), Name = "Fred Flintstone" }; + await Collection.InsertOneAsync(document); + var filter = Builders.Filter.Eq("Name", "Fred Flintstone"); + return await Collection.CountDocumentsAsync(filter); + } +#endif + [LibraryMethod] [Transaction] [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] @@ -469,6 +601,29 @@ public async Task> DistinctAsync() return await Collection.DistinctAsync("Name", filter); } +// EstimatedDocumentCount{Async} did not exist in driver version 2.3 which is bound to net462 in MultiFunctionApplicationHelpers.csproj +#if !MONGODRIVER2_3 + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public long EstimatedDocumentCount() + { + var document = new CustomMongoDbEntity { Id = new ObjectId(), Name = "Fred Flintstone" }; + Collection.InsertOne(document); + return Collection.EstimatedDocumentCount(); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task EstimatedDocumentCountAsync() + { + var document = new CustomMongoDbEntity { Id = new ObjectId(), Name = "Fred Flintstone" }; + await Collection.InsertOneAsync(document); + return await Collection.EstimatedDocumentCountAsync(); + } +#endif + [LibraryMethod] [Transaction] [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] @@ -533,8 +688,8 @@ public async Task> MapReduceAsync() #pragma warning restore CS0618 } -#if NET471_OR_GREATER || NETCOREAPP - //This call will throw exception because the Watch() method only work with MongoDb replica sets, but it is fine as long as the method is executed. +#if !MONGODRIVER2_3 + // This call will throw an exception because the Watch() method only works with MongoDb replica sets, but it is fine as long as the method is executed. [LibraryMethod] [Transaction] [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] @@ -554,7 +709,7 @@ public string Watch() } } - //This call will throw exception because the Watch() method only work with MongoDb replica sets, but it is fine as long as the method is executed. + // This call will throw an exception because the Watch() method only works with MongoDb replica sets, but it is fine as long as the method is executed. [LibraryMethod] [Transaction] [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] @@ -576,10 +731,128 @@ public async Task WatchAsync() } #endif -#endregion + #endregion #region Database +#if !MONGODRIVER2_3 && !MONGODRIVER2_8_1 + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public string AggregateDB() + { + var listLocalSessions = new BsonDocument + { + { + "$listLocalSessions", + new BsonDocument + { + // empty + } + } + }; + + var pipeline = new[] { listLocalSessions }; + var result = Db.Aggregate(pipeline); + return result.FirstOrDefault().ToString(); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task AggregateDBAsync() + { + var listLocalSessions = new BsonDocument + { + { + "$listLocalSessions", + new BsonDocument + { + // empty + } + } + }; + + var pipeline = new[] { listLocalSessions }; + var result = await Db.AggregateAsync(pipeline); + return result.FirstOrDefault().ToString(); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void AggregateDBToCollection() + { + var listLocalSessions = new BsonDocument + { + { + "$listLocalSessions", + new BsonDocument + { + // empty + } + } + }; + + var outStage = new BsonDocument + { + { + "$out", + new BsonDocument + { + { "db", _dbName }, + { "coll", _defaultCollectionName } + } + } + }; + + var pipeline = new[] { listLocalSessions, outStage }; + Db.AggregateToCollection(pipeline); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task AggregateDBToCollectionAsync() + { + var listLocalSessions = new BsonDocument + { + { + "$listLocalSessions", + new BsonDocument + { + // empty + } + } + }; + + var outStage = new BsonDocument + { + { + "$out", + new BsonDocument + { + { "db", _dbName }, + { "coll", _defaultCollectionName } + } + } + }; + + var pipeline = new[] { listLocalSessions, outStage }; + try + { + await Db.AggregateToCollectionAsync(pipeline); + } + catch + { + // This method is throwing an exception in net471 for unknown reasons. However, we just need the method to execute + // so our instrumentation runs and generates the expected metric, so just swallow the exception. + } + } + +#endif + [LibraryMethod] [Transaction] [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] @@ -647,6 +920,27 @@ public async Task ListCollectionsAsync() return collections.Count; } +#if !MONGODRIVER2_3 + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public int ListCollectionNames() + { + var collections = Db.ListCollectionNames().ToList(); + return collections.Count(); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task ListCollectionNamesAsync() + { + var cursor = await Db.ListCollectionNamesAsync(); + var collections = cursor.ToList(); + return collections.Count; + } +#endif + [LibraryMethod] [Transaction] [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] @@ -699,9 +993,44 @@ public async Task RunCommandAsync() return result.ToString(); } -#endregion +#if !MONGODRIVER2_3 + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public string WatchDB() + { + try + { + var result = Db.Watch(); + return "Ok"; + } + catch (MongoCommandException) + { + return "Got exception but it is ok!"; + } + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task WatchDBAsync() + { + try + { + var result = await Db.WatchAsync(); + return "Ok"; + } + catch (MongoCommandException) + { + return "Got exception but it is ok!"; + } + } + +#endif + + #endregion -#region IndexManager + #region IndexManager [LibraryMethod] [Transaction] diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/MicrosoftDataSqlClientExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/MicrosoftDataSqlClientExerciser.cs index 37ccbb990..e1ca7f1f2 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/MicrosoftDataSqlClientExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/MicrosoftDataSqlClientExerciser.cs @@ -86,7 +86,7 @@ public async Task MsSqlAsync(string tableName) using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString)) { - connection.Open(); + await connection.OpenAsync(); using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = 'John'", connection)) { @@ -166,7 +166,7 @@ public async Task MsSqlAsync_WithParameterizedQuery(string tableName, bo using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString)) { - connection.Open(); + await connection.OpenAsync(); using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = @FN", connection)) { diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataExerciser.cs index 495143e03..33b821dc9 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataExerciser.cs @@ -88,7 +88,7 @@ public async Task MsSqlAsync(string tableName) using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString)) { - connection.Open(); + await connection.OpenAsync(); using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = 'John'", connection)) { @@ -169,7 +169,7 @@ public async Task MsSqlAsync_WithParameterizedQuery(string tableName, bo using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString)) { - connection.Open(); + await connection.OpenAsync(); using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = @FN", connection)) { diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataSqlClientExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataSqlClientExerciser.cs index 0b604e5a2..f36b527dc 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataSqlClientExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataSqlClientExerciser.cs @@ -88,7 +88,7 @@ public async Task MsSqlAsync(string tableName) using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString)) { - connection.Open(); + await connection.OpenAsync(); using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = 'John'", connection)) { @@ -169,7 +169,7 @@ public async Task MsSqlAsync_WithParameterizedQuery(string tableName, bo using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString)) { - connection.Open(); + await connection.OpenAsync(); using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = @FN", connection)) { diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlConnectorExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlConnectorExerciser.cs index fa6770a4d..ffef971f8 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlConnectorExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlConnectorExerciser.cs @@ -215,7 +215,7 @@ private async Task ExecuteCommandAsync(Func> using (var connection = new MySqlConnection(MySqlTestConfiguration.MySqlConnectionString)) using (var command = new MySqlCommand("SELECT _date FROM dates WHERE _date LIKE '2%' ORDER BY _date DESC LIMIT 1", connection)) { - connection.Open(); + await connection.OpenAsync(); result = await action(command); } diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlExerciser.cs index 61d102e0b..07f1f220f 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlExerciser.cs @@ -48,7 +48,7 @@ public async Task SingleDateQueryAsync() using (var connection = new MySqlConnection(MySqlTestConfiguration.MySqlConnectionString)) using (var command = new MySqlCommand("SELECT _date FROM dates WHERE _date LIKE '2%' ORDER BY _date DESC LIMIT 10000", connection)) { - connection.Open(); + await connection.OpenAsync(); using (var reader = await command.ExecuteReaderAsync()) { while (await reader.ReadAsync()) diff --git a/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcApplication/Controllers/OracleController.cs b/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcApplication/Controllers/OracleController.cs index 1267ce4a5..73167f61b 100644 --- a/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcApplication/Controllers/OracleController.cs +++ b/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcApplication/Controllers/OracleController.cs @@ -76,7 +76,7 @@ public async Task OracleAsync(string tableName) using (var connection = new OracleConnection(connectionString)) { - connection.Open(); + await connection.OpenAsync(); using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection)) { diff --git a/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcCoreApplication/Controllers/MicrosoftDataSqlClientController.cs b/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcCoreApplication/Controllers/MicrosoftDataSqlClientController.cs index e145b6be5..6accd6a5d 100644 --- a/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcCoreApplication/Controllers/MicrosoftDataSqlClientController.cs +++ b/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcCoreApplication/Controllers/MicrosoftDataSqlClientController.cs @@ -75,7 +75,7 @@ public async Task MsSqlAsync(string tableName) using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString)) { - connection.Open(); + await connection.OpenAsync(); using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = 'John'", connection)) { @@ -153,7 +153,7 @@ public async Task MsSqlAsync_WithParameterizedQuery(string tableName, bo using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString)) { - connection.Open(); + await connection.OpenAsync(); using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = @FN", connection)) { diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/CosmosDB/CosmosDBTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/CosmosDB/CosmosDBTests.cs index 409ea21be..327977c9b 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/CosmosDB/CosmosDBTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/CosmosDB/CosmosDBTests.cs @@ -29,31 +29,35 @@ protected CosmosDBTestsBase(TFixture fixture, ITestOutputHelper output) : base(f _fixture.SetTimeout(TimeSpan.FromMinutes(2)); _fixture.AddCommand($"CosmosDBExerciser StartAgent"); - _fixture.AddCommand($"CosmosDBExerciser CreateReadAndDeleteDatabase {UniqueDbName}"); - _fixture.AddCommand($"CosmosDBExerciser CreateReadAndDeleteContainers {UniqueDbName} {_testContainerName}"); - _fixture.AddCommand($"CosmosDBExerciser CreateAndReadItems {UniqueDbName} {_testContainerName}"); - _fixture.AddCommand($"CosmosDBExerciser CreateAndQueryItems {UniqueDbName} {_testContainerName}"); var itemsToCreate = 20; - _fixture.AddCommand($"CosmosDBExerciser CreateItemsConcurrentlyAsync {UniqueDbName} {_testContainerName} {itemsToCreate}"); - _fixture.AddCommand($"CosmosDBExerciser CreateAndExecuteStoredProc {UniqueDbName} {_testContainerName}"); - _fixture.Actions + _fixture.AddActions ( setupConfiguration: () => { var configPath = fixture.DestinationNewRelicConfigFilePath; var configModifier = new NewRelicConfigModifier(configPath); + configModifier.ConfigureFasterMetricsHarvestCycle(15); + configModifier.ConfigureFasterSqlTracesHarvestCycle(15); + configModifier.ConfigureFasterSpanEventsHarvestCycle(15); + configModifier.ForceTransactionTraces(); CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(configPath, new[] { "configuration", "transactionTracer" }, "explainEnabled", "true"); CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(configPath, new[] { "configuration", "transactionTracer" }, "explainThreshold", "1"); + }, + exerciseApplication: () => + { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.MetricDataLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); @@ -68,23 +72,16 @@ public void CreateReadAndDeleteDatabaseTests() var expectedMetrics = new List { new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/ReadFeedDatabase", metricScope = expectedTransactionName, callCount = 2 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/ReadDatabase", metricScope = expectedTransactionName, callCount = 2 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/DeleteDatabase", metricScope = expectedTransactionName, callCount = 1 }, }; var metrics = _fixture.AgentLog.GetMetrics().ToList(); - var spanEvents = _fixture.AgentLog.GetSpanEvents(); - var traceId = spanEvents.Where(@event => @event.IntrinsicAttributes["name"].ToString().Equals(expectedTransactionName)).FirstOrDefault().IntrinsicAttributes["traceId"]; - var operationDatastoreSpans = spanEvents.Where(@event => @event.IntrinsicAttributes["traceId"].ToString().Equals(traceId) && @event.IntrinsicAttributes["name"].ToString().Contains("Datastore/operation/CosmosDB")); - NrAssert.Multiple ( () => Assertions.MetricsExist(expectedMetrics, metrics), @@ -100,15 +97,10 @@ public void CreateReadAndDeleteContainersTests() var expectedMetrics = new List { new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/ReadDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/DeleteDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateCollection", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/statement/CosmosDB/{_testContainerName}/ReadCollection", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/statement/CosmosDB/{_testContainerName}/DeleteCollection", metricScope = expectedTransactionName, callCount = 1 } }; @@ -127,13 +119,9 @@ public void CreateAndReadItemsTests() var expectedMetrics = new List { new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/ReadDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/DeleteDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateCollection", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/statement/CosmosDB/{_testContainerName}/ReadCollection", metricScope = expectedTransactionName, callCount = 1 }, //From calling Container.CreateItemStreamAsync() and Container.CreateItemAsync() @@ -165,13 +153,9 @@ public void CreateAndQueryItemsTests() var expectedMetrics = new List { new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/ReadDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/DeleteDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateCollection", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/statement/CosmosDB/{_testContainerName}/ReadCollection", metricScope = expectedTransactionName, callCount = 1 }, //From calling Container.CreateItemStreamAsync() and Container.CreateItemAsync() @@ -184,7 +168,6 @@ public void CreateAndQueryItemsTests() new Assertions.ExpectedMetric { metricName = $"Datastore/statement/CosmosDB/{_testContainerName}/QueryDocument", metricScope = expectedTransactionName, callCount = 2 } }; - var expectedSqlTraces = new List { new Assertions.ExpectedSqlTrace @@ -212,13 +195,9 @@ public void BulkCreatingItemsTests() var expectedMetrics = new List { new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/ReadDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/DeleteDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateCollection", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/statement/CosmosDB/{_testContainerName}/BatchDocument", metricScope = expectedTransactionName, callCount = 1 } }; @@ -235,11 +214,8 @@ public void CreateAndExecuteStoredProcTests() var expectedMetrics = new List { new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/CreateDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/ReadDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/DeleteDatabase", metricScope = expectedTransactionName, callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"Datastore/statement/CosmosDB/{_testContainerName}/ReadCollection", metricScope = expectedTransactionName, callCount = 1 }, //From calling Scripts.CreateStoredProcedureAsync() diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Elasticsearch/ElasticsearchTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Elasticsearch/ElasticsearchTests.cs index 3799e2e42..ed249624a 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Elasticsearch/ElasticsearchTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Elasticsearch/ElasticsearchTests.cs @@ -26,7 +26,7 @@ protected enum ClientType protected readonly ConsoleDynamicMethodFixture _fixture; - protected readonly string _host = GetHostFromElasticServer(ElasticSearchConfiguration.ElasticServer); + protected readonly string _host; protected readonly ClientType _clientType; @@ -39,45 +39,42 @@ protected ElasticsearchTestsBase(TFixture fixture, ITestOutputHelper output, Cli _fixture.TestLogger = output; _clientType = clientType; - _fixture.SetTimeout(TimeSpan.FromMinutes(2)); + _host = GetHostFromElasticServer(_clientType); - if (_clientType != ClientType.ElasticClients) - { - // This lets 7.x clients work with an 8.x server - _fixture.SetAdditionalEnvironmentVariable("ELASTIC_CLIENT_APIVERSIONING", "true"); - } + _fixture.SetTimeout(TimeSpan.FromMinutes(2)); _fixture.AddCommand($"ElasticsearchExerciser SetClient {clientType}"); // Async operations - _fixture.AddCommand($"ElasticsearchExerciser IndexAsync"); - _fixture.AddCommand($"ElasticsearchExerciser SearchAsync"); - _fixture.AddCommand($"ElasticsearchExerciser IndexManyAsync"); - _fixture.AddCommand($"ElasticsearchExerciser MultiSearchAsync"); // Sync operations - _fixture.AddCommand($"ElasticsearchExerciser Index"); - _fixture.AddCommand($"ElasticsearchExerciser Search"); - _fixture.AddCommand($"ElasticsearchExerciser IndexMany"); - _fixture.AddCommand($"ElasticsearchExerciser MultiSearch"); - _fixture.AddCommand($"ElasticsearchExerciser GenerateError"); - _fixture.Actions + _fixture.AddActions ( setupConfiguration: () => { var configPath = fixture.DestinationNewRelicConfigFilePath; var configModifier = new NewRelicConfigModifier(configPath); + configModifier.ConfigureFasterMetricsHarvestCycle(15); + configModifier.ConfigureFasterErrorTracesHarvestCycle(15); + configModifier.ConfigureFasterSpanEventsHarvestCycle(15); + configModifier.ForceTransactionTraces(); + }, + exerciseApplication: () => + { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.MetricDataLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.SpanEventDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); @@ -179,8 +176,10 @@ private void ValidateOperation(string operationName) ); } - private static string GetHostFromElasticServer(string elasticServer) + private static string GetHostFromElasticServer(ClientType clientType) { + var elasticServer = clientType == ClientType.ElasticClients ? ElasticSearchConfiguration.ElasticServer : ElasticSearch7Configuration.ElasticServer; + if (elasticServer.StartsWith("https://")) { return elasticServer.Remove(0, "https://".Length); diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverCollectionTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverCollectionTests.cs index 14dffcc04..e31447ba2 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverCollectionTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverCollectionTests.cs @@ -16,59 +16,72 @@ public abstract class MongoDBDriverCollectionTestsBase : NewRelicInteg { private readonly ConsoleDynamicMethodFixture _fixture; - private bool _clientSupportsWatchMethods; + private MongoDBDriverVersion _driverVersion; private string _mongoUrl; private readonly string DatastorePath = "Datastore/statement/MongoDB/myCollection"; - public MongoDBDriverCollectionTestsBase(TFixture fixture, ITestOutputHelper output, string mongoUrl, bool clientSupportsWatchMethods = true) : base(fixture) + public MongoDBDriverCollectionTestsBase(TFixture fixture, ITestOutputHelper output, string mongoUrl, MongoDBDriverVersion driverVersion) : base(fixture) { _fixture = fixture; _fixture.TestLogger = output; _mongoUrl = mongoUrl; - _clientSupportsWatchMethods = clientSupportsWatchMethods; + _driverVersion = driverVersion; _fixture.AddCommand($"MongoDbDriverExerciser SetMongoUrl {_mongoUrl}"); - _fixture.AddCommand("MongoDbDriverExerciser Count"); + // Async methods first + _fixture.AddCommand("MongoDbDriverExerciser AggregateAsync"); + _fixture.AddCommand("MongoDbDriverExerciser BulkWriteAsync"); _fixture.AddCommand("MongoDbDriverExerciser CountAsync"); - _fixture.AddCommand("MongoDbDriverExerciser Distinct"); + _fixture.AddCommand("MongoDbDriverExerciser DeleteManyAsync"); + _fixture.AddCommand("MongoDbDriverExerciser DeleteOneAsync"); _fixture.AddCommand("MongoDbDriverExerciser DistinctAsync"); - _fixture.AddCommand("MongoDbDriverExerciser MapReduce"); - _fixture.AddCommand("MongoDbDriverExerciser MapReduceAsync"); - - // watch and watchasync are unavailable in MongoDB.Driver version 2.3 - if (_clientSupportsWatchMethods) - { - _fixture.AddCommand("MongoDbDriverExerciser Watch"); - _fixture.AddCommand("MongoDbDriverExerciser WatchAsync"); - } - - _fixture.AddCommand("MongoDbDriverExerciser InsertOne"); - _fixture.AddCommand("MongoDbDriverExerciser InsertOneAsync"); - _fixture.AddCommand("MongoDbDriverExerciser InsertMany"); + _fixture.AddCommand("MongoDbDriverExerciser FindAsync"); + _fixture.AddCommand("MongoDbDriverExerciser FindOneAndDeleteAsync"); + _fixture.AddCommand("MongoDbDriverExerciser FindOneAndReplaceAsync"); + _fixture.AddCommand("MongoDbDriverExerciser FindOneAndUpdateAsync"); _fixture.AddCommand("MongoDbDriverExerciser InsertManyAsync"); - _fixture.AddCommand("MongoDbDriverExerciser ReplaceOne"); + _fixture.AddCommand("MongoDbDriverExerciser InsertOneAsync"); + _fixture.AddCommand("MongoDbDriverExerciser MapReduceAsync"); _fixture.AddCommand("MongoDbDriverExerciser ReplaceOneAsync"); - _fixture.AddCommand("MongoDbDriverExerciser UpdateOne"); - _fixture.AddCommand("MongoDbDriverExerciser UpdateOneAsync"); - _fixture.AddCommand("MongoDbDriverExerciser UpdateMany"); _fixture.AddCommand("MongoDbDriverExerciser UpdateManyAsync"); - _fixture.AddCommand("MongoDbDriverExerciser DeleteOne"); - _fixture.AddCommand("MongoDbDriverExerciser DeleteOneAsync"); + _fixture.AddCommand("MongoDbDriverExerciser UpdateOneAsync"); + // Then sync methods + _fixture.AddCommand("MongoDbDriverExerciser Aggregate"); + _fixture.AddCommand("MongoDbDriverExerciser BulkWrite"); + _fixture.AddCommand("MongoDbDriverExerciser Count"); _fixture.AddCommand("MongoDbDriverExerciser DeleteMany"); - _fixture.AddCommand("MongoDbDriverExerciser DeleteManyAsync"); - _fixture.AddCommand("MongoDbDriverExerciser FindSync"); - _fixture.AddCommand("MongoDbDriverExerciser FindAsync"); + _fixture.AddCommand("MongoDbDriverExerciser DeleteOne"); + _fixture.AddCommand("MongoDbDriverExerciser Distinct"); _fixture.AddCommand("MongoDbDriverExerciser FindOneAndDelete"); - _fixture.AddCommand("MongoDbDriverExerciser FindOneAndDeleteAsync"); _fixture.AddCommand("MongoDbDriverExerciser FindOneAndReplace"); - _fixture.AddCommand("MongoDbDriverExerciser FindOneAndReplaceAsync"); _fixture.AddCommand("MongoDbDriverExerciser FindOneAndUpdate"); - _fixture.AddCommand("MongoDbDriverExerciser FindOneAndUpdateAsync"); - _fixture.AddCommand("MongoDbDriverExerciser BulkWrite"); - _fixture.AddCommand("MongoDbDriverExerciser BulkWriteAsync"); - _fixture.AddCommand("MongoDbDriverExerciser Aggregate"); + _fixture.AddCommand("MongoDbDriverExerciser FindSync"); + _fixture.AddCommand("MongoDbDriverExerciser MapReduce"); + _fixture.AddCommand("MongoDbDriverExerciser InsertMany"); + _fixture.AddCommand("MongoDbDriverExerciser InsertOne"); + _fixture.AddCommand("MongoDbDriverExerciser ReplaceOne"); + _fixture.AddCommand("MongoDbDriverExerciser UpdateMany"); + _fixture.AddCommand("MongoDbDriverExerciser UpdateOne"); + + // the following commands are unavailable in MongoDB.Driver version 2.3 + if (_driverVersion > MongoDBDriverVersion.OldestSupportedOnFramework) + { + _fixture.AddCommand("MongoDbDriverExerciser CountDocumentsAsync"); + _fixture.AddCommand("MongoDbDriverExerciser EstimatedDocumentCountAsync"); + _fixture.AddCommand("MongoDbDriverExerciser WatchAsync"); + _fixture.AddCommand("MongoDbDriverExerciser CountDocuments"); + _fixture.AddCommand("MongoDbDriverExerciser EstimatedDocumentCount"); + _fixture.AddCommand("MongoDbDriverExerciser Watch"); + } + + // the following commands are unavailable in MongoDB.Driver versions <2.11 + if (_driverVersion >= MongoDBDriverVersion.AtLeast2_11) + { + _fixture.AddCommand("MongoDbDriverExerciser AggregateToCollectionAsync"); + _fixture.AddCommand("MongoDbDriverExerciser AggregateToCollection"); + } _fixture.AddActions ( @@ -96,250 +109,68 @@ public void CheckForDatastoreInstanceMetrics() Assert.NotNull(m); } - [Fact] - public void Count() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/Count"); - Assert.NotNull(m); - } - - [Fact] - public void CountAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/CountAsync"); - Assert.NotNull(m); - } - - [Fact] - public void Distinct() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/Distinct"); - Assert.NotNull(m); - } - - [Fact] - public void DistinctAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/DistinctAsync"); - Assert.NotNull(m); - } - - [Fact] - public void MapReduce() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/MapReduce"); - Assert.NotNull(m); - } - - [Fact] - public void MapReduceAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/MapReduceAsync"); - Assert.NotNull(m); - } - - [Fact] - public void Watch() - { - if (_clientSupportsWatchMethods) - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/Watch"); - Assert.NotNull(m); - } - } - - [Fact] - public void WatchAsync() - { - if (_clientSupportsWatchMethods) + [Theory] + [InlineData("Count", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("CountAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("Distinct", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("DistinctAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("MapReduce", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("MapReduceAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("InsertOne", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("InsertOneAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("InsertMany", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("InsertManyAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("ReplaceOne", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("ReplaceOneAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("UpdateOne", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("UpdateOneAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("UpdateMany", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("UpdateManyAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("DeleteOne", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("DeleteOneAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("DeleteMany", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("DeleteManyAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("FindSync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("FindAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("FindOneAndDelete", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("FindOneAndDeleteAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("FindOneAndReplace", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("FindOneAndReplaceAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("FindOneAndUpdate", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("FindOneAndUpdateAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("BulkWrite", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("BulkWriteAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("Aggregate", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("AggregateAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + + // Methods unavailable in driver version 2.3 + [InlineData("CountDocuments", MongoDBDriverVersion.OldestSupportedOnCore)] + [InlineData("CountDocumentsAsync", MongoDBDriverVersion.OldestSupportedOnCore)] + [InlineData("EstimatedDocumentCount", MongoDBDriverVersion.OldestSupportedOnCore)] + [InlineData("EstimatedDocumentCountAsync", MongoDBDriverVersion.OldestSupportedOnCore)] + [InlineData("Watch", MongoDBDriverVersion.OldestSupportedOnCore)] + [InlineData("WatchAsync", MongoDBDriverVersion.OldestSupportedOnCore)] + + //Methods availabile in driver versions >= 2.11 + [InlineData("AggregateToCollection", MongoDBDriverVersion.AtLeast2_11)] + [InlineData("AggregateToCollectionAsync", MongoDBDriverVersion.AtLeast2_11)] + + public void CheckForMethodMetrics(string methodName, MongoDBDriverVersion minVersion) + { + if (_driverVersion >= minVersion) { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/WatchAsync"); - Assert.NotNull(m); + var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/{methodName}"); + Assert.True(m != null, $"Did not find metric for db operation named {methodName}"); } } - [Fact] - public void InsertOne() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/InsertOne"); - Assert.NotNull(m); - } - - [Fact] - public void InsertOneAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/InsertOneAsync"); - Assert.NotNull(m); - } - - [Fact] - public void InsertMany() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/InsertMany"); - Assert.NotNull(m); - } - - [Fact] - public void InsertManyAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/InsertManyAsync"); - Assert.NotNull(m); - } - - [Fact] - public void ReplaceOne() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/ReplaceOne"); - Assert.NotNull(m); - } - - [Fact] - public void ReplaceOneAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/ReplaceOneAsync"); - Assert.NotNull(m); - } - - [Fact] - public void UpdateOne() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/UpdateOne"); - Assert.NotNull(m); - } - - [Fact] - public void UpdateOneAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/UpdateOneAsync"); - Assert.NotNull(m); - } - - [Fact] - public void UpdateMany() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/UpdateMany"); - Assert.NotNull(m); - } - - [Fact] - public void UpdateManyAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/UpdateManyAsync"); - Assert.NotNull(m); - } - - [Fact] - public void DeleteOne() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/DeleteOne"); - Assert.NotNull(m); - } - - [Fact] - public void DeleteOneAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/DeleteOneAsync"); - Assert.NotNull(m); - } - - [Fact] - public void DeleteMany() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/DeleteMany"); - Assert.NotNull(m); - } - - [Fact] - public void DeleteManyAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/DeleteManyAsync"); - Assert.NotNull(m); - } - - [Fact] - public void FindSync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/FindSync"); - Assert.NotNull(m); - } - - [Fact] - public void FindAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/FindAsync"); - Assert.NotNull(m); - } - - [Fact] - public void FindOneAndDelete() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/FindOneAndDelete"); - Assert.NotNull(m); - } - - [Fact] - public void FindOneAndDeleteAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/FindOneAndDeleteAsync"); - Assert.NotNull(m); - } - - [Fact] - public void FindOneAndReplace() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/FindOneAndReplace"); - Assert.NotNull(m); - } - - [Fact] - public void FindOneAndReplaceAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/FindOneAndReplaceAsync"); - Assert.NotNull(m); - } - - [Fact] - public void FindOneAndUpdate() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/FindOneAndUpdate"); - Assert.NotNull(m); - } - - [Fact] - public void FindOneAndUpdateAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/FindOneAndUpdateAsync"); - Assert.NotNull(m); - } - - [Fact] - public void BulkWrite() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/BulkWrite"); - Assert.NotNull(m); - } - - [Fact] - public void BulkWriteAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/BulkWriteAsync"); - Assert.NotNull(m); - } - - [Fact] - public void Aggregate() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/Aggregate"); - Assert.NotNull(m); - } - } [NetFrameworkTest] public class MongoDBDriverCollectionTestsFWLatest : MongoDBDriverCollectionTestsBase { public MongoDBDriverCollectionTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.AtLeast2_11) { } } @@ -348,7 +179,7 @@ public MongoDBDriverCollectionTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest public class MongoDBDriverCollectionTestsFW48 : MongoDBDriverCollectionTestsBase { public MongoDBDriverCollectionTestsFW48(ConsoleDynamicMethodFixtureFW48 fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.AtLeast2_11) { } } @@ -357,7 +188,7 @@ public MongoDBDriverCollectionTestsFW48(ConsoleDynamicMethodFixtureFW48 fixture, public class MongoDBDriverCollectionTestsFW471 : MongoDBDriverCollectionTestsBase { public MongoDBDriverCollectionTestsFW471(ConsoleDynamicMethodFixtureFW471 fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.AtLeast2_11) { } } @@ -368,7 +199,7 @@ public class MongoDBDriverCollectionTestsFW462 : MongoDBDriverCollectionTestsBas public MongoDBDriverCollectionTestsFW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) // FW462 is testing MongoDB.Driver version 2.3, which needs to connect to the 3.2 server // 2.3 doesn't support the Watch/WatchAsync methods - : base(fixture, output, MongoDbConfiguration.MongoDb3_2ConnectionString, false) + : base(fixture, output, MongoDbConfiguration.MongoDb3_2ConnectionString, MongoDBDriverVersion.OldestSupportedOnFramework) { } } @@ -377,7 +208,7 @@ public MongoDBDriverCollectionTestsFW462(ConsoleDynamicMethodFixtureFW462 fixtur public class MongoDBDriverCollectionTestsCoreLatest : MongoDBDriverCollectionTestsBase { public MongoDBDriverCollectionTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.AtLeast2_11) { } } @@ -386,9 +217,16 @@ public MongoDBDriverCollectionTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLat public class MongoDBDriverCollectionTestsCoreOldest : MongoDBDriverCollectionTestsBase { public MongoDBDriverCollectionTestsCoreOldest(ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.OldestSupportedOnCore) { } } + public enum MongoDBDriverVersion + { + OldestSupportedOnFramework, // 2.3 + OldestSupportedOnCore, // 2.8.1 + AtLeast2_11 // 2.11 or greater + } + } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverDatabaseTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverDatabaseTests.cs index 80eb3cc8f..998bb1316 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverDatabaseTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverDatabaseTests.cs @@ -15,24 +15,47 @@ public abstract class MongoDBDriverDatabaseTestsBase : NewRelicIntegra { private readonly ConsoleDynamicMethodFixture _fixture; private string _mongoUrl; + private MongoDBDriverVersion _driverVersion; - public MongoDBDriverDatabaseTestsBase(TFixture fixture, ITestOutputHelper output, string mongoUrl) : base(fixture) + private readonly string DatastoreStatementPathBase = "Datastore/statement/MongoDB"; + private readonly string DatastoreOperationPathBase = "Datastore/operation/MongoDB"; + + public MongoDBDriverDatabaseTestsBase(TFixture fixture, ITestOutputHelper output, string mongoUrl, MongoDBDriverVersion driverVersion) : base(fixture) { _fixture = fixture; _fixture.TestLogger = output; _mongoUrl = mongoUrl; + _driverVersion = driverVersion; _fixture.AddCommand($"MongoDbDriverExerciser SetMongoUrl {_mongoUrl}"); - _fixture.AddCommand("MongoDBDriverExerciser CreateCollection"); + // Async methods first _fixture.AddCommand("MongoDBDriverExerciser CreateCollectionAsync"); - _fixture.AddCommand("MongoDBDriverExerciser DropCollection"); _fixture.AddCommand("MongoDBDriverExerciser DropCollectionAsync"); - _fixture.AddCommand("MongoDBDriverExerciser ListCollections"); _fixture.AddCommand("MongoDBDriverExerciser ListCollectionsAsync"); - _fixture.AddCommand("MongoDBDriverExerciser RenameCollection"); _fixture.AddCommand("MongoDBDriverExerciser RenameCollectionAsync"); - _fixture.AddCommand("MongoDBDriverExerciser RunCommand"); _fixture.AddCommand("MongoDBDriverExerciser RunCommandAsync"); + // Then sync methods + _fixture.AddCommand("MongoDBDriverExerciser CreateCollection"); + _fixture.AddCommand("MongoDBDriverExerciser DropCollection"); + _fixture.AddCommand("MongoDBDriverExerciser ListCollections"); + _fixture.AddCommand("MongoDBDriverExerciser RenameCollection"); + _fixture.AddCommand("MongoDBDriverExerciser RunCommand"); + + if (_driverVersion > MongoDBDriverVersion.OldestSupportedOnFramework) + { + _fixture.AddCommand("MongoDBDriverExerciser ListCollectionNamesAsync"); + _fixture.AddCommand("MongoDBDriverExerciser WatchDBAsync"); + _fixture.AddCommand("MongoDBDriverExerciser ListCollectionNames"); + _fixture.AddCommand("MongoDBDriverExerciser WatchDB"); + } + + if (_driverVersion > MongoDBDriverVersion.OldestSupportedOnCore) + { + _fixture.AddCommand("MongoDBDriverExerciser AggregateDBAsync"); + _fixture.AddCommand("MongoDBDriverExerciser AggregateDBToCollectionAsync"); + _fixture.AddCommand("MongoDBDriverExerciser AggregateDB"); + _fixture.AddCommand("MongoDBDriverExerciser AggregateDBToCollection"); + } _fixture.AddActions ( @@ -60,92 +83,50 @@ public void CheckForDatastoreInstanceMetrics() Assert.NotNull(m); } - [Fact] - public void CreateCollection() + [Theory] + [InlineData("createTestCollection", "CreateCollection")] + [InlineData("createTestCollectionAsync", "CreateCollectionAsync")] + [InlineData("dropTestCollection", "DropCollection")] + [InlineData("dropTestCollectionAsync", "DropCollectionAsync")] + public void CheckForStatementMetrics(string collectionName, string operationName) { - var m = _fixture.AgentLog.GetMetricByName("Datastore/statement/MongoDB/createTestCollection/CreateCollection"); - + var m = _fixture.AgentLog.GetMetricByName($"{DatastoreStatementPathBase}/{collectionName}/{operationName}"); Assert.NotNull(m); } - [Fact] - public void CreateCollectionAsync() - { - var m = _fixture.AgentLog.GetMetricByName("Datastore/statement/MongoDB/createTestCollectionAsync/CreateCollectionAsync"); - - Assert.NotNull(m); - } - - [Fact] - public void DropCollection() - { - var m = _fixture.AgentLog.GetMetricByName("Datastore/statement/MongoDB/dropTestCollection/DropCollection"); - - Assert.NotNull(m); - } - - [Fact] - public void DropCollectionAsync() - { - var m = _fixture.AgentLog.GetMetricByName("Datastore/statement/MongoDB/dropTestCollectionAsync/DropCollectionAsync"); - - Assert.NotNull(m); + [Theory] + [InlineData("ListCollections", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("ListCollectionsAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("RenameCollection", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("RenameCollectionAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("RunCommand", MongoDBDriverVersion.OldestSupportedOnFramework)] + [InlineData("RunCommandAsync", MongoDBDriverVersion.OldestSupportedOnFramework)] + // Methods not available in driver version 2.3 + [InlineData("ListCollectionNames", MongoDBDriverVersion.OldestSupportedOnCore)] + [InlineData("ListCollectionNamesAsync", MongoDBDriverVersion.OldestSupportedOnCore)] + [InlineData("Watch", MongoDBDriverVersion.OldestSupportedOnCore)] + [InlineData("WatchAsync", MongoDBDriverVersion.OldestSupportedOnCore)] + // Methods not available in driver version 2.8 + [InlineData("Aggregate", MongoDBDriverVersion.AtLeast2_11)] + [InlineData("AggregateAsync", MongoDBDriverVersion.AtLeast2_11)] + [InlineData("AggregateToCollection", MongoDBDriverVersion.AtLeast2_11)] + [InlineData("AggregateToCollectionAsync", MongoDBDriverVersion.AtLeast2_11)] + public void CheckForOperationMetrics(string operationName, MongoDBDriverVersion minVersion) + { + if (_driverVersion >= minVersion) + { + var m = _fixture.AgentLog.GetMetricByName($"{DatastoreOperationPathBase}/{operationName}"); + Assert.NotNull(m); + } } - [Fact] - public void ListCollections() - { - var m = _fixture.AgentLog.GetMetricByName("Datastore/operation/MongoDB/ListCollections"); - - Assert.NotNull(m); - } - - [Fact] - public void ListCollectionsAsync() - { - var m = _fixture.AgentLog.GetMetricByName("Datastore/operation/MongoDB/ListCollectionsAsync"); - - Assert.NotNull(m); - } - - [Fact] - public void RenameCollection() - { - var m = _fixture.AgentLog.GetMetricByName("Datastore/operation/MongoDB/RenameCollection"); - - Assert.NotNull(m); - } - - [Fact] - public void RenameCollectionAsync() - { - var m = _fixture.AgentLog.GetMetricByName("Datastore/operation/MongoDB/RenameCollectionAsync"); - - Assert.NotNull(m); - } - - [Fact] - public void RunCommand() - { - var m = _fixture.AgentLog.GetMetricByName("Datastore/operation/MongoDB/RunCommand"); - - Assert.NotNull(m); - } - - [Fact] - public void RunCommandAsync() - { - var m = _fixture.AgentLog.GetMetricByName("Datastore/operation/MongoDB/RunCommandAsync"); - - Assert.NotNull(m); - } } [NetFrameworkTest] public class MongoDBDriverDatabaseTestsFWLatest : MongoDBDriverDatabaseTestsBase { public MongoDBDriverDatabaseTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.AtLeast2_11) { } } @@ -154,7 +135,7 @@ public MongoDBDriverDatabaseTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fi public class MongoDBDriverDatabaseTestsFW48 : MongoDBDriverDatabaseTestsBase { public MongoDBDriverDatabaseTestsFW48(ConsoleDynamicMethodFixtureFW48 fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.AtLeast2_11) { } } @@ -163,7 +144,7 @@ public MongoDBDriverDatabaseTestsFW48(ConsoleDynamicMethodFixtureFW48 fixture, I public class MongoDBDriverDatabaseTestsFW471 : MongoDBDriverDatabaseTestsBase { public MongoDBDriverDatabaseTestsFW471(ConsoleDynamicMethodFixtureFW471 fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.AtLeast2_11) { } } @@ -173,7 +154,7 @@ public class MongoDBDriverDatabaseTestsFW462 : MongoDBDriverDatabaseTestsBase { public MongoDBDriverDatabaseTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.AtLeast2_11) { } } @@ -191,7 +172,7 @@ public MongoDBDriverDatabaseTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLates public class MongoDBDriverDatabaseTestsCoreOldest : MongoDBDriverDatabaseTestsBase { public MongoDBDriverDatabaseTestsCoreOldest(ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) - : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString) + : base(fixture, output, MongoDbConfiguration.MongoDb6_0ConnectionString, MongoDBDriverVersion.OldestSupportedOnCore) { } } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverIndexManagerTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverIndexManagerTests.cs index 6f89b728c..e8697f997 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverIndexManagerTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MongoDB/MongoDBDriverIndexManagerTests.cs @@ -25,16 +25,18 @@ public MongoDBDriverIndexManagerTestsBase(TFixture fixture, ITestOutputHelper ou _mongoUrl = mongoUrl; _fixture.AddCommand($"MongoDbDriverExerciser SetMongoUrl {_mongoUrl}"); - _fixture.AddCommand("MongoDBDriverExerciser CreateOne"); + // Async methods first + _fixture.AddCommand("MongoDBDriverExerciser CreateManyAsync"); _fixture.AddCommand("MongoDBDriverExerciser CreateOneAsync"); + _fixture.AddCommand("MongoDBDriverExerciser DropAllAsync"); + _fixture.AddCommand("MongoDBDriverExerciser DropOneAsync"); + _fixture.AddCommand("MongoDBDriverExerciser ListAsync"); + // Then sync _fixture.AddCommand("MongoDBDriverExerciser CreateMany"); - _fixture.AddCommand("MongoDBDriverExerciser CreateManyAsync"); + _fixture.AddCommand("MongoDBDriverExerciser CreateOne"); _fixture.AddCommand("MongoDBDriverExerciser DropAll"); - _fixture.AddCommand("MongoDBDriverExerciser DropAllAsync"); _fixture.AddCommand("MongoDBDriverExerciser DropOne"); - _fixture.AddCommand("MongoDBDriverExerciser DropOneAsync"); _fixture.AddCommand("MongoDBDriverExerciser List"); - _fixture.AddCommand("MongoDBDriverExerciser ListAsync"); _fixture.AddActions ( @@ -62,75 +64,23 @@ public void CheckForDatastoreInstanceMetrics() Assert.NotNull(m); } - [Fact] - public void CreateOne() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/CreateOne"); - Assert.NotNull(m); - } - - [Fact] - public void CreateOneAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/CreateOneAsync"); - Assert.NotNull(m); - } - - [Fact] - public void CreateMany() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/CreateMany"); - Assert.NotNull(m); - } - - [Fact] - public void CreateManyAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/CreateManyAsync"); - Assert.NotNull(m); - } - - [Fact] - public void DropAll() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/DropAll"); - Assert.NotNull(m); - } - - [Fact] - public void DropAllAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/DropAllAsync"); - Assert.NotNull(m); - } - - [Fact] - public void DropOne() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/DropOne"); - Assert.NotNull(m); - } - - [Fact] - public void DropOneAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/DropOneAsync"); - Assert.NotNull(m); - } - - [Fact] - public void List() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/List"); + [Theory] + [InlineData("CreateOne")] + [InlineData("CreateOneAsync")] + [InlineData("CreateMany")] + [InlineData("CreateManyAsync")] + [InlineData("DropAll")] + [InlineData("DropAllAsync")] + [InlineData("DropOne")] + [InlineData("DropOneAsync")] + [InlineData("List")] + [InlineData("ListAsync")] + public void CheckForOperationMetrics(string operationName) + { + var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/{operationName}"); Assert.NotNull(m); } - [Fact] - public void ListAsync() - { - var m = _fixture.AgentLog.GetMetricByName($"{DatastorePath}/ListAsync"); - Assert.NotNull(m); - } } [NetFrameworkTest] diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/EnterpriseLibraryMsSqlTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/EnterpriseLibraryMsSqlTests.cs index 2ca10f762..a7f6b3b09 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/EnterpriseLibraryMsSqlTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/EnterpriseLibraryMsSqlTests.cs @@ -44,6 +44,7 @@ public EnterpriseLibraryMsSqlTests(RemoteServiceFixtures.MsSqlBasicMvcFixture fi exerciseApplication: () => { _fixture.GetEnterpriseLibraryMsSql(); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlAsyncTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlAsyncTests.cs index d86523bec..4a0c9ba9d 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlAsyncTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlAsyncTests.cs @@ -22,13 +22,15 @@ public abstract class MsSqlAsyncTestsBase : NewRelicIntegrationTest { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); @@ -80,6 +83,7 @@ public void Test() new Assertions.ExpectedMetric { metricName = @"Datastore/MSSQL/all", callCount = expectedDatastoreCallCount }, new Assertions.ExpectedMetric { metricName = @"Datastore/allOther", callCount = expectedDatastoreCallCount }, + new Assertions.ExpectedMetric { metricName = $"DotNet/{_libraryName}.SqlConnection/OpenAsync", callCount = 1 }, new Assertions.ExpectedMetric { metricName = @"Datastore/MSSQL/allOther", callCount = expectedDatastoreCallCount }, new Assertions.ExpectedMetric { metricName = $@"Datastore/instance/MSSQL/{CommonUtils.NormalizeHostname(MsSqlConfiguration.MsSqlServer)}/default", callCount = expectedDatastoreCallCount}, new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/select", callCount = 2 }, @@ -102,7 +106,10 @@ public void Test() // The operation metric should not be scoped because the statement metric is scoped instead new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/select", callCount = 3, metricScope = _expectedTransactionName }, new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/insert", callCount = 1, metricScope = _expectedTransactionName }, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/delete", callCount = 1, metricScope = _expectedTransactionName } + new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/delete", callCount = 1, metricScope = _expectedTransactionName }, + + // Don't double count the open + new Assertions.ExpectedMetric { metricName = $"DotNet/{_libraryName}.SqlConnection/Open" }, }; var expectedTransactionTraceSegments = new List { @@ -186,7 +193,8 @@ public MsSqlAsyncTests_SystemData_FWLatest(ConsoleDynamicMethodFixtureFWLatest f : base( fixture: fixture, output: output, - excerciserName: "SystemDataExerciser") + excerciserName: "SystemDataExerciser", + libraryName: "System.Data.SqlClient") { } } @@ -198,7 +206,8 @@ public MsSqlAsyncTests_SystemDataSqlClient_CoreLatest(ConsoleDynamicMethodFixtur : base( fixture: fixture, output: output, - excerciserName: "SystemDataSqlClientExerciser") + excerciserName: "SystemDataSqlClientExerciser", + libraryName: "System.Data.SqlClient") { } } @@ -210,7 +219,8 @@ public MsSqlAsyncTests_SystemDataSqlClient_CoreOldest(ConsoleDynamicMethodFixtur : base( fixture: fixture, output: output, - excerciserName: "SystemDataSqlClientExerciser") + excerciserName: "SystemDataSqlClientExerciser", + libraryName: "System.Data.SqlClient") { } } @@ -222,7 +232,8 @@ public MsSqlAsyncTests_MicrosoftDataSqlClient_FWLatest(ConsoleDynamicMethodFixtu : base( fixture: fixture, output: output, - excerciserName: "MicrosoftDataSqlClientExerciser") + excerciserName: "MicrosoftDataSqlClientExerciser", + libraryName: "Microsoft.Data.SqlClient") { } } @@ -234,7 +245,8 @@ public MsSqlAsyncTests_MicrosoftDataSqlClient_FW462(ConsoleDynamicMethodFixtureF : base( fixture: fixture, output: output, - excerciserName: "MicrosoftDataSqlClientExerciser") + excerciserName: "MicrosoftDataSqlClientExerciser", + libraryName: "Microsoft.Data.SqlClient") { } } @@ -247,7 +259,8 @@ public MsSqlAsyncTests_MicrosoftDataSqlClient_CoreLatest(ConsoleDynamicMethodFix : base( fixture: fixture, output: output, - excerciserName: "MicrosoftDataSqlClientExerciser") + excerciserName: "MicrosoftDataSqlClientExerciser", + libraryName: "Microsoft.Data.SqlClient") { } } @@ -259,7 +272,8 @@ public MsSqlAsyncTests_MicrosoftDataSqlClient_CoreOldest(ConsoleDynamicMethodFix : base( fixture: fixture, output: output, - excerciserName: "MicrosoftDataSqlClientExerciser") + excerciserName: "MicrosoftDataSqlClientExerciser", + libraryName: "Microsoft.Data.SqlClient") { } } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlQueryParamAsyncTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlQueryParamAsyncTests.cs index fbf22aa3c..1068a27c5 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlQueryParamAsyncTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlQueryParamAsyncTests.cs @@ -60,6 +60,7 @@ public MsSqlQueryParamAsyncTestsBase(TFixture fixture, ITestOutputHelper output, }, exerciseApplication: () => { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlQueryParamTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlQueryParamTests.cs index bc09f4d68..52aeada9a 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlQueryParamTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlQueryParamTests.cs @@ -60,6 +60,7 @@ public MsSqlQueryParamTestsBase(TFixture fixture, ITestOutputHelper output, stri }, exerciseApplication: () => { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlStoredProcedureTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlStoredProcedureTests.cs index 304c37fbf..d98aa0d19 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlStoredProcedureTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlStoredProcedureTests.cs @@ -57,6 +57,7 @@ public MsSqlStoredProcedureTestsBase(TFixture fixture, ITestOutputHelper output, }, exerciseApplication: () => { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlStoredProcedureUsingOdbcDriverTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlStoredProcedureUsingOdbcDriverTests.cs index 28d325843..bf949ee0e 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlStoredProcedureUsingOdbcDriverTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlStoredProcedureUsingOdbcDriverTests.cs @@ -57,6 +57,7 @@ public MsSqlStoredProcedureUsingOdbcDriverTestsBase(TFixture fixture, ITestOutpu }, exerciseApplication: () => { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MySql/MySqlAsyncTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MySql/MySqlAsyncTests.cs index 85bd1d44c..9ae1f04bc 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MySql/MySqlAsyncTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/MySql/MySqlAsyncTests.cs @@ -18,8 +18,9 @@ namespace NewRelic.Agent.UnboundedIntegrationTests.MySql public abstract class MySqlAsyncTestsBase : NewRelicIntegrationTest where TFixture : ConsoleDynamicMethodFixture { private readonly ConsoleDynamicMethodFixture _fixture; + private bool _asyncOpen; - public MySqlAsyncTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) + public MySqlAsyncTestsBase(TFixture fixture, ITestOutputHelper output, bool asyncOpen) : base(fixture) { _fixture = fixture; _fixture.TestLogger = output; @@ -54,12 +55,15 @@ public MySqlAsyncTestsBase(TFixture fixture, ITestOutputHelper output) : base(fi ); _fixture.Initialize(); + // AsyncOpen() is only supported in certain versions of the library + _asyncOpen = asyncOpen; } [Fact] public void Test() { var transactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.MySql.MySqlExerciser/SingleDateQueryAsync"; + Assertions.ExpectedMetric openMetric; var expectedMetrics = new List { @@ -71,8 +75,18 @@ public void Test() new Assertions.ExpectedMetric { metricName = $@"Datastore/instance/MySQL/{CommonUtils.NormalizeHostname(MySqlTestConfiguration.MySqlServer)}/{MySqlTestConfiguration.MySqlPort}", callCount = 1}, new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MySQL/select", callCount = 1 }, new Assertions.ExpectedMetric { metricName = @"Datastore/statement/MySQL/dates/select", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = @"Datastore/statement/MySQL/dates/select", callCount = 1, metricScope = transactionName } + new Assertions.ExpectedMetric { metricName = @"Datastore/statement/MySQL/dates/select", callCount = 1, metricScope = transactionName }, }; + + if (_asyncOpen) + { + expectedMetrics.Add(new Assertions.ExpectedMetric { metricName = @"DotNet/MySql.Data.MySqlClient.MySqlConnection/OpenAsync", callCount = 1 }); + } + else + { + expectedMetrics.Add(new Assertions.ExpectedMetric { metricName = @"DotNet/MySql.Data.MySqlClient.MySqlConnection/Open", callCount = 1 }); + } + var unexpectedMetrics = new List { // The datastore operation happened inside a console app so there should be no allWeb metrics @@ -82,6 +96,13 @@ public void Test() // The operation metric should not be scoped because the statement metric is scoped instead new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MySQL/select", callCount = 1, metricScope = transactionName } }; + + // Don't double count the Open + if (_asyncOpen) + { + unexpectedMetrics.Add(new Assertions.ExpectedMetric { metricName = @"DotNet/MySql.Data.MySqlClient.MySqlConnection/Open", callCount = 1 }); + } + var expectedTransactionTraceSegments = new List { "Datastore/statement/MySQL/dates/select" @@ -140,7 +161,7 @@ public void Test() [NetFrameworkTest] public class MySqlAsyncTestsFW462 : MySqlAsyncTestsBase { - public MySqlAsyncTestsFW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) : base(fixture, output) + public MySqlAsyncTestsFW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) : base(fixture, output, false) { } @@ -149,7 +170,7 @@ public MySqlAsyncTestsFW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutpu [NetFrameworkTest] public class MySqlAsyncTestsFW471 : MySqlAsyncTestsBase { - public MySqlAsyncTestsFW471(ConsoleDynamicMethodFixtureFW471 fixture, ITestOutputHelper output) : base(fixture, output) + public MySqlAsyncTestsFW471(ConsoleDynamicMethodFixtureFW471 fixture, ITestOutputHelper output) : base(fixture, output, false) { } @@ -158,7 +179,7 @@ public MySqlAsyncTestsFW471(ConsoleDynamicMethodFixtureFW471 fixture, ITestOutpu [NetFrameworkTest] public class MySqlAsyncTestsFW48 : MySqlAsyncTestsBase { - public MySqlAsyncTestsFW48(ConsoleDynamicMethodFixtureFW48 fixture, ITestOutputHelper output) : base(fixture, output) + public MySqlAsyncTestsFW48(ConsoleDynamicMethodFixtureFW48 fixture, ITestOutputHelper output) : base(fixture, output, false) { } @@ -167,7 +188,7 @@ public MySqlAsyncTestsFW48(ConsoleDynamicMethodFixtureFW48 fixture, ITestOutputH [NetFrameworkTest] public class MySqlAsyncTestsFWLatest : MySqlAsyncTestsBase { - public MySqlAsyncTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) : base(fixture, output) + public MySqlAsyncTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) : base(fixture, output, true) { } @@ -176,7 +197,7 @@ public MySqlAsyncTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITes [NetCoreTest] public class MySqlAsyncTestsCoreOldest : MySqlAsyncTestsBase { - public MySqlAsyncTestsCoreOldest(ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) : base(fixture, output) + public MySqlAsyncTestsCoreOldest(ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) : base(fixture, output, false) { } @@ -185,7 +206,7 @@ public MySqlAsyncTestsCoreOldest(ConsoleDynamicMethodFixtureCoreOldest fixture, [NetCoreTest] public class MySqlAsyncTestsCore : MySqlAsyncTestsBase { - public MySqlAsyncTestsCore(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) + public MySqlAsyncTestsCore(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output, true) { } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/EnterpriseLibraryOracleTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/EnterpriseLibraryOracleTests.cs index 877f8a10c..9cd77b169 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/EnterpriseLibraryOracleTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/EnterpriseLibraryOracleTests.cs @@ -23,12 +23,16 @@ public EnterpriseLibraryOracleTests(RemoteServiceFixtures.OracleBasicMvcFixture { _fixture = fixture; _fixture.TestLogger = output; - _fixture.Actions + + _fixture.AddActions ( setupConfiguration: () => { var configPath = fixture.DestinationNewRelicConfigFilePath; var configModifier = new NewRelicConfigModifier(configPath); + configModifier.ConfigureFasterMetricsHarvestCycle(15); + configModifier.ConfigureFasterTransactionTracesHarvestCycle(15); + configModifier.ConfigureFasterSqlTracesHarvestCycle(15); configModifier.ForceTransactionTraces(); @@ -40,8 +44,11 @@ public EnterpriseLibraryOracleTests(RemoteServiceFixtures.OracleBasicMvcFixture exerciseApplication: () => { _fixture.GetEnterpriseLibraryOracle(); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); + _fixture.Initialize(); } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleAsyncTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleAsyncTests.cs index 0f1ba278d..ed5e66ebf 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleAsyncTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleAsyncTests.cs @@ -23,12 +23,16 @@ public OracleAsyncTests(RemoteServiceFixtures.OracleBasicMvcFixture fixture, ITe { _fixture = fixture; _fixture.TestLogger = output; - _fixture.Actions + + _fixture.AddActions ( setupConfiguration: () => { var configPath = fixture.DestinationNewRelicConfigFilePath; var configModifier = new NewRelicConfigModifier(configPath); + configModifier.ConfigureFasterMetricsHarvestCycle(15); + configModifier.ConfigureFasterTransactionTracesHarvestCycle(15); + configModifier.ConfigureFasterSqlTracesHarvestCycle(15); configModifier.ForceTransactionTraces(); @@ -42,8 +46,11 @@ public OracleAsyncTests(RemoteServiceFixtures.OracleBasicMvcFixture fixture, ITe exerciseApplication: () => { _fixture.GetOracleAsync(); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); + _fixture.Initialize(); } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleStoredProcedureTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleStoredProcedureTests.cs index 68860634f..e30131958 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleStoredProcedureTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleStoredProcedureTests.cs @@ -24,12 +24,16 @@ public OracleStoredProcedureTests(OracleBasicMvcFixture fixture, ITestOutputHelp { _fixture = fixture; _fixture.TestLogger = output; - _fixture.Actions + + _fixture.AddActions ( setupConfiguration: () => { var configPath = fixture.DestinationNewRelicConfigFilePath; var configModifier = new NewRelicConfigModifier(configPath); + configModifier.ConfigureFasterMetricsHarvestCycle(15); + configModifier.ConfigureFasterTransactionTracesHarvestCycle(15); + configModifier.ConfigureFasterSqlTracesHarvestCycle(15); configModifier.ForceTransactionTraces(); configModifier.SetLogLevel("finest"); @@ -42,9 +46,12 @@ public OracleStoredProcedureTests(OracleBasicMvcFixture fixture, ITestOutputHelp exerciseApplication: () => { _fixture.OracleParameterizedStoredProcedure(_procedureName); - _fixture.AgentLog.WaitForLogLine(AgentLogBase.TransactionTransformCompletedLogLineRegex); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.TransactionTransformCompletedLogLineRegex, TimeSpan.FromMinutes(2)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); + _fixture.Initialize(); } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleTests.cs index fc19aa4fc..7ff5d7e02 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleTests.cs @@ -23,12 +23,16 @@ public OracleTests(RemoteServiceFixtures.OracleBasicMvcFixture fixture, ITestOut { _fixture = fixture; _fixture.TestLogger = output; - _fixture.Actions + + _fixture.AddActions ( setupConfiguration: () => { var configPath = fixture.DestinationNewRelicConfigFilePath; var configModifier = new NewRelicConfigModifier(configPath); + configModifier.ConfigureFasterMetricsHarvestCycle(15); + configModifier.ConfigureFasterTransactionTracesHarvestCycle(15); + configModifier.ConfigureFasterSqlTracesHarvestCycle(15); configModifier.ForceTransactionTraces(); @@ -40,8 +44,11 @@ public OracleTests(RemoteServiceFixtures.OracleBasicMvcFixture fixture, ITestOut exerciseApplication: () => { _fixture.GetOracle(); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); } ); + _fixture.Initialize(); } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Postgres/PostgresExecuteScalarAsyncTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Postgres/PostgresExecuteScalarAsyncTests.cs index 8c6f5f568..da374fd44 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Postgres/PostgresExecuteScalarAsyncTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Postgres/PostgresExecuteScalarAsyncTests.cs @@ -64,7 +64,7 @@ public void Test() new Assertions.ExpectedMetric { metricName = @"Datastore/allOther", callCount = expectedDatastoreCallCount }, new Assertions.ExpectedMetric { metricName = @"Datastore/Postgres/all", callCount = expectedDatastoreCallCount }, new Assertions.ExpectedMetric { metricName = @"Datastore/Postgres/allOther", callCount = expectedDatastoreCallCount }, - new Assertions.ExpectedMetric { metricName = @"DotNet/Npgsql.NpgsqlConnection/Open", callCount = 1}, + new Assertions.ExpectedMetric { metricName = @"DotNet/Npgsql.NpgsqlConnection/OpenAsync", callCount = 1}, new Assertions.ExpectedMetric { metricName = $@"Datastore/instance/Postgres/{CommonUtils.NormalizeHostname(PostgresConfiguration.PostgresServer)}/{PostgresConfiguration.PostgresPort}", callCount = expectedDatastoreCallCount}, new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Postgres/select", callCount = 1 }, new Assertions.ExpectedMetric { metricName = @"Datastore/statement/Postgres/teammembers/select", callCount = 1 }, @@ -75,7 +75,8 @@ public void Test() // The datastore operation happened outside a web transaction so there should be no allWeb metrics new Assertions.ExpectedMetric { metricName = @"Datastore/allWeb" }, new Assertions.ExpectedMetric { metricName = @"Datastore/Postgres/allWeb" }, - + // Don't double count the Open + new Assertions.ExpectedMetric { metricName = @"DotNet/Npgsql.NpgsqlConnection/Open" }, // The operation metric should not be scoped because the statement metric is scoped instead new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Postgres/select", callCount = 1, metricScope = expectedTransactionName }, }; diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Postgres/PostgresSimpleQueryAsyncTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Postgres/PostgresSimpleQueryAsyncTests.cs index 86c9dba2b..b8e6d4517 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Postgres/PostgresSimpleQueryAsyncTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Postgres/PostgresSimpleQueryAsyncTests.cs @@ -64,7 +64,7 @@ public void Test() new Assertions.ExpectedMetric { metricName = @"Datastore/allOther", callCount = 1 }, new Assertions.ExpectedMetric { metricName = @"Datastore/Postgres/all", callCount = expectedDatastoreCallCount }, new Assertions.ExpectedMetric { metricName = @"Datastore/Postgres/allOther", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = @"DotNet/Npgsql.NpgsqlConnection/Open", callCount = 1}, + new Assertions.ExpectedMetric { metricName = @"DotNet/Npgsql.NpgsqlConnection/OpenAsync", callCount = 1}, new Assertions.ExpectedMetric { metricName = $@"Datastore/instance/Postgres/{CommonUtils.NormalizeHostname(PostgresConfiguration.PostgresServer)}/{PostgresConfiguration.PostgresPort}", callCount = expectedDatastoreCallCount}, new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Postgres/select", callCount = 1 }, new Assertions.ExpectedMetric { metricName = @"Datastore/statement/Postgres/teammembers/select", callCount = 1 }, @@ -75,7 +75,8 @@ public void Test() // The datastore operation happened outside a web transaction so there should be no allWeb metrics new Assertions.ExpectedMetric { metricName = @"Datastore/allWeb" }, new Assertions.ExpectedMetric { metricName = @"Datastore/Postgres/allWeb" }, - + // Don't double count the Open + new Assertions.ExpectedMetric { metricName = @"DotNet/Npgsql.NpgsqlConnection/Open" }, // The operation metric should not be scoped because the statement metric is scoped instead new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Postgres/select", callCount = 1, metricScope = expectedTransactionName } }; diff --git a/tests/Agent/IntegrationTests/UnboundedServices/docker-compose.yml b/tests/Agent/IntegrationTests/UnboundedServices/docker-compose.yml index 29990c1ed..f1b5fa380 100644 --- a/tests/Agent/IntegrationTests/UnboundedServices/docker-compose.yml +++ b/tests/Agent/IntegrationTests/UnboundedServices/docker-compose.yml @@ -130,8 +130,31 @@ services: memlock: soft: -1 hard: -1 + deploy: + resources: + limits: + memory: 2GB container_name: ElasticServer + elastic7: + image: elasticsearch:7.17.10 + ports: + - 9201:9200 + environment: + - discovery.type=single-node + - ELASTIC_PASSWORD=${ELASTIC_PASSWORD:-ElasticPassword} + - xpack.security.enabled=true + - xpack.security.http.ssl.enabled=false + ulimits: + memlock: + soft: -1 + hard: -1 + deploy: + resources: + limits: + memory: 2GB + container_name: Elastic7Server + # have to manually set the password for kibana_user - use curl to hit the correct endpoint kibana_pw_setup: image: curlimages/curl diff --git a/tests/Agent/MultiverseTesting/ConsoleScanner/ConsoleScanner.csproj b/tests/Agent/MultiverseTesting/ConsoleScanner/ConsoleScanner.csproj index 4c9f65365..d6ee55f46 100644 --- a/tests/Agent/MultiverseTesting/ConsoleScanner/ConsoleScanner.csproj +++ b/tests/Agent/MultiverseTesting/ConsoleScanner/ConsoleScanner.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/tests/Agent/MultiverseTesting/MultiverseScanner/MultiverseScanner.csproj b/tests/Agent/MultiverseTesting/MultiverseScanner/MultiverseScanner.csproj index 026568539..64a9d18a5 100644 --- a/tests/Agent/MultiverseTesting/MultiverseScanner/MultiverseScanner.csproj +++ b/tests/Agent/MultiverseTesting/MultiverseScanner/MultiverseScanner.csproj @@ -6,7 +6,7 @@ - + diff --git a/tests/Agent/MultiverseTesting/ReportBuilder/ReportBuilder.csproj b/tests/Agent/MultiverseTesting/ReportBuilder/ReportBuilder.csproj index 2ffc89a89..a321cd367 100644 --- a/tests/Agent/MultiverseTesting/ReportBuilder/ReportBuilder.csproj +++ b/tests/Agent/MultiverseTesting/ReportBuilder/ReportBuilder.csproj @@ -6,7 +6,7 @@ - + diff --git a/tests/Agent/NewRelic.Testing.Assertions/NewRelic.Testing.Assertions.csproj b/tests/Agent/NewRelic.Testing.Assertions/NewRelic.Testing.Assertions.csproj index d4268d662..a45737fc9 100644 --- a/tests/Agent/NewRelic.Testing.Assertions/NewRelic.Testing.Assertions.csproj +++ b/tests/Agent/NewRelic.Testing.Assertions/NewRelic.Testing.Assertions.csproj @@ -12,4 +12,4 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - \ No newline at end of file + diff --git a/tests/Agent/UnitTests/AsyncLocalTests/AsyncLocalTests.csproj b/tests/Agent/UnitTests/AsyncLocalTests/AsyncLocalTests.csproj index 4eaf0be28..25e54eeaa 100644 --- a/tests/Agent/UnitTests/AsyncLocalTests/AsyncLocalTests.csproj +++ b/tests/Agent/UnitTests/AsyncLocalTests/AsyncLocalTests.csproj @@ -13,9 +13,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Agent/UnitTests/CompositeTests/CompositeTestAgent.cs b/tests/Agent/UnitTests/CompositeTests/CompositeTestAgent.cs index 041575f17..3ce0fcbf5 100644 --- a/tests/Agent/UnitTests/CompositeTests/CompositeTestAgent.cs +++ b/tests/Agent/UnitTests/CompositeTests/CompositeTestAgent.cs @@ -149,26 +149,28 @@ public CompositeTestAgent(bool shouldAllowThreads, bool includeAsyncLocalStorage AgentServices.RegisterServices(_container); // Replace existing registrations with mocks before resolving any services - _container.ReplaceRegistration(mockEnvironment); - _container.ReplaceRegistration>(transactionContextFactories); - _container.ReplaceRegistration( + _container.ReplaceInstanceRegistration(mockEnvironment); + _container.ReplaceInstanceRegistration>(transactionContextFactories); + _container.ReplaceInstanceRegistration( new TestCallStackManagerFactory()); - _container.ReplaceRegistration(wrappers); - _container.ReplaceRegistration(dataTransportService); - _container.ReplaceRegistration(scheduler); - _container.ReplaceRegistration(NativeMethods); + _container.ReplaceInstanceRegistration(wrappers); + _container.ReplaceInstanceRegistration(dataTransportService); + _container.ReplaceInstanceRegistration(scheduler); + _container.ReplaceInstanceRegistration(NativeMethods); - _container.ReplaceRegistration(Mock.Create()); + _container.ReplaceInstanceRegistration(Mock.Create()); if (!_shouldAllowThreads) { - _container.ReplaceRegistration(threadPoolStatic); + _container.ReplaceInstanceRegistration(threadPoolStatic); } - _container.ReplaceRegistration(configurationManagerStatic); + _container.ReplaceInstanceRegistration(configurationManagerStatic); + _container.ReplaceRegistrations(); // creates a new scope, registering the replacement instances from all .ReplaceRegistration() calls above InstrumentationService = _container.Resolve(); InstrumentationWatcher = _container.Resolve(); + AgentServices.StartServices(_container); DisableAgentInitializer(); @@ -239,6 +241,19 @@ private static Func, DataTransportResponseStatu }; } + private static Func SaveDataAndReturnSuccess(LoadedModuleWireModelCollection dataBucket) + { + return datas => + { + if (datas != null) + { + dataBucket = datas; + } + + return DataTransportResponseStatus.RequestSuccessful; + }; + } + public void Dispose() { //Force the created transaction to finish if necessary so that it won't be garbage collected and harvested diff --git a/tests/Agent/UnitTests/CompositeTests/CompositeTests.csproj b/tests/Agent/UnitTests/CompositeTests/CompositeTests.csproj index a13567f78..747241181 100644 --- a/tests/Agent/UnitTests/CompositeTests/CompositeTests.csproj +++ b/tests/Agent/UnitTests/CompositeTests/CompositeTests.csproj @@ -1,30 +1,31 @@ - + - net462 + net462;net7.0 CompositeTests NewRelic.Agent.Core.CompositeTests Full $(SolutionDir)test.runsettings - - all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + @@ -36,6 +37,7 @@ + diff --git a/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/DistributedTracing/TraceContextCrossAgentTests.cs b/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/DistributedTracing/TraceContextCrossAgentTests.cs index 747226b54..a3809eef6 100644 --- a/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/DistributedTracing/TraceContextCrossAgentTests.cs +++ b/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/DistributedTracing/TraceContextCrossAgentTests.cs @@ -60,7 +60,8 @@ private static List GetTraceContextTestData() { var testCaseData = new List(); - var dllPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); + string location = Assembly.GetExecutingAssembly().GetLocation(); + var dllPath = Path.GetDirectoryName(new Uri(location).LocalPath); var jsonPath = Path.Combine(dllPath, "CrossAgentTests", "DistributedTracing", "trace_context.json"); var jsonString = File.ReadAllText(jsonPath); diff --git a/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/SecurityPolicies/SecurityPoliciesCrossAgentTests.cs b/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/SecurityPolicies/SecurityPoliciesCrossAgentTests.cs index 3cb5682f9..0a8a9baa7 100644 --- a/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/SecurityPolicies/SecurityPoliciesCrossAgentTests.cs +++ b/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/SecurityPolicies/SecurityPoliciesCrossAgentTests.cs @@ -13,6 +13,7 @@ using NewRelic.Agent.Core.DataTransport; using NewRelic.Agent.Core.Labels; using NewRelic.Agent.Core.Utilities; +using NewRelic.Agent.TestUtilities; using NewRelic.SystemInterfaces; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -80,7 +81,8 @@ private static List GetSecurityPoliciesTestData() { var testCaseDatas = new List(); - var dllPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); + string location = Assembly.GetExecutingAssembly().GetLocation(); + var dllPath = Path.GetDirectoryName(new Uri(location).LocalPath); var jsonPath = Path.Combine(dllPath, "CrossAgentTests", "SecurityPolicies", "security_policies.json"); var jsonString = File.ReadAllText(jsonPath); var testList = JsonConvert.DeserializeObject>(jsonString); diff --git a/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/SqlObfuscation/SqlObfuscationCrossAgentTests.cs b/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/SqlObfuscation/SqlObfuscationCrossAgentTests.cs index 2c988a6c6..555719295 100644 --- a/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/SqlObfuscation/SqlObfuscationCrossAgentTests.cs +++ b/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/SqlObfuscation/SqlObfuscationCrossAgentTests.cs @@ -9,6 +9,7 @@ using NewRelic.Agent.Api; using NewRelic.Agent.Core.Database; using NewRelic.Agent.Extensions.Providers.Wrapper; +using NewRelic.Agent.TestUtilities; using Newtonsoft.Json; using NUnit.Framework; @@ -85,7 +86,8 @@ private static List GetSqlObfuscationTestDatas() { var testCaseDatas = new List(); - var dllPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); + string location = Assembly.GetExecutingAssembly().GetLocation(); + var dllPath = Path.GetDirectoryName(new Uri(location).LocalPath); var jsonPath = Path.Combine(dllPath, "CrossAgentTests", "SqlObfuscation", "sql_obfuscation.json"); var jsonString = File.ReadAllText(jsonPath); diff --git a/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/Utilization/UtilizationCrossAgentTests.cs b/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/Utilization/UtilizationCrossAgentTests.cs index 24be84f39..e1f65b6ec 100644 --- a/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/Utilization/UtilizationCrossAgentTests.cs +++ b/tests/Agent/UnitTests/CompositeTests/CrossAgentTests/Utilization/UtilizationCrossAgentTests.cs @@ -3,6 +3,7 @@ using NewRelic.Agent.Api; using NewRelic.Agent.Core.Utilization; +using NewRelic.Agent.TestUtilities; using Newtonsoft.Json; using NUnit.Framework; using System; @@ -208,7 +209,8 @@ private static List GetUtilizationTestData() { var testCaseDatas = new List(); - var dllPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); + string location = Assembly.GetExecutingAssembly().GetLocation(); + var dllPath = Path.GetDirectoryName(new Uri(location).LocalPath); var jsonPath = Path.Combine(dllPath, "CrossAgentTests", "Utilization", "utilization_json.json"); var jsonString = File.ReadAllText(jsonPath); diff --git a/tests/Agent/UnitTests/CompositeTests/HarvestDisableWhileReconnectingTest.cs b/tests/Agent/UnitTests/CompositeTests/HarvestDisableWhileReconnectingTest.cs index 62054215e..425af650c 100644 --- a/tests/Agent/UnitTests/CompositeTests/HarvestDisableWhileReconnectingTest.cs +++ b/tests/Agent/UnitTests/CompositeTests/HarvestDisableWhileReconnectingTest.cs @@ -2,17 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 using NewRelic.Agent.Api; -using NewRelic.Agent.Core.Aggregators; -using NewRelic.Agent.Core.Configuration; using NewRelic.Agent.Core.DataTransport; using NewRelic.Agent.Core.Events; using NewRelic.Agent.Core.Time; using NewRelic.Agent.Core.Utilities; -using NewRelic.Agent.Extensions.Providers.Wrapper; -using NewRelic.Testing.Assertions; using NUnit.Framework; using System; -using System.Linq; using Telerik.JustMock; namespace CompositeTests @@ -42,7 +37,10 @@ public void HarvestIsDisabledWhileReconnectingTest() { var connectionHandler = Mock.Create(); - _compositeTestAgent.Container.ReplaceRegistration(connectionHandler); + _compositeTestAgent.Container.ReplaceInstanceRegistration(connectionHandler); +#if NET + _compositeTestAgent.Container.ReplaceRegistrations(); // creates a new scope, registering the replacement instances from all .ReplaceRegistration() calls above +#endif _compositeTestAgent.Container.Resolve(); var numExistingAggregators = 9; //We currently have 9 different aggregators. diff --git a/tests/Agent/UnitTests/CompositeTests/SqlTraceTests.cs b/tests/Agent/UnitTests/CompositeTests/SqlTraceTests.cs index ece4083ec..51257382d 100644 --- a/tests/Agent/UnitTests/CompositeTests/SqlTraceTests.cs +++ b/tests/Agent/UnitTests/CompositeTests/SqlTraceTests.cs @@ -10,7 +10,11 @@ using System; using System.Collections.Generic; using System.Data; +#if NETFRAMEWORK using System.Data.SqlClient; +#else +using Microsoft.Data.SqlClient; +#endif using System.Linq; namespace CompositeTests diff --git a/tests/Agent/UnitTests/CompositeTests/StackExchangeRedisSessionCacheTests.cs b/tests/Agent/UnitTests/CompositeTests/StackExchangeRedisSessionCacheTests.cs new file mode 100644 index 000000000..6eff59e5a --- /dev/null +++ b/tests/Agent/UnitTests/CompositeTests/StackExchangeRedisSessionCacheTests.cs @@ -0,0 +1,381 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NewRelic.Agent.Api; +using NewRelic.Agent.Api.Experimental; +using NewRelic.Agent.Configuration; +using NewRelic.Agent.Core.Time; +using NewRelic.Agent.Core.WireModels; +using NewRelic.Agent.Extensions.Providers.Wrapper; +using NewRelic.Providers.Wrapper.StackExchangeRedis2Plus; +using NUnit.Framework; +using StackExchange.Redis.Profiling; + +namespace CompositeTests +{ + [TestFixture] + public class StackExchangeRedisSessionCacheTests + { + private static CompositeTestAgent _compositeTestAgent; + + private IConfigurationService _configSvc; + + private static readonly string _accountId = "acctid"; + private static readonly string _appId = "appid"; + private static readonly string _trustKey = "trustedkey"; + + [SetUp] + public void SetUp() + { + _compositeTestAgent = new CompositeTestAgent(); + _compositeTestAgent.ServerConfiguration.AccountId = _accountId; + _compositeTestAgent.ServerConfiguration.TrustedAccountKey = _trustKey; + _compositeTestAgent.ServerConfiguration.PrimaryApplicationId = _appId; + var cleanupOverride = new NewRelic.Agent.Core.Config.configurationAdd + { + key = "OverrideStackExchangeRedisCleanupCycle", + value = "2" + }; + _compositeTestAgent.LocalConfiguration.appSettings.Add(cleanupOverride); + _configSvc = _compositeTestAgent.Container.Resolve(); + } + + [TearDown] + public static void TearDown() + { + _compositeTestAgent.Dispose(); + } + + #region Harvest Tests + + [Test] + public void Harvest_SegmentInCache_IsRemoved() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = _compositeTestAgent.GetAgent().StartTransactionSegmentOrThrow("segment"); + + var session = sessionCache.GetProfilingSession().Invoke(); + + var cacheSizeBefore = GetSessionCacheSize(sessionCache); + + sessionCache.Harvest(segment); + + var cacheSizeAfter = GetSessionCacheSize(sessionCache); + + Assert.NotNull(session); + Assert.True(cacheSizeBefore == 1); + Assert.True(cacheSizeAfter == 0); + } + + [Test] + public void Harvest_SegmentNotInCache_DoesNothing() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = _compositeTestAgent.GetAgent().StartTransactionSegmentOrThrow("segment"); + + var session = sessionCache.GetProfilingSession().Invoke(); + + var segmentTwo = _compositeTestAgent.GetAgent().StartTransactionSegmentOrThrow("segmentTwo"); + + var cacheSizeBefore = GetSessionCacheSize(sessionCache); + + sessionCache.Harvest(segmentTwo); + + var cacheSizeAfter = GetSessionCacheSize(sessionCache); + + Assert.NotNull(session); + Assert.True(cacheSizeBefore == 1); + Assert.True(cacheSizeAfter == 1); + } + + [Test] + public void Harvest_SegmentInCache_IsRemoved_TransactionFinished_DoesNotThrow() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = _compositeTestAgent.GetAgent().StartTransactionSegmentOrThrow("segment"); + + var session = sessionCache.GetProfilingSession().Invoke(); + var cacheSizeBefore = GetSessionCacheSize(sessionCache); + + transaction.End(); + + Assert.True(transaction.IsFinished); + Assert.NotNull(session); + Assert.True(cacheSizeBefore == 1); + Assert.DoesNotThrow(() => sessionCache.Harvest(segment)); + + var cacheSizeAfter = GetSessionCacheSize(sessionCache); + Assert.True(cacheSizeAfter == 0); + } + + #endregion + + #region Segments + + [Test] + public void DoneSegment_NoProfilingSession() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = _compositeTestAgent.GetAgent().StartTransactionSegmentOrThrow("segment"); + segment.End(); + + var session = sessionCache.GetProfilingSession().Invoke(); + + var cacheSize = GetSessionCacheSize(sessionCache); + + Assert.True(((ISegmentExperimental)segment).IsDone); + Assert.Null(session); + Assert.True(cacheSize == 0); + } + + [Test] + public void InvalidSegment_NoProfilingSession() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = new NewRelic.Agent.Core.Segments.NoOpSegment(); + + var session = sessionCache.GetProfilingSession().Invoke(); + + var cacheSize = GetSessionCacheSize(sessionCache); + + Assert.False(segment.IsValid); + Assert.Null(session); + Assert.True(cacheSize == 0); + } + + [Test] + public void DatastoreSegment_NoProfilingSession() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = _compositeTestAgent.GetAgent().StartDatastoreRequestSegmentOrThrow("select", DatastoreVendor.MSSQL, "model", "segment"); + + var session = sessionCache.GetProfilingSession().Invoke(); + + var cacheSize = GetSessionCacheSize(sessionCache); + + Assert.Null(session); + Assert.True(cacheSize == 0); + } + + [Test] + public void ActiveSegment_NotCleaned() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = _compositeTestAgent.GetAgent().StartTransactionSegmentOrThrow("segment"); + + _ = sessionCache.GetProfilingSession().Invoke(); + + var cleanupCount = WaitForMetric(); + var cacheSize = GetSessionCacheSize(sessionCache); + + Assert.False(((ISegmentExperimental)segment).IsDone); + Assert.True(cleanupCount != null); + Assert.True(cacheSize == 1); + } + + [Test] + public void OrphanedSegment_IsCleaned() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = _compositeTestAgent.GetAgent().StartTransactionSegmentOrThrow("segment"); + + _ = sessionCache.GetProfilingSession().Invoke(); + + // Since the session cache is not set in the agent, it will not attempt to harvest the session. + segment.End(); + + var cleanupCount = WaitForMetric(); + var cacheSize = GetSessionCacheSize(sessionCache); + + Assert.True(cleanupCount != null); + Assert.True(cacheSize == 0); + } + + #endregion + + #region Transactions + + [Test] + public void FinishedTransaction_NoProfilingSession() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = _compositeTestAgent.GetAgent().StartTransactionSegmentOrThrow("segment"); + segment.End(); + transaction.End(); + + var session = sessionCache.GetProfilingSession().Invoke(); + + var cacheSize = GetSessionCacheSize(sessionCache); + + Assert.True(transaction.IsFinished); + Assert.Null(session); + Assert.True(cacheSize == 0); + } + + [Test] + public void InvalidTransaction_NoProfilingSession() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = new NewRelic.Agent.Core.Transactions.NoOpTransaction(); // Not finished and not valid + + var session = sessionCache.GetProfilingSession().Invoke(); + + var cacheSize = GetSessionCacheSize(sessionCache); + + Assert.False(transaction.IsValid); + Assert.Null(session); + Assert.True(cacheSize == 0); + } + + [Test] + public void ActiveTransaction_NotCleaned() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var transaction = _compositeTestAgent.GetAgent().CreateTransaction( + isWeb: true, + category: EnumNameCache.GetName(WebTransactionType.ASP), + transactionDisplayName: "TransactionName", + doNotTrackAsUnitOfWork: true); + + var segment = _compositeTestAgent.GetAgent().StartTransactionSegmentOrThrow("segment"); + + _ = sessionCache.GetProfilingSession().Invoke(); + + var cleanupCount = WaitForMetric(); + var cacheSize = GetSessionCacheSize(sessionCache); + + Assert.False(transaction.IsFinished); + Assert.True(cleanupCount != null); + Assert.True(cacheSize == 1); + } + + #endregion + + [Test] + public void Cleanup_IsScheduled() + { + var agent = _compositeTestAgent.GetAgent(); + var sessionCache = new SessionCache(agent, 0); + + var sssFieldType = typeof(SimpleSchedulingService).GetField("_executingActions", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var value = sssFieldType.GetValue(agent.SimpleSchedulingService) as List; + + var cleanupAction = value.FirstOrDefault(a => a.Method.Name == "CleanUp"); + + Assert.NotNull(cleanupAction); + + sessionCache.Dispose(); + + var noAction = value.FirstOrDefault(a => a.Method.Name == "CleanUp"); + + Assert.Null(noAction); + } + + private int GetSessionCacheSize(SessionCache sessionCache) + { + var cacheFieldType = typeof(SessionCache).GetField("_sessionCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var value = cacheFieldType.GetValue(sessionCache) as ConcurrentDictionary transaction, ProfilingSession session)>; + + return value.Count; + } + + private MetricWireModel WaitForMetric() + { + // Each look lasts 100 ms, want to want at most 3000ms + const int maxLoops = 30; + var loops = 0; + _compositeTestAgent.Harvest(); + var cleanupCount = _compositeTestAgent.Metrics.FirstOrDefault(m => m.MetricName.Name == "Supportability/Dotnet/RedisSessionCacheCleanup/Count"); + while (cleanupCount == null && loops < maxLoops) + { + _compositeTestAgent.Harvest(); + cleanupCount = _compositeTestAgent.Metrics.FirstOrDefault(m => m.MetricName.Name == "Supportability/Dotnet/RedisSessionCacheCleanup/Count"); + loops++; + Thread.Sleep(100); + } + + return cleanupCount; + } + } +} diff --git a/tests/Agent/UnitTests/CompositeTests/TestTransactionContext.cs b/tests/Agent/UnitTests/CompositeTests/TestTransactionContext.cs index ecd21add5..20ce2d92b 100644 --- a/tests/Agent/UnitTests/CompositeTests/TestTransactionContext.cs +++ b/tests/Agent/UnitTests/CompositeTests/TestTransactionContext.cs @@ -15,7 +15,8 @@ public class TestTransactionContext : IContextStorage public T GetData() { - return (T)_data.GetValueOrDefault(_key); + // call is ambiguous in .NET 7 if you use the extension method invocation + return (T)DictionaryExtensions.GetValueOrDefault(_data, _key); } public void SetData(T value) diff --git a/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterTests.cs b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterTests.cs index 024214639..9b4a44449 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterTests.cs @@ -262,6 +262,11 @@ public void IncrementLogLinesCount_CheckLevelsAndCounts() _agentHealthReporter.IncrementLogLinesCount("DEBUG"); _agentHealthReporter.IncrementLogLinesCount("FINEST"); _agentHealthReporter.IncrementLogLinesCount("MISSING_LEVEL"); + _agentHealthReporter.IncrementLogDeniedCount("INFO"); + _agentHealthReporter.IncrementLogDeniedCount("DEBUG"); + _agentHealthReporter.IncrementLogDeniedCount("FINEST"); + _agentHealthReporter.IncrementLogDeniedCount("MISSING_LEVEL"); + _agentHealthReporter.CollectLoggingMetrics(); var infoLevelLines = _publishedMetrics.First(metric => metric.MetricName.Name == "Logging/lines/INFO"); @@ -270,8 +275,14 @@ public void IncrementLogLinesCount_CheckLevelsAndCounts() var missingLevelLines = _publishedMetrics.First(metric => metric.MetricName.Name == "Logging/lines/MISSING_LEVEL"); var allLines = _publishedMetrics.First(metric => metric.MetricName.Name == "Logging/lines"); + var infoLevelDeniedLines = _publishedMetrics.First(metric => metric.MetricName.Name == "Logging/denied/INFO"); + var debugLevelDeniedLines = _publishedMetrics.First(metric => metric.MetricName.Name == "Logging/denied/DEBUG"); + var finestLevelDeniedLines = _publishedMetrics.First(metric => metric.MetricName.Name == "Logging/denied/FINEST"); + var missingLevelDeniedLines = _publishedMetrics.First(metric => metric.MetricName.Name == "Logging/denied/MISSING_LEVEL"); + var allDeniedLines = _publishedMetrics.First(metric => metric.MetricName.Name == "Logging/denied"); + NrAssert.Multiple( - () => Assert.AreEqual(5, _publishedMetrics.Count), + () => Assert.AreEqual(10, _publishedMetrics.Count), () => Assert.AreEqual($"Logging/lines/INFO", infoLevelLines.MetricName.Name), () => Assert.AreEqual(1, infoLevelLines.Data.Value0), () => Assert.AreEqual($"Logging/lines/DEBUG", debugLevelLines.MetricName.Name), @@ -281,7 +292,17 @@ public void IncrementLogLinesCount_CheckLevelsAndCounts() () => Assert.AreEqual($"Logging/lines/MISSING_LEVEL", missingLevelLines.MetricName.Name), () => Assert.AreEqual(1, missingLevelLines.Data.Value0), () => Assert.AreEqual($"Logging/lines", allLines.MetricName.Name), - () => Assert.AreEqual(4, allLines.Data.Value0) + () => Assert.AreEqual(4, allLines.Data.Value0), + () => Assert.AreEqual($"Logging/denied/INFO", infoLevelDeniedLines.MetricName.Name), + () => Assert.AreEqual(1, infoLevelDeniedLines.Data.Value0), + () => Assert.AreEqual($"Logging/denied/DEBUG", debugLevelDeniedLines.MetricName.Name), + () => Assert.AreEqual(1, debugLevelDeniedLines.Data.Value0), + () => Assert.AreEqual($"Logging/denied/FINEST", finestLevelDeniedLines.MetricName.Name), + () => Assert.AreEqual(1, finestLevelDeniedLines.Data.Value0), + () => Assert.AreEqual($"Logging/denied/MISSING_LEVEL", missingLevelDeniedLines.MetricName.Name), + () => Assert.AreEqual(1, missingLevelDeniedLines.Data.Value0), + () => Assert.AreEqual($"Logging/denied", allDeniedLines.MetricName.Name), + () => Assert.AreEqual(4, allDeniedLines.Data.Value0) ); } @@ -290,7 +311,12 @@ public void ReportLoggingSupportabilityMetrics() { _agentHealthReporter.ReportLoggingEventCollected(); _agentHealthReporter.ReportLoggingEventsSent(2); + _agentHealthReporter.ReportLoggingEventsDropped(3); _agentHealthReporter.ReportLogForwardingFramework("log4net"); + + _agentHealthReporter.ReportLogForwardingEnabledWithFramework("Framework1"); + _agentHealthReporter.ReportLogForwardingEnabledWithFramework("Framework2"); + _agentHealthReporter.CollectMetrics(); @@ -298,10 +324,13 @@ public void ReportLoggingSupportabilityMetrics() { { "Supportability/Logging/Forwarding/Seen", 1 }, { "Supportability/Logging/Forwarding/Sent", 2 }, + { "Supportability/Logging/Forwarding/Dropped", 3 }, { "Supportability/Logging/Metrics/DotNET/enabled", 1 }, { "Supportability/Logging/Forwarding/DotNET/enabled", 1 }, { "Supportability/Logging/LocalDecorating/DotNET/enabled", 1 }, - { "Supportability/Logging/DotNET/log4net/enabled", 1 } + { "Supportability/Logging/DotNET/log4net/enabled", 1 }, + { "Supportability/Logging/Forwarding/DotNET/Framework1/enabled", 1}, + { "Supportability/Logging/Forwarding/DotNET/Framework2/enabled", 1} }; var actualMetricNamesAndValues = _publishedMetrics.Select(x => new KeyValuePair(x.MetricName.Name, x.Data.Value0)); @@ -312,18 +341,24 @@ public void ReportLoggingSupportabilityMetrics() public void LoggingFrameworkOnlyReportedOnce() { _agentHealthReporter.ReportLogForwardingFramework("log4net"); + _agentHealthReporter.ReportLogForwardingEnabledWithFramework("log4net"); _agentHealthReporter.CollectMetrics(); Assert.True(_publishedMetrics.Any(x => x.MetricName.Name == "Supportability/Logging/DotNET/log4net/enabled")); + Assert.True(_publishedMetrics.Any(x => x.MetricName.Name == "Supportability/Logging/Forwarding/DotNET/log4net/enabled")); // Clear out captured metrics, and recollect _publishedMetrics = new List(); _agentHealthReporter.ReportLogForwardingFramework("log4net"); + _agentHealthReporter.ReportLogForwardingEnabledWithFramework("log4net"); _agentHealthReporter.ReportLogForwardingFramework("serilog"); + _agentHealthReporter.ReportLogForwardingEnabledWithFramework("serilog"); _agentHealthReporter.CollectMetrics(); Assert.True(_publishedMetrics.Any(x => x.MetricName.Name == "Supportability/Logging/DotNET/serilog/enabled")); Assert.False(_publishedMetrics.Any(x => x.MetricName.Name == "Supportability/Logging/DotNET/log4net/enabled")); + Assert.True(_publishedMetrics.Any(x => x.MetricName.Name == "Supportability/Logging/Forwarding/DotNET/serilog/enabled")); + Assert.False(_publishedMetrics.Any(x => x.MetricName.Name == "Supportability/Logging/Forwarding/DotNET/log4net/enabled")); } [Test] diff --git a/tests/Agent/UnitTests/Core.UnitTest/Config/ConfigurationLoaderTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Config/ConfigurationLoaderTests.cs index b0cd59fe3..fcdd3172f 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Config/ConfigurationLoaderTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Config/ConfigurationLoaderTests.cs @@ -1,6 +1,8 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +#if NETFRAMEWORK + using System; using System.Collections.Generic; using System.Configuration; @@ -41,7 +43,6 @@ public void GetWebConfigAppSetting_WebApp_ReturnsSettingsForApp() Assert.AreEqual("bar", valueWithProvenance.Value); } } - [Test] public void GetWebConfigAppSetting_WebApp_ReturnsDefaultSettingsIfSettingNotAvailable() { @@ -130,7 +131,6 @@ public void GetAgentConfigFileName_ReturnsConfigFileFromAppConfig() Assert.AreEqual(expectedFileName, agentConfigFileName); } } - [Test] public void TryGetAgentConfigFileFromAppConfig_ReturnsNullWhenFileDoesNotExist() { @@ -176,7 +176,6 @@ public void TryGetAgentConfigFileFromAppRoot_ReturnsNullIfNoAppPath() { ReplaceNewRelicHomeWithNullIfNecessary(staticMocks); staticMocks.UseAppDomainAppVirtualPathFunc(() => "testVirtualPath"); - var actualException = Assert.Catch(() => ConfigurationLoader.GetAgentConfigFileName(), "Expected an exception to be thrown"); StringAssert.Contains("Could not find newrelic.config", actualException.Message); } @@ -662,3 +661,4 @@ public void Dispose() } } } +#endif diff --git a/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs index d897522ad..b8f47961a 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs @@ -20,6 +20,12 @@ internal class TestableDefaultConfiguration : DefaultConfiguration { public TestableDefaultConfiguration(IEnvironment environment, configuration localConfig, ServerConfiguration serverConfig, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration securityPoliciesConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic) : base(environment, localConfig, serverConfig, runTimeConfiguration, securityPoliciesConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic) { } + + public static void ResetStatics() + { + _agentEnabledAppSettingParsed = null; + _appSettingAgentEnabled = false; + } } [TestFixture, Category("Configuration")] @@ -50,15 +56,40 @@ public void SetUp() _dnsStatic = Mock.Create(); _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + + TestableDefaultConfiguration.ResetStatics(); } [Test] public void AgentEnabledShouldPassThroughToLocalConfig() { Assert.IsTrue(_defaultConfig.AgentEnabled); + + _localConfig.agentEnabled = false; + Assert.IsFalse(_defaultConfig.AgentEnabled); + _localConfig.agentEnabled = true; Assert.IsTrue(_defaultConfig.AgentEnabled); - _localConfig.agentEnabled = false; + } + + [Test] + public void AgentEnabledShouldUseCachedAppSetting() + { + Mock.Arrange(() => _configurationManagerStatic.GetAppSetting("NewRelic.AgentEnabled")).Returns("false"); + + Assert.IsFalse(_defaultConfig.AgentEnabled); + Assert.IsFalse(_defaultConfig.AgentEnabled); + + Mock.Assert(() => _configurationManagerStatic.GetAppSetting("NewRelic.AgentEnabled"), Occurs.Once()); + } + + [Test] + public void AgentEnabledShouldPreferAppSettingOverLocalConfig() + { + Mock.Arrange(() => _configurationManagerStatic.GetAppSetting("NewRelic.AgentEnabled")).Returns("false"); + + _localConfig.agentEnabled = true; + Assert.IsFalse(_defaultConfig.AgentEnabled); } @@ -539,7 +570,11 @@ public bool SqlExplainPlansEnabledServerOverridesLocal(bool local, bool? server) } [TestCase(3000.0, null, ExpectedResult = 3000.0)] +#if NET + [TestCase(3000.5, null, ExpectedResult = 3000.5)] // .NET doesn't round timespans the same way Framework did... +#else [TestCase(3000.5, null, ExpectedResult = 3001.0)] +#endif [TestCase(4000.0, 0.5, ExpectedResult = 500.0)] [TestCase(200.0, 5.0, ExpectedResult = 5000.0)] [TestCase(1.0, 0.2, ExpectedResult = 200.0)] @@ -752,7 +787,11 @@ public bool CaptureCustomParametersMostSecureWinsWithSecurityPolicies(bool local [TestCase("apdex_f", null, 5, ExpectedResult = 20000)] [TestCase("1", null, 5, ExpectedResult = 1)] +#if NETFRAMEWORK [TestCase("1.5", null, 5, ExpectedResult = 2)] +#else + [TestCase("1.5", null, 5, ExpectedResult = 1.5)] +#endif [TestCase("apdex_f", 3, 5, ExpectedResult = 3000)] [TestCase("apdex_f", 3.5, 5, ExpectedResult = 3500)] [TestCase("apdex_f", "4", 5, ExpectedResult = 4000)] @@ -2749,6 +2788,16 @@ public void ApplicationLogging_ForwardingMaxSamplesStored_HasCorrectValue() Assert.AreEqual(1, _defaultConfig.LogEventsMaxSamplesStored); } + [Test] + public void ApplicationLogging_ForwardingLogLevelDeniedList_HasCorrectValue() + { + _localConfig.applicationLogging.forwarding.logLevelDenyList = " SomeValue, SomeOtherValue "; + + Assert.AreEqual(2, _defaultConfig.LogLevelDenyList.Count); + Assert.True(_defaultConfig.LogLevelDenyList.Contains("SOMEVALUE")); + Assert.True(_defaultConfig.LogLevelDenyList.Contains("SOMEOTHERVALUE")); + } + [Test] public void LogEventsHarvestCycleUsesDefaultOrEventHarvestConfig() { @@ -3251,6 +3300,7 @@ public void HarvestCycleOverride_DefaultOrNotSet() Assert.AreEqual(60, defaultConfig.GetAgentCommandsCycle.TotalSeconds); Assert.AreEqual(60, defaultConfig.SpanEventsHarvestCycle.TotalSeconds); Assert.AreEqual(60, defaultConfig.SqlTracesHarvestCycle.TotalSeconds); + Assert.AreEqual(60, defaultConfig.StackExchangeRedisCleanupCycle.TotalSeconds); } [TestCase(null)] @@ -3361,6 +3411,24 @@ public void HarvestCycleOverride_SqlTraces_NotValidValueSet(string value) Assert.AreEqual(60, defaultConfig.SqlTracesHarvestCycle.TotalSeconds); } + [TestCase(null)] + [TestCase("0")] + [TestCase("-1")] + [TestCase("")] + [TestCase("a")] + public void HarvestCycleOverride_StackExchangeRedisCleanup_NotValidValueSet(string value) + { + _localConfig.appSettings.Add(new configurationAdd() + { + key = "OverrideStackExchangeRedisCleanupCycle", + value = value + }); + + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + + Assert.AreEqual(60, defaultConfig.StackExchangeRedisCleanupCycle.TotalSeconds); + } + [Test] public void HarvestCycleOverride_Metrics_ValidValueSet() { @@ -3505,6 +3573,30 @@ public void HarvestCycleOverride_SqlTraces_ValidValueSet() Assert.AreEqual(Convert.ToInt32(expectedSeconds), defaultConfig.SqlTracesHarvestCycle.TotalSeconds); } + [Test] + public void HarvestCycleOverride_StackExchangeRedisCleanup_ValidValueSet() + { + var expectedSeconds = "10"; + _localConfig.appSettings.Add(new configurationAdd() + { + key = "OverrideStackExchangeRedisCleanupCycle", + value = expectedSeconds + }); + + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + + Assert.AreEqual(Convert.ToInt32(expectedSeconds), defaultConfig.StackExchangeRedisCleanupCycle.TotalSeconds); + + // Test that the backing field is used after the initial call and not changed. + _localConfig.appSettings.Add(new configurationAdd() + { + key = "OverrideStackExchangeRedisCleanupCycle", + value = "100" + }); + + Assert.AreEqual(Convert.ToInt32(expectedSeconds), defaultConfig.StackExchangeRedisCleanupCycle.TotalSeconds); + } + #endregion private void CreateDefaultConfiguration() diff --git a/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj b/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj index edadb1f51..49a8d10cf 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj +++ b/tests/Agent/UnitTests/Core.UnitTest/Core.UnitTest.csproj @@ -1,28 +1,33 @@ - net462 + net462;net7.0 NewRelic.Agent.Core NewRelic.Agent.Core.UnitTest - Full + Full $(SolutionDir)test.runsettings + + 8.0 + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + @@ -37,16 +42,13 @@ - - - - Properties\SharedLog4NetRepository.cs - + + diff --git a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/CatMapTests.cs b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/CatMapTests.cs index 3d86193f8..d2e84fa22 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/CatMapTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/CatMapTests.cs @@ -32,6 +32,7 @@ using System.Linq; using System.Reflection; using Telerik.JustMock; +using NewRelic.Agent.Api.Experimental; namespace NewRelic.Agent.Core.CrossAgentTests { @@ -83,11 +84,11 @@ public void SetUp() Mock.Arrange(() => transactionBuilderService.GetCurrentInternalTransaction()).Returns(() => _transaction); var agentHealthReporter = Mock.Create(); - + var simpleSchedulingService = Mock.Create(); var logEventAggregator = Mock.Create(); var logContextDataFilter = Mock.Create(); - _agent = new Agent(transactionBuilderService, Mock.Create(), Mock.Create(), _transactionMetricNameMaker, _pathHashMaker, _catHeaderHandler, Mock.Create(), _syntheticsHeaderHandler, Mock.Create(), Mock.Create(), Mock.Create(), _configurationService, agentHealthReporter, Mock.Create(), Mock.Create(), new TraceMetadataFactory(new AdaptiveSampler()), catSupportabilityCounters, logEventAggregator, logContextDataFilter); + _agent = new Agent(transactionBuilderService, Mock.Create(), Mock.Create(), _transactionMetricNameMaker, _pathHashMaker, _catHeaderHandler, Mock.Create(), _syntheticsHeaderHandler, Mock.Create(), Mock.Create(), Mock.Create(), _configurationService, agentHealthReporter, Mock.Create(), Mock.Create(), new TraceMetadataFactory(new AdaptiveSampler()), catSupportabilityCounters, logEventAggregator, logContextDataFilter, simpleSchedulingService); _attribDefSvc = new AttributeDefinitionService((f) => new AttributeDefinitions(f)); _transactionAttributeMaker = new TransactionAttributeMaker(_configurationService, _attribDefSvc); diff --git a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/DataTransport/CollectorHostNameTests.cs b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/DataTransport/CollectorHostNameTests.cs index a788221c8..173ad151a 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/DataTransport/CollectorHostNameTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/DataTransport/CollectorHostNameTests.cs @@ -13,6 +13,7 @@ using NewRelic.Agent.Core.Configuration; using NewRelic.Agent.Core.DataTransport; using System.Reflection; +using NewRelic.Agent.TestUtilities; namespace NewRelic.Agent.Core.CrossAgentTests.DataTransport { @@ -107,7 +108,8 @@ public void RunCrossAgentCollectorHostnameTests(string configFileKey, string env private static List GetCollectorHostnameTestData() { var testDatas = new List(); - var dllPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); + string location = Assembly.GetExecutingAssembly().GetLocation(); + var dllPath = Path.GetDirectoryName(new Uri(location).LocalPath); var jsonPath = Path.Combine(dllPath, "CrossAgentTests", "DataTransport", "collector_hostname.json"); var jsonString = File.ReadAllText(jsonPath); var objectArray = JArray.Parse(jsonString); diff --git a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/ServerSentEvent/ServerSentEventTests.cs b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/ServerSentEvent/ServerSentEventTests.cs index cb24049d1..da2992008 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/ServerSentEvent/ServerSentEventTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/ServerSentEvent/ServerSentEventTests.cs @@ -25,6 +25,7 @@ using System.IO; using System.Linq; using System.Reflection; +using NewRelic.Agent.TestUtilities; using Telerik.JustMock; namespace NewRelic.Agent.Core.CrossAgentTests @@ -203,7 +204,8 @@ public static IEnumerable TestCases { get { - var dllPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); + string location = Assembly.GetExecutingAssembly().GetLocation(); + var dllPath = Path.GetDirectoryName(new Uri(location).LocalPath); var jsonPath = Path.Combine(dllPath, "CrossAgentTests", "ServerSentEvent", "data_collection_server_configuration.json"); var jsonString = File.ReadAllText(jsonPath); diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs index 27cfa245b..3f857324b 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs @@ -24,7 +24,7 @@ public void serializes_correctly() var json = JsonConvert.SerializeObject(agentSettings); - const string expectedJson = @"{""agent.name"":"".NET Agent"",""agent.run_id"":""AgentRunId"",""agent.enabled"":true,""agent.license_key.configured"":true,""agent.application_names"":[""name1"",""name2"",""name3""],""agent.application_names_source"":""ApplicationNameSource"",""agent.auto_start"":true,""browser_monitoring.application_id"":""BrowserMonitoringApplicationId"",""browser_monitoring.auto_instrument"":true,""browser_monitoring.beacon_address"":""BrowserMonitoringBeaconAddress"",""browser_monitoring.error_beacon_address"":""BrowserMonitoringErrorBeaconAddress"",""browser_monitoring.javascript_agent.populated"":true,""browser_monitoring.javascript_agent_file"":""BrowserMonitoringJavaScriptAgentFile"",""browser_monitoring.loader"":""BrowserMonitoringJavaScriptAgentLoaderType"",""browser_monitoring.loader_debug"":false,""browser_monitoring.monitoring_key.populated"":true,""browser_monitoring.use_ssl"":true,""security.policies_token"":""SecurityPoliciesToken"",""security.policies_token_exists"":true,""agent.allow_all_request_headers"":true,""agent.attributes_enabled"":true,""agent.can_use_attributes_includes"":true,""agent.can_use_attributes_includes_source"":""CanUseAttributesIncludesSource"",""agent.attributes_include"":[""include1"",""include2"",""include3""],""agent.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""agent.attributes_default_excludes"":[""defaultExclude1"",""defaultExclude2"",""defaultExclude3""],""transaction_events.attributes_enabled"":false,""transaction_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""transaction_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""transaction_trace.attributes_enabled"":true,""transaction_trace.attributes_include"":[""include1"",""include2"",""include3""],""transaction_trace.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""error_collector.attributes_enabled"":false,""error_collector.attributes_include"":[""include1"",""include2"",""include3""],""error_collector.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""browser_monitoring.attributes_enabled"":false,""browser_monitoring.attributes_include"":[""include1"",""include2"",""include3""],""browser_monitoring.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""custom_parameters.enabled"":false,""custom_parameters.source"":""CaptureCustomParametersSource"",""collector.host"":""CollectorHost"",""collector.port"":1234,""collector.send_data_on_exit"":true,""collector.send_data_on_exit_threshold"":4321.0,""collector.send_environment_info"":true,""collector.sync_startup"":true,""collector.timeout"":1234,""collector.max_payload_size_in_bytes"":4321,""agent.complete_transactions_on_thread"":true,""agent.compressed_content_encoding"":""CompressedContentEncoding"",""agent.configuration_version"":1234,""cross_application_tracer.cross_process_id"":""CrossApplicationTracingCrossProcessId"",""cross_application_tracer.enabled"":true,""distributed_tracing.enabled"":true,""span_events.enabled"":true,""span_events.harvest_cycle"":""00:20:34"",""span_events.attributes_enabled"":true,""span_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""span_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""infinite_tracing.trace_count_consumers"":1234,""infinite_tracing.trace_observer_host"":""InfiniteTracingTraceObserverHost"",""infinite_tracing.trace_observer_port"":""InfiniteTracingTraceObserverPort"",""infinite_tracing.trace_observer_ssl"":""InfiniteTracingTraceObserverSsl"",""infinite_tracing.dev.test_flaky"":1234.0,""infinite_tracing.dev.test_flaky_code"":4321,""infinite_tracing.dev.test_delay_ms"":1234,""infinite_tracing.spans_queue_size"":4321,""infinite_tracing.spans_partition_count"":1234,""infinite_tracing.spans_batch_size"":4321,""infinite_tracing.connect_timeout_ms"":1234,""infinite_tracing.send_data_timeout_ms"":4321,""infinite_tracing.exit_timeout_ms"":1234,""infinite_tracing.compression"":true,""agent.primary_application_id"":""PrimaryApplicationId"",""agent.trusted_account_key"":""TrustedAccountKey"",""agent.account_id"":""AccountId"",""datastore_tracer.name_reporting_enabled"":true,""datastore_tracer.query_parameters_enabled"":true,""error_collector.enabled"":true,""error_collector.capture_events_enabled"":true,""error_collector.max_samples_stored"":1234,""error_collector.harvest_cycle"":""00:20:34"",""error_collector.max_per_period"":4321,""error_collector.expected_classes"":[""expected1"",""expected2"",""expected3""],""error_collector.expected_messages"":{""first"":[""first1"",""first2""],""second"":[""second1"",""second2""]},""error_collector.expected_status_codes"":[""expectedError1"",""expectedError2"",""expectedError3""],""error_collector.expected_errors_config"":{""third"":[""third1"",""third2""],""fourth"":[""fourth1"",""fourth2""]},""error_collector.ignore_errors_config"":{""fifth"":[""fifth1"",""fifth2""],""sixth"":[""sixth1"",""sixth2""]},""error_collector.ignore_classes"":[""ignoreError1"",""ignoreError2"",""ignoreError3""],""error_collector.ignore_messages"":{""seven"":[""seven1"",""seven2""],""eight"":[""eight1"",""eight2""]},""agent.request_headers_map"":{""one"":""1"",""two"":""2""},""cross_application_tracer.encoding_key"":""EncodingKey"",""agent.entity_guid"":""EntityGuid"",""agent.high_security_mode_enabled"":true,""agent.custom_instrumentation_editor_enabled"":true,""agent.custom_instrumentation_editor_enabled_source"":""CustomInstrumentationEditorEnabledSource"",""agent.strip_exception_messages"":true,""agent.strip_exception_messages_source"":""StripExceptionMessagesSource"",""agent.instance_reporting_enabled"":true,""agent.instrumentation_logging_enabled"":true,""agent.labels"":""Labels"",""agent.metric_name_regex_rules"":[{""MatchExpression"":""match1"",""Replacement"":""replacement1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""match1"",""Options"":3}},{""MatchExpression"":""match2"",""Replacement"":""replacement2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""match2"",""Options"":3}}],""agent.new_relic_config_file_path"":""NewRelicConfigFilePath"",""agent.app_settings_config_file_path"":""AppSettingsConfigFilePath"",""proxy.host.configured"":true,""proxy.uri_path.configured"":true,""proxy.port.configured"":true,""proxy.username.configured"":true,""proxy.password.configured"":true,""proxy.domain.configured"":true,""agent.put_for_data_sent"":true,""slow_sql.enabled"":true,""transaction_tracer.explain_threshold"":""00:20:34"",""transaction_tracer.explain_enabled"":true,""transaction_tracer.max_explain_plans"":1234,""transaction_tracer.max_sql_statements"":4321,""transaction_tracer.sql_traces_per_period"":1234,""transaction_tracer.max_stack_trace_lines"":4321,""error_collector.ignore_status_codes"":[""ignore1"",""ignore2"",""ignore3""],""agent.thread_profiling_methods_to_ignore"":[""ignoreMethod1"",""ignoreMethod2"",""ignoreMethod3""],""custom_events.enabled"":true,""custom_events.enabled_source"":""CustomEventsEnabledSource"",""custom_events.attributes_enabled"":true,""custom_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""custom_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""custom_events.max_samples_stored"":1234,""custom_events.harvest_cycle"":""00:20:34"",""agent.disable_samplers"":true,""thread_profiler.enabled"":true,""transaction_events.enabled"":true,""transaction_events.max_samples_stored"":4321,""transaction_events.harvest_cycle"":""01:12:01"",""transaction_events.transactions_enabled"":true,""transaction_name.regex_rules"":[{""MatchExpression"":""matchTrans1"",""Replacement"":""replacementTrans1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""matchTrans1"",""Options"":3}},{""MatchExpression"":""matchTrans2"",""Replacement"":""replacementTrans2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""matchTrans2"",""Options"":3}}],""transaction_name.whitelist_rules"":{""nine"":[""nine1"",""nine2""],""ten"":[""ten1"",""ten2""]},""transaction_tracer.apdex_f"":""00:20:34"",""transaction_tracer.apdex_t"":""01:12:01"",""transaction_tracer.transaction_threshold"":""00:20:34"",""transaction_tracer.enabled"":true,""transaction_tracer.max_segments"":1234,""transaction_tracer.record_sql"":""TransactionTracerRecordSql"",""transaction_tracer.record_sql_source"":""TransactionTracerRecordSqlSource"",""transaction_tracer.stack_trace_threshold"":""01:12:01"",""transaction_tracer.max_stack_traces"":4321,""agent.trusted_account_ids"":[1,2,3],""agent.server_side_config_enabled"":true,""agent.ignore_server_side_config"":true,""agent.url_regex_rules"":[{""MatchExpression"":""matchUrl1"",""Replacement"":""replacementUrl1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""matchUrl1"",""Options"":3}},{""MatchExpression"":""matchUrl2"",""Replacement"":""replacementUrl2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""matchUrl2"",""Options"":3}}],""agent.request_path_exclusion_list"":[{""Pattern"":""asdf"",""Options"":0},{""Pattern"":""qwerty"",""Options"":1},{""Pattern"":""yolo"",""Options"":4}],""agent.web_transactions_apdex"":{""first"":1.0,""second"":2.0},""agent.wrapper_exception_limit"":1234,""utilization.detect_aws_enabled"":true,""utilization.detect_azure_enabled"":true,""utilization.detect_gcp_enabled"":true,""utilization.detect_pcf_enabled"":true,""utilization.detect_docker_enabled"":true,""utilization.detect_kubernetes_enabled"":true,""utilization.logical_processors"":22,""utilization.total_ram_mib"":33,""utilization.billing_host"":""UtilizationBillingHost"",""utilization.hostname"":""UtilizationHostName"",""utilization.full_hostname"":""UtilizationFullHostName"",""diagnostics.capture_agent_timing_enabled"":true,""diagnostics.capture_agent_timing_frequency"":1234,""agent.use_resource_based_naming_for_wcf_enabled"":true,""agent.event_listener_samplers_enabled"":true,""agent.sampling_target"":1234,""span_events.max_samples_stored"":4321,""agent.sampling_target_period_in_seconds"":1234,""agent.payload_success_metrics_enabled"":true,""agent.process_host_display_name"":""ProcessHostDisplayName"",""transaction_tracer.database_statement_cache_capacity"":1234,""agent.force_synchronous_timing_calculation_for_http_client"":true,""agent.exclude_new_relic_header"":true,""application_logging.enabled"":true,""application_logging.metrics.enabled"":true,""application_logging.forwarding.enabled"":true,""application_logging.forwarding.max_samples_stored"":1234,""application_logging.harvest_cycle"":""00:20:34"",""application_logging.local_decorating.enabled"":true,""agent.app_domain_caching_disabled"":true,""agent.force_new_transaction_on_new_thread_enabled"":true,""agent.code_level_metrics_enabled"":true,""agent.app_settings"":{""hello"":""friend"",""we"":""made"",""it"":""to"",""the"":""end""},""application_logging.forwarding.context_data.enabled"":true,""application_logging.forwarding.context_data.include"":[""attr1"",""attr2""],""application_logging.forwarding.context_data.exclude"":[""attr1"",""attr2""],""metrics.harvest_cycle"":""00:01:00"",""transaction_traces.harvest_cycle"":""00:01:00"",""error_traces.harvest_cycle"":""00:01:00"",""get_agent_commands.cycle"":""00:01:00"",""default.harvest_cycle"":""00:01:00"",""sql_traces.harvest_cycle"":""00:01:00""}"; + const string expectedJson = @"{""agent.name"":"".NET Agent"",""agent.run_id"":""AgentRunId"",""agent.enabled"":true,""agent.license_key.configured"":true,""agent.application_names"":[""name1"",""name2"",""name3""],""agent.application_names_source"":""ApplicationNameSource"",""agent.auto_start"":true,""browser_monitoring.application_id"":""BrowserMonitoringApplicationId"",""browser_monitoring.auto_instrument"":true,""browser_monitoring.beacon_address"":""BrowserMonitoringBeaconAddress"",""browser_monitoring.error_beacon_address"":""BrowserMonitoringErrorBeaconAddress"",""browser_monitoring.javascript_agent.populated"":true,""browser_monitoring.javascript_agent_file"":""BrowserMonitoringJavaScriptAgentFile"",""browser_monitoring.loader"":""BrowserMonitoringJavaScriptAgentLoaderType"",""browser_monitoring.loader_debug"":false,""browser_monitoring.monitoring_key.populated"":true,""browser_monitoring.use_ssl"":true,""security.policies_token"":""SecurityPoliciesToken"",""security.policies_token_exists"":true,""agent.allow_all_request_headers"":true,""agent.attributes_enabled"":true,""agent.can_use_attributes_includes"":true,""agent.can_use_attributes_includes_source"":""CanUseAttributesIncludesSource"",""agent.attributes_include"":[""include1"",""include2"",""include3""],""agent.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""agent.attributes_default_excludes"":[""defaultExclude1"",""defaultExclude2"",""defaultExclude3""],""transaction_events.attributes_enabled"":false,""transaction_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""transaction_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""transaction_trace.attributes_enabled"":true,""transaction_trace.attributes_include"":[""include1"",""include2"",""include3""],""transaction_trace.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""error_collector.attributes_enabled"":false,""error_collector.attributes_include"":[""include1"",""include2"",""include3""],""error_collector.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""browser_monitoring.attributes_enabled"":false,""browser_monitoring.attributes_include"":[""include1"",""include2"",""include3""],""browser_monitoring.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""custom_parameters.enabled"":false,""custom_parameters.source"":""CaptureCustomParametersSource"",""collector.host"":""CollectorHost"",""collector.port"":1234,""collector.send_data_on_exit"":true,""collector.send_data_on_exit_threshold"":4321.0,""collector.send_environment_info"":true,""collector.sync_startup"":true,""collector.timeout"":1234,""collector.max_payload_size_in_bytes"":4321,""agent.complete_transactions_on_thread"":true,""agent.compressed_content_encoding"":""CompressedContentEncoding"",""agent.configuration_version"":1234,""cross_application_tracer.cross_process_id"":""CrossApplicationTracingCrossProcessId"",""cross_application_tracer.enabled"":true,""distributed_tracing.enabled"":true,""span_events.enabled"":true,""span_events.harvest_cycle"":""00:20:34"",""span_events.attributes_enabled"":true,""span_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""span_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""infinite_tracing.trace_count_consumers"":1234,""infinite_tracing.trace_observer_host"":""InfiniteTracingTraceObserverHost"",""infinite_tracing.trace_observer_port"":""InfiniteTracingTraceObserverPort"",""infinite_tracing.trace_observer_ssl"":""InfiniteTracingTraceObserverSsl"",""infinite_tracing.dev.test_flaky"":1234.0,""infinite_tracing.dev.test_flaky_code"":4321,""infinite_tracing.dev.test_delay_ms"":1234,""infinite_tracing.spans_queue_size"":4321,""infinite_tracing.spans_partition_count"":1234,""infinite_tracing.spans_batch_size"":4321,""infinite_tracing.connect_timeout_ms"":1234,""infinite_tracing.send_data_timeout_ms"":4321,""infinite_tracing.exit_timeout_ms"":1234,""infinite_tracing.compression"":true,""agent.primary_application_id"":""PrimaryApplicationId"",""agent.trusted_account_key"":""TrustedAccountKey"",""agent.account_id"":""AccountId"",""datastore_tracer.name_reporting_enabled"":true,""datastore_tracer.query_parameters_enabled"":true,""error_collector.enabled"":true,""error_collector.capture_events_enabled"":true,""error_collector.max_samples_stored"":1234,""error_collector.harvest_cycle"":""00:20:34"",""error_collector.max_per_period"":4321,""error_collector.expected_classes"":[""expected1"",""expected2"",""expected3""],""error_collector.expected_messages"":{""first"":[""first1"",""first2""],""second"":[""second1"",""second2""]},""error_collector.expected_status_codes"":[""expectedError1"",""expectedError2"",""expectedError3""],""error_collector.expected_errors_config"":{""third"":[""third1"",""third2""],""fourth"":[""fourth1"",""fourth2""]},""error_collector.ignore_errors_config"":{""fifth"":[""fifth1"",""fifth2""],""sixth"":[""sixth1"",""sixth2""]},""error_collector.ignore_classes"":[""ignoreError1"",""ignoreError2"",""ignoreError3""],""error_collector.ignore_messages"":{""seven"":[""seven1"",""seven2""],""eight"":[""eight1"",""eight2""]},""agent.request_headers_map"":{""one"":""1"",""two"":""2""},""cross_application_tracer.encoding_key"":""EncodingKey"",""agent.entity_guid"":""EntityGuid"",""agent.high_security_mode_enabled"":true,""agent.custom_instrumentation_editor_enabled"":true,""agent.custom_instrumentation_editor_enabled_source"":""CustomInstrumentationEditorEnabledSource"",""agent.strip_exception_messages"":true,""agent.strip_exception_messages_source"":""StripExceptionMessagesSource"",""agent.instance_reporting_enabled"":true,""agent.instrumentation_logging_enabled"":true,""agent.labels"":""Labels"",""agent.metric_name_regex_rules"":[{""MatchExpression"":""match1"",""Replacement"":""replacement1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""match1"",""Options"":3}},{""MatchExpression"":""match2"",""Replacement"":""replacement2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""match2"",""Options"":3}}],""agent.new_relic_config_file_path"":""NewRelicConfigFilePath"",""agent.app_settings_config_file_path"":""AppSettingsConfigFilePath"",""proxy.host.configured"":true,""proxy.uri_path.configured"":true,""proxy.port.configured"":true,""proxy.username.configured"":true,""proxy.password.configured"":true,""proxy.domain.configured"":true,""agent.put_for_data_sent"":true,""slow_sql.enabled"":true,""transaction_tracer.explain_threshold"":""00:20:34"",""transaction_tracer.explain_enabled"":true,""transaction_tracer.max_explain_plans"":1234,""transaction_tracer.max_sql_statements"":4321,""transaction_tracer.sql_traces_per_period"":1234,""transaction_tracer.max_stack_trace_lines"":4321,""error_collector.ignore_status_codes"":[""ignore1"",""ignore2"",""ignore3""],""agent.thread_profiling_methods_to_ignore"":[""ignoreMethod1"",""ignoreMethod2"",""ignoreMethod3""],""custom_events.enabled"":true,""custom_events.enabled_source"":""CustomEventsEnabledSource"",""custom_events.attributes_enabled"":true,""custom_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""custom_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""custom_events.max_samples_stored"":1234,""custom_events.harvest_cycle"":""00:20:34"",""agent.disable_samplers"":true,""thread_profiler.enabled"":true,""transaction_events.enabled"":true,""transaction_events.max_samples_stored"":4321,""transaction_events.harvest_cycle"":""01:12:01"",""transaction_events.transactions_enabled"":true,""transaction_name.regex_rules"":[{""MatchExpression"":""matchTrans1"",""Replacement"":""replacementTrans1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""matchTrans1"",""Options"":3}},{""MatchExpression"":""matchTrans2"",""Replacement"":""replacementTrans2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""matchTrans2"",""Options"":3}}],""transaction_name.whitelist_rules"":{""nine"":[""nine1"",""nine2""],""ten"":[""ten1"",""ten2""]},""transaction_tracer.apdex_f"":""00:20:34"",""transaction_tracer.apdex_t"":""01:12:01"",""transaction_tracer.transaction_threshold"":""00:20:34"",""transaction_tracer.enabled"":true,""transaction_tracer.max_segments"":1234,""transaction_tracer.record_sql"":""TransactionTracerRecordSql"",""transaction_tracer.record_sql_source"":""TransactionTracerRecordSqlSource"",""transaction_tracer.stack_trace_threshold"":""01:12:01"",""transaction_tracer.max_stack_traces"":4321,""agent.trusted_account_ids"":[1,2,3],""agent.server_side_config_enabled"":true,""agent.ignore_server_side_config"":true,""agent.url_regex_rules"":[{""MatchExpression"":""matchUrl1"",""Replacement"":""replacementUrl1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""matchUrl1"",""Options"":3}},{""MatchExpression"":""matchUrl2"",""Replacement"":""replacementUrl2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""matchUrl2"",""Options"":3}}],""agent.request_path_exclusion_list"":[{""Pattern"":""asdf"",""Options"":0},{""Pattern"":""qwerty"",""Options"":1},{""Pattern"":""yolo"",""Options"":4}],""agent.web_transactions_apdex"":{""first"":1.0,""second"":2.0},""agent.wrapper_exception_limit"":1234,""utilization.detect_aws_enabled"":true,""utilization.detect_azure_enabled"":true,""utilization.detect_gcp_enabled"":true,""utilization.detect_pcf_enabled"":true,""utilization.detect_docker_enabled"":true,""utilization.detect_kubernetes_enabled"":true,""utilization.logical_processors"":22,""utilization.total_ram_mib"":33,""utilization.billing_host"":""UtilizationBillingHost"",""utilization.hostname"":""UtilizationHostName"",""utilization.full_hostname"":""UtilizationFullHostName"",""diagnostics.capture_agent_timing_enabled"":true,""diagnostics.capture_agent_timing_frequency"":1234,""agent.use_resource_based_naming_for_wcf_enabled"":true,""agent.event_listener_samplers_enabled"":true,""agent.sampling_target"":1234,""span_events.max_samples_stored"":4321,""agent.sampling_target_period_in_seconds"":1234,""agent.payload_success_metrics_enabled"":true,""agent.process_host_display_name"":""ProcessHostDisplayName"",""transaction_tracer.database_statement_cache_capacity"":1234,""agent.force_synchronous_timing_calculation_for_http_client"":true,""agent.exclude_new_relic_header"":true,""application_logging.enabled"":true,""application_logging.metrics.enabled"":true,""application_logging.forwarding.enabled"":true,""application_logging.forwarding.max_samples_stored"":1234,""application_logging.forwarding.log_level_denylist"":[""testlevel1, testlevel2""],""application_logging.harvest_cycle"":""00:20:34"",""application_logging.local_decorating.enabled"":true,""agent.app_domain_caching_disabled"":true,""agent.force_new_transaction_on_new_thread_enabled"":true,""agent.code_level_metrics_enabled"":true,""agent.app_settings"":{""hello"":""friend"",""we"":""made"",""it"":""to"",""the"":""end""},""application_logging.forwarding.context_data.enabled"":true,""application_logging.forwarding.context_data.include"":[""attr1"",""attr2""],""application_logging.forwarding.context_data.exclude"":[""attr1"",""attr2""],""metrics.harvest_cycle"":""00:01:00"",""transaction_traces.harvest_cycle"":""00:01:00"",""error_traces.harvest_cycle"":""00:01:00"",""get_agent_commands.cycle"":""00:01:00"",""default.harvest_cycle"":""00:01:00"",""sql_traces.harvest_cycle"":""00:01:00"",""update_loaded_modules.cycle"":""00:01:00"",""stackexchangeredis_cleanup.cycle"":""00:01:00""}"; // Confirm that JsonIgnored properties are present, but not serialized Assert.NotNull(agentSettings.AgentLicenseKey); diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs index 89a172e6d..efc8e7d14 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs @@ -60,7 +60,7 @@ public void serializes_correctly() var json = JsonConvert.SerializeObject(connectModel); - const string expectedJson = @"{""pid"":1,""language"":""dotnet"",""display_host"":""customHostName"",""host"":""myHost"",""app_name"":[""name1"",""name2""],""agent_version"":""1.0"",""agent_version_timestamp"":0,""security_settings"":{""transaction_tracer"":{""record_sql"":""raw""}},""high_security"":true,""event_harvest_config"":{""harvest_limits"":{""analytic_event_data"":4321,""custom_event_data"":1234,""error_event_data"":1234,""span_event_data"":4321,""log_event_data"":1234}},""identifier"":""myIdentifier"",""labels"":[{""label_type"":""type1"",""label_value"":""value1""}],""settings"":{""agent.name"":"".NET Agent"",""agent.run_id"":""AgentRunId"",""agent.enabled"":true,""agent.license_key.configured"":true,""agent.application_names"":[""name1"",""name2"",""name3""],""agent.application_names_source"":""ApplicationNameSource"",""agent.auto_start"":true,""browser_monitoring.application_id"":""BrowserMonitoringApplicationId"",""browser_monitoring.auto_instrument"":true,""browser_monitoring.beacon_address"":""BrowserMonitoringBeaconAddress"",""browser_monitoring.error_beacon_address"":""BrowserMonitoringErrorBeaconAddress"",""browser_monitoring.javascript_agent.populated"":true,""browser_monitoring.javascript_agent_file"":""BrowserMonitoringJavaScriptAgentFile"",""browser_monitoring.loader"":""BrowserMonitoringJavaScriptAgentLoaderType"",""browser_monitoring.loader_debug"":false,""browser_monitoring.monitoring_key.populated"":true,""browser_monitoring.use_ssl"":true,""security.policies_token"":""SecurityPoliciesToken"",""security.policies_token_exists"":true,""agent.allow_all_request_headers"":true,""agent.attributes_enabled"":true,""agent.can_use_attributes_includes"":true,""agent.can_use_attributes_includes_source"":""CanUseAttributesIncludesSource"",""agent.attributes_include"":[""include1"",""include2"",""include3""],""agent.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""agent.attributes_default_excludes"":[""defaultExclude1"",""defaultExclude2"",""defaultExclude3""],""transaction_events.attributes_enabled"":false,""transaction_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""transaction_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""transaction_trace.attributes_enabled"":true,""transaction_trace.attributes_include"":[""include1"",""include2"",""include3""],""transaction_trace.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""error_collector.attributes_enabled"":false,""error_collector.attributes_include"":[""include1"",""include2"",""include3""],""error_collector.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""browser_monitoring.attributes_enabled"":false,""browser_monitoring.attributes_include"":[""include1"",""include2"",""include3""],""browser_monitoring.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""custom_parameters.enabled"":false,""custom_parameters.source"":""CaptureCustomParametersSource"",""collector.host"":""CollectorHost"",""collector.port"":1234,""collector.send_data_on_exit"":true,""collector.send_data_on_exit_threshold"":4321.0,""collector.send_environment_info"":true,""collector.sync_startup"":true,""collector.timeout"":1234,""collector.max_payload_size_in_bytes"":4321,""agent.complete_transactions_on_thread"":true,""agent.compressed_content_encoding"":""CompressedContentEncoding"",""agent.configuration_version"":1234,""cross_application_tracer.cross_process_id"":""CrossApplicationTracingCrossProcessId"",""cross_application_tracer.enabled"":true,""distributed_tracing.enabled"":true,""span_events.enabled"":true,""span_events.harvest_cycle"":""00:20:34"",""span_events.attributes_enabled"":true,""span_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""span_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""infinite_tracing.trace_count_consumers"":1234,""infinite_tracing.trace_observer_host"":""InfiniteTracingTraceObserverHost"",""infinite_tracing.trace_observer_port"":""InfiniteTracingTraceObserverPort"",""infinite_tracing.trace_observer_ssl"":""InfiniteTracingTraceObserverSsl"",""infinite_tracing.dev.test_flaky"":1234.0,""infinite_tracing.dev.test_flaky_code"":4321,""infinite_tracing.dev.test_delay_ms"":1234,""infinite_tracing.spans_queue_size"":4321,""infinite_tracing.spans_partition_count"":1234,""infinite_tracing.spans_batch_size"":4321,""infinite_tracing.connect_timeout_ms"":1234,""infinite_tracing.send_data_timeout_ms"":4321,""infinite_tracing.exit_timeout_ms"":1234,""infinite_tracing.compression"":true,""agent.primary_application_id"":""PrimaryApplicationId"",""agent.trusted_account_key"":""TrustedAccountKey"",""agent.account_id"":""AccountId"",""datastore_tracer.name_reporting_enabled"":true,""datastore_tracer.query_parameters_enabled"":true,""error_collector.enabled"":true,""error_collector.capture_events_enabled"":true,""error_collector.max_samples_stored"":1234,""error_collector.harvest_cycle"":""00:20:34"",""error_collector.max_per_period"":4321,""error_collector.expected_classes"":[""expected1"",""expected2"",""expected3""],""error_collector.expected_messages"":{""first"":[""first1"",""first2""],""second"":[""second1"",""second2""]},""error_collector.expected_status_codes"":[""expectedError1"",""expectedError2"",""expectedError3""],""error_collector.expected_errors_config"":{""third"":[""third1"",""third2""],""fourth"":[""fourth1"",""fourth2""]},""error_collector.ignore_errors_config"":{""fifth"":[""fifth1"",""fifth2""],""sixth"":[""sixth1"",""sixth2""]},""error_collector.ignore_classes"":[""ignoreError1"",""ignoreError2"",""ignoreError3""],""error_collector.ignore_messages"":{""seven"":[""seven1"",""seven2""],""eight"":[""eight1"",""eight2""]},""agent.request_headers_map"":{""one"":""1"",""two"":""2""},""cross_application_tracer.encoding_key"":""EncodingKey"",""agent.entity_guid"":""EntityGuid"",""agent.high_security_mode_enabled"":true,""agent.custom_instrumentation_editor_enabled"":true,""agent.custom_instrumentation_editor_enabled_source"":""CustomInstrumentationEditorEnabledSource"",""agent.strip_exception_messages"":true,""agent.strip_exception_messages_source"":""StripExceptionMessagesSource"",""agent.instance_reporting_enabled"":true,""agent.instrumentation_logging_enabled"":true,""agent.labels"":""Labels"",""agent.metric_name_regex_rules"":[{""MatchExpression"":""match1"",""Replacement"":""replacement1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""match1"",""Options"":3}},{""MatchExpression"":""match2"",""Replacement"":""replacement2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""match2"",""Options"":3}}],""agent.new_relic_config_file_path"":""NewRelicConfigFilePath"",""agent.app_settings_config_file_path"":""AppSettingsConfigFilePath"",""proxy.host.configured"":true,""proxy.uri_path.configured"":true,""proxy.port.configured"":true,""proxy.username.configured"":true,""proxy.password.configured"":true,""proxy.domain.configured"":true,""agent.put_for_data_sent"":true,""slow_sql.enabled"":true,""transaction_tracer.explain_threshold"":""00:20:34"",""transaction_tracer.explain_enabled"":true,""transaction_tracer.max_explain_plans"":1234,""transaction_tracer.max_sql_statements"":4321,""transaction_tracer.sql_traces_per_period"":1234,""transaction_tracer.max_stack_trace_lines"":4321,""error_collector.ignore_status_codes"":[""ignore1"",""ignore2"",""ignore3""],""agent.thread_profiling_methods_to_ignore"":[""ignoreMethod1"",""ignoreMethod2"",""ignoreMethod3""],""custom_events.enabled"":true,""custom_events.enabled_source"":""CustomEventsEnabledSource"",""custom_events.attributes_enabled"":true,""custom_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""custom_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""custom_events.max_samples_stored"":1234,""custom_events.harvest_cycle"":""00:20:34"",""agent.disable_samplers"":true,""thread_profiler.enabled"":true,""transaction_events.enabled"":true,""transaction_events.max_samples_stored"":4321,""transaction_events.harvest_cycle"":""01:12:01"",""transaction_events.transactions_enabled"":true,""transaction_name.regex_rules"":[{""MatchExpression"":""matchTrans1"",""Replacement"":""replacementTrans1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""matchTrans1"",""Options"":3}},{""MatchExpression"":""matchTrans2"",""Replacement"":""replacementTrans2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""matchTrans2"",""Options"":3}}],""transaction_name.whitelist_rules"":{""nine"":[""nine1"",""nine2""],""ten"":[""ten1"",""ten2""]},""transaction_tracer.apdex_f"":""00:20:34"",""transaction_tracer.apdex_t"":""01:12:01"",""transaction_tracer.transaction_threshold"":""00:20:34"",""transaction_tracer.enabled"":true,""transaction_tracer.max_segments"":1234,""transaction_tracer.record_sql"":""TransactionTracerRecordSql"",""transaction_tracer.record_sql_source"":""TransactionTracerRecordSqlSource"",""transaction_tracer.stack_trace_threshold"":""01:12:01"",""transaction_tracer.max_stack_traces"":4321,""agent.trusted_account_ids"":[1,2,3],""agent.server_side_config_enabled"":true,""agent.ignore_server_side_config"":true,""agent.url_regex_rules"":[{""MatchExpression"":""matchUrl1"",""Replacement"":""replacementUrl1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""matchUrl1"",""Options"":3}},{""MatchExpression"":""matchUrl2"",""Replacement"":""replacementUrl2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""matchUrl2"",""Options"":3}}],""agent.request_path_exclusion_list"":[{""Pattern"":""asdf"",""Options"":0},{""Pattern"":""qwerty"",""Options"":1},{""Pattern"":""yolo"",""Options"":4}],""agent.web_transactions_apdex"":{""first"":1.0,""second"":2.0},""agent.wrapper_exception_limit"":1234,""utilization.detect_aws_enabled"":true,""utilization.detect_azure_enabled"":true,""utilization.detect_gcp_enabled"":true,""utilization.detect_pcf_enabled"":true,""utilization.detect_docker_enabled"":true,""utilization.detect_kubernetes_enabled"":true,""utilization.logical_processors"":22,""utilization.total_ram_mib"":33,""utilization.billing_host"":""UtilizationBillingHost"",""utilization.hostname"":""UtilizationHostName"",""utilization.full_hostname"":""UtilizationFullHostName"",""diagnostics.capture_agent_timing_enabled"":true,""diagnostics.capture_agent_timing_frequency"":1234,""agent.use_resource_based_naming_for_wcf_enabled"":true,""agent.event_listener_samplers_enabled"":true,""agent.sampling_target"":1234,""span_events.max_samples_stored"":4321,""agent.sampling_target_period_in_seconds"":1234,""agent.payload_success_metrics_enabled"":true,""agent.process_host_display_name"":""ProcessHostDisplayName"",""transaction_tracer.database_statement_cache_capacity"":1234,""agent.force_synchronous_timing_calculation_for_http_client"":true,""agent.exclude_new_relic_header"":true,""application_logging.enabled"":true,""application_logging.metrics.enabled"":true,""application_logging.forwarding.enabled"":true,""application_logging.forwarding.max_samples_stored"":1234,""application_logging.harvest_cycle"":""00:20:34"",""application_logging.local_decorating.enabled"":true,""agent.app_domain_caching_disabled"":true,""agent.force_new_transaction_on_new_thread_enabled"":true,""agent.code_level_metrics_enabled"":true,""agent.app_settings"":{""hello"":""friend"",""we"":""made"",""it"":""to"",""the"":""end""},""application_logging.forwarding.context_data.enabled"":true,""application_logging.forwarding.context_data.include"":[""attr1"",""attr2""],""application_logging.forwarding.context_data.exclude"":[""attr1"",""attr2""],""metrics.harvest_cycle"":""00:01:00"",""transaction_traces.harvest_cycle"":""00:01:00"",""error_traces.harvest_cycle"":""00:01:00"",""get_agent_commands.cycle"":""00:01:00"",""default.harvest_cycle"":""00:01:00"",""sql_traces.harvest_cycle"":""00:01:00""},""metadata"":{""hello"":""there""},""utilization"":{""metadata_version"":5,""logical_processors"":2,""total_ram_mib"":0,""hostname"":""myHost2"",""full_hostname"":""myHost2.domain.com"",""ip_address"":[""1.2.3.4"",""5.6.7.8""],""config"":{""hostname"":""my-host"",""logical_processors"":1,""total_ram_mib"":2048},""vendors"":{""aws"":{""availabilityZone"":""myZone"",""instanceId"":""myInstanceId"",""instanceType"":""myInstanceType""},""azure"":{""location"":""myLocation"",""name"":""myName"",""vmId"":""myVmId"",""vmSize"":""myVmSize""},""gcp"":{""id"":""myId"",""machineType"":""myMachineType"",""name"":""myName"",""zone"":""myZone""},""pcf"":{""cf_instance_guid"":""myInstanceGuid"",""cf_instance_ip"":""myInstanceIp"",""memory_limit"":""myMemoryLimit""},""kubernetes"":{""kubernetes_service_host"":""10.96.0.1""}}},""security_policies"":{""record_sql"":{""enabled"":false},""attributes_include"":{""enabled"":true},""allow_raw_exception_messages"":{""enabled"":false},""custom_events"":{""enabled"":true},""custom_parameters"":{""enabled"":false},""custom_instrumentation_editor"":{""enabled"":true}}}"; + const string expectedJson = @"{""pid"":1,""language"":""dotnet"",""display_host"":""customHostName"",""host"":""myHost"",""app_name"":[""name1"",""name2""],""agent_version"":""1.0"",""agent_version_timestamp"":0,""security_settings"":{""transaction_tracer"":{""record_sql"":""raw""}},""high_security"":true,""event_harvest_config"":{""harvest_limits"":{""analytic_event_data"":4321,""custom_event_data"":1234,""error_event_data"":1234,""span_event_data"":4321,""log_event_data"":1234}},""identifier"":""myIdentifier"",""labels"":[{""label_type"":""type1"",""label_value"":""value1""}],""settings"":{""agent.name"":"".NET Agent"",""agent.run_id"":""AgentRunId"",""agent.enabled"":true,""agent.license_key.configured"":true,""agent.application_names"":[""name1"",""name2"",""name3""],""agent.application_names_source"":""ApplicationNameSource"",""agent.auto_start"":true,""browser_monitoring.application_id"":""BrowserMonitoringApplicationId"",""browser_monitoring.auto_instrument"":true,""browser_monitoring.beacon_address"":""BrowserMonitoringBeaconAddress"",""browser_monitoring.error_beacon_address"":""BrowserMonitoringErrorBeaconAddress"",""browser_monitoring.javascript_agent.populated"":true,""browser_monitoring.javascript_agent_file"":""BrowserMonitoringJavaScriptAgentFile"",""browser_monitoring.loader"":""BrowserMonitoringJavaScriptAgentLoaderType"",""browser_monitoring.loader_debug"":false,""browser_monitoring.monitoring_key.populated"":true,""browser_monitoring.use_ssl"":true,""security.policies_token"":""SecurityPoliciesToken"",""security.policies_token_exists"":true,""agent.allow_all_request_headers"":true,""agent.attributes_enabled"":true,""agent.can_use_attributes_includes"":true,""agent.can_use_attributes_includes_source"":""CanUseAttributesIncludesSource"",""agent.attributes_include"":[""include1"",""include2"",""include3""],""agent.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""agent.attributes_default_excludes"":[""defaultExclude1"",""defaultExclude2"",""defaultExclude3""],""transaction_events.attributes_enabled"":false,""transaction_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""transaction_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""transaction_trace.attributes_enabled"":true,""transaction_trace.attributes_include"":[""include1"",""include2"",""include3""],""transaction_trace.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""error_collector.attributes_enabled"":false,""error_collector.attributes_include"":[""include1"",""include2"",""include3""],""error_collector.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""browser_monitoring.attributes_enabled"":false,""browser_monitoring.attributes_include"":[""include1"",""include2"",""include3""],""browser_monitoring.attributes_exclude"":[""exclude1"",""exclude2"",""exclude3""],""custom_parameters.enabled"":false,""custom_parameters.source"":""CaptureCustomParametersSource"",""collector.host"":""CollectorHost"",""collector.port"":1234,""collector.send_data_on_exit"":true,""collector.send_data_on_exit_threshold"":4321.0,""collector.send_environment_info"":true,""collector.sync_startup"":true,""collector.timeout"":1234,""collector.max_payload_size_in_bytes"":4321,""agent.complete_transactions_on_thread"":true,""agent.compressed_content_encoding"":""CompressedContentEncoding"",""agent.configuration_version"":1234,""cross_application_tracer.cross_process_id"":""CrossApplicationTracingCrossProcessId"",""cross_application_tracer.enabled"":true,""distributed_tracing.enabled"":true,""span_events.enabled"":true,""span_events.harvest_cycle"":""00:20:34"",""span_events.attributes_enabled"":true,""span_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""span_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""infinite_tracing.trace_count_consumers"":1234,""infinite_tracing.trace_observer_host"":""InfiniteTracingTraceObserverHost"",""infinite_tracing.trace_observer_port"":""InfiniteTracingTraceObserverPort"",""infinite_tracing.trace_observer_ssl"":""InfiniteTracingTraceObserverSsl"",""infinite_tracing.dev.test_flaky"":1234.0,""infinite_tracing.dev.test_flaky_code"":4321,""infinite_tracing.dev.test_delay_ms"":1234,""infinite_tracing.spans_queue_size"":4321,""infinite_tracing.spans_partition_count"":1234,""infinite_tracing.spans_batch_size"":4321,""infinite_tracing.connect_timeout_ms"":1234,""infinite_tracing.send_data_timeout_ms"":4321,""infinite_tracing.exit_timeout_ms"":1234,""infinite_tracing.compression"":true,""agent.primary_application_id"":""PrimaryApplicationId"",""agent.trusted_account_key"":""TrustedAccountKey"",""agent.account_id"":""AccountId"",""datastore_tracer.name_reporting_enabled"":true,""datastore_tracer.query_parameters_enabled"":true,""error_collector.enabled"":true,""error_collector.capture_events_enabled"":true,""error_collector.max_samples_stored"":1234,""error_collector.harvest_cycle"":""00:20:34"",""error_collector.max_per_period"":4321,""error_collector.expected_classes"":[""expected1"",""expected2"",""expected3""],""error_collector.expected_messages"":{""first"":[""first1"",""first2""],""second"":[""second1"",""second2""]},""error_collector.expected_status_codes"":[""expectedError1"",""expectedError2"",""expectedError3""],""error_collector.expected_errors_config"":{""third"":[""third1"",""third2""],""fourth"":[""fourth1"",""fourth2""]},""error_collector.ignore_errors_config"":{""fifth"":[""fifth1"",""fifth2""],""sixth"":[""sixth1"",""sixth2""]},""error_collector.ignore_classes"":[""ignoreError1"",""ignoreError2"",""ignoreError3""],""error_collector.ignore_messages"":{""seven"":[""seven1"",""seven2""],""eight"":[""eight1"",""eight2""]},""agent.request_headers_map"":{""one"":""1"",""two"":""2""},""cross_application_tracer.encoding_key"":""EncodingKey"",""agent.entity_guid"":""EntityGuid"",""agent.high_security_mode_enabled"":true,""agent.custom_instrumentation_editor_enabled"":true,""agent.custom_instrumentation_editor_enabled_source"":""CustomInstrumentationEditorEnabledSource"",""agent.strip_exception_messages"":true,""agent.strip_exception_messages_source"":""StripExceptionMessagesSource"",""agent.instance_reporting_enabled"":true,""agent.instrumentation_logging_enabled"":true,""agent.labels"":""Labels"",""agent.metric_name_regex_rules"":[{""MatchExpression"":""match1"",""Replacement"":""replacement1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""match1"",""Options"":3}},{""MatchExpression"":""match2"",""Replacement"":""replacement2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""match2"",""Options"":3}}],""agent.new_relic_config_file_path"":""NewRelicConfigFilePath"",""agent.app_settings_config_file_path"":""AppSettingsConfigFilePath"",""proxy.host.configured"":true,""proxy.uri_path.configured"":true,""proxy.port.configured"":true,""proxy.username.configured"":true,""proxy.password.configured"":true,""proxy.domain.configured"":true,""agent.put_for_data_sent"":true,""slow_sql.enabled"":true,""transaction_tracer.explain_threshold"":""00:20:34"",""transaction_tracer.explain_enabled"":true,""transaction_tracer.max_explain_plans"":1234,""transaction_tracer.max_sql_statements"":4321,""transaction_tracer.sql_traces_per_period"":1234,""transaction_tracer.max_stack_trace_lines"":4321,""error_collector.ignore_status_codes"":[""ignore1"",""ignore2"",""ignore3""],""agent.thread_profiling_methods_to_ignore"":[""ignoreMethod1"",""ignoreMethod2"",""ignoreMethod3""],""custom_events.enabled"":true,""custom_events.enabled_source"":""CustomEventsEnabledSource"",""custom_events.attributes_enabled"":true,""custom_events.attributes_include"":[""attributeInclude1"",""attributeInclude2"",""attributeInclude3""],""custom_events.attributes_exclude"":[""attributeExclude1"",""attributeExclude2"",""attributeExclude3""],""custom_events.max_samples_stored"":1234,""custom_events.harvest_cycle"":""00:20:34"",""agent.disable_samplers"":true,""thread_profiler.enabled"":true,""transaction_events.enabled"":true,""transaction_events.max_samples_stored"":4321,""transaction_events.harvest_cycle"":""01:12:01"",""transaction_events.transactions_enabled"":true,""transaction_name.regex_rules"":[{""MatchExpression"":""matchTrans1"",""Replacement"":""replacementTrans1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""matchTrans1"",""Options"":3}},{""MatchExpression"":""matchTrans2"",""Replacement"":""replacementTrans2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""matchTrans2"",""Options"":3}}],""transaction_name.whitelist_rules"":{""nine"":[""nine1"",""nine2""],""ten"":[""ten1"",""ten2""]},""transaction_tracer.apdex_f"":""00:20:34"",""transaction_tracer.apdex_t"":""01:12:01"",""transaction_tracer.transaction_threshold"":""00:20:34"",""transaction_tracer.enabled"":true,""transaction_tracer.max_segments"":1234,""transaction_tracer.record_sql"":""TransactionTracerRecordSql"",""transaction_tracer.record_sql_source"":""TransactionTracerRecordSqlSource"",""transaction_tracer.stack_trace_threshold"":""01:12:01"",""transaction_tracer.max_stack_traces"":4321,""agent.trusted_account_ids"":[1,2,3],""agent.server_side_config_enabled"":true,""agent.ignore_server_side_config"":true,""agent.url_regex_rules"":[{""MatchExpression"":""matchUrl1"",""Replacement"":""replacementUrl1"",""Ignore"":true,""EvaluationOrder"":1,""TerminateChain"":true,""EachSegment"":true,""ReplaceAll"":true,""MatchRegex"":{""Pattern"":""matchUrl1"",""Options"":3}},{""MatchExpression"":""matchUrl2"",""Replacement"":""replacementUrl2"",""Ignore"":false,""EvaluationOrder"":2,""TerminateChain"":false,""EachSegment"":false,""ReplaceAll"":false,""MatchRegex"":{""Pattern"":""matchUrl2"",""Options"":3}}],""agent.request_path_exclusion_list"":[{""Pattern"":""asdf"",""Options"":0},{""Pattern"":""qwerty"",""Options"":1},{""Pattern"":""yolo"",""Options"":4}],""agent.web_transactions_apdex"":{""first"":1.0,""second"":2.0},""agent.wrapper_exception_limit"":1234,""utilization.detect_aws_enabled"":true,""utilization.detect_azure_enabled"":true,""utilization.detect_gcp_enabled"":true,""utilization.detect_pcf_enabled"":true,""utilization.detect_docker_enabled"":true,""utilization.detect_kubernetes_enabled"":true,""utilization.logical_processors"":22,""utilization.total_ram_mib"":33,""utilization.billing_host"":""UtilizationBillingHost"",""utilization.hostname"":""UtilizationHostName"",""utilization.full_hostname"":""UtilizationFullHostName"",""diagnostics.capture_agent_timing_enabled"":true,""diagnostics.capture_agent_timing_frequency"":1234,""agent.use_resource_based_naming_for_wcf_enabled"":true,""agent.event_listener_samplers_enabled"":true,""agent.sampling_target"":1234,""span_events.max_samples_stored"":4321,""agent.sampling_target_period_in_seconds"":1234,""agent.payload_success_metrics_enabled"":true,""agent.process_host_display_name"":""ProcessHostDisplayName"",""transaction_tracer.database_statement_cache_capacity"":1234,""agent.force_synchronous_timing_calculation_for_http_client"":true,""agent.exclude_new_relic_header"":true,""application_logging.enabled"":true,""application_logging.metrics.enabled"":true,""application_logging.forwarding.enabled"":true,""application_logging.forwarding.max_samples_stored"":1234,""application_logging.forwarding.log_level_denylist"":[""testlevel1, testlevel2""],""application_logging.harvest_cycle"":""00:20:34"",""application_logging.local_decorating.enabled"":true,""agent.app_domain_caching_disabled"":true,""agent.force_new_transaction_on_new_thread_enabled"":true,""agent.code_level_metrics_enabled"":true,""agent.app_settings"":{""hello"":""friend"",""we"":""made"",""it"":""to"",""the"":""end""},""application_logging.forwarding.context_data.enabled"":true,""application_logging.forwarding.context_data.include"":[""attr1"",""attr2""],""application_logging.forwarding.context_data.exclude"":[""attr1"",""attr2""],""metrics.harvest_cycle"":""00:01:00"",""transaction_traces.harvest_cycle"":""00:01:00"",""error_traces.harvest_cycle"":""00:01:00"",""get_agent_commands.cycle"":""00:01:00"",""default.harvest_cycle"":""00:01:00"",""sql_traces.harvest_cycle"":""00:01:00"",""update_loaded_modules.cycle"":""00:01:00"",""stackexchangeredis_cleanup.cycle"":""00:01:00""},""metadata"":{""hello"":""there""},""utilization"":{""metadata_version"":5,""logical_processors"":2,""total_ram_mib"":0,""hostname"":""myHost2"",""full_hostname"":""myHost2.domain.com"",""ip_address"":[""1.2.3.4"",""5.6.7.8""],""config"":{""hostname"":""my-host"",""logical_processors"":1,""total_ram_mib"":2048},""vendors"":{""aws"":{""availabilityZone"":""myZone"",""instanceId"":""myInstanceId"",""instanceType"":""myInstanceType""},""azure"":{""location"":""myLocation"",""name"":""myName"",""vmId"":""myVmId"",""vmSize"":""myVmSize""},""gcp"":{""id"":""myId"",""machineType"":""myMachineType"",""name"":""myName"",""zone"":""myZone""},""pcf"":{""cf_instance_guid"":""myInstanceGuid"",""cf_instance_ip"":""myInstanceIp"",""memory_limit"":""myMemoryLimit""},""kubernetes"":{""kubernetes_service_host"":""10.96.0.1""}}},""security_policies"":{""record_sql"":{""enabled"":false},""attributes_include"":{""enabled"":true},""allow_raw_exception_messages"":{""enabled"":false},""custom_events"":{""enabled"":true},""custom_parameters"":{""enabled"":false},""custom_instrumentation_editor"":{""enabled"":true}}}"; // Confirm that JsonIgnored properties are present, but not serialized Assert.NotNull(agentSettings.AgentLicenseKey); diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs index 6f1bfe0a5..b241ddc80 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs @@ -415,6 +415,8 @@ public class ExhaustiveTestConfiguration : IConfiguration public bool LogDecoratorEnabled => true; + public HashSet LogLevelDenyList => new HashSet { "testlevel1, testlevel2" } ; + public bool AppDomainCachingDisabled => true; public bool ForceNewTransactionOnNewThread => true; @@ -439,6 +441,10 @@ public class ExhaustiveTestConfiguration : IConfiguration public TimeSpan SqlTracesHarvestCycle => TimeSpan.FromMinutes(1); + public TimeSpan UpdateLoadedModulesCycle => TimeSpan.FromMinutes(1); + + public TimeSpan StackExchangeRedisCleanupCycle => TimeSpan.FromMinutes(1); + public IReadOnlyDictionary GetAppSettings() { return new Dictionary diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/HttpCollectorWireTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/HttpCollectorWireTests.cs index 434878d1d..396c8d559 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/HttpCollectorWireTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/HttpCollectorWireTests.cs @@ -15,6 +15,8 @@ using System.Threading.Tasks; using System.Threading; using NewRelic.Agent.Core.Exceptions; +using NewRelic.Agent.Core.Logging; +using Serilog; using Telerik.JustMock; namespace NewRelic.Agent.Core.DataTransport @@ -26,6 +28,7 @@ public class HttpCollectorWireTests private IAgentHealthReporter _agentHealthReporter; private IHttpClientFactory _httpClientFactory; private MockHttpMessageHandler _mockHttpMessageHandler; + private ILogger _mockILogger; [SetUp] public void SetUp() @@ -33,6 +36,9 @@ public void SetUp() _configuration = Mock.Create(); _agentHealthReporter = Mock.Create(); _httpClientFactory = Mock.Create(); + + _mockILogger = Mock.Create(); + Log.Logger = _mockILogger; } private HttpCollectorWire CreateHttpCollectorWire(Dictionary requestHeadersMap = null) @@ -306,6 +312,39 @@ public void SendData_ShouldDropPayload_WhenPayloadSizeExceedsMaxSize() Mock.Assert(() => _httpClientFactory.CreateClient(Arg.IsAny()), Occurs.Never()); Assert.AreEqual(false, _mockHttpMessageHandler.SendAsyncInvoked); } + + [TestCase(false)] + [TestCase(true)] + public void SendData_ShouldNotCallAuditLog_UnlessAuditLogIsEnabled(bool isEnabled) + { + // Arrange + AuditLog.ResetLazyLogger(); + Mock.Arrange(() => _configuration.AgentLicenseKey).Returns("license_key"); + Mock.Arrange(() => _configuration.CollectorMaxPayloadSizeInBytes).Returns(1024); + + var connectionInfo = new ConnectionInfo(_configuration); + var serializedData = "{ \"key\": \"value\" }"; + var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{}") + }; + + CreateMockHttpClient(httpResponse, false); + + var collectorWire = CreateHttpCollectorWire(); + + var mockForContextLogger = Mock.Create(); + Mock.Arrange(() => _mockILogger.ForContext(Arg.AnyString, Arg.AnyObject, false)) + .Returns(() => mockForContextLogger); + + AuditLog.IsAuditLogEnabled = isEnabled; + + // Act + var _ = collectorWire.SendData("test_method", connectionInfo, serializedData, Guid.NewGuid()); + + // Assert + Mock.Assert(() => mockForContextLogger.Fatal(Arg.AnyString), isEnabled ? Occurs.Exactly(3) : Occurs.Never()); + } } public class MockHttpMessageHandler : HttpMessageHandler diff --git a/tests/Agent/UnitTests/Core.UnitTest/DependencyInjection/AgentServicesTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DependencyInjection/AgentServicesTests.cs index f7fe069e8..59af8f9fa 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/DependencyInjection/AgentServicesTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/DependencyInjection/AgentServicesTests.cs @@ -41,7 +41,10 @@ public void AllServicesCanFullyResolve() using (var container = AgentServices.GetContainer()) { AgentServices.RegisterServices(container); - container.ReplaceRegistration(configurationService); + container.ReplaceInstanceRegistration(configurationService); +#if NET + container.ReplaceRegistrations(); // creates a new scope, registering the replacement instances from all .ReplaceRegistration() calls above +#endif Assert.DoesNotThrow(() => container.Resolve()); Assert.DoesNotThrow(() => AgentServices.StartServices(container)); diff --git a/tests/Agent/UnitTests/Core.UnitTest/JsonConverters/LoadedModuleWireModelCollectionJsonConverterTests.cs b/tests/Agent/UnitTests/Core.UnitTest/JsonConverters/LoadedModuleWireModelCollectionJsonConverterTests.cs new file mode 100644 index 000000000..6eaa178be --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/JsonConverters/LoadedModuleWireModelCollectionJsonConverterTests.cs @@ -0,0 +1,52 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Generic; +using Newtonsoft.Json; +using NUnit.Framework; +using NewRelic.Agent.Core.WireModels; +using System.Reflection; +using System; +using System.Text; + +namespace NewRelic.Agent.Core.Utilities +{ + [TestFixture] + public class LoadedModuleWireModelCollectionJsonConverterTests + { + private const string BaseAssemblyName = "MyTestAssembly"; + private const string BaseAssemblyVersion = "1.0.0"; + private const string BaseAssemblyPath = @"C:\path\to\assembly\MyTestAssembly.dll"; + private const string BaseCompanyName = "MyCompany"; + private const string BaseCopyrightValue = "Copyright 2008"; + private const int BaseHashCode = 42; + private const string BasePublicKeyToken = "publickeytoken"; + private const string BasePublicKey = "7075626C69636B6579746F6B656E"; + + [Test] + public void LoadedModuleWireModelCollectionIsJsonSerializable() + { + var expected = @"[""Jars"",[[""MyTestAssembly"",""1.0.0"",{""namespace"":""MyTestAssembly"",""publicKeyToken"":""7075626C69636B6579746F6B656E"",""assemblyHashCode"":""42"",""Implementation-Vendor"":""MyCompany"",""copyright"":""Copyright 2008""}]]]"; + + var baseAssemblyName = new AssemblyName(); + baseAssemblyName.Name = BaseAssemblyName; + baseAssemblyName.Version = new Version(BaseAssemblyVersion); + baseAssemblyName.SetPublicKeyToken(Encoding.ASCII.GetBytes(BasePublicKeyToken)); + + var baseTestAssembly = new TestAssembly(); + baseTestAssembly.SetAssemblyName = baseAssemblyName; + baseTestAssembly.SetDynamic = true; // false uses on disk assembly and this won'y have one. + baseTestAssembly.SetHashCode = BaseHashCode; + baseTestAssembly.SetLocation = BaseAssemblyPath; + baseTestAssembly.AddCustomAttribute(new AssemblyCompanyAttribute(BaseCompanyName)); + baseTestAssembly.AddCustomAttribute(new AssemblyCopyrightAttribute(BaseCopyrightValue)); + + var assemblies = new List(); + assemblies.Add(baseTestAssembly); + var loadedModules = LoadedModuleWireModelCollection.Build(assemblies); + + var serialized = JsonConvert.SerializeObject(new[] { loadedModules }, Formatting.None); + Assert.AreEqual(expected, serialized); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/JsonConverters/LogEventWireModelCollectionJsonConverterTests.cs b/tests/Agent/UnitTests/Core.UnitTest/JsonConverters/LogEventWireModelCollectionJsonConverterTests.cs index a3b9dec7d..dec30ec5f 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/JsonConverters/LogEventWireModelCollectionJsonConverterTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/JsonConverters/LogEventWireModelCollectionJsonConverterTests.cs @@ -5,11 +5,6 @@ using Newtonsoft.Json; using NUnit.Framework; using NewRelic.Agent.Core.WireModels; -using Google.Protobuf.WellKnownTypes; -using NUnit.Framework.Interfaces; -using static Grpc.Core.Metadata; -using System.Diagnostics; -using System.Web.Configuration; namespace NewRelic.Agent.Core.Utilities { diff --git a/tests/Agent/UnitTests/Core.UnitTest/Labels/LabelsServiceTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Labels/LabelsServiceTests.cs index ef8469270..8fc327ccc 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Labels/LabelsServiceTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Labels/LabelsServiceTests.cs @@ -264,33 +264,29 @@ public static IEnumerable CrossAgentTestCases } } - [TestCaseSource(nameof(CrossAgentTestCases))] + [TestCaseSource(nameof(CrossAgentTestCases))] public void cross_agent_tests(TestCase testCase) { - var logAppender = new log4net.Appender.MemoryAppender(); - var logFilter = new log4net.Filter.LevelMatchFilter(); - logFilter.LevelToMatch = log4net.Core.Level.Warn; - logAppender.AddFilter(logFilter); - var logger = (log4net.LogManager.GetRepository() as log4net.Repository.Hierarchy.Hierarchy).Root; - logger.AddAppender(logAppender); - logger.Level = log4net.Core.Level.Warn; - logger.Repository.Configured = true; + using (var logger = new TestUtilities.Logging()) + { - // arrange - var configurationService = Mock.Create(); - Mock.Arrange(() => configurationService.Configuration.Labels).Returns(testCase.LabelConfigurationString); + // arrange + var configurationService = Mock.Create(); + Mock.Arrange(() => configurationService.Configuration.Labels) + .Returns(testCase.LabelConfigurationString); - // act - var labelsService = new LabelsService(configurationService); - var actualResults = JsonConvert.SerializeObject(labelsService.Labels); - var expectedResults = JsonConvert.SerializeObject(testCase.Expected); + // act + var labelsService = new LabelsService(configurationService); + var actualResults = JsonConvert.SerializeObject(labelsService.Labels); + var expectedResults = JsonConvert.SerializeObject(testCase.Expected); - // assert - Assert.AreEqual(expectedResults, actualResults); - if (testCase.Warning) - Assert.AreNotEqual(0, logAppender.GetEvents().Length); - else - Assert.AreEqual(0, logAppender.GetEvents().Length); + // assert + Assert.AreEqual(expectedResults, actualResults); + if (testCase.Warning) + Assert.AreNotEqual(0, logger.WarnCount); + else + Assert.AreEqual(0, logger.MessageCount); + } } } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/Logging/AuditLogTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Logging/AuditLogTests.cs new file mode 100644 index 000000000..1a72e8345 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Logging/AuditLogTests.cs @@ -0,0 +1,70 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using NUnit.Framework; +using Serilog; +using Serilog.Configuration; +using Telerik.JustMock; + +namespace NewRelic.Agent.Core.Logging.Tests +{ + [TestFixture] + public class AuditLogTests + { + private ILogger _mockILogger; + + [SetUp] + public void SetUp() + { + _mockILogger = Mock.Create(); + Log.Logger = _mockILogger; + + // reset state for each test + AuditLog.ResetLazyLogger(); + AuditLog.IsAuditLogEnabled = false; + } + + [TearDown] + public void TearDown() + { + AuditLog.ResetLazyLogger(); + } + + [Test] + public void IncludeOnlyAuditLog_EnablesAuditLog() + { + Assert.False(AuditLog.IsAuditLogEnabled); + + var _ = new LoggerConfiguration().IncludeOnlyAuditLog(); + + Assert.True(AuditLog.IsAuditLogEnabled); + } + + [Test] + public void ExcludeAuditLog_DisablesAuditLog() + { + AuditLog.IsAuditLogEnabled = true; + + var _ = new LoggerConfiguration().ExcludeAuditLog(); + + Assert.False(AuditLog.IsAuditLogEnabled); + } + + [TestCase(true)] + [TestCase(false)] + public void Log_OnlyLogsWhenAuditLogEnabled(bool logEnabled) + { + var mockForContextLogger = Mock.Create(); + Mock.Arrange(() => _mockILogger.ForContext(Arg.AnyString, Arg.AnyObject, false)) + .Returns(() => mockForContextLogger); + + AuditLog.IsAuditLogEnabled = logEnabled; + + var message = "This is an audit message"; + + AuditLog.Log(message); + + Mock.Assert(() => mockForContextLogger.Fatal(message), logEnabled ? Occurs.Once() : Occurs.Never()); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Logging/InMemorySinkTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Logging/InMemorySinkTests.cs new file mode 100644 index 000000000..3dc9b1c98 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Logging/InMemorySinkTests.cs @@ -0,0 +1,58 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using NUnit.Framework; +using Serilog.Events; +using System; +using System.Linq; +using Serilog.Parsing; + +namespace NewRelic.Agent.Core.Logging.Tests +{ + [TestFixture] + public class InMemorySinkTests + { + private InMemorySink _sink; + private LogEvent _logEvent; + + [SetUp] + public void SetUp() + { + _sink = new InMemorySink(); + _logEvent = new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate("Test", Enumerable.Empty()), Enumerable.Empty()); + } + + [Test] + public void Emit_Enqueues_LogEvent() + { + _sink.Emit(_logEvent); + Assert.AreEqual(1, _sink.LogEvents.Count()); + } + + [Test] + public void LogEvents_ReturnsCorrectEvents() + { + _sink.Emit(_logEvent); + var logEvents = _sink.LogEvents; + + Assert.AreEqual(1, logEvents.Count()); + Assert.AreEqual(_logEvent, logEvents.First()); + } + + [Test] + public void Clear_LogEvents_EmptiesQueue() + { + _sink.Emit(_logEvent); + _sink.Clear(); + Assert.AreEqual(0, _sink.LogEvents.Count()); + } + + [Test] + public void Dispose_ClearsLogEvents() + { + _sink.Emit(_logEvent); + _sink.Dispose(); + Assert.AreEqual(0, _sink.LogEvents.Count()); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Logging/LogLevelExtensionsTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Logging/LogLevelExtensionsTests.cs new file mode 100644 index 000000000..5450103c6 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Logging/LogLevelExtensionsTests.cs @@ -0,0 +1,113 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using NUnit.Framework; +using Serilog.Events; +using Telerik.JustMock; +using ILogger = Serilog.ILogger; + +namespace NewRelic.Agent.Core.Logging.Tests +{ + [TestFixture] + public class LogLevelExtensionsTests + { + private ILogger _serilogLogger; + + [SetUp] + public void Setup() + { + _serilogLogger = Mock.Create(); + Serilog.Log.Logger = _serilogLogger; + } + [Test] + [TestCase("Alert", ExpectedResult = true)] + [TestCase("Critical", ExpectedResult = true)] + [TestCase("Emergency", ExpectedResult = true)] + [TestCase("Fatal", ExpectedResult = true)] + [TestCase("Finer", ExpectedResult = true)] + [TestCase("Trace", ExpectedResult = true)] + [TestCase("Notice", ExpectedResult = true)] + [TestCase("Severe", ExpectedResult = true)] + [TestCase("Verbose", ExpectedResult = true)] + [TestCase("Fine", ExpectedResult = true)] + [TestCase("Other", ExpectedResult = false)] + [TestCase("", ExpectedResult = false)] + [TestCase(null, ExpectedResult = false)] + public bool IsLogLevelDeprecated_ReturnsCorrectResult(string logLevel) + { + return logLevel.IsLogLevelDeprecated(); + } + + [Test] + [TestCase("Verbose", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Fine", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Finer", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Finest", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Trace", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("All", ExpectedResult = LogEventLevel.Verbose)] + [TestCase("Debug", ExpectedResult = LogEventLevel.Debug)] + [TestCase("Info", ExpectedResult = LogEventLevel.Information)] + [TestCase("Notice", ExpectedResult = LogEventLevel.Information)] + [TestCase("Warn", ExpectedResult = LogEventLevel.Warning)] + [TestCase("Alert", ExpectedResult = LogEventLevel.Warning)] + [TestCase("Error", ExpectedResult = LogEventLevel.Error)] + [TestCase("Critical", ExpectedResult = LogEventLevel.Error)] + [TestCase("Emergency", ExpectedResult = LogEventLevel.Error)] + [TestCase("Fatal", ExpectedResult = LogEventLevel.Error)] + [TestCase("Severe", ExpectedResult = LogEventLevel.Error)] + [TestCase("Off", ExpectedResult = (LogEventLevel)6)] + [TestCase(LogLevelExtensions.AuditLevel, ExpectedResult = LogEventLevel.Information)] + [TestCase("NonExistent", ExpectedResult = LogEventLevel.Information)] + public LogEventLevel MapToSerilogLogLevel_ReturnsCorrectResult(string configLogLevel) + { + return configLogLevel.MapToSerilogLogLevel(); + } + + [Test] + public void MapToSerilogLogLevel_LogsDeprecationWarning_IfLogLevelIsDeprecated() + { + string deprecatedLogLevel = "Severe"; + deprecatedLogLevel.MapToSerilogLogLevel(); + + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Once()); + } + + [Test] + public void MapToSerilogLogLevel_LogsWarning_IfLogLevelIsAudit() + { + string auditLevel = LogLevelExtensions.AuditLevel; + auditLevel.MapToSerilogLogLevel(); + + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Once()); + } + + [Test] + public void MapToSerilogLogLevel_LogsWarning_IfLogLevelIsInvalid() + { + string deprecatedLogLevel = "FOOBAR"; + deprecatedLogLevel.MapToSerilogLogLevel(); + + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Once()); + } + + [Test] + [TestCase(LogEventLevel.Verbose, ExpectedResult = "FINEST")] + [TestCase(LogEventLevel.Debug, ExpectedResult = "DEBUG")] + [TestCase(LogEventLevel.Information, ExpectedResult = "INFO")] + [TestCase(LogEventLevel.Warning, ExpectedResult = "WARN")] + [TestCase(LogEventLevel.Error, ExpectedResult = "ERROR")] + public string TranslateLogLevel_ReturnsCorrectResult(LogEventLevel logEventLevel) + { + return logEventLevel.TranslateLogLevel(); + } + + [Test] + public void TranslateLogLevel_Throws_IfLogEventLevelIsInvalid() + { + var invalidLogLevel = (LogEventLevel)9999; + + Assert.Throws(() => invalidLogLevel.TranslateLogLevel()); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs new file mode 100644 index 000000000..66bdee6cc --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs @@ -0,0 +1,247 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using NewRelic.Agent.Extensions.Logging; +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using Serilog; +using Serilog.Events; +using Telerik.JustMock; + +namespace NewRelic.Agent.Core.Logging.Tests +{ + [TestFixture] + public class LoggerTests + { + private Logger _logger; + private Serilog.ILogger _serilogLogger; + private string _testMessage; + private Exception _testException; + private string _testFormat; + private object[] _testArgs; + + [SetUp] + public void SetUp() + { + _serilogLogger = Mock.Create(); + Log.Logger = _serilogLogger; + _logger = new Logger(); + + _testMessage = "Test message"; + _testException = new Exception("Test exception"); + _testFormat = "Test format {0}"; + _testArgs = new object[] { "arg1" }; + } + + [Test] + [TestCase(Level.Finest, LogEventLevel.Verbose)] + [TestCase(Level.Debug, LogEventLevel.Debug)] + [TestCase(Level.Info, LogEventLevel.Information)] + [TestCase(Level.Warn, LogEventLevel.Warning)] + [TestCase(Level.Error, LogEventLevel.Error)] + public void IsEnabledFor_Level_ReturnsCorrectValue(Level level, LogEventLevel logEventLevel) + { + Mock.Arrange(() => _serilogLogger.IsEnabled(logEventLevel)).Returns(true); + + var result = _logger.IsEnabledFor(level); + + Assert.IsTrue(result); + } + + [Test] + public void IsEnabledFor_UnsupportedLevel_ReturnsFalse() + { + var result = _logger.IsEnabledFor((Level)9999); + + Assert.IsFalse(result); + } + + [Test] + [TestCase(Level.Finest, LogEventLevel.Verbose)] + [TestCase(Level.Debug, LogEventLevel.Debug)] + [TestCase(Level.Info, LogEventLevel.Information)] + [TestCase(Level.Warn, LogEventLevel.Warning)] + [TestCase(Level.Error, LogEventLevel.Error)] + public void Log_ValidLevel_CallsSerilogLogger(Level level, LogEventLevel logEventLevel) + { + string message = "Test message"; + Mock.Arrange(() => _serilogLogger.IsEnabled(logEventLevel)).Returns(true); + _logger.Log(level, message); + + switch (level) + { + case Level.Finest: + Mock.Assert(() => _serilogLogger.Verbose(message), Occurs.Once()); + break; + case Level.Debug: + Mock.Assert(() => _serilogLogger.Debug(message), Occurs.Once()); + break; + case Level.Info: + Mock.Assert(() => _serilogLogger.Information(message), Occurs.Once()); + break; + case Level.Warn: + Mock.Assert(() => _serilogLogger.Warning(message), Occurs.Once()); + break; + case Level.Error: + Mock.Assert(() => _serilogLogger.Error(message), Occurs.Once()); + break; + } + } + + [Test] + public void Log_UnsupportedLevel_NoSerilogCalls() + { + _logger.Log((Level)9999, _testMessage); + Mock.Assert(() => _serilogLogger.Verbose(Arg.AnyString), Occurs.Never()); + Mock.Assert(() => _serilogLogger.Debug(Arg.AnyString), Occurs.Never()); + Mock.Assert(() => _serilogLogger.Information(Arg.AnyString), Occurs.Never()); + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString), Occurs.Never()); + Mock.Assert(() => _serilogLogger.Error(Arg.AnyString), Occurs.Never()); + } + + + [Test] + public void IsErrorEnabled_ReturnsCorrectValue() + { + Mock.Arrange(() => _serilogLogger.IsEnabled(LogEventLevel.Error)).Returns(true); + + var result = _logger.IsErrorEnabled; + + Assert.IsTrue(result); + } + + [Test] + public void Error_LogsError() + { + string message = "Test Error"; + _logger.Error(message); + + Mock.Assert(() => _serilogLogger.Error(message), Occurs.Once()); + } + + [Test] + public void Error_Exception_LogsError() + { + var exception = new Exception("Test Exception"); + _logger.Error(exception); + + Mock.Assert(() => _serilogLogger.Error(exception, ""), Occurs.Once()); + } + + // Level methods + + [Test] + public void IsEnabledFor_ShouldCallSerilogLoggerIsEnabled() + { + _logger.IsEnabledFor(Level.Debug); + Mock.Assert(() => _serilogLogger.IsEnabled(Arg.IsAny()), Occurs.Once()); + } + + // Error methods + + [Test] + public void Error_Message_CallsSerilogLoggerError() + { + _logger.Error(_testMessage); + Mock.Assert(() => _serilogLogger.Error(_testMessage), Occurs.Once()); + } + + [Test] + public void Error_Exception_CallsSerilogLoggerError() + { + _logger.Error(_testException); + Mock.Assert(() => _serilogLogger.Error(_testException, Arg.AnyString), Occurs.Once()); + } + + [Test] + public void ErrorFormat_CallsSerilogLoggerError() + { + Mock.Arrange(() => _serilogLogger.IsEnabled(Arg.IsAny())).Returns(true); + + _logger.ErrorFormat(_testFormat, _testArgs); + Mock.Assert(() => _serilogLogger.Error(Arg.AnyString, Arg.IsAny()), Occurs.Once()); + } + + // Warn methods + + [Test] + public void Warn_Message_CallsSerilogLoggerWarning() + { + _logger.Warn(_testMessage); + Mock.Assert(() => _serilogLogger.Warning(_testMessage), Occurs.Once()); + } + + [Test] + public void Warn_Exception_CallsSerilogLoggerWarning() + { + _logger.Warn(_testException); + Mock.Assert(() => _serilogLogger.Warning(_testException, Arg.AnyString), Occurs.Once()); + } + + [Test] + public void WarnFormat_CallsSerilogLoggerWarning() + { + Mock.Arrange(() => _serilogLogger.IsEnabled(Arg.IsAny())).Returns(true); + + _logger.WarnFormat(_testFormat, _testArgs); + Mock.Assert(() => _serilogLogger.Warning(Arg.AnyString, Arg.IsAny()), Occurs.Once()); + } + + // Info methods + + [Test] + public void Info_Message_CallsSerilogLoggerInformation() + { + _logger.Info(_testMessage); + Mock.Assert(() => _serilogLogger.Information(_testMessage), Occurs.Once()); + } + + [Test] + public void Info_Exception_CallsSerilogLoggerInformation() + { + _logger.Info(_testException); + Mock.Assert(() => _serilogLogger.Information(_testException, Arg.AnyString), Occurs.Once()); + } + + [Test] + public void InfoFormat_CallsSerilogLoggerInformation() + { + Mock.Arrange(() => _serilogLogger.IsEnabled(Arg.IsAny())).Returns(true); + + _logger.InfoFormat(_testFormat, _testArgs); + Mock.Assert(() => _serilogLogger.Information(Arg.AnyString, Arg.IsAny()), Occurs.Once()); + } + + // Debug methods + + [Test] + public void Debug_Message_CallsSerilogLoggerDebug() + { + _logger.Debug(_testMessage); + Mock.Assert(() => _serilogLogger.Debug(_testMessage), Occurs.Once()); + } + + [Test] + public void Debug_Exception_CallsSerilogLoggerDebug() + { + _logger.Debug(_testException); + Mock.Assert(() => _serilogLogger.Debug(_testException, Arg.AnyString), Occurs.Once()); + } + + [Test] + public void DebugFormat_CallsSerilogLoggerDebug() + { + Mock.Arrange(() => _serilogLogger.IsEnabled(Arg.IsAny())).Returns(true); + + _logger.DebugFormat(_testFormat, _testArgs); + Mock.Assert(() => _serilogLogger.Debug(Arg.AnyString, Arg.IsAny()), Occurs.Once()); + } + [TearDown] + public void TearDown() + { + _logger = null; + _serilogLogger = null; + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Metrics/MetricNamesTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Metrics/MetricNamesTests.cs index c58eedc47..aea598eec 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Metrics/MetricNamesTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Metrics/MetricNamesTests.cs @@ -207,6 +207,15 @@ public static void MetricNamesTest_Events() Is.EqualTo("Supportability/Events/Customer/TryResizeReservoir")); } + [Test] + public static void MetricNamesTest_Logging() + { + Assert.That(MetricNames.GetLoggingMetricsLinesName(), Is.EqualTo("Logging/lines")); + Assert.That(MetricNames.GetLoggingMetricsDeniedName(), Is.EqualTo("Logging/denied")); + Assert.That(MetricNames.GetLoggingMetricsLinesBySeverityName("foo"), Is.EqualTo("Logging/lines/foo")); + Assert.That(MetricNames.GetLoggingMetricsDeniedBySeverityName("foo"), Is.EqualTo("Logging/denied/foo")); + } + [Test] public static void MetricNamesTest_AnalyticEvents() { diff --git a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.Config.FromLegacy/BootstrapConfigTest.cs b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.Config.FromLegacy/BootstrapConfigTest.cs index 5da055b3a..3fa33e2cb 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.Config.FromLegacy/BootstrapConfigTest.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.Config.FromLegacy/BootstrapConfigTest.cs @@ -27,8 +27,8 @@ public void TestInvalidServiceAttribute() ConfigurationLoader.InitializeFromXml(bogusConfigXml, configSchemaSource); var errorMessage = Type.GetType("Mono.Runtime") == null ? - "An error occurred parsing newrelic.config - The 'bogus' attribute is not declared." : - "An error occurred parsing newrelic.config - XmlSchema error: Attribute declaration was not found for bogus"; + "The 'bogus' attribute is not declared" : + "XmlSchema error: Attribute declaration was not found for bogus"; Assert.IsTrue(logging.HasMessageThatContains(errorMessage)); } } @@ -53,9 +53,7 @@ public void TestMissingOrEmptyConfigXsd() // While this error message is somewhat cryptic, in an actual agent run it would be // preceeded by a warning message regarding failure to read the schema file contents from disk - var errorMessage = Type.GetType("Mono.Runtime") == null ? - "An error occurred parsing newrelic.config - Root element is missing." : - "An error occurred parsing newrelic.config - Root element is missing."; + var errorMessage = "Root element is missing"; Assert.IsTrue(logging.HasMessageThatContains(errorMessage)); } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs deleted file mode 100644 index 3c80ac724..000000000 --- a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -using System; -using System.Reflection; -using System.Linq; -using System.Threading; -using NewRelic.Agent.Core.Config; -using NewRelic.Core.Logging; -using NUnit.Framework; -using log4net.Appender; -using log4net.Core; -using System.IO; - -namespace NewRelic.Agent.Core -{ - - [TestFixture] - public class LoggerBootstrapperTest - { - [Test] - public static void IsDebugEnabled_is_false_when_config_log_is_off() - { - ILogConfig config = GetLogConfig("off"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - Assert.IsFalse(Log.IsDebugEnabled); - } - - [Test] - public static void IsDebugEnabled_is_true_when_config_log_is_all() - { - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - Assert.That(Log.IsDebugEnabled); - } - - [Test] - public static void IsInfoEnabled_is_true_when_config_log_is_info() - { - ILogConfig config = GetLogConfig("info"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - Assert.That(Log.IsInfoEnabled); - } - - [Test] - public static void IsDebugEnabled_is_false_when_config_log_is_info() - { - ILogConfig config = GetLogConfig("info"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - Assert.IsFalse(Log.IsDebugEnabled); - } - - [Test] - public static void IsDebugEnabled_is_true_when_config_log_is_debug() - { - ILogConfig config = GetLogConfig("debug"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - Assert.That(Log.IsDebugEnabled); - } - - [Test] - public static void IsEnabledFor_finest_is_false_when_config_log_is_debug() - { - ILogConfig config = GetLogConfig("debug"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - Assert.IsFalse(Log.IsFinestEnabled); - - } - - [Test] - public static void ConsoleAppender_exists_and_has_correct_level_when_console_true_in_config() - { - ILogConfig config = LogConfigFixtureWithConsoleLogEnabled("debug"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - var logger = GetLogger(); - var consoleAppender = logger.Appenders.OfType().First(); - - Assert.IsFalse(Log.IsFinestEnabled); - Assert.That(logger.Level == Level.Debug); - // If the appender's threshold is null, it basically - // inherits the parent logger's level. - Assert.That(consoleAppender.Threshold == null); - } - - [Test] - public static void ConsoleAppender_does_not_exist_when_console_false_in_config() - { - ILogConfig config = GetLogConfig("debug"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - var logger = GetLogger(); - var consoleAppenders = logger.Appenders.OfType(); - - Assert.IsFalse(Log.IsFinestEnabled); - Assert.IsEmpty(consoleAppenders); - } - - [Test] - public static void Config_IsAuditEnabled_for_config_is_true_when_auditLog_true_in_config() - { - ILogConfig config = LogConfigFixtureWithAuditLogEnabled("debug"); - Assert.That(config.IsAuditLogEnabled); - } - - [Test] - public static void Config_IsAuditEnabled_for_config_is_false_when_not_added_to_config() - { - ILogConfig config = GetLogConfig("debug"); - Assert.IsFalse(config.IsAuditLogEnabled); - } - - [Test] - public static void Config_IsConsoleEnabled_for_config_is_true_when_console_true_in_config() - { - ILogConfig config = LogConfigFixtureWithConsoleLogEnabled("debug"); - Assert.That(config.Console); - } - - [Test] - public static void Config_IsConsoleEnabled_for_config_is_false_when_not_added_to_config() - { - ILogConfig config = GetLogConfig("debug"); - Assert.IsFalse(config.Console); - } - - [Test] - public static void Logging_sets_threadid_property_for_Info() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Info("Please set my thread id."); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Info_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Info(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_InfoFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.InfoFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Debug() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Debug("debug mah"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Debug_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Debug(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_DebugFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.DebugFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_ErrorFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.ErrorFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Error() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Error("debug mah"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Error_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Error(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_FinestFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.FinestFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Finest() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Finest("debug mah"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Finest_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Finest(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_WarnFormat() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.WarnFormat("My message {0}", "works"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Warn() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Warn("warn mah"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_Warn_Exception() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.Warn(new Exception("oh no!")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_LogMessage() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.LogMessage(LogLevel.Info, "Test Message"); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - [Test] - public static void Logging_sets_threadid_property_for_LogException() - { - log4net.ThreadContext.Properties["threadid"] = null; - - ILogConfig config = GetLogConfig("all"); - LoggerBootstrapper.Initialize(); - LoggerBootstrapper.ConfigureLogger(config); - - Log.LogException(LogLevel.Info, new Exception("Test exception")); - - Assert.AreEqual(log4net.ThreadContext.Properties["threadid"], Thread.CurrentThread.ManagedThreadId); - } - - - - static private ILogConfig GetLogConfig(string logLevel) - { - var xml = string.Format( - "" + - " " + - " " + - " Test" + - " " + - " " + - "", - logLevel); - - var xsdFile = Path.Combine(TestContext.CurrentContext.TestDirectory, "Configuration.xsd"); - Func configSchemaSource = () => File.ReadAllText(xsdFile); - - var configuration = ConfigurationLoader.InitializeFromXml(xml, configSchemaSource); - return configuration.LogConfig; - } - - static private ILogConfig LogConfigFixtureWithAuditLogEnabled(string logLevel) - { - var xml = string.Format( - "" + - " " + - " " + - " Test" + - " " + - " " + - "", - logLevel); - - var xsdFile = Path.Combine(TestContext.CurrentContext.TestDirectory, "Configuration.xsd"); - Func configSchemaSource = () => File.ReadAllText(xsdFile); - - var configuration = ConfigurationLoader.InitializeFromXml(xml, configSchemaSource); - return configuration.LogConfig; - } - - static private ILogConfig LogConfigFixtureWithConsoleLogEnabled(string logLevel) - { - var xml = string.Format( - "" + - " " + - " " + - " Test" + - " " + - " " + - "", - logLevel); - - var xsdFile = Path.Combine(TestContext.CurrentContext.TestDirectory, "Configuration.xsd"); - Func configSchemaSource = () => File.ReadAllText(xsdFile); - - var configuration = ConfigurationLoader.InitializeFromXml(xml, configSchemaSource); - return configuration.LogConfig; - } - - private static log4net.Repository.Hierarchy.Logger GetLogger() - { - var hierarchy = - log4net.LogManager.GetRepository(Assembly.GetCallingAssembly()) as - log4net.Repository.Hierarchy.Hierarchy; - var logger = hierarchy.Root; - return logger; - } - } -} diff --git a/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/LoggerBootstrapperTest.cs b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/LoggerBootstrapperTest.cs new file mode 100644 index 000000000..95dc4f1fd --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/LoggerBootstrapperTest.cs @@ -0,0 +1,171 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using NewRelic.Agent.Core.Config; +using NewRelic.Core.Logging; +using NUnit.Framework; +using System.IO; +using NewRelic.Testing.Assertions; + +namespace NewRelic.Agent.Core +{ + + [TestFixture] + public class LoggerBootstrapperTest + { + [Test] + public static void No_log_levels_are_enabled_when_config_log_is_off() + { + ILogConfig config = GetLogConfig("off"); + LoggerBootstrapper.Initialize(); + LoggerBootstrapper.ConfigureLogger(config); + NrAssert.Multiple( + () => Assert.IsFalse(Log.IsFinestEnabled), + () => Assert.IsFalse(Log.IsDebugEnabled), + () => Assert.IsFalse(Log.IsInfoEnabled), + () => Assert.IsFalse(Log.IsWarnEnabled), + () => Assert.IsFalse(Log.IsErrorEnabled) + ); + } + + [Test] + public static void All_log_levels_are_enabled_when_config_log_is_all() + { + ILogConfig config = GetLogConfig("all"); + LoggerBootstrapper.Initialize(); + LoggerBootstrapper.ConfigureLogger(config); + + NrAssert.Multiple( + () => Assert.IsTrue(Log.IsFinestEnabled), + () => Assert.IsTrue(Log.IsDebugEnabled), + () => Assert.IsTrue(Log.IsInfoEnabled), + () => Assert.IsTrue(Log.IsWarnEnabled), + () => Assert.IsTrue(Log.IsErrorEnabled) + ); + } + + [Test] + public static void IsInfoEnabled_is_true_when_config_log_is_info() + { + ILogConfig config = GetLogConfig("info"); + LoggerBootstrapper.Initialize(); + LoggerBootstrapper.ConfigureLogger(config); + + Assert.That(Log.IsInfoEnabled); + } + + [Test] + public static void IsDebugEnabled_is_false_when_config_log_is_info() + { + ILogConfig config = GetLogConfig("info"); + LoggerBootstrapper.Initialize(); + LoggerBootstrapper.ConfigureLogger(config); + Assert.IsFalse(Log.IsDebugEnabled); + } + + [Test] + public static void IsDebugEnabled_is_true_when_config_log_is_debug() + { + ILogConfig config = LogConfigFixtureWithConsoleLogEnabled("debug"); // just to increase code coverage + LoggerBootstrapper.Initialize(); + LoggerBootstrapper.ConfigureLogger(config); + Assert.That(Log.IsDebugEnabled); + } + + [Test] + public static void IsEnabledFor_finest_is_false_when_config_log_is_debug() + { + ILogConfig config = GetLogConfig("debug"); + LoggerBootstrapper.Initialize(); + LoggerBootstrapper.ConfigureLogger(config); + Assert.IsFalse(Log.IsFinestEnabled); + + } + + [Test] + public static void Config_IsAuditEnabled_for_config_is_true_when_auditLog_true_in_config() + { + ILogConfig config = LogConfigFixtureWithAuditLogEnabled("debug"); + Assert.That(config.IsAuditLogEnabled); + } + + [Test] + public static void Config_IsAuditEnabled_for_config_is_false_when_not_added_to_config() + { + ILogConfig config = GetLogConfig("debug"); + Assert.IsFalse(config.IsAuditLogEnabled); + } + + [Test] + public static void Config_IsConsoleEnabled_for_config_is_true_when_console_true_in_config() + { + ILogConfig config = LogConfigFixtureWithConsoleLogEnabled("debug"); + Assert.That(config.Console); + } + + [Test] + public static void Config_IsConsoleEnabled_for_config_is_false_when_not_added_to_config() + { + ILogConfig config = GetLogConfig("debug"); + Assert.IsFalse(config.Console); + } + + private static ILogConfig GetLogConfig(string logLevel) + { + var xml = string.Format( + "" + + " " + + " " + + " Test" + + " " + + " " + + "", + logLevel); + + var xsdFile = Path.Combine(TestContext.CurrentContext.TestDirectory, "Configuration.xsd"); + Func configSchemaSource = () => File.ReadAllText(xsdFile); + + var configuration = ConfigurationLoader.InitializeFromXml(xml, configSchemaSource); + return configuration.LogConfig; + } + + private static ILogConfig LogConfigFixtureWithAuditLogEnabled(string logLevel) + { + var xml = string.Format( + "" + + " " + + " " + + " Test" + + " " + + " " + + "", + logLevel); + + var xsdFile = Path.Combine(TestContext.CurrentContext.TestDirectory, "Configuration.xsd"); + Func configSchemaSource = () => File.ReadAllText(xsdFile); + + var configuration = ConfigurationLoader.InitializeFromXml(xml, configSchemaSource); + return configuration.LogConfig; + } + + private static ILogConfig LogConfigFixtureWithConsoleLogEnabled(string logLevel) + { + var xml = string.Format( + "" + + " " + + " " + + " Test" + + " " + + " " + + "", + logLevel); + + var xsdFile = Path.Combine(TestContext.CurrentContext.TestDirectory, "Configuration.xsd"); + Func configSchemaSource = () => File.ReadAllText(xsdFile); + + var configuration = ConfigurationLoader.InitializeFromXml(xml, configSchemaSource); + return configuration.LogConfig; + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Spans/GrpcWrapperTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Spans/GrpcWrapperTests.cs index dfa541ad0..f0cc15115 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Spans/GrpcWrapperTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Spans/GrpcWrapperTests.cs @@ -7,7 +7,12 @@ using NUnit.Framework; using NewRelic.Agent.Core.DataTransport; using System.Threading.Tasks; + +#if NETFRAMEWORK using GrpcChannel = Grpc.Core.Channel; +#else +using Grpc.Net.Client; +#endif namespace NewRelic.Agent.Core.GrpcWrapper.Tests { @@ -75,13 +80,13 @@ public FakeGrpcWrapper(bool connectShouldSucceed, Exception ex) _ex = ex; } - protected override AsyncDuplexStreamingCall CreateStreamsImpl(Channel channel, Metadata headers, int connectTimeoutMs, CancellationToken cancellationToken) + protected override AsyncDuplexStreamingCall CreateStreamsImpl(GrpcChannel channel, Metadata headers, int connectTimeoutMs, CancellationToken cancellationToken) { if (_ex != null) { throw _ex; } - + return new AsyncDuplexStreamingCall(new FakeGrpcStreamWriter(), new FakeGrpcStreamReader(), null, null, null, (o) => { }, null); } @@ -102,6 +107,7 @@ internal interface FakeGrpcResponse { } +#if NETFRAMEWORK // CreateChannel doesn't actually create a channel under grpc-dotnet (NETSTANDARD2.0), so we can't test it [Test] public void TestCreateChannel() { @@ -125,6 +131,7 @@ public void TestCreateChannel() Assert.IsFalse(grpc.CreateChannel("localhost", 0, false, null, 0, new CancellationToken())); Assert.IsFalse(grpc.IsConnected); } +#endif [Test] public void TestCreateStreams() diff --git a/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling.FromLegacy/ThreadProfilingBucketTest.cs b/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling.FromLegacy/ThreadProfilingBucketTest.cs index 705f7bd4b..1cf456b85 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling.FromLegacy/ThreadProfilingBucketTest.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling.FromLegacy/ThreadProfilingBucketTest.cs @@ -61,7 +61,7 @@ public void verify_UpdateTree_handles_null_StackInfo_argument() { ThreadProfilingBucket bucket = new ThreadProfilingBucket(new MockThreadProfilingService()); bucket.UpdateTree(null); - Assert.IsTrue(logging.HasMessageBeginingWith("fids passed to UpdateTree is null")); + Assert.IsTrue(logging.HasMessageBeginningWith("fids passed to UpdateTree is null")); } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling/ThreadProfilingServiceTests.cs b/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling/ThreadProfilingServiceTests.cs index 8d83a20fa..6f1273a39 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling/ThreadProfilingServiceTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/ThreadProfiling/ThreadProfilingServiceTests.cs @@ -158,7 +158,7 @@ public void FullCycleTest_IsSuccessful() Assert.AreEqual(0, (actualModels[0].Samples["OTHER"] as ProfileNodes).Count); // Teardown - Marshal.Release(fidGizmoIntPtr); + Marshal.FreeHGlobal(fidGizmoIntPtr); } [Test] diff --git a/tests/Agent/UnitTests/Core.UnitTest/Time/SimpleSchedulingServiceTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Time/SimpleSchedulingServiceTests.cs new file mode 100644 index 000000000..37f195be5 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Time/SimpleSchedulingServiceTests.cs @@ -0,0 +1,81 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace NewRelic.Agent.Core.Time +{ + public class SimpleSchedulingServiceTests + { + private Scheduler _scheduler; + private SimpleSchedulingService _simpleSchedulingService; + + [SetUp] + public void SetUp() + { + _scheduler = new Scheduler(); + _simpleSchedulingService = new SimpleSchedulingService(_scheduler); + } + + [TearDown] + public void TearDown() + { + _simpleSchedulingService.Dispose(); + _scheduler.Dispose(); + } + + [Test] + public void StartAndStopTest() + { + _simpleSchedulingService.StartExecuteEvery(DoWork, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + _simpleSchedulingService.StartExecuteEvery(MoreWork, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + + var sssFieldType = typeof(SimpleSchedulingService).GetField("_executingActions", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var value = sssFieldType.GetValue(_simpleSchedulingService) as List; + + Assert.That(value.Count, Is.EqualTo(2)); + + var action = value.FirstOrDefault(a => a.Method.Name == "DoWork"); + + Assert.NotNull(action); + + _simpleSchedulingService.StopExecuting(DoWork); + + var noAction = value.FirstOrDefault(a => a.Method.Name == "DoWork"); + + Assert.Null(noAction); + Assert.That(value.Count, Is.EqualTo(1)); + } + + [Test] + public void DisposeTest() + { + _simpleSchedulingService.StartExecuteEvery(DoWork, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + + var sssFieldType = typeof(SimpleSchedulingService).GetField("_executingActions", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var value = sssFieldType.GetValue(_simpleSchedulingService) as List; + var action = value.FirstOrDefault(a => a.Method.Name == "DoWork"); + + Assert.NotNull(action); + + _simpleSchedulingService.Dispose(); + + var noAction = value.FirstOrDefault(a => a.Method.Name == "DoWork"); + + Assert.Null(noAction); + } + + private void DoWork() + { + + } + + private void MoreWork() + { + + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/ErrorTraceMakerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/ErrorTraceMakerTests.cs index 2a38c1ada..7b88c7574 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/ErrorTraceMakerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/ErrorTraceMakerTests.cs @@ -86,7 +86,11 @@ public void GetErrorTrace_ReturnsErrorTrace_IfStatusCodeIs404() Assert.NotNull(errorTrace); NrAssert.Multiple( () => Assert.AreEqual("WebTransaction/Name", errorTrace.Path), +#if NET + () => Assert.AreEqual("404", errorTrace.Message), +#else () => Assert.AreEqual("Not Found", errorTrace.Message), +#endif () => Assert.AreEqual("404", errorTrace.ExceptionClassName), () => Assert.AreEqual(transaction.Guid, errorTrace.Guid), () => Assert.AreEqual(null, errorTrace.Attributes.StackTrace) diff --git a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionAttributeMakerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionAttributeMakerTests.cs index fa9ffdb9c..44b0210ef 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionAttributeMakerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionAttributeMakerTests.cs @@ -417,8 +417,13 @@ string GetHeaderValue(Dictionary headers, string key) () => Assert.AreEqual("pathHash", GetAttributeValue(transactionAttributes, "nr.alternatePathHashes")), () => Assert.AreEqual("400", GetAttributeValue(transactionAttributes, "error.class")), () => Assert.AreEqual("400", GetAttributeValue(transactionAttributes, "errorType")), +#if NET + () => Assert.AreEqual("400", GetAttributeValue(transactionAttributes, "errorMessage")), + () => Assert.AreEqual("400", GetAttributeValue(transactionAttributes, "error.message")), +#else () => Assert.AreEqual("Bad Request", GetAttributeValue(transactionAttributes, "errorMessage")), () => Assert.AreEqual("Bad Request", GetAttributeValue(transactionAttributes, "error.message")), +#endif () => Assert.AreEqual(true, GetAttributeValue(transactionAttributes, "error")), () => Assert.True(DoAttributesContain(transactionAttributes, "host.displayName")), () => Assert.AreEqual("value1", GetAttributeValue(transactionAttributes, "request.headers.key1")), diff --git a/tests/Agent/UnitTests/Core.UnitTest/Utilities/UpdatedLoadedModulesServiceTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Utilities/UpdatedLoadedModulesServiceTests.cs new file mode 100644 index 000000000..0354a8e8d --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Utilities/UpdatedLoadedModulesServiceTests.cs @@ -0,0 +1,121 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using NewRelic.Agent.Configuration; +using NewRelic.Agent.Core.DataTransport; +using NewRelic.Agent.Core.Events; +using NewRelic.Agent.Core.Fixtures; +using NewRelic.Agent.Core.Time; +using NewRelic.Agent.Core.WireModels; +using NUnit.Framework; +using Telerik.JustMock; +using Telerik.JustMock.Helpers; + +namespace NewRelic.Agent.Core.Utilities +{ + [TestFixture] + public class UpdatedLoadedModulesServiceTests + { + private IDataTransportService _dataTransportService; + private UpdatedLoadedModulesService _updatedLoadedModulesService; + + private Action _getLoadedModulesAction; + private ConfigurationAutoResponder _configurationAutoResponder; + private TimeSpan? _harvestCycle; + + [SetUp] + public void SetUp() + { + var configuration = Mock.Create(); + Mock.Arrange(() => configuration.CollectorSendDataOnExit).Returns(true); + Mock.Arrange(() => configuration.CollectorSendDataOnExitThreshold).Returns(0); + Mock.Arrange(() => configuration.UpdateLoadedModulesCycle).Returns(TimeSpan.FromMinutes(1)); + _configurationAutoResponder = new ConfigurationAutoResponder(configuration); + + var configurationService = Mock.Create(); + Mock.Arrange(() => configurationService.Configuration).Returns(configuration); + + _dataTransportService = Mock.Create(); + + var scheduler = Mock.Create(); + + Mock.Arrange(() => scheduler.ExecuteEvery(Arg.IsAny(), Arg.IsAny(), Arg.IsAny())) + .DoInstead((action, harvestCycle, __) => { _getLoadedModulesAction = action; _harvestCycle = harvestCycle; }); + + _updatedLoadedModulesService = new UpdatedLoadedModulesService(scheduler, _dataTransportService, configurationService); + + EventBus.Publish(new AgentConnectedEvent()); + } + + [TearDown] + public void TearDown() + { + _updatedLoadedModulesService.Dispose(); + _configurationAutoResponder.Dispose(); + } + + [Test] + public void GetLoadedModules_SendsModules() + { + LoadedModuleWireModelCollection loadedModulesCollection = (LoadedModuleWireModelCollection)null; + Mock.Arrange(() => _dataTransportService.Send(Arg.IsAny())) + .DoInstead(modules => loadedModulesCollection = modules) + .Returns(DataTransportResponseStatus.RequestSuccessful); + + _getLoadedModulesAction(); + + var loadedModules = loadedModulesCollection.LoadedModules; + + Assert.NotNull(loadedModulesCollection); + Assert.IsTrue(loadedModules.Count > 0); + } + + [Test] + public void GetLoadedModules_NoNewModules() + { + LoadedModuleWireModelCollection loadedModulesCollection = (LoadedModuleWireModelCollection)null; + Mock.Arrange(() => _dataTransportService.Send(Arg.IsAny())) + .DoInstead(modules => loadedModulesCollection = modules) + .Returns(DataTransportResponseStatus.RequestSuccessful); + + _getLoadedModulesAction(); + + var initialModules = loadedModulesCollection.LoadedModules; + + // double sure that no new modules are loaded. + _getLoadedModulesAction(); + + _ = loadedModulesCollection.LoadedModules; + + _getLoadedModulesAction(); + + var loadedModules = loadedModulesCollection.LoadedModules; + + Assert.AreEqual(initialModules.Count, loadedModules.Count); + } + + [Test] + public void GetLoadedModules_SendError_DuplciatesNotSaved() + { + LoadedModuleWireModelCollection loadedModulesCollection = (LoadedModuleWireModelCollection)null; + var result = Mock.Arrange(() => _dataTransportService.Send(Arg.IsAny())) + .DoInstead(modules => loadedModulesCollection = modules) + .Returns(DataTransportResponseStatus.Discard); + + _getLoadedModulesAction(); + + var initialModules = loadedModulesCollection.LoadedModules; + + _getLoadedModulesAction(); + + var loadedModules = loadedModulesCollection.LoadedModules; + + Assert.Greater(initialModules.Count, 0); + Assert.Greater(loadedModules.Count, 0); + Assert.AreEqual(initialModules.Count, loadedModules.Count); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/WireModels/LoadedModuleWireModelCollectionTests.cs b/tests/Agent/UnitTests/Core.UnitTest/WireModels/LoadedModuleWireModelCollectionTests.cs new file mode 100644 index 000000000..2023c13c4 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/WireModels/LoadedModuleWireModelCollectionTests.cs @@ -0,0 +1,358 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using NewRelic.Agent.Core.Metrics; +using NewRelic.Collections; +using NUnit.Framework; +using Telerik.JustMock; +using static Google.Protobuf.Reflection.SourceCodeInfo.Types; + +namespace NewRelic.Agent.Core.WireModels +{ + [TestFixture] + public class LoadedModuleWireModelCollectionTests + { + private const string BaseAssemblyName = "MyTestAssembly"; + private const string BaseAssemblyVersion = "1.0.0"; + private const string BaseAssemblyPath = @"C:\path\to\assembly\MyTestAssembly.dll"; + private const string BaseCompanyName = "MyCompany"; + private const string BaseCopyrightValue = "Copyright 2008"; + private const int BaseHashCode = 42; + private const string BasePublicKeyToken = "publickeytoken"; + private const string BasePublicKey = "7075626C69636B6579746F6B656E"; + + private AssemblyName _baseAssemblyName; + private TestAssembly _baseTestAssembly; + + [SetUp] + public void SetUp() + { + _baseAssemblyName = new AssemblyName(); + _baseAssemblyName.Name = BaseAssemblyName; + _baseAssemblyName.Version = new Version(BaseAssemblyVersion); + _baseAssemblyName.SetPublicKeyToken(Encoding.ASCII.GetBytes(BasePublicKeyToken)); + + _baseTestAssembly = new TestAssembly(); + _baseTestAssembly.SetAssemblyName = _baseAssemblyName; + _baseTestAssembly.SetDynamic = true; // false uses on disk assembly and this won'y have one. + _baseTestAssembly.SetHashCode = BaseHashCode; + _baseTestAssembly.SetLocation = BaseAssemblyPath; + _baseTestAssembly.AddCustomAttribute(new AssemblyCompanyAttribute(BaseCompanyName)); + _baseTestAssembly.AddCustomAttribute(new AssemblyCopyrightAttribute(BaseCopyrightValue)); + } + + [TearDown] public void TearDown() + { + _baseAssemblyName = null; + _baseTestAssembly= null; + } + + [TestCase(BaseAssemblyName, true, ExpectedResult = 1)] + [TestCase(BaseAssemblyName, false, ExpectedResult = 1)] + [TestCase(null, true, ExpectedResult = 0)] + [TestCase(null, false, ExpectedResult = 0)] + public int TryGetAssemblyName_UsingCollectionCount(string assemblyName, bool isDynamic) + { + _baseAssemblyName.Name = assemblyName; + if (string.IsNullOrWhiteSpace(assemblyName)) + { + _baseTestAssembly.SetLocation = null; + } + + _baseTestAssembly.SetAssemblyName = _baseAssemblyName; + _baseTestAssembly.SetDynamic = isDynamic; + + var assemblies = new List(); + assemblies.Add(_baseTestAssembly); + + var loadedModules = LoadedModuleWireModelCollection.Build(assemblies); + + return loadedModules.LoadedModules.Count; + } + + [Test] + public void ValidateAllData() + { + var assemblies = new List(); + assemblies.Add(_baseTestAssembly); + + var loadedModules = LoadedModuleWireModelCollection.Build(assemblies); + + Assert.AreEqual(1, loadedModules.LoadedModules.Count); + + var loadedModule = loadedModules.LoadedModules[0]; + + Assert.AreEqual(BaseAssemblyName, loadedModule.AssemblyName); + Assert.AreEqual(BaseAssemblyVersion, loadedModule.Version); + Assert.AreEqual(BaseAssemblyName, loadedModule.Data["namespace"]); + Assert.AreEqual(BaseHashCode.ToString(), loadedModule.Data["assemblyHashCode"]); + Assert.AreEqual(BasePublicKey, loadedModule.Data["publicKeyToken"]); + Assert.AreEqual(BaseCompanyName, loadedModule.Data["Implementation-Vendor"]); + Assert.AreEqual(BaseCopyrightValue, loadedModule.Data["copyright"]); + Assert.False(loadedModule.Data.ContainsKey("sha1Checksum")); + Assert.False(loadedModule.Data.ContainsKey("sha512Checksum")); + } + + [Test] + public void ErrorsHandled_TryGetAssemblyName_GetName() + { + var evilAssembly = new EvilTestAssembly(_baseTestAssembly); + evilAssembly.ItemToTest = "GetName"; + + var assemblies = new List(); + assemblies.Add(evilAssembly); + + var loadedModules = LoadedModuleWireModelCollection.Build(assemblies); + + Assert.AreEqual(0, loadedModules.LoadedModules.Count); + } + + [Test] + public void ErrorsHandled_TryGetAssemblyName_IsDynamic() + { + var evilAssembly = new EvilTestAssembly(_baseTestAssembly); + evilAssembly.ItemToTest = "IsDynamic"; + + var assemblies = new List(); + assemblies.Add(evilAssembly); + + var loadedModules = LoadedModuleWireModelCollection.Build(assemblies); + + Assert.AreEqual(0, loadedModules.LoadedModules.Count); + } + + [Test] + public void ErrorsHandled_TryGetAssemblyHashCode() + { + var evilAssembly = new EvilTestAssembly(_baseTestAssembly); + evilAssembly.ItemToTest = "GetHashCode"; + + var assemblies = new List(); + assemblies.Add(evilAssembly); + + var loadedModules = LoadedModuleWireModelCollection.Build(assemblies); + + Assert.AreEqual(1, loadedModules.LoadedModules.Count); + + var loadedModule = loadedModules.LoadedModules[0]; + + Assert.AreEqual(BaseAssemblyName, loadedModule.AssemblyName); + Assert.AreEqual(BaseAssemblyVersion, loadedModule.Version); + Assert.AreEqual(BaseAssemblyName, loadedModule.Data["namespace"]); + Assert.False(loadedModule.Data.ContainsKey("assemblyHashCode")); + Assert.AreEqual(BasePublicKey, loadedModule.Data["publicKeyToken"]); + Assert.AreEqual(BaseCompanyName, loadedModule.Data["Implementation-Vendor"]); + Assert.AreEqual(BaseCopyrightValue, loadedModule.Data["copyright"]); + Assert.False(loadedModule.Data.ContainsKey("sha1Checksum")); + Assert.False(loadedModule.Data.ContainsKey("sha512Checksum")); + } + + [Test] + public void ErrorsHandled_TryGetAssemblyName_Location() + { + _baseTestAssembly.SetDynamic = false; + var evilAssembly = new EvilTestAssembly(_baseTestAssembly); + evilAssembly.ItemToTest = "Location"; + + var assemblies = new List(); + assemblies.Add(evilAssembly); + + var loadedModules = LoadedModuleWireModelCollection.Build(assemblies); + + Assert.AreEqual(0, loadedModules.LoadedModules.Count); + } + + [Test] + public void ErrorsHandled_GetCustomAttributes() + { + var evilAssembly = new EvilTestAssembly(_baseTestAssembly); + evilAssembly.ItemToTest = "GetCustomAttributes"; + + var assemblies = new List(); + assemblies.Add(evilAssembly); + + var loadedModules = LoadedModuleWireModelCollection.Build(assemblies); + + Assert.AreEqual(1, loadedModules.LoadedModules.Count); + + var loadedModule = loadedModules.LoadedModules[0]; + + Assert.AreEqual(BaseAssemblyName, loadedModule.AssemblyName); + Assert.AreEqual(BaseAssemblyVersion, loadedModule.Version); + Assert.AreEqual(BaseAssemblyName, loadedModule.Data["namespace"]); + Assert.AreEqual(BaseHashCode.ToString(), loadedModule.Data["assemblyHashCode"]); + Assert.AreEqual(BasePublicKey, loadedModule.Data["publicKeyToken"]); + Assert.False(loadedModule.Data.ContainsKey("Implementation-Vendor")); + Assert.False(loadedModule.Data.ContainsKey("copyright")); + Assert.False(loadedModule.Data.ContainsKey("sha1Checksum")); + Assert.False(loadedModule.Data.ContainsKey("sha512Checksum")); + } + + [Test] + public void ErrorsHandled_PublickeyToken() + { + var evilAssembly = new EvilTestAssembly(_baseTestAssembly); + evilAssembly.GetName().SetPublicKeyToken(null); + + var assemblies = new List(); + assemblies.Add(evilAssembly); + + var loadedModules = LoadedModuleWireModelCollection.Build(assemblies); + + Assert.AreEqual(1, loadedModules.LoadedModules.Count); + + var loadedModule = loadedModules.LoadedModules[0]; + + Assert.AreEqual(BaseAssemblyName, loadedModule.AssemblyName); + Assert.AreEqual(BaseAssemblyVersion, loadedModule.Version); + Assert.AreEqual(BaseAssemblyName, loadedModule.Data["namespace"]); + Assert.AreEqual(BaseHashCode.ToString(), loadedModule.Data["assemblyHashCode"]); + Assert.False(loadedModule.Data.ContainsKey("publicKeyToken")); + Assert.AreEqual(BaseCompanyName, loadedModule.Data["Implementation-Vendor"]); + Assert.AreEqual(BaseCopyrightValue, loadedModule.Data["copyright"]); + Assert.False(loadedModule.Data.ContainsKey("sha1Checksum")); + Assert.False(loadedModule.Data.ContainsKey("sha512Checksum")); + } + } + + public class TestAssembly : Assembly + { + private bool _isDynamic; + + private AssemblyName _assemblyName; + + private int _hashCode; + + private string _location; + + private List _customAttributes = new List(); + + public AssemblyName SetAssemblyName + { + set { _assemblyName = value; } + } + + public override AssemblyName GetName() + { + return _assemblyName; + } + + public override bool IsDynamic => _isDynamic; + + public bool SetDynamic + { + set { _isDynamic = value; } + } + + public int SetHashCode + { + set { _hashCode = value; } + } + + public override int GetHashCode() + { + return _hashCode; + } + + public string SetLocation + { + set { _location = value; } + } + + public override string Location => _location; + + public void AddCustomAttribute(object attribute) + { + _customAttributes.Add(attribute); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + var objects = new List(); + + foreach (var attribute in _customAttributes) + { + if (attribute.GetType() == attributeType) + { + objects.Add(attribute); + } + } + + return objects.ToArray(); + } + } + + public class EvilTestAssembly : Assembly + { + private Assembly _assembly; + + public string ItemToTest; + + public EvilTestAssembly(Assembly assembly) + { + _assembly= assembly; + } + + public override AssemblyName GetName() + { + if (ItemToTest != "GetName") + { + return _assembly.GetName(); + } + + throw new Exception(); + } + + public override bool IsDynamic + { + get + { + if (ItemToTest != "IsDynamic") + { + return _assembly.IsDynamic; + } + + throw new Exception(); + } + } + + public override int GetHashCode() + { + if (ItemToTest != "GetHashCode") + { + return _assembly.GetHashCode(); + } + + throw new Exception(); + } + + public override string Location + { + get + { + if (ItemToTest != "Location") + { + return _assembly.Location; + } + + throw new Exception(); + } + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + if (ItemToTest != "GetCustomAttributes") + { + return _assembly.GetCustomAttributes(attributeType, inherit); + } + + throw new Exception(); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/WireModels/LoadedModuleWireModelTests.cs b/tests/Agent/UnitTests/Core.UnitTest/WireModels/LoadedModuleWireModelTests.cs new file mode 100644 index 000000000..0082a9692 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/WireModels/LoadedModuleWireModelTests.cs @@ -0,0 +1,30 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace NewRelic.Agent.Core.WireModels +{ + [TestFixture] + public class LoadedModuleWireModelTests + { + [Test] + public void ConstructorTest() + { + var assemblyName = "My.Assembly"; + var version = "1.0.0"; + var objectUnderTest = new LoadedModuleWireModel(assemblyName, version); + + Assert.NotNull(objectUnderTest); + Assert.AreEqual(assemblyName, objectUnderTest.AssemblyName); + Assert.AreEqual(version, objectUnderTest.Version); + Assert.NotNull(objectUnderTest.Data); + Assert.AreEqual(0, objectUnderTest.Data.Count); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs index 3864bd382..cf380cf21 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs @@ -36,7 +36,6 @@ using System.Threading; using Telerik.JustMock; using NewRelic.Agent.Api.Experimental; -using NewRelic.Agent.Core.Spans; using NewRelic.Agent.TestUtilities; using NewRelic.Agent.Core.Aggregators; using NewRelic.Agent.Core.WireModels; @@ -45,7 +44,6 @@ using NewRelic.Agent.Core.DataTransport; using NewRelic.Collections; using NewRelic.Agent.Core.Utils; -using NewRelic.Agent.Helpers; namespace NewRelic.Agent.Core.Wrapper.AgentWrapperApi { @@ -81,7 +79,7 @@ public class AgentWrapperApiTests private IConfigurationService _configurationService; private IAgentHealthReporter _agentHealthReporter; - + private ISimpleSchedulingService _simpleSchedulingService; private ITraceMetadataFactory _traceMetadataFactory; private ICATSupportabilityMetricCounters _catMetrics; private IThreadPoolStatic _threadPoolStatic; @@ -143,7 +141,7 @@ public void SetUp() _agentTimerService = Mock.Create(); _metricNameService = new MetricNameService(); _catMetrics = Mock.Create(); - + _simpleSchedulingService = Mock.Create(); _distributedTracePayloadHandler = Mock.Create(); _traceMetadataFactory = Mock.Create(); _errorService = new ErrorService(_configurationService); @@ -155,7 +153,7 @@ public void SetUp() _logEventAggregator = new LogEventAggregator(Mock.Create(), scheduler, Mock.Create(), _agentHealthReporter); _logContextDataFilter = new LogContextDataFilter(_configurationService); - _agent = new Agent(_transactionService, _transactionTransformer, _threadPoolStatic, _transactionMetricNameMaker, _pathHashMaker, _catHeaderHandler, _distributedTracePayloadHandler, _syntheticsHeaderHandler, _transactionFinalizer, _browserMonitoringPrereqChecker, _browserMonitoringScriptMaker, _configurationService, _agentHealthReporter, _agentTimerService, _metricNameService, _traceMetadataFactory, _catMetrics, _logEventAggregator, _logContextDataFilter); + _agent = new Agent(_transactionService, _transactionTransformer, _threadPoolStatic, _transactionMetricNameMaker, _pathHashMaker, _catHeaderHandler, _distributedTracePayloadHandler, _syntheticsHeaderHandler, _transactionFinalizer, _browserMonitoringPrereqChecker, _browserMonitoringScriptMaker, _configurationService, _agentHealthReporter, _agentTimerService, _metricNameService, _traceMetadataFactory, _catMetrics, _logEventAggregator, _logContextDataFilter, _simpleSchedulingService); } private class CallStackManagerFactory : ICallStackManagerFactory @@ -225,7 +223,7 @@ public void EndTransaction_ShouldNotLogResponseTimeAlreadyCaptured() { _agent.CurrentTransaction.End(); - var foundResponseTimeAlreadyCapturedMessage = logging.HasMessageBeginingWith("Transaction has already captured the response time."); + var foundResponseTimeAlreadyCapturedMessage = logging.HasMessageBeginningWith("Transaction has already captured the response time."); Assert.False(foundResponseTimeAlreadyCapturedMessage); } } @@ -613,7 +611,7 @@ public void AcceptDistributedTraceHeaders_DoesNotThrow_IfContentLengthIsNull() public void AcceptDistributedTraceHeaders__ReportsSupportabilityMetric_NullPayload() { _distributedTracePayloadHandler = new DistributedTracePayloadHandler(_configurationService, _agentHealthReporter, new AdaptiveSampler()); - _agent = new Agent(_transactionService, _transactionTransformer, _threadPoolStatic, _transactionMetricNameMaker, _pathHashMaker, _catHeaderHandler, _distributedTracePayloadHandler, _syntheticsHeaderHandler, _transactionFinalizer, _browserMonitoringPrereqChecker, _browserMonitoringScriptMaker, _configurationService, _agentHealthReporter, _agentTimerService, _metricNameService, _traceMetadataFactory, _catMetrics, _logEventAggregator, _logContextDataFilter); + _agent = new Agent(_transactionService, _transactionTransformer, _threadPoolStatic, _transactionMetricNameMaker, _pathHashMaker, _catHeaderHandler, _distributedTracePayloadHandler, _syntheticsHeaderHandler, _transactionFinalizer, _browserMonitoringPrereqChecker, _browserMonitoringScriptMaker, _configurationService, _agentHealthReporter, _agentTimerService, _metricNameService, _traceMetadataFactory, _catMetrics, _logEventAggregator, _logContextDataFilter, _simpleSchedulingService); SetupTransaction(); Mock.Arrange(() => _configurationService.Configuration.DistributedTracingEnabled).Returns(true); @@ -1269,6 +1267,8 @@ public void RecordLogMessage_NoTransaction_Success() { Mock.Arrange(() => _configurationService.Configuration.LogEventCollectorEnabled) .Returns(true); + Mock.Arrange(() => _configurationService.Configuration.LogMetricsCollectorEnabled) + .Returns(true); Mock.Arrange(() => _configurationService.Configuration.ContextDataEnabled) .Returns(true); @@ -1305,6 +1305,8 @@ public void RecordLogMessage_NoTransaction_Success() Assert.AreEqual(traceId, logEvent.TraceId); Assert.AreEqual(contextData, logEvent.ContextData); Assert.IsNotNull(logEvent.Priority); + + Mock.Assert(() => _agentHealthReporter.IncrementLogLinesCount(Arg.AnyString), Occurs.Once()); } [Test] @@ -1748,6 +1750,55 @@ public void RecordLogMessage_ContextDataDisabled() Assert.IsNull(logEvent.ContextData); } + [Test] + public void RecordLogMessage_WithDenyList_DropsMessageAndIncrementsDeniedCount() + { + Mock.Arrange(() => _configurationService.Configuration.LogEventCollectorEnabled) + .Returns(true); + Mock.Arrange(() => _configurationService.Configuration.LogMetricsCollectorEnabled) + .Returns(true); + Mock.Arrange(() => _configurationService.Configuration.ContextDataEnabled) + .Returns(false); + Mock.Arrange(() => _configurationService.Configuration.LogLevelDenyList) + .Returns(new HashSet() { "DEBUG" }); + + var timestamp = DateTime.Now; + var timestampUnix = timestamp.ToUnixTimeMilliseconds(); + var level = "DEBUG"; + var message = "message"; + var exception = NotNewRelic.ExceptionBuilder.BuildException("exception message"); + var fixedStackTrace = string.Join(" \n", StackTraces.ScrubAndTruncate(exception.StackTrace, 300)); + var contextData = new Dictionary() { + { "key1", "value1" }, + { "key2", 1 } }; + + Func getLevelFunc = (l) => level; + Func getTimestampFunc = (l) => timestamp; + Func getMessageFunc = (l) => message; + Func getLogExceptionFunc = (l) => exception; + Func> getContextDataFunc = (l) => contextData; + + var spanId = "spanid"; + var traceId = "traceid"; + var loggingFramework = "testFramework"; + + SetupTransaction(); + var transaction = _transactionService.GetCurrentInternalTransaction(); + var priority = transaction.Priority; + transaction.HarvestLogEvents(); + + var xapi = _agent as IAgentExperimental; + xapi.RecordLogMessage(loggingFramework, new object(), getTimestampFunc, getLevelFunc, getMessageFunc, getLogExceptionFunc, getContextDataFunc, spanId, traceId); + + var privateAccessor = new PrivateAccessor(_logEventAggregator); + var logEvents = privateAccessor.GetField("_logEvents") as ConcurrentPriorityQueue>; + + Assert.AreEqual(0, logEvents.Count); + Mock.Assert(() => _agentHealthReporter.IncrementLogDeniedCount(Arg.AnyString), Occurs.Once()); + + + } + #endregion private void SetupTransaction() diff --git a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/DistributedTracing/DistributedTracePayloadHandlerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/DistributedTracing/DistributedTracePayloadHandlerTests.cs index 587069e36..7da66bb8a 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/DistributedTracing/DistributedTracePayloadHandlerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/DistributedTracing/DistributedTracePayloadHandlerTests.cs @@ -1031,6 +1031,55 @@ public void TraceIdShouldBeSameForAllSpansWhenNoTraceIdReceived() #endregion TraceID Tests + [TestCase(true)] + [TestCase(true, "")] + [TestCase(true, "k1=v1", "k2=v2")] + [TestCase(false)] + [TestCase(false, "")] + [TestCase(false, "k1=v1", "k2=v2")] + public void W3C_BuildTracestate_EmptyVendors_NoCommas(bool hasIncomingPayload, params string[] vendorState) + { + // Arrange + Mock.Arrange(() => _configuration.SpanEventsEnabled).Returns(true); + Mock.Arrange(() => _configuration.PayloadSuccessMetricsEnabled).Returns(true); + + var transaction = BuildMockTransaction(hasIncomingPayload: hasIncomingPayload, sampled: true); + + var transactionGuid = GuidGenerator.GenerateNewRelicGuid(); + Mock.Arrange(() => transaction.Guid).Returns(transactionGuid); + + var expectedSpanGuid = GuidGenerator.GenerateNewRelicGuid(); + var segment = Mock.Create(); + Mock.Arrange(() => segment.SpanId).Returns(expectedSpanGuid); + + Mock.Arrange(() => transaction.CurrentSegment).Returns(segment); + + var headers = new List>(); + var setHeaders = new Action>, string, string>((carrier, key, value) => + { + carrier.Add(new KeyValuePair(key, value)); + }); + + var tracingState = Mock.Create(); + + var vendorStateEntries = vendorState.ToList(); + + Mock.Arrange(() => tracingState.VendorStateEntries).Returns(vendorStateEntries); + Mock.Arrange(() => transaction.TracingState).Returns(tracingState); + + Mock.Arrange(() => transaction.InsertDistributedTraceHeaders( + Arg.IsAny>>(), + Arg.IsAny>, string, string>>())) + .DoInstead(() => _distributedTracePayloadHandler.InsertDistributedTraceHeaders(transaction, headers, setHeaders)); + + // Act + transaction.InsertDistributedTraceHeaders(headers, setHeaders); + + var tracestateHeaderValue = headers.Where(header => header.Key == TracestateHeaderName).Select(header => header.Value).FirstOrDefault(); + + Assert.That(!tracestateHeaderValue.EndsWith(","), "W3C Tracestate string has a trailing comma."); + } + #endregion #region Supportability Metrics diff --git a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs index e21c11129..e130ad51c 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs @@ -160,6 +160,7 @@ public void BeforeWrappedMethod_DoesNotSetNullOnFirstThrownException() Mock.Assert(_wrapperMap); } +#if NETFRAMEWORK //TODO: update this test to use something other than `System.Web.HttpApplication` [Test] public void BeforeWrappedMethod_SetsNoOpWhenThrowsExceptionTooManyTimes() { @@ -191,7 +192,7 @@ public void BeforeWrappedMethod_SetsNoOpWhenThrowsExceptionTooManyTimes() Assert.DoesNotThrow(() => wrapperService.BeforeWrappedMethod(type, methodName, argumentSignature, invocationTarget, arguments, tracerFactoryName, metricName, EmptyTracerArgs, 0)); Mock.Assert(_noOpWrapper); } - +#endif [Test] public void AfterWrappedMethod_DoesNotSetNullOnFirstThrownException() { @@ -212,6 +213,7 @@ public void AfterWrappedMethod_DoesNotSetNullOnFirstThrownException() Mock.Assert(_wrapperMap); } +#if NETFRAMEWORK //TODO: update this test to use something other than `System.Web.HttpApplication` [Test] public void AfterWrappedMethod_SetsNoOpWhenThrowsExceptionTooManyTimes() { @@ -244,6 +246,7 @@ public void AfterWrappedMethod_SetsNoOpWhenThrowsExceptionTooManyTimes() var afterWrappedMethod2 = wrapperService.BeforeWrappedMethod(type, methodName, argumentSignature, invocationTarget, arguments, tracerFactoryName, metricName, EmptyTracerArgs, 0); Assert.DoesNotThrow(() => afterWrappedMethod2(null, null)); } +#endif [Test] public void BeforeWrappedMethod_ReturnsNoOp_IfTheCurrentSegmentIsLeaf() diff --git a/tests/Agent/UnitTests/NewRelic.Agent.Extensions.Tests/NewRelic.Agent.Extensions.Tests.csproj b/tests/Agent/UnitTests/NewRelic.Agent.Extensions.Tests/NewRelic.Agent.Extensions.Tests.csproj index 34074b0dd..b3dae4d6c 100644 --- a/tests/Agent/UnitTests/NewRelic.Agent.Extensions.Tests/NewRelic.Agent.Extensions.Tests.csproj +++ b/tests/Agent/UnitTests/NewRelic.Agent.Extensions.Tests/NewRelic.Agent.Extensions.Tests.csproj @@ -11,9 +11,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/AssemblyExtensions.cs b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/AssemblyExtensions.cs new file mode 100644 index 000000000..fddf71ab4 --- /dev/null +++ b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/AssemblyExtensions.cs @@ -0,0 +1,20 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; + +namespace NewRelic.Agent.TestUtilities +{ + public static class AssemblyExtensions + { + public static string GetLocation(this Assembly assembly) + { +#if NETFRAMEWORK + return assembly.CodeBase; +#else + return assembly.Location; +#endif + + } + } +} diff --git a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs index a261bf028..99a74f819 100644 --- a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs +++ b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/Logging.cs @@ -1,72 +1,53 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -using NewRelic.Agent.Core.Logging; -using NewRelic.Core.Logging; -using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Text; - -// This ensures that we use a different logging repository from the rest of the process that we end up in. -[assembly: log4net.Config.Repository("NewRelic Log4Net Repository")] +using NewRelic.Agent.Core.Logging; +using Serilog.Events; +using Serilog; +using Log = NewRelic.Core.Logging.Log; +using Logger = NewRelic.Agent.Core.Logging.Logger; namespace NewRelic.Agent.TestUtilities { /// - /// While this object is in scope, log4net will log to a memory appender. + /// While this object is in scope, serilog will log to an in-memory sink /// public class Logging : IDisposable { - public readonly log4net.Appender.MemoryAppender MemoryAppender = new log4net.Appender.MemoryAppender(); - public readonly log4net.Repository.Hierarchy.Logger Logger = (log4net.LogManager.GetRepository() as log4net.Repository.Hierarchy.Hierarchy).Root; - private readonly log4net.Appender.AppenderCollection _previousAppenders = new log4net.Appender.AppenderCollection(); + private readonly InMemorySink _inMemorySink = new InMemorySink(); /// - /// Initializes log4net to log to a memory appender which can then be referenced + /// Initializes serilog to log to an in-memory sink which can then be queried /// - public Logging(log4net.Core.Level level = null) + public Logging(LogEventLevel logLevel = LogEventLevel.Information) { - Logger.Level = level ?? log4net.Core.Level.All; + var loggerConfig = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Sink(_inMemorySink); - Logger.RemoveAllAppenders(); - Logger.AddAppender(MemoryAppender); - - Logger.Repository.Configured = true; + Serilog.Log.Logger = loggerConfig.CreateLogger(); Log.Initialize(new Logger()); } - /// - /// When you dispose of this object the memory appender will be removed from the logging system. - /// public void Dispose() { - Logger.Repository.Configured = false; - - if (Logger.Appenders == null) - Assert.Fail("We somehow ended up with no log appenders, test is invalid."); - - if (Logger.Appenders.Count != 1) - Assert.Fail("Someone added or removed log appenders during the execution of this test potentially invalidating it."); - - Logger.RemoveAllAppenders(); } public override string ToString() { var builder = new StringBuilder(); - var logEvents = MemoryAppender.GetEvents(); + var logEvents = _inMemorySink.LogEvents; if (logEvents == null) return "Nothing was logged."; foreach (var logEvent in logEvents) { - if (logEvent == null) - continue; - - builder.AppendLine(logEvent.RenderedMessage); + builder.AppendLine(logEvent.RenderMessage()); } return builder.ToString(); @@ -77,118 +58,66 @@ public override string ToString() /// /// The message you want to check for. /// True if the message was logged, false otherwise. - public bool HasMessage(string message) - { - var events = MemoryAppender.GetEvents(); - foreach (var item in events) - { - if (item.MessageObject.ToString() == message) - return true; - } - return false; - } + public bool HasMessage(string message) => _inMemorySink.LogEvents.Any(e => e.RenderMessage() == message); /// /// checks for messages that begins with a segment /// /// /// - public bool HasMessageBeginingWith(string segment) - { - var events = MemoryAppender.GetEvents(); - return events.Any(item => item.MessageObject.ToString().StartsWith(segment)); - } + public bool HasMessageBeginningWith(string segment) => _inMemorySink.LogEvents.Any(item => item.RenderMessage().StartsWith(segment)); /// /// checks for messages that begins with a segment /// /// /// - public bool HasMessageThatContains(string segment) - { - var events = MemoryAppender.GetEvents(); - return events.Any(item => item.MessageObject.ToString().Contains(segment)); - } - - /// - /// Returns the exception associated with the message if it exists. - /// - /// The message to look for in the message collection. - /// The exception associated with the given message or null if either the message wasn't found or no exception was associated with the message. - public Exception TryGetExceptionForMessage(string message) - { - var events = MemoryAppender.GetEvents(); - foreach (var item in events) - { - if (item.MessageObject.ToString() == message) - return item.ExceptionObject; - } - return null; - } + public bool HasMessageThatContains(string segment) => _inMemorySink.LogEvents.Any(item => item.RenderMessage().Contains(segment)); /// /// Counts the number of messages that were logged since the construction of this object. /// - public int MessageCount { get { return MemoryAppender.GetEvents().Length; } } + public int MessageCount => _inMemorySink.LogEvents.Count(); /// /// Counts the number of [level] messages that were logged since the construction of this object. /// /// The number of messages logged at [level] level. - private int LevelCount(log4net.Core.Level level) - { - var events = MemoryAppender.GetEvents(); - int count = 0; - foreach (var item in events) - { - if (item.Level == level) - { - ++count; - } - } - - return count; - - } + private int LevelCount(LogEventLevel level) => _inMemorySink.LogEvents.Count(e => e.Level == level); - public IEnumerable ErrorMessages - { - get - { - return MemoryAppender.GetEvents() - .Where(@event => @event.Level == log4net.Core.Level.Error) - .Select(@event => @event.RenderedMessage); - } - } + public IEnumerable ErrorMessages => + _inMemorySink.LogEvents + .Where(@event => @event.Level == LogEventLevel.Error) + .Select(@event => @event.RenderMessage()); /// /// Counts the number of error level messages that were logged since construction of this object. /// /// - public int ErrorCount { get { return LevelCount(log4net.Core.Level.Error); } } + public int ErrorCount => LevelCount(LogEventLevel.Error); /// /// Counts the number of warn level messages that were logged since construction of this object. /// /// - public int WarnCount { get { return LevelCount(log4net.Core.Level.Warn); } } + public int WarnCount => LevelCount(LogEventLevel.Warning); /// /// Counts the number of info level messages that were logged since construction of this object. /// /// - public int InfoCount { get { return LevelCount(log4net.Core.Level.Info); } } + public int InfoCount => LevelCount(LogEventLevel.Information); /// /// Counts the number of debug level messages that were logged since construction of this object. /// /// - public int DebugCount { get { return LevelCount(log4net.Core.Level.Debug); } } + public int DebugCount => LevelCount(LogEventLevel.Debug); /// /// Counts the number of finest level messages that were logged since construction of this object. /// /// - public int FinestCount { get { return LevelCount(log4net.Core.Level.Finest); } } + public int FinestCount => LevelCount(LogEventLevel.Verbose); } } diff --git a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/NewRelic.Agent.TestUtilities.csproj b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/NewRelic.Agent.TestUtilities.csproj index 4505205c0..79e2357c4 100644 --- a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/NewRelic.Agent.TestUtilities.csproj +++ b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/NewRelic.Agent.TestUtilities.csproj @@ -1,6 +1,6 @@ - net462 + net462;net7.0 NewRelic.Agent.TestUtilities NewRelic.Agent.TestUtilities @@ -9,14 +9,16 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + - + diff --git a/tests/Agent/UnitTests/NewRelic.Testing.Assertions.UnitTests/NewRelic.Testing.Assertions.UnitTests.csproj b/tests/Agent/UnitTests/NewRelic.Testing.Assertions.UnitTests/NewRelic.Testing.Assertions.UnitTests.csproj index 4badbe0ad..4c33ad2ac 100644 --- a/tests/Agent/UnitTests/NewRelic.Testing.Assertions.UnitTests/NewRelic.Testing.Assertions.UnitTests.csproj +++ b/tests/Agent/UnitTests/NewRelic.Testing.Assertions.UnitTests/NewRelic.Testing.Assertions.UnitTests.csproj @@ -31,4 +31,4 @@ - \ No newline at end of file + diff --git a/tests/Agent/UnitTests/NewRelic.Testing.Assertions.UnitTests/Properties/AssemblyInfo.cs b/tests/Agent/UnitTests/NewRelic.Testing.Assertions.UnitTests/Properties/AssemblyInfo.cs deleted file mode 100644 index 2a51626e5..000000000 --- a/tests/Agent/UnitTests/NewRelic.Testing.Assertions.UnitTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("bb568fea-078f-41e4-841d-fcb5e9ff0dbe")] diff --git a/tests/Agent/UnitTests/ParsingTests/ParsingTests.csproj b/tests/Agent/UnitTests/ParsingTests/ParsingTests.csproj index da9191ded..506680731 100644 --- a/tests/Agent/UnitTests/ParsingTests/ParsingTests.csproj +++ b/tests/Agent/UnitTests/ParsingTests/ParsingTests.csproj @@ -11,9 +11,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/AwsLambda/UnitTests/AwsLambdaOpenTracerTests/AwsLambdaOpenTracerTests.csproj b/tests/AwsLambda/UnitTests/AwsLambdaOpenTracerTests/AwsLambdaOpenTracerTests.csproj index 3e8723963..abd411730 100644 --- a/tests/AwsLambda/UnitTests/AwsLambdaOpenTracerTests/AwsLambdaOpenTracerTests.csproj +++ b/tests/AwsLambda/UnitTests/AwsLambdaOpenTracerTests/AwsLambdaOpenTracerTests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net7.0 NewRelic.Tests.AwsLambda.AwsLambdaOpenTracerTests NewRelic.Tests.AwsLambda.AwsLambdaOpenTracerTests true @@ -14,9 +14,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/AwsLambda/UnitTests/AwsLambdaWrapperTests/AwsLambdaWrapperTests.csproj b/tests/AwsLambda/UnitTests/AwsLambdaWrapperTests/AwsLambdaWrapperTests.csproj index ea8163701..cf7bb6859 100644 --- a/tests/AwsLambda/UnitTests/AwsLambdaWrapperTests/AwsLambdaWrapperTests.csproj +++ b/tests/AwsLambda/UnitTests/AwsLambdaWrapperTests/AwsLambdaWrapperTests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net7.0 NewRelic.Tests.AwsLambda.AwsLambdaWrapperTests NewRelic.Tests.AwsLambda.AwsLambdaWrapperTests true @@ -29,9 +29,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/NewRelic.Core.Tests/NewRelic.Core.Tests.csproj b/tests/NewRelic.Core.Tests/NewRelic.Core.Tests.csproj index c0ec89eb0..480e84acf 100644 --- a/tests/NewRelic.Core.Tests/NewRelic.Core.Tests.csproj +++ b/tests/NewRelic.Core.Tests/NewRelic.Core.Tests.csproj @@ -9,9 +9,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive