From 84319d69c723dc7c800f6cc9e53ceec20d5ed86d Mon Sep 17 00:00:00 2001 From: Vadim Penchak Date: Wed, 28 Nov 2018 21:33:27 +0200 Subject: [PATCH] Add unit tests for EventLogTarget To improve testability wrap EventLog with EventLogStaticWrapper and EventLogInstanceWrapper - implementations of IEventLogStaticWrapper and IEventLogInstanceWrapper. Fix spelling, typos, clarify some comments. Refactor several unit tests into a 'Theory' with InlineData, in particular "TruncatedMessagesShould..." and "SplittedMessagesShould..." tests. Add unit tests to check MaxKilobytes initialized from configuration, EventLogTarget.MaxKilobytes setter for positive, negative, corner cases. Add unit tests to check the behaviour during installation using EventLogStaticMock and EventLogInstanceStub. --- src/NLog/Targets/EventLogTarget.cs | 210 ++++++++-- .../Targets/EventLogTargetTests.cs | 369 ++++++++++-------- 2 files changed, 404 insertions(+), 175 deletions(-) diff --git a/src/NLog/Targets/EventLogTarget.cs b/src/NLog/Targets/EventLogTarget.cs index 3b29bfaf23..dbca5b77bb 100644 --- a/src/NLog/Targets/EventLogTarget.cs +++ b/src/NLog/Targets/EventLogTarget.cs @@ -66,7 +66,7 @@ namespace NLog.Targets [Target("EventLog")] public class EventLogTarget : TargetWithLayout, IInstallable { - private EventLog eventLogInstance; + private IEventLogInstanceWrapper eventLogInstanceWrapper; /// /// Initializes a new instance of the class. @@ -98,6 +98,12 @@ public EventLogTarget(string name) Name = name; } + /// + /// Gets or sets the provider of Windows event log static methods to be utilized by . + /// + /// Introduced to improve the testability of the class. + protected internal IEventLogStaticWrapper EventLogStaticMethods { get; set; } = EventLogStaticWrapper.Instance; + /// /// Gets or sets the name of the machine on which Event Log service is running. /// @@ -161,12 +167,11 @@ public int MaxMessageLength /// /// Gets or sets the maximum Event log size in kilobytes. - /// - /// If null, the value won't be set. - /// - /// Default is 512 Kilobytes as specified by Eventlog API /// - /// MaxKilobytes cannot be less than 64 or greater than 4194240 or not a multiple of 64. If null, use the default value + /// + /// MaxKilobytes cannot be less than 64 or greater than 4194240 or not a multiple of 64. + /// If null, the value will not be specified while creating the Event log. The default value of 512 will be set, as specified by Eventlog API. + /// /// [DefaultValue(null)] public long? MaxKilobytes @@ -175,7 +180,7 @@ public int MaxMessageLength set { //Event log API restriction if (value != null && (value < 64 || value > 4194240 || (value % 64 != 0))) - throw new ArgumentException("MaxKilobytes must be a multitude of 64, and between 64 and 4194240"); + throw new ArgumentException("MaxKilobytes must be a multiple of 64, and between 64 and 4194240"); maxKilobytes = value; } } @@ -212,7 +217,7 @@ public void Uninstall(InstallationContext installationContext) } else { - EventLog.DeleteEventSource(fixedSource, MachineName); + EventLogStaticMethods.DeleteEventSource(fixedSource, MachineName); } } @@ -229,7 +234,7 @@ public void Uninstall(InstallationContext installationContext) if (!string.IsNullOrEmpty(fixedSource)) { - return EventLog.SourceExists(fixedSource, MachineName); + return EventLogStaticMethods.SourceExists(fixedSource, MachineName); } InternalLogger.Debug("EventLogTarget(Name={0}): Unclear if event source exists because it contains layout renderers", Name); return null; //unclear! @@ -299,7 +304,7 @@ protected override void Write(LogEventInfo logEvent) internal virtual void WriteEntry(LogEventInfo logEventInfo, string message, EventLogEntryType entryType, int eventId, short category) { - var eventLog = GetEventLog(logEventInfo); + IEventLogInstanceWrapper eventLog = GetEventLog(logEventInfo); eventLog.WriteEntry(message, entryType, eventId, category); } @@ -358,18 +363,18 @@ internal string GetFixedSource() /// /// Event if the source needs to be rendered. /// - private EventLog GetEventLog(LogEventInfo logEvent) + private IEventLogInstanceWrapper GetEventLog(LogEventInfo logEvent) { var renderedSource = RenderSource(logEvent); - var isCacheUpToDate = eventLogInstance != null && renderedSource == eventLogInstance.Source && - eventLogInstance.Log == Log && eventLogInstance.MachineName == MachineName; + var isCacheUpToDate = eventLogInstanceWrapper != null && renderedSource == eventLogInstanceWrapper.Source && + eventLogInstanceWrapper.Log == Log && eventLogInstanceWrapper.MachineName == MachineName; if (!isCacheUpToDate) { - eventLogInstance = new EventLog(Log, MachineName, renderedSource); + eventLogInstanceWrapper = EventLogStaticMethods.CreateEventLogInstanceWrapper(Log, MachineName, renderedSource); } - return eventLogInstance; + return eventLogInstanceWrapper; } internal string RenderSource(LogEventInfo logEvent) @@ -378,7 +383,7 @@ internal string RenderSource(LogEventInfo logEvent) } /// - /// (re-)create a event source, if it isn't there. Works only with fixed sourcenames. + /// (re-)create an event source, if it isn't there. Works only with fixed sourcenames. /// /// sourcename. If source is not fixed (see , then pass null or emptystring. /// always throw an Exception when there is an error @@ -387,26 +392,26 @@ private void CreateEventSourceIfNeeded(string fixedSource, bool alwaysThrowError if (string.IsNullOrEmpty(fixedSource)) { InternalLogger.Debug("EventLogTarget(Name={0}): Skipping creation of event source because it contains layout renderers", Name); - //we can only create event sources if the source is fixed (no layout) + // we can only create event sources if the source is fixed (no layout) return; } // if we throw anywhere, we remain non-operational try { - if (EventLog.SourceExists(fixedSource, MachineName)) + if (EventLogStaticMethods.SourceExists(fixedSource, MachineName)) { - string currentLogName = EventLog.LogNameFromSourceName(fixedSource, MachineName); + string currentLogName = EventLogStaticMethods.LogNameFromSourceName(fixedSource, MachineName); if (!currentLogName.Equals(Log, StringComparison.CurrentCultureIgnoreCase)) { // re-create the association between Log and Source - EventLog.DeleteEventSource(fixedSource, MachineName); + EventLogStaticMethods.DeleteEventSource(fixedSource, MachineName); var eventSourceCreationData = new EventSourceCreationData(fixedSource, Log) { MachineName = MachineName }; - EventLog.CreateEventSource(eventSourceCreationData); + EventLogStaticMethods.CreateEventSource(eventSourceCreationData); } } else @@ -415,12 +420,12 @@ private void CreateEventSourceIfNeeded(string fixedSource, bool alwaysThrowError { MachineName = MachineName }; - EventLog.CreateEventSource(eventSourceCreationData); + EventLogStaticMethods.CreateEventSource(eventSourceCreationData); } if (MaxKilobytes.HasValue && GetEventLog(null).MaximumKilobytes < MaxKilobytes) { - eventLogInstance.MaximumKilobytes = MaxKilobytes.Value; + GetEventLog(null).MaximumKilobytes = MaxKilobytes.Value; } } catch (Exception exception) @@ -432,6 +437,165 @@ private void CreateEventSourceIfNeeded(string fixedSource, bool alwaysThrowError } } } + + /// + /// A wrapper for Windows event log static methods. + /// + protected internal interface IEventLogStaticWrapper + { + /// + /// Creates an event log instance wrapper, that implements interface. + /// + /// A wrapped instance. + IEventLogInstanceWrapper CreateEventLogInstanceWrapper(string logName, string machineName, string source); + + /// + /// A wrapper for the static method . + /// + void DeleteEventSource(string source, string machineName); + + /// + /// A wrapper for the static method . + /// + bool SourceExists(string source, string machineName); + + /// + /// A wrapper for the static method . + /// + string LogNameFromSourceName(string source, string machineName); + + /// + /// A wrapper for the static method . + /// + void CreateEventSource(EventSourceCreationData sourceData); + } + + /// + /// A wrapper for Windows event log instance methods and properties. + /// + protected internal interface IEventLogInstanceWrapper + { + /// + /// A wrapper for the property . + /// + string Source { get; } + + /// + /// A wrapper for the property . + /// + string Log { get; set; } + + /// + /// A wrapper for the property . + /// + string MachineName { get; set; } + + /// + /// A wrapper for the property . + /// + long MaximumKilobytes { get; set; } + + /// + /// A wrapper for the method . + /// + void WriteEntry(string message, EventLogEntryType entryType, int eventId, short category); + } + + /// + /// The implementation of , that uses Windows event log through static methods. + /// + /// + /// System.Lazy{T} is not present in .net35, so using a nested constructor for the singleton implementation. + /// + protected internal sealed class EventLogStaticWrapper : IEventLogStaticWrapper + { + #region Singleton implementation + + private EventLogStaticWrapper() { } + + /// + /// Gets the singleton instance of . + /// + public static EventLogStaticWrapper Instance => Nested.Instance; + + // ReSharper disable once ClassNeverInstantiated.Local + private class Nested + { + // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit + static Nested() + { + } + + internal static readonly EventLogStaticWrapper Instance = new EventLogStaticWrapper(); + } + + #endregion Singleton implementation + + /// + /// + /// Creates a Windows event log instance of type wrapped in the implementation of the interface. + /// + public IEventLogInstanceWrapper CreateEventLogInstanceWrapper(string logName, string machineName, string source) => + new EventLogInstanceWrapper(new EventLog(logName, machineName, source)); + + /// + public void DeleteEventSource(string source, string machineName) => EventLog.DeleteEventSource(source, machineName); + + /// + public bool SourceExists(string source, string machineName) => EventLog.SourceExists(source, machineName); + + /// + public string LogNameFromSourceName(string source, string machineName) => EventLog.LogNameFromSourceName(source, machineName); + + /// + public void CreateEventSource(EventSourceCreationData sourceData) => EventLog.CreateEventSource(sourceData); + } + + /// + /// The implementation of , that uses Windows event log through an instance. + /// + protected internal class EventLogInstanceWrapper : IEventLogInstanceWrapper + { + private readonly EventLog _windowsEventLog; + + /// + /// Initializes the . + /// + /// + protected internal EventLogInstanceWrapper(EventLog windowsEventLog) => _windowsEventLog = windowsEventLog; + + /// + public string Source + { + get => _windowsEventLog.Source; + set => _windowsEventLog.Source = value; + } + + /// + public string Log + { + get => _windowsEventLog.Log; + set => _windowsEventLog.Log = value; + } + + /// + public string MachineName + { + get => _windowsEventLog.MachineName; + set => _windowsEventLog.MachineName = value; + } + + /// + public long MaximumKilobytes + { + get => _windowsEventLog.MaximumKilobytes; + set => _windowsEventLog.MaximumKilobytes = value; + } + + /// + public void WriteEntry(string message, EventLogEntryType entryType, int eventId, short category) => + _windowsEventLog.WriteEntry(message, entryType, eventId, category); + } } } diff --git a/tests/NLog.UnitTests/Targets/EventLogTargetTests.cs b/tests/NLog.UnitTests/Targets/EventLogTargetTests.cs index 787ee73038..bac384e81a 100644 --- a/tests/NLog.UnitTests/Targets/EventLogTargetTests.cs +++ b/tests/NLog.UnitTests/Targets/EventLogTargetTests.cs @@ -115,170 +115,168 @@ public void ConfigurationShouldThrowException_WhenMaxMessageLengthIsNegativeOrZe } [Theory] - [InlineData(0)] - [InlineData(-1)] - public void ShouldThrowException_WhenMaxMessageLengthSetNegativeOrZero(int maxMessageLength) + [InlineData(0)] // Is multiple of 64, but less than the min value of 64 + [InlineData(65)] // Isn't multiple of 64 + [InlineData(4194304)] // Is multiple of 64, but bigger than the max value of 4194240 + public void Configuration_ShouldThrowException_WhenMaxKilobytesIsInvalid(long? maxKilobytes) + { + string configrationText = $@" + + + + + + + + "; + + NLogConfigurationException ex = Assert.Throws(() => XmlLoggingConfiguration.CreateFromXmlString(configrationText)); + Assert.Equal("MaxKilobytes must be a multiple of 64, and between 64 and 4194240", ex.InnerException.InnerException.Message); + } + + [Theory] + [InlineData(0)] // Is multiple of 64, but less than the min value of 64 + [InlineData(65)] // Isn't multiple of 64 + [InlineData(4194304)] // Is multiple of 64, but bigger than the max value of 4194240 + public void MaxKilobytes_ShouldThrowException_WhenMaxKilobytesIsInvalid(long? maxKilobytes) { ArgumentException ex = Assert.Throws(() => { var target = new EventLogTarget(); - target.MaxMessageLength = maxMessageLength; + target.MaxKilobytes = maxKilobytes; }); - Assert.Equal("MaxMessageLength cannot be zero or negative.", ex.Message); + Assert.Equal("MaxKilobytes must be a multiple of 64, and between 64 and 4194240", ex.Message); } - - private void AssertMessageAndLogLevelForTruncatedMessages(LogLevel loglevel, EventLogEntryType expectedEventLogEntryType, string expectedMessage, Layout entryTypeLayout) + [Theory] + // 'null' case is omitted, as it isn't a valid value for Int64 XML property. + [InlineData(64)] // Min value + [InlineData(4194240)] // Max value + [InlineData(16384)] // Acceptable value + public void ConfigurationMaxKilobytes_ShouldBeAsSpecified_WhenMaxKilobytesIsValid(long? maxKilobytes) { - var eventRecords = WriteWithMock(loglevel, expectedEventLogEntryType, expectedMessage, entryTypeLayout, EventLogTargetOverflowAction.Truncate).ToList(); + var expectedMaxKilobytes = maxKilobytes; - Assert.Single(eventRecords); - AssertWrittenMessage(eventRecords, expectedMessage); - } + string configrationText = $@" + + + + + + + + "; + LoggingConfiguration configuration = XmlLoggingConfiguration.CreateFromXmlString(configrationText); - [Fact] - public void TruncatedMessagesShouldBeWrittenAtInformationLevel_WhenNLogLevelIsTrace() - { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Trace, EventLogEntryType.Information, "TruncatedMessagesShouldBeWrittenAtInformationLevel_WhenNLogLevelIsTrace", null); + var eventLog1 = configuration.FindTargetByName("eventLog1"); + Assert.Equal(expectedMaxKilobytes, eventLog1.MaxKilobytes); } - [Fact] - public void TruncatedMessagesShouldBeWrittenAtInformationLevel_WhenNLogLevelIsDebug() + [Theory] + [InlineData(null)] // A possible value, that should not change anything + [InlineData(64)] // Min value + [InlineData(4194240)] // Max value + [InlineData(16384)] // Acceptable value + public void MaxKilobytes_ShouldBeAsSpecified_WhenValueIsValid(long? maxKilobytes) { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Debug, EventLogEntryType.Information, "TruncatedMessagesShouldBeWrittenAtInformationLevel_WhenNLogLevelIsDebug", null); - } + var expectedMaxKilobytes = maxKilobytes; - [Fact] - public void TruncatedMessagesShouldBeWrittenAtInformationLevel_WhenNLogLevelIsInfo() - { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Info, EventLogEntryType.Information, "TruncatedMessagesShouldBeWrittenAtInformationLevel_WhenNLogLevelIsInfo", null); - } + var target = new EventLogTarget(); + target.MaxKilobytes = maxKilobytes; - [Fact] - public void TruncatedMessagesShouldBeWrittenAtWarningLevel_WhenNLogLevelIsWarn() - { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Warn, EventLogEntryType.Warning, "TruncatedMessagesShouldBeWrittenAtWarningLevel_WhenNLogLevelIsWarn", null); + Assert.Equal(expectedMaxKilobytes, target.MaxKilobytes); } - [Fact] - public void TruncatedMessagesShouldBeWrittenAtErrorLevel_WhenNLogLevelIsError() - { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Error, EventLogEntryType.Error, "TruncatedMessagesShouldBeWrittenAtErrorLevel_WhenNLogLevelIsError", null); - } + [Theory] + [InlineData(32768, 16384, 32768)] // Should set MaxKilobytes when value is set and valid + [InlineData(16384, 32768, 32768)] // Should not change MaxKilobytes when initial MaximumKilobytes is bigger + [InlineData(null, EventLogInstanceStub.MaxKBytesDefValue, EventLogInstanceStub.MaxKBytesDefValue)] // Should not change MaxKilobytes when the value is null + public void ShouldSetMaxKilobytes_WhenNeeded(long? newValue, long initialValue, long expectedValue) + { + var target = CreateEventLogTarget("NLog.UnitTests" + Guid.NewGuid().ToString("N"), EventLogTargetOverflowAction.Truncate, 16384); + var eventLogInstanceStub = new EventLogInstanceStub(target.Log, target.MachineName, target.GetFixedSource()) { MaximumKilobytes = initialValue }; + var eventLogStaticMock = new EventLogStaticMock( + sourceExistsFunction: (source, machineName) => false, + deleteEventSourceFunction: (source, machineName) => { }, + logNameFromSourceNameFunction: (source, machineName) => target.Log, + createEventSourceFunction: (sourceData) => { }, + eventLogInstances: new Dictionary + { + {target.Log, eventLogInstanceStub} + }); - [Fact] - public void TruncatedMessagesShouldBeWrittenAtErrorLevel_WhenNLogLevelIsFatal() - { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Fatal, EventLogEntryType.Error, "TruncatedMessagesShouldBeWrittenAtErrorLevel_WhenNLogLevelIsFatal", null); - } + target.EventLogStaticMethods = eventLogStaticMock; - [Fact] - public void TruncatedMessagesShouldBeWrittenAtSuccessAuditLevel_WhenEntryTypeLayoutSpecifiedAsSuccessAudit() - { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Warn, EventLogEntryType.SuccessAudit, "TruncatedMessagesShouldBeWrittenAtSuccessAuditLevel_WhenEntryTypeLayoutSpecifiedAsSuccessAudit", new SimpleLayout("SuccessAudit")); - } + target.MaxKilobytes = newValue; + target.Install(new InstallationContext()); - [Fact] - public void TruncatedMessagesShouldBeWrittenAtSuccessAuditLevel_WhenEntryTypeLayoutSpecifiedAsSuccessAudit_Uppercase() - { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Warn, EventLogEntryType.SuccessAudit, "TruncatedMessagesShouldBeWrittenAtSuccessAuditLevel_WhenEntryTypeLayoutSpecifiedAsSuccessAudit_Uppercase", new SimpleLayout("SUCCESSAUDIT")); + Assert.Equal(expectedValue, eventLogInstanceStub.MaximumKilobytes); } - [Fact] - public void TruncatedMessagesShouldBeWrittenAtFailureAuditLevel_WhenEntryTypeLayoutSpecifiedAsFailureAudit() + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void ShouldThrowException_WhenMaxMessageLengthSetNegativeOrZero(int maxMessageLength) { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Debug, EventLogEntryType.FailureAudit, "TruncatedMessagesShouldBeWrittenAtFailureAuditLevel_WhenEntryTypeLayoutSpecifiedAsFailureAudit", new SimpleLayout("FailureAudit")); - } + ArgumentException ex = Assert.Throws(() => + { + var target = new EventLogTarget(); + target.MaxMessageLength = maxMessageLength; + }); - [Fact] - public void TruncatedMessagesShouldBeWrittenAtErrorLevel_WhenEntryTypeLayoutSpecifiedAsError() - { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Debug, EventLogEntryType.Error, "TruncatedMessagesShouldBeWrittenAtErrorLevel_WhenEntryTypeLayoutSpecifiedAsError", new SimpleLayout("error")); + Assert.Equal("MaxMessageLength cannot be zero or negative.", ex.Message); } - [Fact] - public void TruncatedMessagesShouldBeWrittenAtSpecifiedNLogLevel_WhenWrongEntryTypeLayoutSupplied() - { - AssertMessageAndLogLevelForTruncatedMessages(LogLevel.Warn, EventLogEntryType.Warning, "TruncatedMessagesShouldBeWrittenAtSpecifiedNLogLevel_WhenWrongEntryTypeLayoutSupplied", new SimpleLayout("fallback to auto determined")); - } + [Theory] + [InlineData(0, EventLogEntryType.Information, "AtInformationLevel_WhenNLogLevelIsTrace", null)] + [InlineData(1, EventLogEntryType.Information, "AtInformationLevel_WhenNLogLevelIsDebug", null)] + [InlineData(2, EventLogEntryType.Information, "AtInformationLevel_WhenNLogLevelIsInfo", null)] + [InlineData(3, EventLogEntryType.Warning, "AtWarningLevel_WhenNLogLevelIsWarn", null)] + [InlineData(4, EventLogEntryType.Error, "AtErrorLevel_WhenNLogLevelIsError", null)] + [InlineData(5, EventLogEntryType.Error, "AtErrorLevel_WhenNLogLevelIsFatal", null)] + [InlineData(3, EventLogEntryType.SuccessAudit, "AtSuccessAuditLevel_WhenEntryTypeLayoutSpecifiedAsSuccessAudit", "SuccessAudit")] + [InlineData(3, EventLogEntryType.SuccessAudit, "AtSuccessAuditLevel_WhenEntryTypeLayoutSpecifiedAsSuccessAudit_Uppercase", "SUCCESSAUDIT")] + [InlineData(1, EventLogEntryType.FailureAudit, "AtFailureAuditLevel_WhenEntryTypeLayoutSpecifiedAsFailureAudit", "FailureAudit")] + [InlineData(1, EventLogEntryType.Error, "AtErrorLevel_WhenEntryTypeLayoutSpecifiedAsError", "error")] + [InlineData(3, EventLogEntryType.Warning, "AtSpecifiedNLogLevel_WhenWrongEntryTypeLayoutSupplied", "fallback to auto determined")] + public void TruncatedMessagesShouldBeWrittenAtCorrenpondingNLogLevel(int logLevelOrdinal, EventLogEntryType expectedEventLogEntryType, string expectedMessage, string layoutString) + { + LogLevel logLevel = LogLevel.FromOrdinal(logLevelOrdinal); + Layout entryTypeLayout = layoutString != null ? new SimpleLayout(layoutString) : null; + + var eventRecords = WriteWithMock(logLevel, expectedEventLogEntryType, expectedMessage, entryTypeLayout, EventLogTargetOverflowAction.Truncate).ToList(); + Assert.Single(eventRecords); + AssertWrittenMessage(eventRecords, expectedMessage); + } + [Theory] + [InlineData(0, EventLogEntryType.Information, null)] // AtInformationLevel_WhenNLogLevelIsTrace + [InlineData(1, EventLogEntryType.Information, null)] // AtInformationLevel_WhenNLogLevelIsDebug + [InlineData(2, EventLogEntryType.Information, null)] // AtInformationLevel_WhenNLogLevelIsInfo + [InlineData(3, EventLogEntryType.Warning, null)] // AtWarningLevel_WhenNLogLevelIsWarn + [InlineData(4, EventLogEntryType.Error, null)] // AtErrorLevel_WhenNLogLevelIsError + [InlineData(5, EventLogEntryType.Error, null)] // AtErrorLevel_WhenNLogLevelIsFatal + [InlineData(1, EventLogEntryType.SuccessAudit, "SuccessAudit")] // AtSuccessAuditLevel_WhenEntryTypeLayoutSpecifiedAsSuccessAudit + [InlineData(1, EventLogEntryType.FailureAudit, "FailureAudit")] // AtFailureLevel_WhenEntryTypeLayoutSpecifiedAsFailureAudit + [InlineData(1, EventLogEntryType.Error, "error")] // AtErrorLevel_WhenEntryTypeLayoutSpecifiedAsError + [InlineData(2, EventLogEntryType.Information, "wrong entry type level")] // AtSpecifiedNLogLevel_WhenWrongEntryTypeLayoutSupplied + public void SplitMessagesShouldBeWrittenAtCorrenpondingNLogLevel(int logLevelOrdinal, EventLogEntryType expectedEventLogEntryType, string layoutString) + { + LogLevel logLevel = LogLevel.FromOrdinal(logLevelOrdinal); + Layout entryTypeLayout = layoutString != null ? new SimpleLayout(layoutString) : null; - private void AssertMessageCountAndLogLevelForSplittedMessages(LogLevel loglevel, EventLogEntryType expectedEventLogEntryType, Layout entryTypeLayout) - { const int maxMessageLength = 16384; const int expectedEntryCount = 2; string messagePart1 = string.Join("", Enumerable.Repeat("l", maxMessageLength)); - string messagePart2 = "this part must be splitted"; + string messagePart2 = "this part must be split"; string testMessage = messagePart1 + messagePart2; - var entries = WriteWithMock(loglevel, expectedEventLogEntryType, testMessage, entryTypeLayout, EventLogTargetOverflowAction.Split, maxMessageLength).ToList(); + var entries = WriteWithMock(logLevel, expectedEventLogEntryType, testMessage, entryTypeLayout, EventLogTargetOverflowAction.Split, maxMessageLength).ToList(); Assert.Equal(expectedEntryCount, entries.Count); } - [Fact] - public void SplittedMessagesShouldBeWrittenAtInformationLevel_WhenNLogLevelIsTrace() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Trace, EventLogEntryType.Information, null); - } - - [Fact] - public void SplittedMessagesShouldBeWrittenAtInformationLevel_WhenNLogLevelIsDebug() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Debug, EventLogEntryType.Information, null); - } - - [Fact] - public void SplittedMessagesShouldBeWrittenAtInformationLevel_WhenNLogLevelIsInfo() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Info, EventLogEntryType.Information, null); - } - - [Fact] - public void SplittedMessagesShouldBeWrittenAtWarningLevel_WhenNLogLevelIsWarn() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Warn, EventLogEntryType.Warning, null); - } - - [Fact] - public void SplittedMessagesShouldBeWrittenAtErrorLevel_WhenNLogLevelIsError() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Error, EventLogEntryType.Error, null); - } - - [Fact] - public void SplittedMessagesShouldBeWrittenAtErrorLevel_WhenNLogLevelIsFatal() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Fatal, EventLogEntryType.Error, null); - } - - [Fact] - public void SplittedMessagesShouldBeWrittenAtSuccessAuditLevel_WhenEntryTypeLayoutSpecifiedAsSuccessAudit() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Debug, EventLogEntryType.SuccessAudit, new SimpleLayout("SuccessAudit")); - } - - [Fact] - public void SplittedMessagesShouldBeWrittenAtFailureLevel_WhenEntryTypeLayoutSpecifiedAsFailureAudit() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Debug, EventLogEntryType.FailureAudit, new SimpleLayout("FailureAudit")); - } - - [Fact] - public void SplittedMessagesShouldBeWrittenAtErrorLevel_WhenEntryTypeLayoutSpecifiedAsError() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Debug, EventLogEntryType.Error, new SimpleLayout("error")); - } - - - [Fact] - public void SplittedMessagesShouldBeWrittenAtSpecifiedNLogLevel_WhenWrongEntryTypeLayoutSupplied() - { - AssertMessageCountAndLogLevelForSplittedMessages(LogLevel.Info, EventLogEntryType.Information, new SimpleLayout("wrong entry type level")); - } - - [Fact] public void WriteEventLogEntryLargerThanMaxMessageLengthWithOverflowTruncate_TruncatesTheMessage() { @@ -305,7 +303,7 @@ public void WriteEventLogEntryEqualToMaxMessageLengthWithOverflowTruncate_TheMes } [Fact] - public void WriteEventLogEntryLargerThanMaxMessageLengthWithOverflowSplitEntries_TheMessageShouldBeSplitted() + public void WriteEventLogEntryLargerThanMaxMessageLengthWithOverflowSplitEntries_TheMessageShouldBeSplit() { const int maxMessageLength = 16384; const int expectedEntryCount = 5; @@ -313,7 +311,7 @@ public void WriteEventLogEntryLargerThanMaxMessageLengthWithOverflowSplitEntries string messagePart2 = string.Join("", Enumerable.Repeat("b", maxMessageLength)); string messagePart3 = string.Join("", Enumerable.Repeat("c", maxMessageLength)); string messagePart4 = string.Join("", Enumerable.Repeat("d", maxMessageLength)); - string messagePart5 = "this part must be splitted too"; + string messagePart5 = "this part must be split too"; string testMessage = messagePart1 + messagePart2 + messagePart3 + messagePart4 + messagePart5; var entries = WriteWithMock(LogLevel.Info, EventLogEntryType.Information, testMessage, null, EventLogTargetOverflowAction.Split, maxMessageLength).ToList(); @@ -328,7 +326,7 @@ public void WriteEventLogEntryLargerThanMaxMessageLengthWithOverflowSplitEntries } [Fact] - public void WriteEventLogEntryEqual2MaxMessageLengthWithOverflowSplitEntries_TheMessageShouldBeSplittedInTwoChunk() + public void WriteEventLogEntryEqualToMaxMessageLengthWithOverflowSplitEntries_TheMessageShouldBeSplitInTwoChunks() { const int maxMessageLength = 16384; const int expectedEntryCount = 2; @@ -389,7 +387,7 @@ public void WriteEventLogEntryWithDynamicSource() const int maxMessageLength = 10; string expectedMessage = string.Join("", Enumerable.Repeat("a", maxMessageLength)); - var target = CreateEventLogTarget(null, "NLog.UnitTests" + Guid.NewGuid().ToString("N"), EventLogTargetOverflowAction.Split, maxMessageLength); + var target = CreateEventLogTarget("NLog.UnitTests" + Guid.NewGuid().ToString("N"), EventLogTargetOverflowAction.Split, maxMessageLength); target.Layout = new SimpleLayout("${message}"); target.Source = new SimpleLayout("${event-properties:item=DynamicSource}"); SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); @@ -426,7 +424,7 @@ public void LogEntryWithStaticEventIdAndCategoryInTargetLayout() var rnd = new Random(); int eventId = rnd.Next(1, short.MaxValue); int category = rnd.Next(1, short.MaxValue); - var target = CreateEventLogTarget(null, "NLog.UnitTests" + Guid.NewGuid().ToString("N"), EventLogTargetOverflowAction.Truncate, 5000); + var target = CreateEventLogTarget("NLog.UnitTests" + Guid.NewGuid().ToString("N"), EventLogTargetOverflowAction.Truncate, 5000); target.EventId = new SimpleLayout(eventId.ToString()); target.Category = new SimpleLayout(category.ToString()); SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); @@ -451,7 +449,7 @@ public void LogEntryWithDynamicEventIdAndCategory() var rnd = new Random(); int eventId = rnd.Next(1, short.MaxValue); int category = rnd.Next(1, short.MaxValue); - var target = CreateEventLogTarget(null, "NLog.UnitTests" + Guid.NewGuid().ToString("N"), EventLogTargetOverflowAction.Truncate, 5000); + var target = CreateEventLogTarget("NLog.UnitTests" + Guid.NewGuid().ToString("N"), EventLogTargetOverflowAction.Truncate, 5000); target.EventId = new SimpleLayout("${event-properties:EventId}"); target.Category = new SimpleLayout("${event-properties:Category}"); SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); @@ -476,7 +474,7 @@ public void LogEntryWithDynamicEventIdAndCategory() private static IEnumerable WriteWithMock(LogLevel logLevel, EventLogEntryType expectedEventLogEntryType, string logMessage, Layout entryType = null, EventLogTargetOverflowAction overflowAction = EventLogTargetOverflowAction.Truncate, int maxMessageLength = 16384) { - var target = CreateEventLogTarget(entryType, "NLog.UnitTests" + Guid.NewGuid().ToString("N"), overflowAction, maxMessageLength); + var target = CreateEventLogTarget("NLog.UnitTests" + Guid.NewGuid().ToString("N"), overflowAction, maxMessageLength, entryType); SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); var logger = LogManager.GetLogger("WriteEventLogEntry"); @@ -513,8 +511,6 @@ public EventRecordMock(int id, string logName, string providerName, EventLogEntr LogName = logName; ProviderName = providerName; - - if (type == EventLogEntryType.FailureAudit) { Keywords = (long)StandardEventKeywords.AuditFailure; @@ -534,13 +530,10 @@ public EventRecordMock(int id, string logName, string providerName, EventLogEntr Level = (byte)StandardEventLevel.Informational; } - var eventProperty = CreateEventProperty(message); Properties = new List { eventProperty }; - - - } + /// /// EventProperty ctor is internal /// @@ -695,7 +688,6 @@ public EventLogTargetMock() internal override void WriteEntry(LogEventInfo logEventInfo, string message, EventLogEntryType entryType, int eventId, short category) { - var source = RenderSource(logEventInfo); CapturedEvents.Add(new EventRecordMock(eventId, Log, source, entryType, message, category)); @@ -710,25 +702,24 @@ private void AssertWrittenMessage(IEnumerable eventLogs, string exp Assert.True(messages.Any(), $"Event records has not the expected message: '{expectedMessage}'"); } - private static TEventLogTarget CreateEventLogTarget(Layout entryType, string sourceName, EventLogTargetOverflowAction overflowAction, int maxMessageLength) + private static TEventLogTarget CreateEventLogTarget( + string sourceName, EventLogTargetOverflowAction overflowAction, int maxMessageLength, Layout entryType = null) where TEventLogTarget : EventLogTarget, new() { - var target = new TEventLogTarget(); - //The Log to write to is intentionally lower case!! - target.Log = "application"; - // set the source explicitly to prevent random AppDomain name being used as the source name - target.Source = sourceName; - //Be able to check message length and content, the Layout is intentionally only ${message}. - target.Layout = new SimpleLayout("${message}"); + var target = new TEventLogTarget + { + Log = "application", // The Log to write to is intentionally lower case!! + Source = sourceName, // set the source explicitly to prevent random AppDomain name being used as the source name + Layout = new SimpleLayout("${message}"), //Be able to check message length and content, the Layout is intentionally only ${message}. + OnOverflow = overflowAction, + MaxMessageLength = maxMessageLength, + }; + if (entryType != null) { - //set only when not default target.EntryType = entryType; } - target.OnOverflow = overflowAction; - target.MaxMessageLength = maxMessageLength; - return target; } @@ -770,6 +761,80 @@ private static bool HasEntryType(EventRecord eventRecord, EventLogEntryType entr } return false; } + + private class EventLogStaticMock : EventLogTarget.IEventLogStaticWrapper + { + public EventLogStaticMock( + Func sourceExistsFunction, + Action deleteEventSourceFunction, + Func logNameFromSourceNameFunction, + Action createEventSourceFunction, + IDictionary eventLogInstances) + { + SourceExistsFunction = sourceExistsFunction ?? throw new ArgumentNullException(nameof(sourceExistsFunction)); + DeleteEventSourceFunction = deleteEventSourceFunction ?? throw new ArgumentNullException(nameof(deleteEventSourceFunction)); + LogNameFromSourceNameFunction = logNameFromSourceNameFunction ?? throw new ArgumentNullException(nameof(logNameFromSourceNameFunction)); + CreateEventSourceFunction = createEventSourceFunction ?? throw new ArgumentNullException(nameof(createEventSourceFunction)); + EventLogInstances = eventLogInstances; + } + + internal IDictionary EventLogInstances { get; } + + private Func SourceExistsFunction { get; } + private Action DeleteEventSourceFunction { get; } + private Func LogNameFromSourceNameFunction { get; } + private Action CreateEventSourceFunction { get; } + + /// + public EventLogTarget.IEventLogInstanceWrapper CreateEventLogInstanceWrapper(string logName, string machineName, string source) + { + return EventLogInstances[logName]; + } + + /// + public void DeleteEventSource(string source, string machineName) => DeleteEventSourceFunction(source, machineName); + + /// + public bool SourceExists(string source, string machineName) => SourceExistsFunction(source, machineName); + + /// + public string LogNameFromSourceName(string source, string machineName) => LogNameFromSourceNameFunction(source, machineName); + + /// + public void CreateEventSource(EventSourceCreationData sourceData) => CreateEventSourceFunction(sourceData); + } + + private class EventLogInstanceStub : EventLogTarget.IEventLogInstanceWrapper + { + public const long MaxKBytesDefValue = 512; + + public EventLogInstanceStub(string logName, string machineName, string source) + { + Log = logName; + MachineName = machineName; + Source = source; + } + + /// + public string Log { get; set; } + + /// + public string MachineName { get; set; } + + /// + public string Source { get; } + + /// + public long MaximumKilobytes { get; set; } = MaxKBytesDefValue; + + internal List WrittenEntries { get; } = new List(); + + /// + public void WriteEntry(string message, EventLogEntryType entryType, int eventId, short category) + { + WrittenEntries.Add(new EventRecordMock(eventId, Log, Source, entryType, message, category)); + } + } } }