diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/AuditLogFile.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/AuditLogFile.cs new file mode 100644 index 0000000000..f13ca6ca3c --- /dev/null +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/AuditLogFile.cs @@ -0,0 +1,82 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Threading; +using Xunit.Abstractions; + +namespace NewRelic.Agent.IntegrationTestHelpers +{ + public class AuditLogFile : AgentLogBase + { + private readonly string _filePath; + private readonly string _fileName; + + public bool Found => File.Exists(_filePath); + + public AuditLogFile(string logDirectoryPath, ITestOutputHelper testLogger, string fileName = "", TimeSpan? timeoutOrZero = null, bool throwIfNotFound = true) + : base(testLogger) + { + Contract.Assert(logDirectoryPath != null); + + _fileName = fileName; + + var timeout = timeoutOrZero ?? TimeSpan.Zero; + + var timeTaken = Stopwatch.StartNew(); + + var searchPattern = _fileName != string.Empty ? _fileName : "*_audit.log"; + + do + { + var mostRecentlyUpdatedFile = Directory.Exists(logDirectoryPath) ? + Directory.GetFiles(logDirectoryPath, searchPattern) + .OrderByDescending(File.GetLastWriteTimeUtc) + .FirstOrDefault() : null; + + if (mostRecentlyUpdatedFile != null) + { + _filePath = mostRecentlyUpdatedFile; + return; + } + + Thread.Sleep(TimeSpan.FromSeconds(1)); + } while (timeTaken.Elapsed < timeout); + + if (throwIfNotFound) + throw new Exception($"Waited {timeout.TotalSeconds:N0}s but didn't find an audit log matching {Path.Combine(logDirectoryPath, searchPattern)}."); + } + + public override IEnumerable GetFileLines() + { + if (!Found) + yield break; + + string line; + using (var fileStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var streamReader = new StreamReader(fileStream)) + while ((line = streamReader.ReadLine()) != null) + { + yield return line; + } + } + + public string GetFullLogAsString() + { + using (var fileStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var streamReader = new StreamReader(fileStream)) + { + return streamReader.ReadToEnd(); + } + } + + public const string AuditLogLinePrefixRegex = @"^.*?NewRelic Audit: "; + public const string AuditDataSentLogLineRegex = AuditLogLinePrefixRegex + "Data Sent from the InstrumentedApp : .*"; + public const string AuditDataReceivedLogLineRegex = AuditLogLinePrefixRegex + "Data Received from the Collector : .*"; + } +} diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs index 33cee41095..fd5ed7fe79 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs @@ -414,5 +414,11 @@ public NewRelicConfigModifier EnableAspNetCore6PlusBrowserInjection(bool enableB CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(_configFilePath, new[] { "configuration", "appSettings", "add"}, "value", $"{enableBrowserInjection}"); return this; } + + public void EnableAuditLog(bool enableAuditLog) + { + CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(_configFilePath, new[] { "configuration", "log" }, "auditLog", + enableAuditLog.ToString().ToLower()); + } } } diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplicationFixture.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplicationFixture.cs index c4679a7ab6..7aba3981b8 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplicationFixture.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/RemoteServiceFixtures/RemoteApplicationFixture.cs @@ -54,6 +54,10 @@ public void SetTestClassType(Type testClassType) public AgentLogFile AgentLog => _agentLogFile ?? (_agentLogFile = new AgentLogFile(DestinationNewRelicLogFileDirectoryPath, TestLogger, AgentLogFileName, Timing.TimeToWaitForLog, AgentLogExpected)); + private AuditLogFile _auditLogFile; + public bool AuditLogExpected { get; set; } = false; + public AuditLogFile AuditLog => _auditLogFile ?? (_auditLogFile = new AuditLogFile(DestinationNewRelicLogFileDirectoryPath, TestLogger, timeoutOrZero: Timing.TimeToWaitForLog, throwIfNotFound: AuditLogExpected)); + public ProfilerLogFile ProfilerLog { get { return RemoteApplication.ProfilerLog; } } diff --git a/tests/Agent/IntegrationTests/IntegrationTests/Logging/AuditLogTests.cs b/tests/Agent/IntegrationTests/IntegrationTests/Logging/AuditLogTests.cs new file mode 100644 index 0000000000..4a06defb50 --- /dev/null +++ b/tests/Agent/IntegrationTests/IntegrationTests/Logging/AuditLogTests.cs @@ -0,0 +1,59 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Linq; +using NewRelic.Agent.IntegrationTestHelpers; +using Xunit; +using Xunit.Abstractions; + +namespace NewRelic.Agent.IntegrationTests.Logging.AuditLog +{ + [NetCoreTest] + public class AuditLogTests : NewRelicIntegrationTest + { + private readonly RemoteServiceFixtures.AspNetCoreMvcBasicRequestsFixture _fixture; + + public AuditLogTests(RemoteServiceFixtures.AspNetCoreMvcBasicRequestsFixture fixture, ITestOutputHelper output) : + base(fixture) + { + _fixture = fixture; + _fixture.TestLogger = output; + + _fixture.AuditLogExpected = true; + + _fixture.AddActions + ( + setupConfiguration: () => + { + var configModifier = new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath); + + configModifier.EnableAuditLog(true); + }, + exerciseApplication: () => + { + _fixture.Get(); + + _fixture.AgentLog.WaitForLogLine(AgentLogBase.ShutdownLogLineRegex, TimeSpan.FromMinutes(2)); + } + ); + + _fixture.Initialize(); + + } + + [Fact] + public void AuditLogExistsAndHasSentAndReceivedData() + { + Assert.True(_fixture.AuditLog.Found); + + var dataSentLogLines = _fixture.AuditLog.TryGetLogLines(AuditLogFile.AuditDataSentLogLineRegex).ToList(); + var dataReceivedLogLines = _fixture.AuditLog.TryGetLogLines(AuditLogFile.AuditDataReceivedLogLineRegex).ToList(); + + Assert.Multiple(() => + { + Assert.True(dataSentLogLines.Count == 2 * dataReceivedLogLines.Count); // audit log always contains 2 "sent" lines for every "received" line + }); + } + } +}