From a5e2b0df0356ef37377d2b33be26507abb208bb8 Mon Sep 17 00:00:00 2001 From: chynesNR Date: Mon, 24 Jul 2023 08:39:53 -0700 Subject: [PATCH] Squashed commit of the following: commit a5445923373be4674a372ff7263944af78d30145 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Fri Jul 21 15:46:32 2023 -0500 ci: bump JamesIves/github-pages-deploy-action from 4.4.2 to 4.4. (#1786) commit 798946ffd3f905879380b17a95680b7bf354cb08 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu Jul 20 13:55:03 2023 -0500 chore: Use Autofac instead of Castle.Windsor for .Net Framework builds (#1782) (#1784) commit fb41c063caf10af0c4d64f4f7f63d097c674b822 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Wed Jul 19 16:47:15 2023 -0700 test: add an Elasticsearch 7.x server to test infrastructure and use it to test older clients (#1783) * Added 7.x Elasticsearch server to services and updated test apps and tests * Removed obsolete comment * Refactor credentials access commit 974335cc0c3a385998aff763fb265a932757347f Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Wed Jul 19 09:28:51 2023 -0500 test: Add .NET 7 Unit Test Build Targets (#1778) commit 561897d605b4ee86c37c26c1236c3e22cee96265 Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue Jul 18 09:12:29 2023 -0700 chore(main): release 10.13.0 (#1747) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> commit 790a3b75dd7609d76638ea3625a9289f58b24378 Author: Jacob Affinito Date: Fri Jul 14 15:32:26 2023 -0700 fix: Remove invalid trailing comma added to W3C tracestate header. (#1779) commit 4b4e37559e34e93517510a8b022b7a694abb4ede Author: Chris Hynes <111462425+chynesNR@users.noreply.github.com> Date: Fri Jul 14 08:29:23 2023 -0700 test: Update libraries called out by Dotty (#1774) (#1757) (#1776) commit aadce3a09f9fe3c77a93f557686f1ddc26fc6169 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu Jul 13 11:40:03 2023 -0500 summary: Log Level Filtering (#1770) feat: 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) (#1761) (#1762) (#1766) commit 6c98ba2820a3f511ffcb2399692088dc510097f9 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Wed Jul 12 14:24:11 2023 -0500 ci: Add sha to pin debian:buster-slim image (#1772) commit 8341818b72bfa7f0655e7455e6da9d541b0b7200 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Wed Jul 12 13:41:43 2023 -0500 ci: Update debian image used in package deploy Dockerfile (#1771) commit eee7564cbe79b653ad7909af36f09c9a64cdb731 Author: Chris Hynes <111462425+chynesNR@users.noreply.github.com> Date: Wed Jul 12 11:05:27 2023 -0700 security: Update Grpc.Net.Client library to address Dependabot alerts. (#1768) (#1769) commit ec70f4e0e911136d71fd08db76a3dd1ffa70a995 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon Jul 10 15:47:34 2023 -0500 ci: Update AwsLamba unit tests to .NET 7.0 (#1759) commit 32f966da36ac8b56e52a21def79f0e781bfea9d0 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Mon Jul 10 06:45:46 2023 -0700 chore: Weekly dependabot roundup 20230630 (#1753) * chore(deps): bump ossf/scorecard-action from 2.1.3 to 2.2.0 Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.1.3 to 2.2.0. - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/80e868c13c90f172d68d1f4501dee99e2479f7af...08b4669551908b1024bb425080c797723083c031) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump google-github-actions/release-please-action Bumps [google-github-actions/release-please-action](https://github.com/google-github-actions/release-please-action) from 3.7.9 to 3.7.10. - [Release notes](https://github.com/google-github-actions/release-please-action/releases) - [Changelog](https://github.com/google-github-actions/release-please-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/google-github-actions/release-please-action/compare/51ee8ae2605bd5ce1cfdcc5938684908f1cd9f69...8016a6649226f2ec88ed05441c11bb5410a22d29) --- updated-dependencies: - dependency-name: google-github-actions/release-please-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump step-security/harden-runner from 2.4.0 to 2.4.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.4.0 to 2.4.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/128a63446a954579617e875aaab7d2978154e969...55d479fb1c5bcad5a4f9099a5d9f37c8857b2845) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 902b025c8c420b8bc288b15d914b47aabc1bd426 Author: Jacob Affinito Date: Fri Jun 30 15:36:31 2023 -0700 fix: Refactor StackExchange.Redis v2+ instrumentation to eliminate potential memory leaks. commit 881989c47252f9dc61c89412b2208611686280d2 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu Jun 29 16:23:01 2023 -0500 ci: Add MSI certificate validation to ArtifactBuilder (#1752) commit 3fbc54310ed3989f915e6f39b27ef8867ed573db Author: Jacob Affinito Date: Tue Jun 27 14:27:22 2023 -0700 fix: Update the MSI UI to clean up formatting and readability issues. (#1748) commit a695ce6de7e56bc3f803c9b9f6c8c09b30c106fd Author: Chris Hynes <111462425+chynesNR@users.noreply.github.com> Date: Tue Jun 27 09:58:12 2023 -0700 feat: Instrument OpenAsync() for SQL libraries. (#1725) commit dd490dc5622977706e6862476b36c7f07f7f09bf Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon Jun 26 17:30:55 2023 -0500 chore(main): release 10.12.1 (#1743) commit 386a27705701a07d591a95f95830bda27898d255 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon Jun 26 17:27:02 2023 -0500 fix: 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. commit 68bdcf964e6d884b07f434e70810620c97b2a77f Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon Jun 26 17:17:20 2023 -0500 ci: Update all solutions workflow to create self-signed cert only on non-release builds. (#1742) commit f70ffcafe13716a97fd0da73ef4d89484962cb59 Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon Jun 26 10:57:24 2023 -0500 chore(main): release 10.12.0 (#1709) commit fc9e4cfdaaa2a1921e4e26d4562b72442cd92399 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Fri Jun 23 14:29:13 2023 -0700 chore: update latest versions of instrumented libraries tested (#1738) * Update latest Serilog version tested to 3.0.1 * Update latest MongoDB.Driver version tested to 2.20.0 * Update latest version of Stackexchange.Redis tested to 2.6.116 commit 0f197fa165e8a0c3c126f06cd966e2a3d96e3267 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Fri Jun 23 11:19:23 2023 -0700 chore: weekly dependabot roundup 20230623 (#1741) * chore(deps): bump peter-evans/create-pull-request from 5.0.1 to 5.0.2 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 5.0.1 to 5.0.2. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/284f54f989303d2699d373481a0cfa13ad5a6666...153407881ec5c347639a548ade7d8ad1d6740e38) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump aws-actions/configure-aws-credentials Bumps [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases) - [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws-actions/configure-aws-credentials/compare/5727f247b64f324ec403ac56ae05e220fd02b65f...5fd3084fc36e372ff1fff382a39b10d03659f355) --- updated-dependencies: - dependency-name: aws-actions/configure-aws-credentials dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 3b91dff0ad9aa2fc4218cd85d28fb6d0892cc7fb Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Fri Jun 23 13:13:53 2023 -0500 fix: Update install script to correctly stop and restart IIS. (#1740) commit f03077cb199fad24aa4aadd4187a23d4637747d0 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Fri Jun 23 11:12:57 2023 -0700 chore: remove mongocsharpdriver from Dotty's list of concerns (#1739) commit f71521f2540311e97d13646ff6d6524dfcc3965f Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Fri Jun 23 13:12:35 2023 -0500 fix: Format and log audit-level messages only when audit logging is enabled. (#1734) commit 1a5661243eaa84683694e022fe9806768b8af9f7 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Fri Jun 23 13:11:42 2023 -0500 fix: Include config file path in the "Agent is disabled " message on all platforms. (#1727) commit 1aa5680a8f7f855895203a45b8dfcc5059d656e0 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Wed Jun 21 15:20:31 2023 -0700 feat: add instrumentation for newer MongoDB.Client methods (#1732) * Instrument CountDocuments and CountDocumentsAsync * Refactor to simplify test logic and driver version logic * Instrument EstimatedDocumentCount(Async) Also add test for AggregateAsync * Instrument and test AggregateToCollection(Async) * Instrument and test ListCollectionNames(Async) * Refactor database tests * Instrument and test Db.Aggregate(Async) * Instrument and test Db.AggregateToCollection(Async) * Instrument and test Db.Watch(Async) * Refactor indexmanager tests to use theory * Test stability improvements commit beb32bf6a00c1e92f6e37c9c76eebb1a68879ce7 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Fri Jun 16 11:24:21 2023 -0500 chore: Weekly dependabot roundup. (#1726) commit ce120171185d6836a145f9617ace1249fd8d8baf Author: Chris Ventura <45495992+nrcventura@users.noreply.github.com> Date: Thu Jun 15 10:11:22 2023 -0700 chore: Update Arm64 base image to use a pinned image. (#1724) commit 1624938ab48b63c1fa6e98037d74976dbc8186da Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu Jun 15 11:05:31 2023 -0500 fix: Cache the AgentEnabled setting value. (#1723) commit 269023cfb9f8e9915d921f88a0e0ffd1d29f474b Author: Chris Ventura <45495992+nrcventura@users.noreply.github.com> Date: Wed Jun 14 12:56:53 2023 -0700 chore: Update multiverse scanner dependencies. (#1721) commit 5b4e384e01d26133fb449f249934ceec2effda75 Author: Chris Ventura <45495992+nrcventura@users.noreply.github.com> Date: Wed Jun 14 12:16:13 2023 -0700 chore: Add dockerfile and docker-compose to build arm64 profiler locally. (#1717) commit d7bb7f290beae8394599cee1ea9b3213cf2dc473 Author: Chris Ventura <45495992+nrcventura@users.noreply.github.com> Date: Tue Jun 13 13:11:16 2023 -0700 fix: Add more validation to msi installer. (#1716) commit 2fcce95093ed4ef6d1efe67489c8d1ae6c9b29e6 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon Jun 12 12:57:34 2023 -0500 fix: Exclude WebResource.axd and ScriptResource.axd from browser instrumentation (via default config). (#1711) commit 69d15dfbed178fb5698695253160ae12a4f7a410 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Sat Jun 10 08:25:51 2023 -0700 feat: add support for MySql.Data version 8.0.33+ (#1708) * Add async instrumentation points for 8.0.33+ * Add comment re: MySql 8.0.33 in MFAH.csproj * Move new instrumentation points to async tracer factory * Move new async data reader instrumentation points to async factory commit 79b1c6f3b7a79940a3571e829c1d238406053678 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Fri Jun 9 17:15:06 2023 -0700 chore: Dependabot weekly roundup 20230609 (#1712) * chore(deps): bump aws-actions/configure-aws-credentials Bumps [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases) - [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws-actions/configure-aws-credentials/compare/e1e17a757e536f70e52b5a12b2e8d1d1c60e04ef...5727f247b64f324ec403ac56ae05e220fd02b65f) --- updated-dependencies: - dependency-name: aws-actions/configure-aws-credentials dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump github/codeql-action from 2.3.5 to 2.3.6 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.3.5 to 2.3.6. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/0225834cc549ee0ca93cb085b92954821a145866...83f0fe6c4988d98a455712a27f0255212bba9bd4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 8b734a59a53cfd218322d83acbe9d7eb4e7cc055 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Fri Jun 9 08:47:56 2023 -0500 fix: Handle empty Request.Path values in AspNetCore middleware wrapper. (#1704) commit b644dfd8cecbc020bb344f987a7000ae381d2242 Author: Chris Ventura <45495992+nrcventura@users.noreply.github.com> Date: Thu Jun 8 15:27:09 2023 -0700 chore: Update community support url. (#1707) commit 5850145e9a410b58fa054efa63860f774782c579 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu Jun 8 16:34:27 2023 -0500 ci: Give repolinter write permission for issues (#1705) commit e0f3dc1be044fe499cf78e1f5e42b53a7c4075c4 Author: Chris Ventura <45495992+nrcventura@users.noreply.github.com> Date: Thu Jun 8 14:07:13 2023 -0700 chore: Remove old settings from profiler testing tool. (#1701) commit a9fcb74b02e81987122af0b7742919bf52825d81 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Thu Jun 8 12:31:20 2023 -0700 test: update latest tested versions of core tech libraries, June 2023 roundup (#1703) * Update Microsoft.Data.SqlClient to latest version * Update latest RabbitMQ tested to 6.5.0 * Update latest Stackexchange.Redis version tested to 2.6.111 * Update latest version of Serilog tested to 2.12.0 * Update MultiFunctionApplicationHelpers.csproj Update latest NLog version tested to 5.2.0 * Update latest version of Elastic.Clients.Elasticsearch tested to 8.1.1 * Update the latest version of MEL (and friends) tested to 7.0.0 commit a41d1204ec7d03aa1a4f6e64c1feef1d22a76fec Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Wed Jun 7 13:18:07 2023 -0700 ci: add new core tech libraries to the list to scan for updates (#1700) commit 3520f7f5744556f9d17000ef0a10483456170bd1 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Wed Jun 7 13:40:20 2023 -0500 ci: Artifactbuilder parses output from nuget pack (#1699) * Modifies Nuget build / validation to capture and parse stdout from the `nuget pack` command, to obtain the actual filename of the NuGet package that was generated and use that in content validation. * Use regex to parse output from nuget pack commit 0e86e4bab1ed508fed3e671e88dba73dd19ee90d Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Wed Jun 7 10:10:17 2023 -0700 ci: let the core tech update scanner create issues in our repo for the new package versions it finds (#1696) commit a5ce546309ffe85d6fc1270a546290f1b09af043 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Tue Jun 6 15:42:32 2023 -0700 ci: let the ArtifactBuilder's content validation logic handle semantic versioning of NuGet packages (#1697) commit 68ac37989f4e3eafe9e74a6753323068e7559810 Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue Jun 6 12:27:19 2023 -0700 chore(main): release 10.11.0 (#1614) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> commit f2497536dadb34caded7aa916b5f404ebf19e52a Author: Jacob Affinito Date: Fri Jun 2 17:14:06 2023 -0700 feat: Add detailed assembly reporting to enable Vulnerability Management support. (#1685) commit e8bdc34072f044e7b056dd2ce773f184aed3bfe5 Author: Chris Ventura <45495992+nrcventura@users.noreply.github.com> Date: Wed May 31 09:38:20 2023 -0700 fix: Stop double injecting headers with HttpClient on .NET Framework (#1679) commit 51080df3848e36e0b6aa29b6cb9a0e94a1638b6f Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Tue May 30 15:30:52 2023 -0500 feat: Use Serilog instead of log4net for internal logging. (#1661) commit 0882d471ee901b2c8f54d3fc478a0e89d5d19b5d Author: Chris Hynes <111462425+chynesNR@users.noreply.github.com> Date: Tue May 30 10:01:51 2023 -0700 chore: Update weekly dependabot issues. (#1671) commit ddaf483846c30211983605a22f9e1ae924ed257f Author: Jacob Affinito Date: Fri May 26 17:22:42 2023 -0700 test: Improve unbounded test logging and reliability for MsSQL, Oracle, Elastic, and CosmosDB (#1648) commit b835b8297fb589cae60a69c08874b8957b6b1c20 Author: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Fri May 26 15:53:52 2023 -0500 ci: Update codecov configuration, add some status badges to Readme. (#1668) commit af01977fb6f41eb28fe3e2ec654b068e947b0bf8 Author: Chris Hynes <111462425+chynesNR@users.noreply.github.com> Date: Fri May 26 10:44:09 2023 -0700 test: Fix bug in new retry logic (#1670) commit 81454ed5d65e9365ac4c7fd180895741808575f6 Author: Chris Ventura <45495992+nrcventura@users.noreply.github.com> Date: Thu May 25 13:54:30 2023 -0700 test: Update MongoDB driver in unbounded tests. (#1666) commit cc7cedecc113812b5f7274e7a6bf1aa5a2511720 Author: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Thu May 25 13:45:33 2023 -0700 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. commit 2116e88c5e3ee830128586a4707d3b56173d3970 Author: Chris Hynes <111462425+chynesNR@users.noreply.github.com> Date: Wed May 24 08:24:24 2023 -0700 test: Retry tests on known unrelated errors. (#1644) --- .github/workflows/all_solutions.yml | 65 +-- .github/workflows/assignproj.yml | 2 +- .github/workflows/awslambda_release.yml | 4 +- .../build_download_site_index_files.yml | 2 +- .github/workflows/build_profiler.yml | 20 +- .github/workflows/deploy_agent.yml | 4 +- .github/workflows/deploy_awslambda.yml | 2 +- .github/workflows/deploy_siteextension.yml | 2 +- .github/workflows/get_release_checksums.yml | 2 +- .github/workflows/markdowncheck.yml | 4 +- .github/workflows/multiverse_run.yml | 14 +- .../workflows/nuget_slack_notifications.yml | 24 +- .github/workflows/release-please.yml | 4 +- .github/workflows/repolinter.yml | 5 +- .github/workflows/run_integration_tests.yml | 6 +- .github/workflows/run_unit_tests.yml | 4 +- .github/workflows/scorecard.yml | 8 +- .../nugetSlackNotifications/Program.cs | 28 +- .../nugetSlackNotifications.csproj | 1 + .github/workflows/set_community_label.yml | 2 +- .github/workflows/siteextension_release.yml | 2 +- .github/workflows/stale_issue.yml | 2 +- .github/workflows/stale_pr.yml | 2 +- FullAgent.sln | 7 - README.md | 7 +- build/ArtifactBuilder/AgentComponents.cs | 17 + .../Artifacts/AzureSiteExtension.cs | 8 +- .../ArtifactBuilder/Artifacts/MsiInstaller.cs | 7 + build/ArtifactBuilder/Artifacts/NugetAgent.cs | 8 +- .../Artifacts/NugetAgentApi.cs | 8 +- .../Artifacts/NugetAzureCloudServices.cs | 8 +- .../Artifacts/NugetAzureWebSites.cs | 12 +- build/ArtifactBuilder/NuGetHelpers.cs | 39 +- build/ArtifactBuilder/NugetPackage.cs | 4 +- build/ArtifactBuilder/SecurityHelpers.cs | 226 +++++++++ build/BuildTools.sln | 31 ++ .../content/newrelic.cmd | 6 +- codecov.yml | 24 +- deploy/linux/Dockerfile | 2 +- licenses/LICENSE.rtf | 10 +- licenses/THIRD_PARTY_NOTICES.txt | 207 -------- src/Agent/CHANGELOG.md | 66 +++ src/Agent/Configuration/newrelic.config | 7 +- .../Installer/LicenseKeyDialog.wxs | 2 +- src/Agent/MsiInstaller/Installer/WizardUI.wxs | 6 +- .../InstallerActions/CustomActions.cs | 161 ++++++- src/Agent/NewRelic/Agent/Core/Agent.cs | 23 +- .../Core/AgentHealth/AgentHealthReporter.cs | 23 + .../Core/AgentHealth/IAgentHealthReporter.cs | 1 + .../NewRelic/Agent/Core/AgentInitializer.cs | 2 +- .../Core/Aggregators/CustomEventAggregator.cs | 5 + .../Core/Aggregators/ErrorEventAggregator.cs | 5 + .../Core/Aggregators/ErrorTraceAggregator.cs | 5 + .../Core/Aggregators/LogEventAggregator.cs | 10 + .../Core/Aggregators/SpanEventAggregator.cs | 5 + .../Core/Aggregators/SqlTraceAggregator.cs | 5 + .../Aggregators/TransactionEventAggregator.cs | 5 + .../Agent/Core/Config/Configuration.cs | 72 +-- .../Agent/Core/Config/Configuration.xsd | 22 +- .../Agent/Core/Config/ConfigurationLoader.cs | 24 +- .../NewRelic/Agent/Core/Config/ILogConfig.cs | 3 - .../Configuration/ConfigurationService.cs | 11 +- .../Configuration/DefaultConfiguration.cs | 97 +++- .../Configuration/ReportedConfiguration.cs | 9 + src/Agent/NewRelic/Agent/Core/Core.csproj | 42 +- .../DataTransport/DataTransportService.cs | 11 + .../Agent/Core/DataTransport/GrpcWrapper.cs | 2 +- .../Core/DataTransport/HttpCollectorWire.cs | 10 +- .../DependencyInjection/AgentContainer.cs | 127 +++++ .../Core/DependencyInjection/AgentServices.cs | 31 +- .../Core/DependencyInjection/CoreContainer.cs | 96 ---- .../Core/DependencyInjection/IContainer.cs | 6 +- .../DependencyInjection/WindsorContainer.cs | 92 ---- .../DistributedTracePayloadHandler.cs | 14 +- .../Agent/Core/Errors/ExceptionFormatter.cs | 5 +- ...dModuleWireModelCollectionJsonConverter.cs | 55 +++ .../Agent/Core/Labels/LabelsService.cs | 12 +- .../NewRelic/Agent/Core/Logging/AuditLog.cs | 42 +- .../Agent/Core/Logging/InMemorySink.cs | 45 ++ .../Agent/Core/Logging/LogLevelExtensions.cs | 96 ++++ .../NewRelic/Agent/Core/Logging/Logger.cs | 92 ++-- .../Agent/Core/Logging/LoggerBootstrapper.cs | 453 ++++++------------ .../Agent/Core/Logging/NrLogLevelEnricher.cs | 21 + .../Agent/Core/Logging/ProcessIdEnricher.cs | 21 + .../Agent/Core/Logging/ThreadIdEnricher.cs | 20 + .../Agent/Core/Metrics/MetricNameService.cs | 2 +- .../NewRelic.Agent.Core.Metric/MetricNames.cs | 11 + .../Core/NewRelic.Agent.Core/AgentManager.cs | 4 +- .../Core/NewRelic.Agent.Core/AgentShim.cs | 11 +- .../RuntimeEnvironmentInfo.cs | 7 +- .../Agent/Core/Samplers/ThreadStatsSampler.cs | 4 +- .../Agent/Core/Segments/NoOpSegment.cs | 6 + .../NewRelic/Agent/Core/Segments/Segment.cs | 19 +- .../Core/Time/SimpleSchedulingService.cs | 46 ++ .../Agent/Core/Transactions/Transaction.cs | 2 +- .../NewRelic/Agent/Core/Utilities/EventBus.cs | 3 +- .../Agent/Core/Utilities/ExtensionsLoader.cs | 2 + .../Agent/Core/Utilities/RequestBus.cs | 4 +- .../Agent/Core/Utilities/SignalableAction.cs | 6 +- .../Utilities/UpdatedLoadedModulesService.cs | 65 +++ .../Agent/Core/WireModels/IMetricBuilder.cs | 4 + .../Core/WireModels/LoadedModuleWireModel.cs | 23 + .../LoadedModuleWireModelCollection.cs | 223 +++++++++ .../Agent/Core/WireModels/MetricWireModel.cs | 12 + .../Api/Experimental/IAgentExperimental.cs | 2 + .../Api/Experimental/ISegmentExperimental.cs | 10 + .../Experimental/ISimpleSchedulingService.cs | 14 + .../Configuration/IConfiguration.cs | 3 + .../AspNetCore/WrapPipelineMiddleware.cs | 12 +- .../Providers/Wrapper/HttpClient/SendAsync.cs | 4 +- .../HttpWebRequest/SerializeHeadersWrapper.cs | 11 +- .../Wrapper/MongoDb26/Instrumentation.xml | 194 ++++---- .../Providers/Wrapper/Sql/Instrumentation.xml | 71 ++- .../Wrapper/Sql/OpenConnectionWrapper.cs | 47 +- .../StackExchangeRedis2Plus/SessionCache.cs | 85 +++- .../Profiler/ProfiledMethods/newrelic.config | 2 +- .../NewRelic/Profiler/docker-compose.yml | 11 +- .../NewRelic/Profiler/linux/Arm64Dockerfile | 38 ++ src/Agent/Shared/SharedLog4NetRepository.cs | 5 - src/NewRelic.Core/Logging/Log.cs | 32 +- .../BenchmarkingTests.csproj | 8 +- .../OpenRastaWebApplication.csproj | 9 - .../IntegrationTestHelpers/AgentLogBase.cs | 45 +- .../IntegrationTestHelpers/AgentLogFile.cs | 4 +- .../IntegrationTestHelpers/AgentLogString.cs | 29 -- .../Models/UpdateLoadedModulesPayload.cs | 48 ++ .../NewRelicConfigModifier.cs | 13 + .../RemoteApplication.cs | 2 +- .../RemoteApplicationFixture.cs | 44 +- .../VulnerabilityManagementTests.cs | 117 +++++ .../LogLevelAndDirectoryEnvironmentTests.cs | 6 +- .../Logging/LogLevelDenyListTests.cs | 297 ++++++++++++ .../IntegrationTests/WCF/WCFLogHelpers.cs | 4 +- .../Shared/ElasticSearchConfiguration.cs | 75 ++- .../ConsoleDynamicMethodFixture.cs | 2 - .../MultiFunctionApplicationHelpers.csproj | 76 +-- .../ElasticsearchElasticClient.cs | 25 +- .../Elasticsearch/ElasticsearchNestClient.cs | 27 +- .../Elasticsearch/ElasticsearchNetClient.cs | 24 +- .../Elasticsearch/ElasticsearchTestClient.cs | 14 +- .../MongoDB/MongoDBDriverExerciser.cs | 347 +++++++++++++- .../MsSql/MicrosoftDataSqlClientExerciser.cs | 4 +- .../MsSql/SystemDataExerciser.cs | 4 +- .../MsSql/SystemDataSqlClientExerciser.cs | 4 +- .../MySql/MySqlConnectorExerciser.cs | 2 +- .../MySql/MySqlExerciser.cs | 2 +- .../Controllers/OracleController.cs | 2 +- .../MicrosoftDataSqlClientController.cs | 4 +- .../CosmosDB/CosmosDBTests.cs | 46 +- .../Elasticsearch/ElasticsearchTests.cs | 35 +- .../MongoDB/MongoDBDriverCollectionTests.cs | 382 +++++---------- .../MongoDB/MongoDBDriverDatabaseTests.cs | 151 +++--- .../MongoDB/MongoDBDriverIndexManagerTests.cs | 92 +--- .../MsSql/EnterpriseLibraryMsSqlTests.cs | 1 + .../MsSql/MsSqlAsyncTests.cs | 32 +- .../MsSql/MsSqlQueryParamAsyncTests.cs | 1 + .../MsSql/MsSqlQueryParamTests.cs | 1 + .../MsSql/MsSqlStoredProcedureTests.cs | 1 + ...sSqlStoredProcedureUsingOdbcDriverTests.cs | 1 + .../MySql/MySqlAsyncTests.cs | 37 +- .../Oracle/EnterpriseLibraryOracleTests.cs | 9 +- .../Oracle/OracleAsyncTests.cs | 9 +- .../Oracle/OracleStoredProcedureTests.cs | 11 +- .../Oracle/OracleTests.cs | 9 +- .../PostgresExecuteScalarAsyncTests.cs | 5 +- .../Postgres/PostgresSimpleQueryAsyncTests.cs | 5 +- .../UnboundedServices/docker-compose.yml | 23 + .../ConsoleScanner/ConsoleScanner.csproj | 4 +- .../MultiverseScanner.csproj | 2 +- .../ReportBuilder/ReportBuilder.csproj | 2 +- .../NewRelic.Testing.Assertions.csproj | 2 +- .../AsyncLocalTests/AsyncLocalTests.csproj | 6 +- .../CompositeTests/CompositeTestAgent.cs | 35 +- .../CompositeTests/CompositeTests.csproj | 20 +- .../TraceContextCrossAgentTests.cs | 3 +- .../SecurityPoliciesCrossAgentTests.cs | 4 +- .../SqlObfuscationCrossAgentTests.cs | 4 +- .../Utilization/UtilizationCrossAgentTests.cs | 4 +- .../HarvestDisableWhileReconnectingTest.cs | 10 +- .../UnitTests/CompositeTests/SqlTraceTests.cs | 4 + .../StackExchangeRedisSessionCacheTests.cs | 381 +++++++++++++++ .../CompositeTests/TestTransactionContext.cs | 3 +- .../AgentHealth/AgentHealthReporterTests.cs | 41 +- .../Config/ConfigurationLoaderTests.cs | 6 +- .../DefaultConfigurationTests.cs | 94 +++- .../Core.UnitTest/Core.UnitTest.csproj | 26 +- .../CrossAgentTests/CatMapTests.cs | 5 +- .../DataTransport/CollectorHostNameTests.cs | 4 +- .../ServerSentEvent/ServerSentEventTests.cs | 4 +- .../DataTransport/AgentSettingsTests.cs | 2 +- .../DataTransport/ConnectModelTests.cs | 2 +- .../ExhaustiveTestConfiguration.cs | 6 + .../DataTransport/HttpCollectorWireTests.cs | 39 ++ .../DependencyInjection/AgentServicesTests.cs | 5 +- ...leWireModelCollectionJsonConverterTests.cs | 52 ++ ...ntWireModelCollectionJsonConverterTests.cs | 5 - .../Labels/LabelsServiceTests.cs | 40 +- .../Core.UnitTest/Logging/AuditLogTests.cs | 70 +++ .../Logging/InMemorySinkTests.cs | 58 +++ .../Logging/LogLevelExtensionsTests.cs | 113 +++++ .../Core.UnitTest/Logging/LoggerTests.cs | 247 ++++++++++ .../Core.UnitTest/Metrics/MetricNamesTests.cs | 9 + .../BootstrapConfigTest.cs | 8 +- .../AgentLoggerTest.cs | 439 ----------------- .../LoggerBootstrapperTest.cs | 171 +++++++ .../Core.UnitTest/Spans/GrpcWrapperTests.cs | 11 +- .../ThreadProfilingBucketTest.cs | 2 +- .../ThreadProfilingServiceTests.cs | 2 +- .../Time/SimpleSchedulingServiceTests.cs | 81 ++++ .../ErrorTraceMakerTests.cs | 4 + .../TransactionAttributeMakerTests.cs | 5 + .../UpdatedLoadedModulesServiceTests.cs | 121 +++++ .../LoadedModuleWireModelCollectionTests.cs | 358 ++++++++++++++ .../WireModels/LoadedModuleWireModelTests.cs | 30 ++ .../AgentWrapperApi/AgentWrapperApiTests.cs | 65 ++- .../DistributedTracePayloadHandlerTests.cs | 49 ++ .../Core.UnitTest/Wrapper/WrapperService.cs | 5 +- .../NewRelic.Agent.Extensions.Tests.csproj | 6 +- .../AssemblyExtensions.cs | 20 + .../NewRelic.Agent.TestUtilities/Logging.cs | 129 ++--- .../NewRelic.Agent.TestUtilities.csproj | 10 +- ...wRelic.Testing.Assertions.UnitTests.csproj | 2 +- .../Properties/AssemblyInfo.cs | 14 - .../ParsingTests/ParsingTests.csproj | 6 +- .../AwsLambdaOpenTracerTests.csproj | 8 +- .../AwsLambdaWrapperTests.csproj | 8 +- .../NewRelic.Core.Tests.csproj | 6 +- 227 files changed, 6078 insertions(+), 2580 deletions(-) create mode 100644 build/ArtifactBuilder/SecurityHelpers.cs create mode 100644 build/BuildTools.sln create mode 100644 src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentContainer.cs delete mode 100644 src/Agent/NewRelic/Agent/Core/DependencyInjection/CoreContainer.cs delete mode 100644 src/Agent/NewRelic/Agent/Core/DependencyInjection/WindsorContainer.cs create mode 100644 src/Agent/NewRelic/Agent/Core/JsonConverters/LoadedModuleWireModelCollectionJsonConverter.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/InMemorySink.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/LogLevelExtensions.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/NrLogLevelEnricher.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/ProcessIdEnricher.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Logging/ThreadIdEnricher.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Time/SimpleSchedulingService.cs create mode 100644 src/Agent/NewRelic/Agent/Core/Utilities/UpdatedLoadedModulesService.cs create mode 100644 src/Agent/NewRelic/Agent/Core/WireModels/LoadedModuleWireModel.cs create mode 100644 src/Agent/NewRelic/Agent/Core/WireModels/LoadedModuleWireModelCollection.cs create mode 100644 src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/ISimpleSchedulingService.cs create mode 100644 src/Agent/NewRelic/Profiler/linux/Arm64Dockerfile delete mode 100644 src/Agent/Shared/SharedLog4NetRepository.cs delete mode 100644 tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogString.cs create mode 100644 tests/Agent/IntegrationTests/IntegrationTestHelpers/Models/UpdateLoadedModulesPayload.cs create mode 100644 tests/Agent/IntegrationTests/IntegrationTests/AgentFeatures/VulnerabilityManagementTests.cs create mode 100644 tests/Agent/IntegrationTests/IntegrationTests/Logging/LogLevelDenyListTests.cs create mode 100644 tests/Agent/UnitTests/CompositeTests/StackExchangeRedisSessionCacheTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/JsonConverters/LoadedModuleWireModelCollectionJsonConverterTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/Logging/AuditLogTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/Logging/InMemorySinkTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/Logging/LogLevelExtensionsTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/Logging/LoggerTests.cs delete mode 100644 tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/AgentLoggerTest.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/NewRelic.Agent.Core.FromLegacy/LoggerBootstrapperTest.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/Time/SimpleSchedulingServiceTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/Utilities/UpdatedLoadedModulesServiceTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/WireModels/LoadedModuleWireModelCollectionTests.cs create mode 100644 tests/Agent/UnitTests/Core.UnitTest/WireModels/LoadedModuleWireModelTests.cs create mode 100644 tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/AssemblyExtensions.cs delete mode 100644 tests/Agent/UnitTests/NewRelic.Testing.Assertions.UnitTests/Properties/AssemblyInfo.cs diff --git a/.github/workflows/all_solutions.yml b/.github/workflows/all_solutions.yml index f039a94c58..c1e126b3c6 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 0f0596de9a..b07c79234a 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 d12e01d4c3..c01101d90f 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 53602ddc7e..37f209c978 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 0c48637530..0b075940b8 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 7e2611db17..066133a193 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 300a39858c..ebc5e6b68e 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 6df8dd02e3..be2e727344 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 4d9396bf91..33b5a6b94e 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 77f85a3e12..16959319d8 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 0b71ed731c..805ffb9dd5 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 f130251ea6..a88bb66f35 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 bbbc128237..8494b84157 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 cce1c1c9d1..5a690900ca 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 1eef8bd9b0..a57101a80d 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 e2d15937b3..ed209ae837 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 c1af9f45cb..c7d58ef19e 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 ed9594c978..c236d6e6f9 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 8e73d5e0ad..b4eaccd11f 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 ddb5021751..c45fb84acf 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 a456dd88a8..dd5628ee2a 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 549366a936..06a7de4ce4 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 ec86d5cbcb..7d9c99cf95 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 82612dd1af..b7060abb1a 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 d37389222c..6b9431a212 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 3712239dfa..b8d1cede78 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 0ed6501acc..b6a6e16604 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 5b6a8e78cb..2a3ed1dd31 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 3713e9f2c4..a6fee5c1e7 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 26883fc5f8..bbdb6d1fcc 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 d19863617c..4c8d5587ed 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 80170c405d..09e9386870 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 a78a0e91a2..1b2879f758 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 1d72eb3355..dbd22c550d 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 0000000000..1bb3682e45 --- /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 0000000000..2789b312c7 --- /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 2a7cdfac73..9ade047d15 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 6b44a4e8a2..3a022b7d0a 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 c009728ea7..d61e61103e 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 772933d7f8..fbb3c971df 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 c9bb0a468c..1457260337 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 6e7695801b..460423bb54 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 723e0dbc5f..9d3c5c24d7 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 0a827316df..8e048ccc43 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 08881a4241..ae6cfb68a3 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 a8c3be98d2..9f7a76edf1 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 37212fe9d3..43050c5e45 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 83a9472a3c..f1567dceff 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 e58dceb31d..609da830c3 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 e59ba63f2d..907585bfd5 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 010344d7a9..250ed03de3 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 5d02fed964..9943e42e7e 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 aa2bc9474c..87a06b6c75 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 3ee55f8911..bd2b46528c 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 a2d8f63fe8..2c68b6f159 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 b554f45ed8..2ac83d6bcb 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 2ad62e7633..7f0227d26f 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 237943159b..4b30c64a01 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 324189c74b..66c7c7f24d 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 63762c69b0..8b072cbdd8 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 ed2abb61d5..28fb62029e 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 b72509ccaa..9c3389930a 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 e781d13a1a..096bf85349 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 cf61c9713d..8e38d8be42 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 a155edb5e7..03b8ee65af 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 1096781294..715797162c 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 93f9ac210c..e83401b2f8 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 0000000000..fadb253e5e --- /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 7220888a20..d87130831c 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 899595428c..0000000000 --- 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 9e99741b21..0ecb583186 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 e4293cf026..0000000000 --- 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 d6e93a7fd7..b9e44515da 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 73a62ab07a..99ad098360 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 0000000000..7cbb58d470 --- /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 f7e91f3c76..07345ad0a3 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 5c5000e1ce..50fcab3436 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 0000000000..ae48b788fb --- /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 0000000000..c5171e9c7f --- /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 0000000000..5fbc34b31e --- /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 93b9cced20..8826d2158d 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 882d7f27b0..96cbd2661b 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 e4bff496a6..b280ad896f 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 950fe13106..eb6ff4c614 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 067e36c89d..b3fa14f30f 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 68b4a8321b..fcc7691319 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 ab5c5fcbd1..4a2892d3bd 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 c3667805b3..938e1e9397 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 0000000000..b7faf8d1d7 --- /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 71c3e16e10..6af2f98fb8 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 c2ca77c9eb..bddb6a9545 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 f5e10b9a18..fffc8bfa67 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 7dad1e56ff..1cf3d03717 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 72dd2a03ef..9c681666c7 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 0000000000..63a1bfb1b5 --- /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 9c65cd82c7..1223811a7b 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 0000000000..63ec4a6da8 --- /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 0000000000..f03c6d5047 --- /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 c3e75fde57..61d5ce2214 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 d54a198b1d..5040635d79 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 2bf257e31e..9f3ab34bf1 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 0000000000..836497f5a6 --- /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 f6c865ea2b..65dabe6155 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 57349ac050..3b3c223a55 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 4407074b43..9444693898 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 8364f635ae..b31d7d977c 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 9ed3a14fe8..65d9f483a2 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 e4c18395cb..386c2adcf8 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 033994afb2..c7c7bd3c53 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 1b2b1eea01..0d09792201 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 97d43aebbd..83bfaa80f1 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 fcf7339212..cc9e47a695 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 0000000000..de96192166 --- /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 a1d12a3e1c..0000000000 --- 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 90502d279c..d7b0a94182 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 dd9463e623..15b370d528 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 b90b9f51af..c8f7df44f7 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 3d64eeb642..ccd6ff983c 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 6df1312588..087d155a49 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 437d908ea3..0000000000 --- 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 0000000000..8c62dfc852 --- /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 2ffedc826d..bd2ded841d 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 0260748a02..8d2c06487a 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 023f2ca4ba..8e8820310c 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 0000000000..c26925fd20 --- /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 8b56aad1c3..31a20c1e13 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 0000000000..5ca7976da6 --- /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 cfd2c23376..3bfe561b31 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 342a2532ba..408facb8aa 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 cbe9fc328b..56a0443758 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 e3071db4df..4b66db8043 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 c474788afb..fd7d514978 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 1b99d63bdd..f05bfcc0e9 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 ce09d731c1..bbd6a68f57 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 6d1e7ba8f7..f199d35f3b 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 e9c16cb210..3859811e53 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 37ccbb9909..e1ca7f1f29 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 495143e036..33b821dc95 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 0b604e5a22..f36b527dca 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 fa6770a4db..ffef971f87 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 61d102e0be..07f1f220f7 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 1267ce4a58..73167f61b0 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 e145b6be5b..6accd6a5d1 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 409ea21be5..327977c9b4 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 3799e2e427..ed249624a0 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 14dffcc042..e31447ba29 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 80eb3cc8f6..998bb13169 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 6f89b728c0..e8697f997b 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 2ca10f7624..a7f6b3b093 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 d86523bec5..4a0c9ba9de 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 fbf22aa3c3..1068a27c5b 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 bc09f4d68a..52aeada9a2 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 304c37fbf6..d98aa0d197 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 28d3258430..bf949ee0e0 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 85bd1d44c8..9ae1f04bc7 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 877f8a10c3..9cd77b1699 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 0f1ba278dd..ed5e66ebf8 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 68860634f2..e301319585 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 fc19aa4fcd..7ff5d7e022 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 8c6f5f568f..da374fd440 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 86c9dba2b7..b8e6d4517b 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 29990c1ed5..f1b5fa3802 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 4c9f65365a..d6ee55f460 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 026568539e..64a9d18a53 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 2ffc89a896..a321cd3676 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 d4268d6623..a45737fc9b 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 4eaf0be283..25e54eeaa2 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 041575f17f..3ce0fcbf5a 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 a13567f78b..7472411812 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 747226b549..a3809eef68 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 3cb5682f9e..0a8a9baa73 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 2c988a6c64..555719295f 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 24be84f39c..e1f65b6ece 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 62054215e4..425af650cb 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 ece4083ec5..51257382d5 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 0000000000..6eff59e5aa --- /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 ecd21add52..20ce2d92b1 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 0242146394..9b4a44449d 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 b0cd59fe3e..fcdd3172f0 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 d897522add..b8f47961a2 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 edadb1f512..49a8d10cfb 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 3d86193f8f..d2e84fa226 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 a788221c87..173ad151ae 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 cb24049d19..da29920083 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 27cfa245bb..3f857324b6 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 89a172e6d9..efc8e7d149 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 6f1bfe0a56..b241ddc806 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 434878d1dd..396c8d5592 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 f7fe069e87..59af8f9fa4 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 0000000000..6eaa178bef --- /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 a3b9dec7dd..dec30ec5f6 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 ef84692707..8fc327ccc7 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 0000000000..1a72e83450 --- /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 0000000000..3dc9b1c98a --- /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 0000000000..5450103c67 --- /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 0000000000..66bdee6ccb --- /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 c58eedc475..aea598eec6 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 5da055b3a2..3fa33e2cb6 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 3c80ac7242..0000000000 --- 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 0000000000..95dc4f1fd0 --- /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 dfa541ad0b..f0cc15115f 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 705f7bd4b4..1cf456b85a 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 8d83a20fa9..6f1273a390 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 0000000000..37f195be5d --- /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 2a38c1adaa..7b88c75743 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 fa9ffdb9c7..44b0210ef4 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 0000000000..0354a8e8da --- /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 0000000000..2023c13c4c --- /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 0000000000..0082a9692c --- /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 3864bd3826..cf380cf21b 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 587069e36c..7da66bb8af 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 e21c111296..e130ad51cd 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 34074b0dd2..b3dae4d6c5 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 0000000000..fddf71ab4c --- /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 a261bf0287..99a74f819a 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 4505205c09..79e2357c41 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 4badbe0ad1..4c33ad2ac4 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 2a51626e57..0000000000 --- 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 da9191ded4..5066807312 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 3e87239639..abd4117303 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 ea81637014..cf7bb68590 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 c0ec89eb01..480e84acf5 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