Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit 6c29db3

Browse files
committed
Split large messages when logging into EventLog
1 parent f7c9fc3 commit 6c29db3

File tree

6 files changed

+283
-4
lines changed

6 files changed

+283
-4
lines changed

src/Microsoft.Extensions.Logging.EventLog/EventLogLogger.cs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Diagnostics;
6+
using Microsoft.Extensions.Logging.EventLog.Internal;
67

78
namespace Microsoft.Extensions.Logging.EventLog
89
{
@@ -11,9 +12,11 @@ namespace Microsoft.Extensions.Logging.EventLog
1112
/// </summary>
1213
public class EventLogLogger : ILogger
1314
{
14-
private readonly System.Diagnostics.EventLog _eventLog;
1515
private readonly string _name;
1616
private readonly EventLogSettings _settings;
17+
private const string ContinuationString = "...";
18+
private readonly int _beginOrEndMessageSegmentSize;
19+
private readonly int _intermediateMessageSegmentSize;
1720

1821
/// <summary>
1922
/// Initializes a new instance of the <see cref="EventLogLogger"/> class.
@@ -42,9 +45,20 @@ public EventLogLogger(string name, EventLogSettings settings)
4245
// 1. Log name & source name existence check only works on local computer.
4346
// 2. Source name existence check requires Administrative privileges.
4447

45-
_eventLog = new System.Diagnostics.EventLog(logName, machineName, sourceName);
48+
EventLog = settings.EventLog ?? new WindowsEventLog(logName, machineName, sourceName);
49+
50+
// Examples:
51+
// 1. An error occur...
52+
// 2. ...esponse stream
53+
_beginOrEndMessageSegmentSize = EventLog.MaxMessageSize - ContinuationString.Length;
54+
55+
// Example:
56+
// ...red while writ...
57+
_intermediateMessageSegmentSize = EventLog.MaxMessageSize - 2 * ContinuationString.Length;
4658
}
4759

60+
public IEventLog EventLog { get; }
61+
4862
/// <inheritdoc />
4963
public IDisposable BeginScopeImpl(object state)
5064
{
@@ -96,8 +110,52 @@ public void Log(
96110

97111
message = _name + Environment.NewLine + message;
98112

99-
// category '0' translates to 'None' in event log
100-
_eventLog.WriteEntry(message, GetEventLogEntryType(logLevel), eventId, category: 0);
113+
WriteMessage(message, GetEventLogEntryType(logLevel), eventId);
114+
}
115+
116+
// category '0' translates to 'None' in event log
117+
private void WriteMessage(string message, EventLogEntryType eventLogEntryType, int eventId)
118+
{
119+
if (message.Length <= EventLog.MaxMessageSize)
120+
{
121+
EventLog.WriteEntry(message, eventLogEntryType, eventId, category: 0);
122+
return;
123+
}
124+
125+
var startIndex = 0;
126+
string messageSegment = null;
127+
while (true)
128+
{
129+
// Begin segment
130+
// Example: An error occur...
131+
if (startIndex == 0)
132+
{
133+
messageSegment = message.Substring(startIndex, _beginOrEndMessageSegmentSize) + ContinuationString;
134+
startIndex += _beginOrEndMessageSegmentSize;
135+
}
136+
else
137+
{
138+
// Check if rest of the message can fit within the maximum message size
139+
// Example: ...esponse stream
140+
if ((message.Length - (startIndex + 1)) <= _beginOrEndMessageSegmentSize)
141+
{
142+
messageSegment = ContinuationString + message.Substring(startIndex);
143+
EventLog.WriteEntry(messageSegment, eventLogEntryType, eventId, category: 0);
144+
break;
145+
}
146+
else
147+
{
148+
// Example: ...red while writ...
149+
messageSegment =
150+
ContinuationString
151+
+ message.Substring(startIndex, _intermediateMessageSegmentSize)
152+
+ ContinuationString;
153+
startIndex += _intermediateMessageSegmentSize;
154+
}
155+
}
156+
157+
EventLog.WriteEntry(messageSegment, eventLogEntryType, eventId, category: 0);
158+
}
101159
}
102160

103161
private EventLogEntryType GetEventLogEntryType(LogLevel level)

src/Microsoft.Extensions.Logging.EventLog/EventLogSettings.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using Microsoft.Extensions.Logging.EventLog.Internal;
56

67
namespace Microsoft.Extensions.Logging.EventLog
78
{
@@ -29,5 +30,10 @@ public class EventLogSettings
2930
/// The function used to filter events based on the log level.
3031
/// </summary>
3132
public Func<string, LogLevel, bool> Filter { get; set; }
33+
34+
/// <summary>
35+
/// For unit testing purposes only.
36+
/// </summary>
37+
public IEventLog EventLog { get; set; }
3238
}
3339
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Diagnostics;
5+
6+
namespace Microsoft.Extensions.Logging.EventLog.Internal
7+
{
8+
public interface IEventLog
9+
{
10+
int MaxMessageSize { get; }
11+
12+
void WriteEntry(string message, EventLogEntryType type, int eventID, short category);
13+
}
14+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Diagnostics;
5+
using Microsoft.Extensions.Logging.EventLog.Internal;
6+
7+
namespace Microsoft.Extensions.Logging.EventLog
8+
{
9+
public class WindowsEventLog : IEventLog
10+
{
11+
// https://msdn.microsoft.com/EN-US/library/windows/desktop/aa363679.aspx
12+
private const int MaximumMessageSize = 31839;
13+
14+
public WindowsEventLog(string logName, string machineName, string sourceName)
15+
{
16+
DiagnosticsEventLog = new System.Diagnostics.EventLog(logName, machineName, sourceName);
17+
}
18+
19+
public System.Diagnostics.EventLog DiagnosticsEventLog { get; }
20+
21+
public int MaxMessageSize
22+
{
23+
get
24+
{
25+
return MaximumMessageSize;
26+
}
27+
}
28+
29+
public void WriteEntry(string message, EventLogEntryType type, int eventID, short category)
30+
{
31+
DiagnosticsEventLog.WriteEntry(message, type, eventID, category);
32+
}
33+
}
34+
}

test/Microsoft.Extensions.Logging.Test/EventLogLoggerTest.cs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
#if DNX451
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Diagnostics;
8+
using Microsoft.AspNet.Testing.xunit;
59
using Microsoft.Extensions.Logging.EventLog;
10+
using Microsoft.Extensions.Logging.EventLog.Internal;
611
using Xunit;
712

813
namespace Microsoft.Extensions.Logging
@@ -37,6 +42,167 @@ public void CallingBeginScopeOnLogger_ReturnsNonNullableInstance()
3742
// Assert
3843
Assert.NotNull(disposable);
3944
}
45+
46+
[Fact]
47+
public void WindowsEventLog_Constructor_CreatesWithExpectedInformation()
48+
{
49+
// Arrange
50+
var logName = "foo";
51+
var machineName = "bar";
52+
var sourceName = "blah";
53+
54+
// Act
55+
var windowsEventLog = new WindowsEventLog(logName, machineName, sourceName);
56+
57+
// Assert
58+
Assert.NotNull(windowsEventLog.DiagnosticsEventLog);
59+
Assert.Equal(logName, windowsEventLog.DiagnosticsEventLog.Log);
60+
Assert.Equal(machineName, windowsEventLog.DiagnosticsEventLog.MachineName);
61+
Assert.Equal(sourceName, windowsEventLog.DiagnosticsEventLog.Source);
62+
}
63+
64+
[Fact]
65+
public void Constructor_CreatesWindowsEventLog_WithExpectedInformation()
66+
{
67+
// Arrange & Act
68+
var eventLogLogger = new EventLogLogger("Test");
69+
70+
// Assert
71+
var windowsEventLog = Assert.IsType<WindowsEventLog>(eventLogLogger.EventLog);
72+
Assert.Equal("Application", windowsEventLog.DiagnosticsEventLog.Log);
73+
Assert.Equal("Application", windowsEventLog.DiagnosticsEventLog.Source);
74+
Assert.Equal(".", windowsEventLog.DiagnosticsEventLog.MachineName);
75+
}
76+
77+
[Fact]
78+
public void Constructor_CreatesWindowsEventLog_WithSuppliedEventLogSettings()
79+
{
80+
// Arrange
81+
var settings = new EventLogSettings()
82+
{
83+
SourceName = "foo",
84+
LogName = "bar",
85+
MachineName = "blah",
86+
EventLog = null
87+
};
88+
89+
// Act
90+
var eventLogLogger = new EventLogLogger("Test", settings);
91+
92+
// Assert
93+
var windowsEventLog = Assert.IsType<WindowsEventLog>(eventLogLogger.EventLog);
94+
Assert.Equal(settings.LogName, windowsEventLog.DiagnosticsEventLog.Log);
95+
Assert.Equal(settings.SourceName, windowsEventLog.DiagnosticsEventLog.Source);
96+
Assert.Equal(settings.MachineName, windowsEventLog.DiagnosticsEventLog.MachineName);
97+
}
98+
99+
[Theory]
100+
[InlineData(50)]
101+
[InlineData(49)]
102+
[InlineData(36)]
103+
public void MessageWithinMaxSize_WritesFullMessage(int messageSize)
104+
{
105+
// Arrange
106+
var loggerName = "Test";
107+
var maxMessageSize = 50 + loggerName.Length + Environment.NewLine.Length;
108+
var message = new string('a', messageSize);
109+
var expectedMessage = loggerName + Environment.NewLine + message;
110+
var testEventLog = new TestEventLog(maxMessageSize);
111+
var logger = new EventLogLogger(loggerName, new EventLogSettings() { EventLog = testEventLog });
112+
113+
// Act
114+
logger.LogInformation(message);
115+
116+
// Assert
117+
Assert.Equal(1, testEventLog.Messages.Count);
118+
Assert.Equal(expectedMessage, testEventLog.Messages[0]);
119+
}
120+
121+
public static TheoryData<int, string[]> WritesSplitMessagesData
122+
{
123+
get
124+
{
125+
var loggerName = "Test";
126+
127+
return new TheoryData<int, string[]>
128+
{
129+
// loggername + newline combined length is 7
130+
{
131+
1,
132+
new[]
133+
{
134+
loggerName + Environment.NewLine + "a"
135+
}
136+
},
137+
{
138+
5,
139+
new[]
140+
{
141+
loggerName + Environment.NewLine + "a...",
142+
"...aaaa"
143+
}
144+
},
145+
{
146+
10, // equaling the max message size
147+
new[]
148+
{
149+
loggerName + Environment.NewLine + "a...",
150+
"...aaaa...",
151+
"...aaaaa"
152+
}
153+
},
154+
{
155+
15,
156+
new[]
157+
{
158+
loggerName + Environment.NewLine + "a...",
159+
"...aaaa...",
160+
"...aaaa...",
161+
"...aaaaaa"
162+
}
163+
}
164+
};
165+
}
166+
}
167+
168+
[ConditionalTheory]
169+
[OSSkipCondition(OperatingSystems.Linux)]
170+
[OSSkipCondition(OperatingSystems.MacOSX)]
171+
[MemberData(nameof(WritesSplitMessagesData))]
172+
public void MessageExceedingMaxSize_WritesSplitMessages(int messageSize, string[] expectedMessages)
173+
{
174+
// Arrange
175+
var loggerName = "Test";
176+
var maxMessageSize = 10;
177+
var message = new string('a', messageSize);
178+
var testEventLog = new TestEventLog(maxMessageSize);
179+
var logger = new EventLogLogger(loggerName, new EventLogSettings() { EventLog = testEventLog });
180+
181+
// Act
182+
logger.LogInformation(message);
183+
184+
// Assert
185+
Assert.Equal(expectedMessages.Length, testEventLog.Messages.Count);
186+
Assert.Equal(expectedMessages, testEventLog.Messages);
187+
}
188+
189+
private class TestEventLog : IEventLog
190+
{
191+
public TestEventLog(int maxMessageSize)
192+
{
193+
MaxMessageSize = maxMessageSize;
194+
Messages = new List<string>();
195+
}
196+
197+
public int MaxMessageSize { get; }
198+
199+
public List<string> Messages { get; }
200+
201+
public void WriteEntry(string message, EventLogEntryType type, int eventID, short category)
202+
{
203+
Messages.Add(message);
204+
}
205+
}
40206
}
41207
}
42208
#endif

test/Microsoft.Extensions.Logging.Test/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"warningsAsErrors": true
44
},
55
"dependencies": {
6+
"Microsoft.AspNet.Testing": "1.0.0-*",
67
"Microsoft.Extensions.Logging": "1.0.0-*",
78
"Microsoft.Extensions.Logging.Console": "1.0.0-*",
89
"Microsoft.Extensions.Logging.Debug": "1.0.0-*",

0 commit comments

Comments
 (0)