Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --logger for customize loggers on commandline #449

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void SetupTest()
[TestCase("Group")]
[TestCase("Health")]
[TestCase("Modules")]
[TestCase("CUDATestGenerator")]
// [TestCase("CUDATestGenerator")]
public async Task TestDCGMIExecutesExpectedCommandsOnUbuntu(string subsystem)
{
int diagnosticsCommandsexecuted;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ namespace VirtualClient.Api
public class StateControllerTests
{
private MockFixture mockFixture;
private IConfiguration mockConfiguration;
private Mock<InMemoryFileSystemStream> mockFileStream;
private State mockState;
private Item<JObject> mockStateInstance;
Expand All @@ -35,7 +34,6 @@ public class StateControllerTests
public void SetupTests()
{
this.mockFixture = new MockFixture();
this.mockConfiguration = new ConfigurationBuilder().Build();
this.mockFileStream = new Mock<InMemoryFileSystemStream>();
this.mockState = new State(new Dictionary<string, IConvertible>
{
Expand Down Expand Up @@ -186,7 +184,6 @@ public async Task StateControllerReturnsTheExpectedResultWhenTheStateObjectExist
Assert.IsInstanceOf<JObject>(result.Value);
}


[Test]
public async Task StateControllerReturnsTheExpectedResultWhenTheStateObjectDoesNotExist()
{
Expand Down
96 changes: 81 additions & 15 deletions src/VirtualClient/VirtualClient.Main/CommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace VirtualClient
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
Expand All @@ -26,6 +27,7 @@ namespace VirtualClient
using VirtualClient.Contracts;
using VirtualClient.Contracts.Metadata;
using VirtualClient.Contracts.Proxy;
using VirtualClient.Identity;
using VirtualClient.Proxy;

/// <summary>
Expand All @@ -34,6 +36,7 @@ namespace VirtualClient
public abstract class CommandBase
{
private static List<ILogger> proxyApiDebugLoggers = new List<ILogger>();
private List<string> loggerDefinitions = new List<string>();

/// <summary>
/// Initializes a new instance of the <see cref="CommandBase"/> class.
Expand Down Expand Up @@ -78,7 +81,12 @@ protected CommandBase()
/// <summary>
/// Describes the target Event Hub namespace to which telemetry should be sent.
/// </summary>
public DependencyEventHubStore EventHubStore { get; set; }
public string EventHubStore { get; set; }

/// <summary>
/// Describes the target Event Hub namespace to which telemetry should be sent.
/// </summary>
public IEnumerable<string> Loggers { get; set; }

/// <summary>
/// The execution system/environment platform (e.g. Azure).
Expand Down Expand Up @@ -188,6 +196,11 @@ public bool IsCleanRequested
/// </remarks>
public string StateDirectory { get; set; }

/// <summary>
/// Certificate manager overwritable for unit testing.
/// </summary>
protected ICertificateManager CertificateManager { get; set; }

/// <summary>
/// Issues a request to the OS to reboot.
/// </summary>
Expand Down Expand Up @@ -411,14 +424,13 @@ protected virtual IServiceCollection InitializeDependencies(string[] args)
IConvertible telemetrySource = null;
this.Parameters?.TryGetValue(GlobalParameter.TelemetrySource, out telemetrySource);

ILogger logger = CommandBase.CreateLogger(
IList<ILoggerProvider> loggerProviders = this.CreateLoggerProviders(
configuration,
platformSpecifics,
this.EventHubStore,
this.ProxyApiUri,
this.LoggingLevel,
telemetrySource?.ToString());

ILogger logger = loggerProviders.Any() ? new LoggerFactory(loggerProviders).CreateLogger("VirtualClient") : NullLogger.Instance;

ISystemManagement systemManagement = DependencyFactory.CreateSystemManager(
this.ClientId,
this.ExperimentId,
Expand Down Expand Up @@ -600,25 +612,79 @@ private static void AddProxyApiLogging(List<ILoggerProvider> loggingProviders, I
}
}

private static ILogger CreateLogger(IConfiguration configuration, PlatformSpecifics platformSpecifics, DependencyEventHubStore eventHubStore, Uri proxyApiUri, LogLevel level, string source = null)
/// <summary>
/// Creates a logger instance based on the specified configuration and loggers.
/// </summary>
/// <param name="configuration"></param>
/// <param name="platformSpecifics"></param>
/// <param name="source"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
protected IList<ILoggerProvider> CreateLoggerProviders(IConfiguration configuration, PlatformSpecifics platformSpecifics, string source = null)
{
// Application loggers. Events are routed to different loggers based upon
// the EventId defined when the message is logged (e.g. Trace, Error, SystemEvent, TestMetrics).
List<ILoggerProvider> loggingProviders = new List<ILoggerProvider>();
this.loggerDefinitions = this.Loggers.ToList();

CommandBase.AddConsoleLogging(loggingProviders, level);
CommandBase.AddFileLogging(loggingProviders, configuration, platformSpecifics, level);
// Add default console and file logging
if (!this.loggerDefinitions.Any(l => l.Equals("console", StringComparison.OrdinalIgnoreCase) || l.StartsWith("console=", StringComparison.OrdinalIgnoreCase)))
{
this.loggerDefinitions.Add("console");
}

if (proxyApiUri != null)
// Add default console and file logging
if (!this.loggerDefinitions.Any(l => l.Equals("file", StringComparison.OrdinalIgnoreCase) || l.StartsWith("file=", StringComparison.OrdinalIgnoreCase)))
{
CommandBase.AddProxyApiLogging(loggingProviders, configuration, platformSpecifics, proxyApiUri, source);
this.loggerDefinitions.Add("file");
}
else

// backward compatibility for --eventhub
if (!string.IsNullOrEmpty(this.EventHubStore))
{
CommandBase.AddEventHubLogging(loggingProviders, configuration, eventHubStore, level);
this.loggerDefinitions.Add($"eventhub={this.EventHubStore}");
}

if (!this.loggerDefinitions.Any(l => l.Equals("proxy", StringComparison.OrdinalIgnoreCase) || l.StartsWith("proxy=", StringComparison.OrdinalIgnoreCase))
&& this.ProxyApiUri!=null)
{
this.loggerDefinitions.Add($"proxy={this.ProxyApiUri.ToString()}");
}

foreach (string loggerDefinition in this.loggerDefinitions)
{
string loggerName = loggerDefinition;
string definitionValue = string.Empty;
if (loggerDefinition.Contains("="))
{
loggerName = loggerDefinition.Substring(0, loggerDefinition.IndexOf("=", StringComparison.Ordinal)).Trim();
definitionValue = loggerDefinition.Substring(loggerDefinition.IndexOf("=", StringComparison.Ordinal) + 1);
}

switch(loggerName.ToLowerInvariant())
{
case "console":
CommandBase.AddConsoleLogging(loggingProviders, this.LoggingLevel);
break;

case "file":
CommandBase.AddFileLogging(loggingProviders, configuration, platformSpecifics, this.LoggingLevel);
break;

case "eventhub":
DependencyEventHubStore store = EndpointUtility.CreateEventHubStoreReference(DependencyStore.Telemetry, endpoint: definitionValue, this.CertificateManager ?? new CertificateManager());
CommandBase.AddEventHubLogging(loggingProviders, configuration, store, this.LoggingLevel);
break;

case "proxy":
CommandBase.AddProxyApiLogging(loggingProviders, configuration, platformSpecifics, new Uri(definitionValue), source);
break;

default:
// TODO: Support referencing logger by namespace
throw new NotSupportedException($"Specified logger '{loggerName}' is not supported.");
}
}

return loggingProviders.Any() ? new LoggerFactory(loggingProviders).CreateLogger("VirtualClient") : NullLogger.Instance;
return loggingProviders;
}
}
}
54 changes: 24 additions & 30 deletions src/VirtualClient/VirtualClient.Main/OptionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,17 +272,12 @@ public static Option CreateDependenciesFlag(bool required = true, object default
/// </summary>
/// <param name="required">Sets this option as required.</param>
/// <param name="defaultValue">Sets the default value when none is provided.</param>
/// <param name="certificateManager">Optional parameter defines the certificate manager to use for accessing certificates on the system.</param>
public static Option CreateEventHubStoreOption(bool required = false, object defaultValue = null, ICertificateManager certificateManager = null)
public static Option CreateEventHubStoreOption(bool required = false, object defaultValue = null)
{
// Note:
// Only the first 3 of these will display in help output (i.e. --help).
Option<DependencyEventHubStore> option = new Option<DependencyEventHubStore>(
new string[] { "--eh", "--eventhub", "--event-hub", "--eventHub", "--eventHubConnectionString", "--eventhubconnectionstring" },
new ParseArgument<DependencyEventHubStore>(result => OptionFactory.ParseEventHubStore(
result,
DependencyStore.Telemetry,
certificateManager ?? OptionFactory.defaultCertificateManager)))
Option<string> option = new Option<string>(
new string[] { "--eh", "--eventhub", "--event-hub", "--eventHub", "--eventHubConnectionString", "--eventhubconnectionstring" })
{
Name = "EventHubStore",
Description = "An endpoint URI or connection string/access policy defining an Event Hub to which telemetry should be sent/uploaded.",
Expand Down Expand Up @@ -475,6 +470,26 @@ public static Option CreateLogDirectoryOption(bool required = true, object defau
return option;
}

/// <summary>
///
/// </summary>
/// <param name="required">Sets this option as required.</param>
/// <param name="defaultValue">Sets the default value when none is provided.</param>
public static Option CreateLoggerOption(bool required = true, object defaultValue = null)
{
Option<IEnumerable<string>> option = new Option<IEnumerable<string>>(new string[] { "-l", "--logger" })
{
Name = "Loggers",
Description = "Defines custom logger definitions.",
ArgumentHelpName = "logger-definition",
AllowMultipleArgumentsPerToken = false,
};

OptionFactory.SetOptionRequirements(option, required, defaultValue);

return option;
}

/// <summary>
/// Command line option indicates the logging level for VC telemetry output. These levels correspond directly to the .NET LogLevel
/// enumeration (0 = Trace, 1 = Debug, 2 = Information, 3 = Warning, 4 = Error, 5 = Critical).
Expand Down Expand Up @@ -567,7 +582,7 @@ public static Option CreateLogToFileFlag(bool required = true, object defaultVal
public static Option CreateMetadataOption(bool required = true, object defaultValue = null)
{
Option<IDictionary<string, IConvertible>> option = new Option<IDictionary<string, IConvertible>>(
new string[] { "--mt", "--metadata" },
new string[] { "--mt", "--metadata"},
new ParseArgument<IDictionary<string, IConvertible>>(arg => OptionFactory.ParseDelimitedKeyValuePairs(arg)))
{
Name = "Metadata",
Expand Down Expand Up @@ -1118,27 +1133,6 @@ private static DependencyStore ParseBlobStore(ArgumentResult parsedResult, strin
$"- https://microsoft.github.io/VirtualClient/docs/guides/0600-integration-blob-storage/{Environment.NewLine}");
}


return store;
}

private static DependencyEventHubStore ParseEventHubStore(ArgumentResult parsedResult, string storeName, ICertificateManager certificateManager)
{
string endpoint = OptionFactory.GetValue(parsedResult);
DependencyEventHubStore store = EndpointUtility.CreateEventHubStoreReference(storeName, endpoint, certificateManager);

if (store == null)
{
throw new SchemaException(
$"The value provided for the Event Hub endpoint is invalid. The value must be one of the following supported identifiers:{Environment.NewLine}" +
$"1) A valid Event Hub namespace access policy/connection string{Environment.NewLine}" +
$"2) A URI with Microsoft Entra ID/App identity information(e.g. using certificate-based authentication){Environment.NewLine}" +
$"3) A URI with Microsoft Azure Managed Identity information{Environment.NewLine}{Environment.NewLine}" +
$"See the following documentation for additional details and examples:{Environment.NewLine}" +
$"- https://microsoft.github.io/VirtualClient/docs/guides/0010-command-line/{Environment.NewLine}" +
$"- https://microsoft.github.io/VirtualClient/docs/guides/0610-integration-event-hub/{Environment.NewLine}");
}

return store;
}

Expand Down
3 changes: 3 additions & 0 deletions src/VirtualClient/VirtualClient.Main/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ internal static CommandLineBuilder SetupCommandLine(string[] args, CancellationT
// --log-dir
OptionFactory.CreateLogDirectoryOption(required: false),

// --logger
OptionFactory.CreateLoggerOption(required: false),

// --log-level
OptionFactory.CreateLogLevelOption(required: false, LogLevel.Information),

Expand Down
Loading
Loading