Skip to content

Commit 26d2f7c

Browse files
committed
Add metadata/marker file logger to set of supported loggers.
1 parent b089b8c commit 26d2f7c

23 files changed

Lines changed: 776 additions & 185 deletions

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.1.36
1+
2.1.37

src/VirtualClient/VirtualClient.Contracts.UnitTests/VirtualClientLoggingExtensionsTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,26 @@ public void AddProcessResultsExtensionAddsTheExpectedProcessInformationToTheEven
287287
}
288288

289289
[Test]
290+
[TestCase(null, null)]
291+
[TestCase("", "")]
292+
[TestCase(" ", " ")]
293+
[TestCase("p", "p")]
294+
[TestCase("P", "p")]
295+
[TestCase("MAC", "mac")]
290296
[TestCase("PropertyName", "propertyName")]
291297
[TestCase("propertyName", "propertyName")]
298+
[TestCase("propertyname", "propertyname")]
299+
[TestCase("IPAddress", "ipAddress")]
300+
[TestCase("MACAddress", "macAddress")]
301+
[TestCase("OperatingSystemPlatform", "operatingSystemPlatform")]
302+
[TestCase("operatingSystemPlatform", "operatingSystemPlatform")]
303+
[TestCase("Property_Name", "property_Name")]
304+
[TestCase("property_Name", "property_Name")]
305+
[TestCase("Operating_System_Platform", "operating_System_Platform")]
306+
[TestCase("operating_System_Platform", "operating_System_Platform")]
307+
[TestCase("MAC_Address", "mac_Address")]
308+
[TestCase("_MAC_Address", "_mac_Address")]
309+
[TestCase("__MAC__Address", "__mac__Address")]
292310
public void CamelCasedExtensionCreatesTheExpectedPropertyName(string propertyName, string expectedValue)
293311
{
294312
string actualValue = propertyName.CamelCased();

src/VirtualClient/VirtualClient.Contracts/AliasAttribute.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
namespace VirtualClient.Contracts
55
{
66
using System;
7-
using System.Collections.Generic;
87
using VirtualClient.Common.Extensions;
98

109
/// <summary>
@@ -20,12 +19,12 @@ public class AliasAttribute : Attribute
2019
public AliasAttribute(string alias)
2120
{
2221
alias.ThrowIfNullOrEmpty(nameof(alias));
23-
this.Aliases = alias.Split(',');
22+
this.Alias = alias;
2423
}
2524

2625
/// <summary>
2726
/// The aliases for the logger provider.
2827
/// </summary>
29-
public IEnumerable<string> Aliases { get; }
28+
public string Alias { get; }
3029
}
3130
}

src/VirtualClient/VirtualClient.Contracts/ComponentTypeCache.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public bool TryGetComponentTypes(Type baseType, string alias, [DynamicallyAccess
151151
IEnumerable<AliasAttribute> aliases = matchedType.Type.GetCustomAttributes<AliasAttribute>();
152152
foreach (AliasAttribute attribute in aliases)
153153
{
154-
if (attribute.Aliases.Contains(alias, StringComparer.OrdinalIgnoreCase))
154+
if (string.Equals(attribute.Alias, alias, StringComparison.OrdinalIgnoreCase))
155155
{
156156
matchingAliasedTypes.Add(matchedType.Type);
157157
break;

src/VirtualClient/VirtualClient.Contracts/VirtualClientLoggingExtensions.cs

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace VirtualClient.Contracts
88
using System.Diagnostics;
99
using System.Linq;
1010
using System.Net.Http;
11+
using System.Reflection.Metadata;
1112
using System.Runtime.InteropServices;
1213
using System.Threading.Tasks;
1314
using Microsoft.Extensions.Logging;
@@ -299,20 +300,60 @@ public static EventContext AddResponseContext(this EventContext telemetryContext
299300
/// <param name="propertyName">The property name to camel-case.</param>
300301
public static string CamelCased(this string propertyName)
301302
{
302-
// We are not trying to get overly fancy here. We just make sure the first character
303-
// is lower-cased and leave it at that.
304-
return $"{propertyName.Substring(0, 1).ToLowerInvariant()}{propertyName.Substring(1)}";
305-
}
303+
if (string.IsNullOrWhiteSpace(propertyName))
304+
{
305+
return propertyName;
306+
}
306307

307-
/// <summary>
308-
/// Returns a pascal-cased version of the property name (e.g. someValue -> SomeValue).
309-
/// </summary>
310-
/// <param name="propertyName">The property name to pascal-case.</param>
311-
public static string PascalCased(this string propertyName)
312-
{
313-
// We are not trying to get overly fancy here. We just make sure the first character
314-
// is lower-cased and leave it at that.
315-
return $"{propertyName.Substring(0, 1).ToUpperInvariant()}{propertyName.Substring(1)}";
308+
// Should handle all of the following correctly:
309+
// - PropertyName -> propertyName
310+
// - OperatingSystemPlatform -> operatingSystemPlatform
311+
// - IPAddress -> ipAddress
312+
// - MACAddress -> macAddress
313+
// - Property_Name -> property_Name
314+
315+
int precedingUpperCasedLetterCount = 0;
316+
317+
// Count the number of letters from the start of the word/property that are
318+
// upper-cased.
319+
foreach (char letter in propertyName)
320+
{
321+
if (char.IsUpper(letter) || !char.IsAsciiLetter(letter))
322+
{
323+
precedingUpperCasedLetterCount++;
324+
continue;
325+
}
326+
327+
break;
328+
}
329+
330+
// Case:
331+
// No upper case letters at all (e.g. timestamp).
332+
if (precedingUpperCasedLetterCount == 0)
333+
{
334+
return propertyName;
335+
}
336+
337+
// Case:
338+
// There is only 1 upper case letter at the start (e.g. Property).
339+
if (precedingUpperCasedLetterCount == 1)
340+
{
341+
return propertyName.Substring(0, 1).ToLowerInvariant() + propertyName.Substring(1);
342+
}
343+
344+
// Case:
345+
// The entire word is upper-cased (e.g. MAC).
346+
if (precedingUpperCasedLetterCount == propertyName.Length)
347+
{
348+
return propertyName.ToLowerInvariant();
349+
}
350+
351+
// Case:
352+
// There are multiple upper case letters at the start of the word (e.g. MACAddress).
353+
string prefix = propertyName.Substring(0, precedingUpperCasedLetterCount - 1).ToLowerInvariant();
354+
string suffix = propertyName.Substring(precedingUpperCasedLetterCount - 1);
355+
356+
return $"{prefix}{suffix}";
316357
}
317358

318359
/// <summary>
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace VirtualClient.Logging
5+
{
6+
using System;
7+
using System.Collections.Generic;
8+
using System.IO;
9+
using System.Runtime.InteropServices;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Moq;
12+
using NUnit.Framework;
13+
using VirtualClient.Common.Extensions;
14+
using VirtualClient.Contracts;
15+
16+
[TestFixture]
17+
[Category("Unit")]
18+
public class MetadataFileLoggerTests
19+
{
20+
private MockFixture mockFixture;
21+
22+
public void SetupTest(PlatformID platform)
23+
{
24+
this.mockFixture = new MockFixture();
25+
this.mockFixture.Setup(platform, useUnixStylePathsOnly: true);
26+
}
27+
28+
[Test]
29+
[TestCase(PlatformID.Unix)]
30+
[TestCase(PlatformID.Win32NT)]
31+
public void MetadataFileLoggerIdentifiesTheExpectedMetadata(PlatformID platform)
32+
{
33+
this.SetupTest(platform);
34+
35+
var logger = new TestMetadataFileLogger(this.mockFixture.Dependencies, null);
36+
IDictionary<string, IConvertible> metadata = logger.GetMetadata();
37+
38+
Assert.IsTrue(metadata.ContainsKey("clientId"));
39+
Assert.IsTrue(metadata.ContainsKey("experimentId"));
40+
Assert.IsTrue(metadata.ContainsKey("machineName"));
41+
Assert.IsTrue(metadata.ContainsKey("platformArchitecture"));
42+
Assert.IsTrue(metadata.ContainsKey("operatingSystemVersion"));
43+
Assert.IsTrue(metadata.ContainsKey("operatingSystemDescription"));
44+
Assert.IsTrue(metadata.ContainsKey("timestamp"));
45+
Assert.IsTrue(metadata.ContainsKey("timezone"));
46+
47+
ISystemInfo systemInfo = this.mockFixture.Dependencies.GetService<ISystemInfo>();
48+
PlatformSpecifics platformSpecifics = this.mockFixture.PlatformSpecifics;
49+
50+
Assert.AreEqual(systemInfo.AgentId, metadata["clientId"]);
51+
Assert.AreEqual(systemInfo.ExperimentId, metadata["experimentId"]);
52+
Assert.AreEqual(Environment.MachineName, metadata["machineName"]);
53+
Assert.AreEqual(platformSpecifics.PlatformArchitectureName, metadata["platformArchitecture"]);
54+
Assert.AreEqual(Environment.OSVersion.ToString(), metadata["operatingSystemVersion"]);
55+
Assert.AreEqual(RuntimeInformation.OSDescription, metadata["operatingSystemDescription"]);
56+
Assert.AreEqual(TimeZoneInfo.Local.StandardName, metadata["timezone"]);
57+
}
58+
59+
[Test]
60+
[TestCase(PlatformID.Unix)]
61+
[TestCase(PlatformID.Win32NT)]
62+
public void MetadataFileLoggerWritesTheExpectedMetadataToFile(PlatformID platform)
63+
{
64+
this.SetupTest(platform);
65+
66+
var logger = new TestMetadataFileLogger(this.mockFixture.Dependencies, null);
67+
IDictionary<string, IConvertible> expectedMetadata = logger.GetMetadata();
68+
69+
this.mockFixture.FileSystem
70+
.Setup(fs => fs.Path.GetFullPath(It.IsAny<string>()))
71+
.Returns<string>(path => Path.GetFullPath(path));
72+
73+
this.mockFixture.FileSystem
74+
.Setup(fs => fs.File.WriteAllText(It.IsAny<string>(), It.IsAny<string>()))
75+
.Callback<string, string>((filePath, content) =>
76+
{
77+
Assert.IsTrue(content.Contains("clientId"));
78+
Assert.IsTrue(content.Contains("experimentId"));
79+
Assert.IsTrue(content.Contains("machineName"));
80+
Assert.IsTrue(content.Contains("platformArchitecture"));
81+
Assert.IsTrue(content.Contains("operatingSystemVersion"));
82+
Assert.IsTrue(content.Contains("operatingSystemDescription"));
83+
Assert.IsTrue(content.Contains("timestamp"));
84+
Assert.IsTrue(content.Contains("timezone"));
85+
});
86+
87+
logger.WriteMetadataFile(expectedMetadata);
88+
}
89+
90+
[Test]
91+
[Platform("Win")]
92+
[TestCase(PlatformID.Unix)]
93+
[TestCase(PlatformID.Win32NT)]
94+
public void MetadataFileLoggerWritesTheMetadataToTheExpectedFilePathLocation(PlatformID platform)
95+
{
96+
this.SetupTest(platform);
97+
98+
string expectedFilePath = this.mockFixture.PlatformSpecifics.GetLogsPath("metadata.log");
99+
bool confirmed = false;
100+
101+
var logger = new TestMetadataFileLogger(this.mockFixture.Dependencies, null);
102+
var metadata = new Dictionary<string, IConvertible>
103+
{
104+
{ "any", "metadata" }
105+
};
106+
107+
this.mockFixture.FileSystem
108+
.Setup(fs => fs.Path.GetFullPath(It.IsAny<string>()))
109+
.Returns<string>(path => Path.GetFullPath(path));
110+
111+
this.mockFixture.FileSystem
112+
.Setup(fs => fs.File.WriteAllText(It.IsAny<string>(), It.IsAny<string>()))
113+
.Callback<string, string>((actualFilePath, content) =>
114+
{
115+
Assert.AreEqual(expectedFilePath, actualFilePath);
116+
confirmed = true;
117+
});
118+
119+
logger.WriteMetadataFile(metadata);
120+
Assert.IsTrue(confirmed);
121+
}
122+
123+
[Test]
124+
[Platform("Win")]
125+
[TestCase(PlatformID.Unix)]
126+
[TestCase(PlatformID.Win32NT)]
127+
public void MetadataFileLoggerWritesTheMetadataToTheExpectedFilePathLocationWhenAFileNameIsDefined(PlatformID platform)
128+
{
129+
this.SetupTest(platform);
130+
131+
string expectedFileName = "marker.log";
132+
string expectedFilePath = this.mockFixture.PlatformSpecifics.GetLogsPath(expectedFileName);
133+
bool confirmed = false;
134+
135+
var logger = new TestMetadataFileLogger(this.mockFixture.Dependencies, expectedFileName);
136+
var metadata = new Dictionary<string, IConvertible>
137+
{
138+
{ "any", "metadata" }
139+
};
140+
141+
this.mockFixture.FileSystem.Setup(fs => fs.Path.GetDirectoryName(It.IsAny<string>()))
142+
.Returns(null as string);
143+
144+
this.mockFixture.FileSystem
145+
.Setup(fs => fs.File.WriteAllText(It.IsAny<string>(), It.IsAny<string>()))
146+
.Callback<string, string>((actualFilePath, content) =>
147+
{
148+
Assert.AreEqual(expectedFilePath, actualFilePath);
149+
confirmed = true;
150+
});
151+
152+
logger.WriteMetadataFile(metadata);
153+
Assert.IsTrue(confirmed);
154+
}
155+
156+
[Test]
157+
[Platform("Win")]
158+
[TestCase(".\\logs\\marker.log")]
159+
[TestCase("..\\..\\logs\\marker.log")]
160+
[TestCase("..\\..\\test\\marker.log")]
161+
public void MetadataFileLoggerWritesTheMetadataToTheExpectedFilePathLocationWhenARelativeFilePathIsDefined(string relativeFilePath)
162+
{
163+
this.SetupTest(PlatformID.Win32NT);
164+
165+
string expectedFilePath = Path.GetFullPath(relativeFilePath);
166+
bool confirmed = false;
167+
168+
var logger = new TestMetadataFileLogger(this.mockFixture.Dependencies, relativeFilePath);
169+
var metadata = new Dictionary<string, IConvertible>
170+
{
171+
{ "any", "metadata" }
172+
};
173+
174+
this.mockFixture.FileSystem.Setup(fs => fs.Path.GetDirectoryName(It.IsAny<string>()))
175+
.Returns<string>(path => Path.GetDirectoryName(path));
176+
177+
this.mockFixture.FileSystem
178+
.Setup(fs => fs.Path.GetFullPath(It.IsAny<string>()))
179+
.Returns<string>(path => Path.GetFullPath(path));
180+
181+
this.mockFixture.FileSystem
182+
.Setup(fs => fs.File.WriteAllText(It.IsAny<string>(), It.IsAny<string>()))
183+
.Callback<string, string>((actualFilePath, content) =>
184+
{
185+
Assert.AreEqual(expectedFilePath, actualFilePath);
186+
confirmed = true;
187+
});
188+
189+
logger.WriteMetadataFile(metadata);
190+
Assert.IsTrue(confirmed);
191+
}
192+
193+
[Test]
194+
[Platform("Win")]
195+
[TestCase("C:\\Users\\User\\logs\\marker.log")]
196+
[TestCase("S:\\Users\\User\\test\\marker.log")]
197+
public void MetadataFileLoggerWritesTheMetadataToTheExpectedFilePathLocationWhenAFullFilePathIsDefined(string relativeFilePath)
198+
{
199+
this.SetupTest(PlatformID.Win32NT);
200+
201+
string expectedFilePath = Path.GetFullPath(relativeFilePath);
202+
bool confirmed = false;
203+
204+
var logger = new TestMetadataFileLogger(this.mockFixture.Dependencies, relativeFilePath);
205+
var metadata = new Dictionary<string, IConvertible>
206+
{
207+
{ "any", "metadata" }
208+
};
209+
210+
this.mockFixture.FileSystem.Setup(fs => fs.Path.GetDirectoryName(It.IsAny<string>()))
211+
.Returns<string>(path => Path.GetDirectoryName(path));
212+
213+
this.mockFixture.FileSystem
214+
.Setup(fs => fs.Path.GetFullPath(It.IsAny<string>()))
215+
.Returns<string>(path => Path.GetFullPath(path));
216+
217+
this.mockFixture.FileSystem
218+
.Setup(fs => fs.File.WriteAllText(It.IsAny<string>(), It.IsAny<string>()))
219+
.Callback<string, string>((actualFilePath, content) =>
220+
{
221+
Assert.AreEqual(expectedFilePath, actualFilePath);
222+
confirmed = true;
223+
});
224+
225+
logger.WriteMetadataFile(metadata);
226+
Assert.IsTrue(confirmed);
227+
}
228+
229+
private class TestMetadataFileLogger : MetadataFileLogger
230+
{
231+
public TestMetadataFileLogger(IServiceCollection dependencies, string filePath)
232+
: base(dependencies, filePath)
233+
{
234+
}
235+
236+
public new IDictionary<string, IConvertible> GetMetadata()
237+
{
238+
return base.GetMetadata();
239+
}
240+
241+
public new void WriteMetadataFile(IDictionary<string, IConvertible> metadata)
242+
{
243+
base.WriteMetadataFile(metadata);
244+
}
245+
}
246+
}
247+
}

0 commit comments

Comments
 (0)