Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/npm_and_yarn/website/mermaid-10.9.3
Browse files Browse the repository at this point in the history
  • Loading branch information
yangpanMS authored Oct 28, 2024
2 parents a4e7d60 + f3e788d commit 76ad8ac
Show file tree
Hide file tree
Showing 15 changed files with 537 additions and 263 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.16.1
1.16.3
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,9 @@ await executor.ExecuteAsync(ProfileTiming.OneIteration(), CancellationToken.None
[TestCase("PERF-MEMCACHED.json")]
public async Task MemcachedMemtierWorkloadProfileExecutesTheWorkloadAsExpectedOfServerOnUnixPlatformMultiVM(string profile)
{
this.mockFixture.PlatformSpecifics.EnvironmentVariables.Add(
EnvironmentVariable.SUDO_USER,
"mockuser");

IEnumerable<string> expectedCommands = new List<string>
{
$"sudo -u mockuser bash -c \"numactl -C {string.Join(",", Enumerable.Range(0, Environment.ProcessorCount))} /.+/memcached -p 6379 -t 4 -m 30720 -c 16384 :: &\""
$"sudo -u [a-z]+ bash -c \"numactl -C {string.Join(",", Enumerable.Range(0, Environment.ProcessorCount))} /.+/memcached -p 6379 -t 4 -m 30720 -c 16384 :: &\""
};

// Setup the expectations for the workload
Expand All @@ -91,7 +87,11 @@ public async Task MemcachedMemtierWorkloadProfileExecutesTheWorkloadAsExpectedOf
await apiClient.CreateStateAsync(nameof(ServerState), state, CancellationToken.None);
this.mockFixture.ProcessManager.OnCreateProcess = (command, arguments, workingDir) =>
{
IProcessProxy process = this.mockFixture.CreateProcess(command, arguments, workingDir);
InMemoryProcess process = this.mockFixture.CreateProcess(command, arguments, workingDir);
process.EnvironmentVariables.Add(
EnvironmentVariable.SUDO_USER,
"mockuser");
return process;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace VirtualClient.Actions
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using VirtualClient.Common;
using VirtualClient.Common.Extensions;
using VirtualClient.Common.Platform;
Expand Down Expand Up @@ -295,6 +296,12 @@ private Task CaptureMetricsAsync(string results, string commandArguments, DateTi
// simpler in the workload executor and additionally makes testing the 2 different classes easier.
ExampleWorkloadMetricsParser resultsParser = new ExampleWorkloadMetricsParser(results);
IList<Metric> workloadMetrics = resultsParser.Parse();
foreach (var item in workloadMetrics)
{
item.Metadata.Add("product", "VirtualClient");
item.Metadata.Add("company", "Microsoft");
item.Metadata.Add("csp", "Azure");
}

// We have extension methods in the Virtual Client codebase that make it easier to log certain types
// of data in a consistent way. The 'LogTestMetrics' method is an extension method to ILogger instances
Expand Down Expand Up @@ -351,6 +358,31 @@ await workloadProcess.StartAndWaitAsync(cancellationToken)
await this.LogProcessDetailsAsync(workloadProcess, telemetryContext, "ExampleWorkload", logToFile: true)
.ConfigureAwait(false);
this.Logger.LogSystemEvent(
"ProcessResult",
"ExampleWorkload",
"The results of the example workload process",
workloadProcess.ExitCode,
new Dictionary<string, object>
{
{ "toolset", "ExampleWorkload" },
{ "command", $"{this.WorkloadExecutablePath} {commandArguments}" },
{ "exitCode", workloadProcess.ExitCode },
{ "standardOutput", workloadProcess.StandardOutput?.ToString() },
{ "standardError", workloadProcess.StandardError?.ToString() },
{ "workingDirectory", this.WorkloadPackage.Path }
},
workloadProcess.ExitCode == 0 ? LogLevel.Information : LogLevel.Error,
telemetryContext);
this.Logger.LogSystemEvent(
"KeyResult",
"ExampleWorkload",
"The results of the example workload process",
100,
workloadProcess.ExitCode == 0 ? LogLevel.Information : LogLevel.Error,
telemetryContext);
// If the workload process returned a non-success exit code, we throw an exception typically. The ErrorReason used here
// will NOT cause VC to crash.
workloadProcess.ThrowIfErrored<WorkloadException>(ProcessProxy.DefaultSuccessCodes, errorReason: ErrorReason.WorkloadFailed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace VirtualClient.Common.Telemetry
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using global::Serilog;
Expand All @@ -14,7 +15,6 @@ namespace VirtualClient.Common.Telemetry
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using VirtualClient.Common.Contracts;
using VirtualClient.Common.Extensions;
using ILogger = Microsoft.Extensions.Logging.ILogger;

Expand Down Expand Up @@ -132,29 +132,72 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
}
}

MessageTemplate template = new MessageTemplateParser().Parse(eventMessage);
List<LogEventProperty> properties = this.GetEventProperties(eventContext);
LogEvent logEvent = new LogEvent(DateTime.Now, level, exception, template, properties);
List<LogEventProperty> properties = new List<LogEventProperty>
{
new LogEventProperty("id", new ScalarValue(Guid.NewGuid().ToString().ToLowerInvariant())),
new LogEventProperty("message", new ScalarValue(eventMessage)),
new LogEventProperty("operationId", new ScalarValue(eventContext.ActivityId)),
new LogEventProperty("transactionId", new ScalarValue(eventContext.TransactionId)),
new LogEventProperty("durationMs", new ScalarValue(eventContext.DurationMs))
};

if (eventContext != null)
{
foreach (var entry in eventContext.Properties)
{
SerilogFileLogger.AddProperties(properties, entry.Key, entry.Value);
}
}

LogEvent logEvent = new LogEvent(
DateTime.Now,
level,
exception,
new MessageTemplateParser().Parse(eventMessage),
properties);

this.logger.Write(logEvent);
}
}

private List<LogEventProperty> GetEventProperties(EventContext context)
/// <summary>
/// Adds the property (or nested properties) to the set of <see cref="LogEventProperty"/> values.
/// </summary>
/// <param name="properties">Serilog logging framework properties collection.</param>
/// <param name="propertyName">The name of the property to add.</param>
/// <param name="propertyValue">The value of the property (including primitive data types as well as collections).</param>
protected static void AddProperties(List<LogEventProperty> properties, string propertyName, object propertyValue)
{
List<LogEventProperty> properties = new List<LogEventProperty>();
if (context != null)
try
{
properties.Add(new LogEventProperty("transactionId", new ScalarValue(context.TransactionId)));
properties.Add(new LogEventProperty("durationMs", new ScalarValue(context.DurationMs)));
if (propertyValue is IDictionary)
{
List<LogEventProperty> dictionaryProperties = new List<LogEventProperty>();
foreach (DictionaryEntry entry in propertyValue as IDictionary)
{
SerilogFileLogger.AddProperties(dictionaryProperties, entry.Key.ToString(), entry.Value);
}

Dictionary<ScalarValue, LogEventPropertyValue> propertyValues = new Dictionary<ScalarValue, LogEventPropertyValue>();
if (dictionaryProperties.Any())
{
foreach (var entry in dictionaryProperties)
{
propertyValues.Add(new ScalarValue(entry.Name), entry.Value);
}
}

foreach (KeyValuePair<string, object> property in context.Properties.OrderBy(p => p.Key))
properties.Add(new LogEventProperty(propertyName, new DictionaryValue(propertyValues)));
}
else
{
string serializedProperty = property.Value.ToJson(SerilogFileLogger.SerializationSettings);
properties.Add(new LogEventProperty(property.Key, new ScalarValue(serializedProperty)));
properties.Add(new LogEventProperty(propertyName, new ScalarValue(propertyValue)));
}
}

return properties;
catch
{
// Best Effort
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace VirtualClient.Common.Logging
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Serilog.Events;
using Serilog.Formatting;
using VirtualClient.Common.Contracts;
using VirtualClient.Common.Telemetry;

/// <summary>
/// Formats Serilog framework log/trace messages as JSON text.
/// </summary>
public class SerilogJsonTextFormatter : ITextFormatter
{
internal static readonly JsonSerializerSettings SerializationSettings = new JsonSerializerSettings
{
// Format: 2012-03-21T05:40:12.340Z
DateFormatHandling = DateFormatHandling.IsoDateFormat,
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,

// We tried using PreserveReferenceHandling.All and Object, but ran into issues
// when deserializing string arrays and read only dictionaries
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,

// This is the default setting, but to avoid remote code execution bugs do NOT change
// this to any other setting.
TypeNameHandling = TypeNameHandling.None,

// By default, ALL properties in the JSON structure will be camel-cased including
// dictionary keys.
ContractResolver = new CamelCasePropertyNamesContractResolver(),

// We will be serializing IEnumerable<KeyValuePair<string, object>> instances
Converters = new List<JsonConverter> { new ContextPropertiesJsonConverter() }
};

/// <summary>
/// Initializes a new instance of the <see cref="SerilogJsonTextFormatter"/> class.
/// </summary>
/// <param name="excludeProperties">A set of JSON properties to exclude from final output.</param>
public SerilogJsonTextFormatter(IEnumerable<string> excludeProperties = null)
: base()
{
if (excludeProperties?.Any() == true)
{
this.ExcludeProperties = new List<string>(excludeProperties);
}
}

/// <summary>
/// A list of properties to exclude from output.
/// </summary>
public List<string> ExcludeProperties { get; }

/// <summary>
/// Formats the log information as a JSON-structured object.
/// </summary>
public void Format(LogEvent logEvent, TextWriter output)
{
if (logEvent != null)
{
// Ensure that we can represent the timestamp in universal, round-trip format. VC libraries
// always emit the timestamps in UTC form but that designation may not be passed onward by
// the Serilog framework.
DateTime timestamp = new DateTime(logEvent.Timestamp.Ticks, DateTimeKind.Utc);

// Note:
// There is a bug in the Newtonsoft libraries for serializing JObject instances
// with camel-casing. We are using a dictionary as a reasonable workaround.
IDictionary<string, object> outputProperties = new Dictionary<string, object>();

foreach (var property in logEvent.Properties)
{
if (this.ExcludeProperties == null || !this.ExcludeProperties.Contains(property.Key, StringComparer.OrdinalIgnoreCase))
{
try
{
SerilogJsonTextFormatter.AddProperties(outputProperties, property.Key, property.Value);
}
catch
{
// Best effort.
}
}
}

output.WriteLine(outputProperties.OrderBy(entry => entry.Key).ToJson(SerilogJsonTextFormatter.SerializationSettings));
output.WriteLine("---");
}
}

private static void AddProperties(IDictionary<string, object> properties, string propertyName, LogEventPropertyValue propertyValue)
{
try
{
if (!string.IsNullOrWhiteSpace(propertyName))
{
if (propertyValue == null)
{
properties.Add(propertyName, null);
}
else if (propertyValue is ScalarValue)
{
ScalarValue scalarValue = propertyValue as ScalarValue;
if (scalarValue.Value == null)
{
properties.Add(propertyName, null);
}
else
{
properties.Add(propertyName, scalarValue.Value);
}
}
else if (propertyValue is DictionaryValue)
{
IDictionary<string, object> nestedProperties = new Dictionary<string, object>();

foreach (var entry in (propertyValue as DictionaryValue).Elements)
{
SerilogJsonTextFormatter.AddProperties(nestedProperties, entry.Key?.Value?.ToString(), entry.Value);
}

properties.Add(propertyName, nestedProperties);
}
}
}
catch
{
// Best Effort
}
}

private class SdkDateTimeConverter : JsonConverter
{
private static readonly Type DateTimeType = typeof(DateTime);
private static readonly Type DateTimeOffsetType = typeof(DateTimeOffset);

public override bool CanConvert(Type objectType)
{
return objectType == SdkDateTimeConverter.DateTimeType || objectType == SdkDateTimeConverter.DateTimeOffsetType;
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value != null)
{
if (value.GetType() == SdkDateTimeConverter.DateTimeOffsetType)
{
serializer.Serialize(writer, ((DateTimeOffset)value).DateTime.ToString("o"));
}
}
}
}
}
}
Loading

0 comments on commit 76ad8ac

Please sign in to comment.