Skip to content

Commit

Permalink
Refactor to use ILogger #1837 (#1853)
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Jul 13, 2024
1 parent 1e4654c commit 9b1c93f
Show file tree
Hide file tree
Showing 12 changed files with 373 additions and 91 deletions.
93 changes: 93 additions & 0 deletions src/PSRule.Types/Runtime/EventId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace PSRule.Runtime;

/// <summary>
/// Identifies a logging event.
/// The primary identifier is the "Id" property, with the "Name" property providing a short description of this type of event.
/// </summary>
public readonly struct EventId : IEquatable<EventId>
{
/// <summary>
/// Implicitly creates an EventId from the given <see cref="int"/>.
/// </summary>
/// <param name="i">The <see cref="int"/> to convert to an EventId.</param>
public static implicit operator EventId(int i)
{
return new EventId(i);
}

/// <summary>
/// Checks if two specified <see cref="EventId"/> instances have the same value. They are equal if they have the same Id.
/// </summary>
/// <param name="left">The first <see cref="EventId"/>.</param>
/// <param name="right">The second <see cref="EventId"/>.</param>
/// <returns><see langword="true" /> if the objects are equal.</returns>
public static bool operator ==(EventId left, EventId right)
{
return left.Equals(right);
}

/// <summary>
/// Checks if two specified <see cref="EventId"/> instances have different values.
/// </summary>
/// <param name="left">The first <see cref="EventId"/>.</param>
/// <param name="right">The second <see cref="EventId"/>.</param>
/// <returns><see langword="true" /> if the objects are not equal.</returns>
public static bool operator !=(EventId left, EventId right)
{
return !left.Equals(right);
}

/// <summary>
/// Initializes an instance of the <see cref="EventId"/> struct.
/// </summary>
/// <param name="id">The numeric identifier for this event.</param>
/// <param name="name">The name of this event.</param>
public EventId(int id, string? name = null)
{
Id = id;
Name = name;
}

/// <summary>
/// Gets the numeric identifier for this event.
/// </summary>
public int Id { get; }

/// <summary>
/// Gets the name of this event.
/// </summary>
public string? Name { get; }

/// <inheritdoc />
public override string ToString()
{
return Name ?? Id.ToString();
}

/// <summary>
/// Indicates whether the current object is equal to another object of the same type. Two events are equal if they have the same id.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns><see langword="true" /> if the current object is equal to the other parameter; otherwise, <see langword="false" />.</returns>
public bool Equals(EventId other)
{
return Id == other.Id;
}

/// <inheritdoc />
public override bool Equals(object? obj)
{
if (obj is null) return false;

return obj is EventId eventId && Equals(eventId);
}

/// <inheritdoc />
public override int GetHashCode()
{
return Id;
}
}
64 changes: 64 additions & 0 deletions src/PSRule.Types/Runtime/FormattedLogValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;

namespace PSRule.Runtime;

/// <summary>
/// Enable formatted log values in diagnostic messages.
/// </summary>
internal readonly struct FormattedLogValues : IReadOnlyList<KeyValuePair<string, object?>>
{
private const string NullFormat = "[null]";

private readonly object?[]? _Values;
private readonly string _OriginalMessage;

public FormattedLogValues(string? format, params object?[]? values)
{
_OriginalMessage = format ?? NullFormat;
_Values = values;
}

public KeyValuePair<string, object?> this[int index]
{
get
{
if (index < 0 || index >= Count) throw new IndexOutOfRangeException(nameof(index));

if (index == Count - 1)
{
return new KeyValuePair<string, object?>("{OriginalFormat}", _OriginalMessage);
}

return new KeyValuePair<string, object?>($"{index}", _Values?[index]);
}
}

public int Count
{
get
{
return _Values == null ? 1 : _Values.Length + 1;
}
}

public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
{
for (int i = 0; i < Count; ++i)
{
yield return this[i];
}
}

public override string ToString()
{
return string.Format(Thread.CurrentThread.CurrentCulture, _OriginalMessage, _Values);
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
28 changes: 28 additions & 0 deletions src/PSRule.Types/Runtime/ILogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace PSRule.Runtime;

/// <summary>
/// Log diagnostic messages at runtime.
/// </summary>
public interface ILogger
{
/// <summary>
/// Determine if the specified type of diagnostic message should be logged.
/// </summary>
/// <param name="logLevel">The type of the diagnostic message.</param>
/// <returns>Returns <c>true</c> if the <see cref="LogLevel"/> should be logged.</returns>
public bool IsEnabled(LogLevel logLevel);

/// <summary>
/// Log a diagnostic message.
/// </summary>
/// <typeparam name="TState">Additional information that describes the diagnostic state to log.</typeparam>
/// <param name="logLevel">The type of the diagnostic message.</param>
/// <param name="eventId">An event identifier for the diagnostic message.</param>
/// <param name="state">Additional information that describes the diagnostic state to log.</param>
/// <param name="exception">An optional exception which the diagnostic message is related to.</param>
/// <param name="formatter">A function to format the diagnostic message for the outpuyt stream.</param>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter);
}
45 changes: 45 additions & 0 deletions src/PSRule.Types/Runtime/LogLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace PSRule.Runtime;

/// <summary>
/// A set of log levels which indicate different types of diagnostic messages.
/// </summary>
public enum LogLevel
{
/// <summary>
///
/// </summary>
Trace = 0,

/// <summary>
///
/// </summary>
Debug = 1,

/// <summary>
///
/// </summary>
Information = 2,

/// <summary>
///
/// </summary>
Warning = 3,

/// <summary>
///
/// </summary>
Error = 4,

/// <summary>
///
/// </summary>
Critical = 5,

/// <summary>
///
/// </summary>
None = 6
}
62 changes: 62 additions & 0 deletions src/PSRule.Types/Runtime/LoggerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace PSRule.Runtime;

/// <summary>
/// Extension for <see cref="ILogger"/> to log diagnostic messages.
/// </summary>
public static class LoggerExtensions
{
private static readonly Func<FormattedLogValues, Exception?, string> _messageFormatter = MessageFormatter;

/// <summary>
/// Log a warning message.
/// </summary>
/// <param name="logger">A valid <see cref="ILogger"/> instance.</param>
/// <param name="eventId">An event identifier for the warning.</param>
/// <param name="message">The format message text.</param>
/// <param name="args">Additional arguments to use within the format message.</param>
public static void LogWarning(this ILogger logger, EventId eventId, string? message, params object?[] args)
{
logger.Log(LogLevel.Warning, eventId, default, message, args);
}

/// <summary>
/// Log an error message.
/// </summary>
/// <param name="logger">A valid <see cref="ILogger"/> instance.</param>
/// <param name="eventId">An event identifier for the error.</param>
/// <param name="exception">An optional exception which the error message is related to.</param>
/// <param name="message">The format message text.</param>
/// <param name="args">Additional arguments to use within the format message.</param>
public static void LogError(this ILogger logger, EventId eventId, Exception? exception, string? message, params object?[] args)
{
logger.Log(LogLevel.Error, eventId, exception, message, args);
}

/// <summary>
/// Log a diagnostic message.
/// </summary>
/// <param name="logger">A valid <see cref="ILogger"/> instance.</param>
/// <param name="logLevel">The type of diagnostic message.</param>
/// <param name="eventId">An event identifier for the diagnostic message.</param>
/// <param name="exception">An optional exception which the diagnostic message is related to.</param>
/// <param name="message">The format message text.</param>
/// <param name="args">Additional arguments to use within the format message.</param>
public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, Exception? exception, string? message, params object?[] args)
{
if (logger == null || !logger.IsEnabled(logLevel))
return;

logger.Log(logLevel, eventId, new FormattedLogValues(message, args), exception, _messageFormatter);
}

/// <summary>
/// Format log messages with values.
/// </summary>
private static string MessageFormatter(FormattedLogValues state, Exception? error)
{
return state.ToString();
}
}
41 changes: 33 additions & 8 deletions src/PSRule/Common/LoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,51 @@

namespace PSRule;

/// <summary>
/// Extension for <see cref="ILogger"/> to log common messages.
/// </summary>
internal static class LoggerExtensions
{
private static readonly EventId PSR0004 = new(4, "PSR0004");
private static readonly EventId PSR0005 = new(5, "PSR0005");

/// <summary>
/// PSR0005: The {0} '{1}' is obsolete.
/// </summary>
internal static void WarnResourceObsolete(this ILogger logger, ResourceKind kind, string id)
{
if (logger == null || !logger.ShouldLog(LogLevel.Warning))
if (logger == null || !logger.IsEnabled(LogLevel.Warning))
return;

logger.Warning(PSRuleResources.ResourceObsolete, Enum.GetName(typeof(ResourceKind), kind), id);
logger.LogWarning
(
PSR0005,
PSRuleResources.PSR0005,
Enum.GetName(typeof(ResourceKind), kind),
id
);
}

/// <summary>
/// PSR0004: The specified {0} resource '{1}' is not known.
/// </summary>
internal static void ErrorResourceUnresolved(this ILogger logger, ResourceKind kind, string id)
{
if (logger == null || !logger.ShouldLog(LogLevel.Error))
if (logger == null || !logger.IsEnabled(LogLevel.Error))
return;

logger.Error(new PipelineBuilderException(string.Format(
Thread.CurrentThread.CurrentCulture,
logger.LogError
(
PSR0004,
new PipelineBuilderException(string.Format(
Thread.CurrentThread.CurrentCulture,
PSRuleResources.PSR0004,
Enum.GetName(typeof(ResourceKind),
kind), id
)),
PSRuleResources.PSR0004,
Enum.GetName(typeof(ResourceKind),
kind), id
)), "PSR0004");
Enum.GetName(typeof(ResourceKind), kind),
id
);
}
}
18 changes: 9 additions & 9 deletions src/PSRule/Resources/PSRuleResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/PSRule/Resources/PSRuleResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ResourceObsolete" xml:space="preserve">
<value>The {0} '{1}' is obsolete. Consider switching to an alternative {0}.</value>
<data name="PSR0005" xml:space="preserve">
<value>PSR0005: The {0} '{1}' is obsolete.</value>
<comment>Occurs when a resource is used that has been flagged as obsolete.</comment>
</data>
<data name="ArgumentFormatInvalid" xml:space="preserve">
Expand Down
Loading

0 comments on commit 9b1c93f

Please sign in to comment.