Skip to content

Instantly share code, notes, and snippets.

@chrisfcarroll
Last active June 23, 2022 11:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisfcarroll/f00944ecc100e7ffb0b070bb67d803ca to your computer and use it in GitHub Desktop.
Save chrisfcarroll/f00944ecc100e7ffb0b070bb67d803ca to your computer and use it in GitHub Desktop.
Log.Assert
#nullable enable
using Microsoft.Extensions.Logging;
using Microsoft.VisualBasic;
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace LogAssert
{
/// <summary>Taken from https://gist.github.com/chrisfcarroll/f00944ecc100e7ffb0b070bb67d803ca</summary>
public static class LogAssert
{
/// <summary>
/// If <paramref name="condition" /> evaluates true,
/// then call <see cref="ILogger.LogWarning(string,object[]" /><c>(message, args)</c>.
/// If <paramref name="@condition" /> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log">your Serilog logger</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool WarnIf(this ILogger log, bool condition, string message, params object?[] args)
{
return If(log, LogLevel.Warning, condition, message, args);
}
/// <summary>
/// If <paramref name="condition" /> evaluates true,
/// then call <see cref="ILogger.LogError(string,object[]" /><c>(message, args)</c>.
/// If <paramref name="@condition" /> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log">your Serilog logger</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool ErrorIf(this ILogger log, bool condition, string message, params object?[] args)
{
return If(log, LogLevel.Error, condition, message, args);
}
/// <summary>
/// If <paramref name="condition" /> evaluates true,
/// then call <see cref="Information" /><c>(message, args)</c>.
/// If <paramref name="condition" /> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log">your Serilog logger</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool InformationIf(this ILogger log, bool condition, string message, params object?[] args)
{
return If(log, LogLevel.Information, condition, message, args);
}
/// <summary>
/// If <paramref name="condition" /> evaluates true,
/// then call <see cref="Debug" /><c>(message, args)</c>.
/// If <paramref name="condition" /> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log">your Serilog logger</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool DebugIf(this ILogger log, bool condition, string message, params object?[] args)
{
return If(log, LogLevel.Debug, condition, message, args);
}
/// <summary>
/// If <paramref name="condition" /> evaluates true,
/// then call <see cref="ILogger.LogWarning(string,object[]" /><c>(message, args)</c>.
/// If <paramref name="@condition" /> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log">your Serilog logger</param>
/// <param name="LogLevel">The <see cref="LogLevel" /> at which to log</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
private static bool If(ILogger log, LogLevel LogLevel, bool condition, string message, object?[] args)
{
try
{
if (condition)
{
log.Log(LogLevel, message, args);
}
}
catch (Exception e)
{
log.LogError(e, "({Message},{@args}) was true, but logging it threw an Exception", message, args);
}
return condition;
}
/// <summary>
/// If <paramref name="condition" /> evaluates true,
/// then call <see cref="ILogger.LogWarning(string,object[]" /><c>(message, args)</c>.
/// If <paramref name="@condition" /> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">
/// if <paramref name="condition" /> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>condition</c> is false.
/// </param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool WarnIf<T>(this ILogger log, bool condition, string message, Func<T> delayedArgs)
{
return If(log, LogLevel.Warning, condition, message, delayedArgs);
}
/// <summary>
/// If <paramref name="condition" /> evaluates true,
/// then call <see cref="ILogger.LogError(string,object[]" /><c>(message, args)</c>.
/// If <paramref name="@condition" /> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">
/// if <paramref name="condition" /> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>condition</c> is false.
/// </param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool ErrorIf<T>(this ILogger log, bool condition, string message, Func<T> delayedArgs)
{
return If(log, LogLevel.Error, condition, message, delayedArgs);
}
/// <summary>
/// If <paramref name="condition" /> evaluates true,
/// then call <see cref="Information" /><c>(message, args)</c>.
/// If <paramref name="condition" /> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">
/// if <paramref name="condition" /> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>condition</c> is false.
/// </param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool InformationIf<T>(this ILogger log, bool condition, string message, Func<T> delayedArgs)
{
return If(log, LogLevel.Information, condition, message, delayedArgs);
}
/// <summary>
/// If <paramref name="condition" /> evaluates true,
/// then call <see cref="Debug" /><c>(message, args)</c>.
/// If <paramref name="condition" /> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">
/// if <paramref name="condition" /> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>condition</c> is false.
/// </param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool DebugIf<T>(this ILogger log, bool condition, string message, Func<T> delayedArgs)
{
return If(log, LogLevel.Debug, condition, message, delayedArgs);
}
private static bool If<T>(ILogger log, LogLevel LogLevel, bool condition, string message, Func<T> delayedArgs)
{
try
{
if (condition)
{
log.Log(LogLevel, message, delayedArgs());
}
}
catch (Exception e)
{
log.LogError(e, "({Message},{@delayedArgs}) was true, but logging it threw an Exception",
message, delayedArgs);
}
return condition;
}
/// <summary>
/// If <c>that(<paramref name="@this" />)</c> evaluates true,
/// then call <see cref="ILogger.LogWarning(string,object[]" /><c>(message, args)</c>.
/// If <c>that(<paramref name="@this" />)</c> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="@this">the object to test with <paramref name="that" /></param>
/// <param name="that">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="that" />(<paramref name="@this" />).
/// If evaluation throws an exception, then null is returned.
/// </returns>
public static bool? WarnIf<T>(this ILogger log, T @this, Func<T, bool> that, string message,
params object[] args)
{
bool outcome;
try
{
outcome = that(@this);
if (outcome)
{
log.LogWarning(message, args);
}
}
catch (Exception e)
{
log.LogError(e, "({Message},{@args}) threw when evaluating it.", message, args);
return null;
}
return outcome;
}
/// <summary>
/// If <c>that(<paramref name="@this" />)</c> evaluates true,
/// then call <see cref="ILogger.LogWarning(string,object[]" /><c>(message, args)</c>.
/// If <c>that(<paramref name="@this" />)</c> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="@this">the object to test with <paramref name="that" /></param>
/// <param name="that">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">
/// if <c>that(<paramref name="@this" />)</c> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>that(<paramref name="@this" />)</c> is false.
/// </param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
/// <returns>
/// <paramref name="that" />(<paramref name="@this" />).
/// If evaluation throws an exception, then null is returned.
/// </returns>
public static bool? WarnIf<T, Ta>(this ILogger log, T @this, Func<T, bool> that, string message,
Func<Ta> delayedArgs)
{
bool outcome;
try
{
outcome = that(@this);
if (outcome)
{
log.LogWarning(message, delayedArgs());
}
}
catch (Exception e)
{
log.LogError(e, "({Message},{delayedArgs}) threw when evaluating it.", message, delayedArgs);
return null;
}
return outcome;
}
/// <summary>
/// If <paramref name="condition" /> is true, do nothing.
/// If <paramref name="condition" /> is false, then
/// call <see cref="ILogger.LogWarning(string,object[]" /><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool WarnIfNot(this ILogger log, bool condition, string message, params object[] args)
{
return IfNot(log, LogLevel.Warning, condition, message, args);
}
/// <summary>
/// If <paramref name="condition" /> is true, do nothing.
/// If <paramref name="condition" /> is false, then
/// call <see cref="ILogger.LogError(string,object[]" /><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool ErrorIfNot(this ILogger log, bool condition, string message, params object[] args)
{
return IfNot(log, LogLevel.Error, condition, message, args);
}
/// <summary>
/// If <paramref name="condition" /> is true, do nothing.
/// If <paramref name="condition" /> is false, then
/// call <see cref="Information" /><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool InformationIfNot(this ILogger log, bool condition, string message, params object[] args)
{
return IfNot(log, LogLevel.Information, condition, message, args);
}
private static bool IfNot(ILogger log, LogLevel LogLevel, bool condition, string message, object[] args)
{
try
{
if (!condition)
{
log.Log(LogLevel, message, args);
}
}
catch (Exception e)
{
log.LogError(e, "({Message},{@args}) was false, but logging it threw an Exception", message, args);
}
return condition;
}
/// <summary>
/// If <c>that(<paramref name="@this" />)</c> evaluates true, do nothing.
/// if <c>that(<paramref name="@this" />)</c> evaluates false,
/// then call <see cref="ILogger.LogWarning(string,object[]" /><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="@this">the object to test with <paramref name="that" /></param>
/// <param name="that">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="that" />(<paramref name="@this" />).
/// If evaluation throws an exception, then null is returned.
/// </returns>
public static bool? WarnIfNot<T>(this ILogger log, T @this, Func<T, bool> that, string message,
params object[] args)
{
bool outcome;
try
{
outcome = that(@this);
if (!outcome)
{
log.LogWarning(message, args);
}
}
catch (Exception e)
{
log.LogError(e, "({Message},{@args}) threw when evaluating it.", message, args);
return null;
}
return outcome;
}
/// <summary>
/// AssertNotNull is a synonym for <see cref="ThrowIfNull{T}(Microsoft.Extensions.Logging.ILogger,T?,string,object[])" />(<c><paramref name="it" /> is null</c>)
/// If <paramref name="it" /> evaluates not null, do nothing.
/// If <paramref name="it" /> evaluates null,then call <see cref="ILogger.LogError(string,object[]" />
/// <c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="it">the object under test</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="that" />
/// </returns>
[return: NotNull]
public static T AssertNotNull<T>(this ILogger log, T? it, string message, params object[] args) => ThrowIfNull(log, it, message, args);
/// <summary>
/// Log.Assert is a synonym for <see cref="ThrowIfNot" />.
/// If <paramref name="condition" /> is true, do nothing.
/// If <paramref name="condition" /> is false then call <see cref="ILogger.LogError(string,object[]" />
/// <c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.
/// </summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns>
/// <paramref name="condition" />
/// </returns>
public static bool Assert(this ILogger log, bool condition, string message, params object[] args) => ThrowIfNot(log, condition, message, args);
/// <summary>Log <paramref name="exception" /> and return it, but don't throw it.</summary>
/// <param name="log"></param>
/// <param name="exception"></param>
/// <returns>
/// <paramref name="exception" />
/// </returns>
[return: NotNullIfNotNull("exception")]
public static Exception? Exception(this ILogger log, Exception? exception, params object[] args)
{
if (exception is null)
{
log.LogError("Tried to log a null exception {args}", args);
return exception;
}
log.LogError(exception, exception.GetType().ToString(), args);
return exception;
}
/// <summary>
/// Log <paramref name="exception" /> and then throw it.
/// Note that <see cref="System.Exception" /> is usually
/// preferable, both because the compiler can understand it, and because the resulting stack
/// trace is more to the point.
/// </summary>
/// <param name="log"></param>
/// <param name="exception"></param>
/// <exception><paramref name="exception" /> is thrown immediately after logging.</exception>
/// <see cref="System.Exception" />
/// <remarks>
/// Note that <see cref="System.Exception" /> is usually
/// preferable, both because the compiler can understand it, and because the resulting stack
/// trace is more to the point.
/// </remarks>
/// <returns>Never returns. The exception is thrown.</returns>
public static Exception ExceptionThenThrow(this ILogger log, Exception exception, params object[] args)
{
exception ??= new Exception("Tried to log a null exception");
log.LogError(exception, exception.GetType().ToString(), args);
throw exception;
}
/// <summary>Log <paramref name="exception" /> and then <see cref="Environment.Exit" />> the current Process</summary>
/// <param name="log"></param>
/// <param name="exception"></param>
/// <param name="exitCode">The exitCode to return to the operating system or calling process</param>
/// <returns>Nothing. The Process is halted.</returns>
public static void ExceptionThenSystemExitCurrentProcessWithExitCode(this ILogger log, Exception exception,
int exitCode, params object[] args)
{
if (exception is null) { log.LogError("Tried to log a null exception {args}", args); }
else { log.LogError(exception, exception.GetType().ToString(), args); }
Environment.Exit(exitCode);
}
/// <summary>
/// Create a new <see cref="ApplicationException" /> with <paramref name="message" />,
/// log it with <paramref name="args" />, then return it. But don't throw it.
/// </summary>
/// <param name="log"></param>
/// <param name="message"></param>
/// <returns>
/// The new <see cref="ApplicationException" /> with <paramref name="message" />
/// Returns null if there is no message and no arguments.
/// </returns>
public static ApplicationException? Exception(this ILogger log, string message, params object[] args)
{
if (string.IsNullOrWhiteSpace(message) && args.Length == 0)
{
log.LogError("Tried to log an empty exception");
return null;
}
ApplicationException ex = new(message);
log.LogError(ex, message ?? "{args}", args);
return ex;
}
/// <summary>
/// If <paramref name="condition" /> is true do nothing.
/// Otherwise log it as Error and throw an InvalidOperationException containing <paramref name="message"/> and <paramref name="args"/>
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.
/// </summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">The log message if <paramref name="condition" /> is false.
/// </param>
/// <param name="args">will be passed to <see cref="ILogger.LogError" /> if <paramref name="condition" /> is false</param>
/// <exception cref="System.Exception">
/// <see cref="InvalidOperationException"/> will be thrown using <paramref name="message"/> and <paramref name="args"/>
/// if <paramref name="condition"/> is false.
/// </exception>
/// <returns><paramref name="condition" />.</returns>
public static bool ThrowIfNot(this ILogger log, bool condition, string message, params object[] args)
{
try
{
if (condition) { return condition; }
InvalidOperationException exception = new(PoorFormat(message, args));
log.LogError(exception, message, args);
throw exception;
}
catch (Exception e)
{
log.LogError(e, MessageThrew, args);
throw;
}
}
/// <summary>
/// If <paramref name="that" /> is true do nothing.
/// Otherwise log it as Error and throw the Exception generated by <paramref name="exception" />
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.
/// </summary>
/// <param name="log"></param>
/// <param name="that">the condition</param>
/// <param name="exception">an exception to throw if <paramref name="that" /> is false.</param>
/// <param name="args">will be passed to <see cref="ILogger.LogError" /> if <paramref name="that" /> is false</param>
/// <exception cref="System.Exception">
/// <paramref name="exception" />
/// </exception>
/// <returns><paramref name="that" />.</returns>
public static bool ThrowIfNot(this ILogger log, bool that, Exception exception, params object[] args)
{
try
{
if (that) { return that; }
log.LogError(exception, MessageWasFalse, args);
throw exception;
}
catch (Exception e)
{
log.LogError(e, MessageThrew, args);
throw;
}
}
/// <summary>
/// If <paramref name="that" /> is true do nothing.
/// Otherwise log it as Error and throw the Exception generated by <paramref name="exception" />
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.
/// </summary>
/// <param name="log"></param>
/// <param name="that">the condition</param>
/// <param name="exception">a function that will generate the exception</param>
/// <param name="args">
/// will be passed to <see cref="ILogger.LogError" /> as args,
/// if <paramref name="that" /> is false
/// </param>
/// <exception cref="System.Exception">
/// <paramref name="exception" />
/// </exception>
/// <returns>
/// <paramref name="that" />
/// </returns>
public static bool ThrowIfNot(this ILogger log, bool that, Func<Exception> exception, params object[] args)
{
try
{
if (that) { return that; }
Exception ex = exception();
log.LogError(ex, MessageWasFalse, args);
throw ex;
}
catch (Exception e)
{
log.LogError(e, MessageThrew, args);
throw;
}
}
/// <summary>
/// If <paramref name="that" /> is true do nothing.
/// Otherwise log it as Error and throw the Exception generated by <paramref name="exception" />
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.
/// </summary>
/// <param name="log"></param>
/// <param name="that">the condition</param>
/// <param name="exception">a function that will generate the exception</param>
/// <param name="delayedArgs">
/// will be evaluated only if <paramref name="that" /> is false,
/// then passed as args to <see cref="ILogger.LogError" />
/// </param>
/// <exception cref="System.Exception">
/// <paramref name="exception" />
/// </exception>
/// <returns>
/// <paramref name="that" />
/// </returns>
public static bool ThrowIfNot(this ILogger log, bool that, Func<Exception> exception,
Func<object[]> delayedArgs)
{
try
{
if (that) { return that; }
Exception ex = exception();
log.LogError(ex, MessageWasFalse, delayedArgs());
throw ex;
}
catch (Exception e)
{
log.LogError(e, MessageThrew, "(Func<object[]> delayedArgs not evaluated)");
throw;
}
}
/// <summary>
/// If <paramref name="it" /> is not null, do nothing.
/// Otherwise log it as Error and throw and <see cref="ArgumentNullException"/> with <paramref name="message"/> and <paramref name="args"/>
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.
/// </summary>
/// <param name="log"></param>
/// <param name="it">the object which should not be null</param>
/// <param name="message">the message to log, and to put in the exception if <paramref name="it" /> is null.</param>
/// <param name="args">will be passed to <see cref="ILogger.LogError" /> if <paramref name="it" /> is null.</param>
/// <exception cref="System.Exception">
/// <see cref="ArgumentNullException"/>
/// </exception>
/// <returns><paramref name="it" />.</returns>
[return: NotNull]public static T ThrowIfNull<T>(this ILogger log, T? it, string message, params object[] args)
{
try
{
if (it is not null) { return it; }
ArgumentNullException exception = new(PoorFormat(message, args));
log.LogError(exception, message, args);
throw exception;
}
catch (Exception e)
{
log.LogError(e, MessageThrew, args);
throw;
}
}
/// <summary>
/// If <paramref name="it" /> is not null, do nothing.
/// Otherwise log it as Error and throw the Exception generated by <paramref name="exception" />
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.
/// </summary>
/// <param name="log"></param>
/// <param name="it">the object which should not be null</param>
/// <param name="exception">an exception to throw if <paramref name="that" /> is false.</param>
/// <param name="args">will be passed to <see cref="ILogger.LogError" /> if <paramref name="that" /> is false</param>
/// <exception cref="System.Exception">
/// <paramref name="exception" />
/// </exception>
/// <returns><paramref name="it" />.</returns>
[return: NotNullIfNotNull("it")]
public static T ThrowIfNull<T>(this ILogger log, T? it, Exception exception, params object[] args)
{
try
{
if (it is not null)
{
return it;
}
log.LogError(exception, MessageWasFalse, args);
throw exception;
}
catch (Exception e)
{
log.LogError(e, MessageThrew, args);
throw;
}
}
/// <summary>
/// If <paramref name="it" /> is true do nothing.
/// Otherwise log it as Error and throw the Exception generated by <paramref name="exception" />
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.
/// </summary>
/// <param name="log"></param>
/// <param name="it">the object which should not be null</param>
/// <param name="exception">a function that will generate the exception</param>
/// <param name="args">
/// will be passed to <see cref="ILogger.LogError" /> as args,
/// if <paramref name="it" /> is false
/// </param>
/// <exception cref="System.Exception">
/// <paramref name="exception" />
/// </exception>
/// <returns>
/// <paramref name="it" />
/// </returns>
[return: NotNullIfNotNull("it")]
public static T ThrowIfNull<T>(this ILogger log, T? it, Func<Exception> exception,
params object[] args)
{
try
{
if (it is not null)
{
return it!;
}
Exception ex = exception();
log.LogError(ex, MessageWasFalse, args);
throw ex;
}
catch (Exception e)
{
log.LogError(e, MessageThrew, args);
throw;
}
}
/// <summary>
/// If <paramref name="it" /> is not null do nothing.
/// Otherwise log it as Error and throw the Exception generated by <paramref name="exception" />
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.
/// </summary>
/// <param name="log"></param>
/// <param name="it">the object which should not be null</param>
/// <param name="exception">a function that will generate the exception</param>
/// <param name="delayedArgs">
/// will be evaluated only if <paramref name="it" /> is false,
/// then passed as args to <see cref="ILogger.LogError" />
/// </param>
/// <exception cref="System.Exception">
/// <paramref name="exception" />
/// </exception>
/// <returns>
/// <paramref name="it" />
/// </returns>
[return: NotNullIfNotNull("it")]
public static T ThrowIfNull<T>(this ILogger log, T it, Func<Exception> exception,
Func<object[]> delayedArgs)
{
try
{
if (it is not null)
{
return it!;
}
Exception ex = exception();
log.LogError(ex, MessageWasFalse, delayedArgs());
throw ex;
}
catch (Exception e)
{
log.LogError(e, MessageThrew, "(Func<object[]> delayedArgs not evaluated)");
throw;
}
}
public static string PoorFormat(string message, object[] args)
{
return (args != null)
? $"{message} args={string.Join(",", args.Select(a => a?.ToString()))}"
:message??"";
}
private const string MessageThisThrew = "EnsureElseLogAndThrow({@this},) threw during evaluation.";
private const string MessageThisWasFalse = "EnsureElseLogAndThrow({@this},) was false.";
private const string MessageThrew = "EnsureElseLogAndThrow threw during evaluation. {args}";
private const string MessageWasFalse = "EnsureElseLogAndThrow was false. {args}";
private static readonly string nl = Environment.NewLine;
private static readonly Exception FailedToEvaluateOrGetExceptionException
= new("Failed to evaluate an assertion and failed to evaluate the exception to describe it");
}
}
using System;
using System.Diagnostics.CodeAnalysis;
using Serilog;
using Serilog.Events;
namespace LogAssert
{
/// <summary>Taken from https://gist.github.com/chrisfcarroll/f00944ecc100e7ffb0b070bb67d803ca</summary>
public static class LogAssert_v1
{
/// <summary>If <paramref name="condition"/> evaluates true,
/// then call <see cref="ILogger.Warning(string,object[]"/><c>(message, args)</c>.
/// If <paramref name="@condition"/> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log">your Serilog logger</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="condition"/></returns>
public static bool WarnIf(this ILogger log, bool condition, string message, params object?[] args)
=> If(log, LogEventLevel.Warning, condition, message, args);
/// <summary>If <paramref name="condition"/> evaluates true,
/// then call <see cref="ILogger.Error(string,object[]"/><c>(message, args)</c>.
/// If <paramref name="@condition"/> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log">your Serilog logger</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="condition"/></returns>
public static bool ErrorIf(this ILogger log, bool condition, string message, params object?[] args)
=> If(log, LogEventLevel.Error, condition, message, args);
/// <summary>If <paramref name="condition"/> evaluates true,
/// then call <see cref="ILogger.Information(string,object[]"/><c>(message, args)</c>.
/// If <paramref name="@condition"/> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log">your Serilog logger</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="condition"/></returns>
public static bool InformationIf(this ILogger log, bool condition, string message, params object?[] args)
=> If(log, LogEventLevel.Information, condition, message, args);
/// <summary>If <paramref name="condition"/> evaluates true,
/// then call <see cref="ILogger.Debug(string,object[]"/><c>(message, args)</c>.
/// If <paramref name="@condition"/> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log">your Serilog logger</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="condition"/></returns>
public static bool DebugIf(this ILogger log, bool condition, string message, params object?[] args)
=> If(log, LogEventLevel.Debug, condition, message, args);
/// <summary>If <paramref name="condition"/> evaluates true,
/// then call <see cref="ILogger.Warning(string,object[]"/><c>(message, args)</c>.
/// If <paramref name="@condition"/> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log">your Serilog logger</param>
/// <param name="logEventLevel">The <see cref="LogEventLevel"/> at which to log</param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="condition"/></returns>
static bool If(ILogger log, LogEventLevel logEventLevel, bool condition, string message, object?[] args)
{
try { if (condition) log.Write(logEventLevel, message, args); }
catch (Exception e)
{
log.Error(e, "({Message},{@args}) was true, but logging it threw an Exception", message, args);
}
return condition;
}
/// <summary>If <paramref name="condition"/> evaluates true,
/// then call <see cref="ILogger.Warning(string,object[]"/><c>(message, args)</c>.
/// If <paramref name="@condition"/> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">if <paramref name="condition"/> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>condition</c> is false.</param>
/// <returns><paramref name="condition"/></returns>
public static bool WarnIf<T>(this ILogger log, bool condition, string message, Func<T> delayedArgs)
=> If(log, LogEventLevel.Warning, condition, message, delayedArgs);
/// <summary>If <paramref name="condition"/> evaluates true,
/// then call <see cref="ILogger.Error(string,object[]"/><c>(message, args)</c>.
/// If <paramref name="@condition"/> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">if <paramref name="condition"/> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>condition</c> is false.</param>
/// <returns><paramref name="condition"/></returns>
public static bool ErrorIf<T>(this ILogger log, bool condition, string message, Func<T> delayedArgs)
=> If(log, LogEventLevel.Error, condition, message, delayedArgs);
/// <summary>If <paramref name="condition"/> evaluates true,
/// then call <see cref="ILogger.Information(string,object[]"/><c>(message, args)</c>.
/// If <paramref name="@condition"/> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">if <paramref name="condition"/> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>condition</c> is false.</param>
/// <returns><paramref name="condition"/></returns>
public static bool InformationIf<T>(this ILogger log, bool condition, string message, Func<T> delayedArgs)
=> If(log, LogEventLevel.Information, condition, message, delayedArgs);
/// <summary>If <paramref name="condition"/> evaluates true,
/// then call <see cref="ILogger.Debug(string,object[]"/><c>(message, args)</c>.
/// If <paramref name="@condition"/> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">if <paramref name="condition"/> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>condition</c> is false.</param>
/// <returns><paramref name="condition"/></returns>
public static bool DebugIf<T>(this ILogger log, bool condition, string message, Func<T> delayedArgs)
=> If(log, LogEventLevel.Debug, condition, message, delayedArgs);
static bool If<T>(ILogger log, LogEventLevel logEventLevel, bool condition, string message, Func<T> delayedArgs)
{
try { if (condition) log.Write(logEventLevel, message, delayedArgs()); }
catch (Exception e)
{
log.Error(e, "({Message},{@delayedArgs}) was true, but logging it threw an Exception",
message, delayedArgs);
}
return condition;
}
/// <summary>If <c>that(<paramref name="@this"/>)</c> evaluates true,
/// then call <see cref="ILogger.Warning(string,object[]"/><c>(message, args)</c>.
/// If <c>that(<paramref name="@this"/>)</c> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="@this">the object to test with <paramref name="that"/></param>
/// <param name="that">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="that"/>(<paramref name="@this"/>).
/// If evaluation throws an exception, then null is returned.</returns>
public static bool? WarnIf<T>(this ILogger log, T @this, Func<T,bool> that, string message, params object[] args)
{
bool outcome;
try { outcome= that(@this); if (outcome)log.Warning(message, args); }
catch (Exception e)
{
log.Error(e, "({Message},{@args}) threw when evaluating it.",message,args);
return null;
}
return outcome;
}
/// <summary>If <c>that(<paramref name="@this"/>)</c> evaluates true,
/// then call <see cref="ILogger.Warning(string,object[]"/><c>(message, args)</c>.
/// If <c>that(<paramref name="@this"/>)</c> evaluates false, do nothing.
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="@this">the object to test with <paramref name="that"/></param>
/// <param name="that">the condition</param>
/// <param name="message">message format</param>
/// <param name="delayedArgs">if <c>that(<paramref name="@this"/>)</c> is true then this function is
/// evaluated to generate args for the log. This allows logging of values that may be
/// expensive to compute, or may be invalid when <c>that(<paramref name="@this"/>)</c> is false.</param>
/// <returns><paramref name="condition"/></returns>
/// <returns><paramref name="that"/>(<paramref name="@this"/>).
/// If evaluation throws an exception, then null is returned.</returns>
public static bool? WarnIf<T,Ta>(this ILogger log, T @this, Func<T,bool> that, string message, Func<Ta> delayedArgs)
{
bool outcome;
try { outcome= that(@this); if (outcome)log.Warning(message, delayedArgs()); }
catch (Exception e)
{
log.Error(e, "({Message},{delayedArgs}) threw when evaluating it.",message,delayedArgs);
return null;
}
return outcome;
}
/// <summary>If <paramref name="condition"/> is true, do nothing.
/// If <paramref name="condition"/> is false, then
/// call <see cref="ILogger.Warning(string,object[]"/><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="condition"/></returns>
public static bool WarnIfNot(this ILogger log, bool condition, string message, params object[] args)
{
return IfNot(log, LogEventLevel.Warning, condition, message, args);
}
/// <summary>If <paramref name="condition"/> is true, do nothing.
/// If <paramref name="condition"/> is false, then
/// call <see cref="ILogger.Error(string,object[]"/><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="condition"/></returns>
public static bool ErrorIfNot(this ILogger log, bool condition, string message, params object[] args)
{
return IfNot(log, LogEventLevel.Error, condition, message, args);
}
/// <summary>If <paramref name="condition"/> is true, do nothing.
/// If <paramref name="condition"/> is false, then
/// call <see cref="ILogger.Information(string,object[]"/><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="condition"/></returns>
public static bool InformationIfNot(this ILogger log, bool condition, string message, params object[] args)
{
return IfNot(log, LogEventLevel.Information, condition, message, args);
}
static bool IfNot(ILogger log, LogEventLevel logEventLevel, bool condition, string message, object[] args)
{
try { if (!condition) log.Write(logEventLevel,message, args); }
catch (Exception e)
{
log.Error(e, "({Message},{@args}) was false, but logging it threw an Exception", message, args);
}
return condition;
}
/// <summary>If <c>that(<paramref name="@this"/>)</c> evaluates true, do nothing.
/// if <c>that(<paramref name="@this"/>)</c> evaluates false,
/// then call <see cref="ILogger.Warning(string,object[]"/><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="@this">the object to test with <paramref name="that"/></param>
/// <param name="that">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="that"/>(<paramref name="@this"/>).
/// If evaluation throws an exception, then null is returned.</returns>
public static bool? WarnIfNot<T>(this ILogger log, T @this, Func<T,bool> that, string message, params object[] args)
{
bool outcome;
try { outcome= that(@this); if (!outcome)log.Warning(message, args); }
catch (Exception e)
{
log.Error(e, "({Message},{@args}) threw when evaluating it.",message,args);
return null;
}
return outcome;
}
/// <summary>AssertNotNull is an abbreviation for <see cref="ErrorIf"/>(<c><paramref name="it"/> is null</c>)
/// If <paramref name="it"/> evaluates not null, do nothing.
/// If <paramref name="it"/> evaluates null,then call <see cref="ILogger.Error(string,object[]"/><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="it">the object under test</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="that"/></returns>
[return:NotNull]
public static T AssertNotNull<T>(this ILogger log, T? it, string message, params object[] args)
{
ErrorIf(log, it is null, message, args);
return it!;
}
/// <summary>Log.Assert is a synonym for <see cref="ErrorIfNot"/>.
/// If <paramref name="condition"/> is true, do nothing.
/// If <paramref name="condition"/> is false then call <see cref="ILogger.Error(string,object[]"/><c>(message, args)</c>
/// If an exception is thrown during evaluation, then catch it and log that as an Error,
/// and continue.</summary>
/// <param name="log"></param>
/// <param name="condition">the condition</param>
/// <param name="message">message format</param>
/// <param name="args">args to message format</param>
/// <returns><paramref name="condition"/></returns>
public static bool Assert(this ILogger log, bool condition, string message, params object[] args)
=> ErrorIfNot(log, condition, message, args);
/// <summary>Log <paramref name="exception"/> and return it, but don't throw it.</summary>
/// <param name="log"></param>
/// <param name="exception"></param>
/// <returns><paramref name="exception"/></returns>
public static Exception Exception(this ILogger log, Exception exception, params object[] args)
{
if(exception is null){log.Error("Tried to log a null exception",args); return exception;}
log.Error(exception, exception.GetType().ToString(),args);
return exception;
}
/// <summary>Log <paramref name="exception"/> and then throw it.
/// Note that <see cref="Exception(Serilog.ILogger,System.Exception,object[])"/> is usually
/// preferable, both because the compiler can understand it, and because the resulting stack
/// trace is more to the point.</summary>
/// <param name="log"></param>
/// <param name="exception"></param>
/// <exception><paramref name="exception"/> is thrown immediately after logging.</exception>
/// <see cref="Exception(Serilog.ILogger,System.Exception,object[])"/>
/// <remarks>Note that <see cref="Exception(Serilog.ILogger,System.Exception,object[])"/> is usually
/// preferable, both because the compiler can understand it, and because the resulting stack
/// trace is more to the point.</remarks>
/// <returns>Never returns. The exception is thrown.</returns>
public static Exception ExceptionThenThrow(this ILogger log, Exception exception, params object[] args)
{
exception ??= new Exception("Tried to log a null exception");
log.Error(exception, exception.GetType().ToString(), args);
throw exception;
}
/// <summary>Log <paramref name="exception"/> and then <see cref="Environment.Exit"/>> the current Process</summary>
/// <param name="log"></param>
/// <param name="exception"></param>
/// <param name="exitCode">The exitCode to return to the operating system or calling process</param>
/// <returns>Nothing. The Process is halted.</returns>
public static void ExceptionThenSystemExitCurrentProcessWithExitCode(this ILogger log, Exception exception, int exitCode, params object[] args)
{
if(exception is null){log.Error("Tried to log a null exception",args);}
else { log.Error(exception, exception.GetType().ToString(), args); }
System.Environment.Exit(exitCode);
}
/// <summary>Create a new <see cref="ApplicationException"/> with <paramref name="message"/>,
/// log it with <paramref name="args"/>, then return it. But don't throw it.</summary>
/// <param name="log"></param>
/// <param name="message"></param>
/// <returns>The new <see cref="ApplicationException"/> with <paramref name="message"/></returns>
public static ApplicationException Exception(this ILogger log, string message,params object[] args)
{
if(string.IsNullOrWhiteSpace(message) && args.Length==0){log.Error("Tried to log an empty exception"); return null;}
var ex = new ApplicationException(message);
log.Error(ex, message,args);
return ex;
}
/// <summary>If <paramref name="that"/> is true do nothing.
/// Otherwise log it as Error and throw the Exception generated by <paramref name="exception"/>
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.</summary>
/// <param name="log"></param>
/// <param name="that">the condition</param>
/// <param name="exception">an exception to throw if <paramref name="that"/> is false.</param>
/// <param name="args">will be passed to <see cref="ILogger.Error"/> if <paramref name="that"/> is false</param>
/// <exception cref="System.Exception"><paramref name="exception"/></exception>
/// <returns><paramref name="that"/>.</returns>
public static bool EnsureElseThrow(this ILogger log, bool that, Exception exception, params object[] args)
{
try { if (that) return that; log.Error(exception,messageWasFalse,args); throw exception;}
catch (Exception e) { log.Error(e,messageThrew); throw;}
}
/// <summary>If <paramref name="that"/> is true do nothing.
/// Otherwise log it as Error and throw the Exception generated by <paramref name="exception"/>
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.</summary>
/// <param name="log"></param>
/// <param name="that">the condition</param>
/// <param name="exception">a function that will generate the exception</param>
/// <param name="args">will be passed to <see cref="ILogger.Error"/> as args,
/// if <paramref name="that"/> is false</param>
/// <exception cref="System.Exception"><paramref name="exception"/></exception>
/// <returns><paramref name="that"/></returns>
public static bool EnsureElseThrow(this ILogger log, bool that, Func<Exception> exception, params object[] args)
{
try { if (that) return that; var ex = exception(); log.Error(ex,messageWasFalse,args); throw ex;}
catch (Exception e) { log.Error(e,messageThrew); throw;}
}
/// <summary>If <paramref name="that"/> is true do nothing.
/// Otherwise log it as Error and throw the Exception generated by <paramref name="exception"/>
/// If an exception is thrown during evaluation, then log that as an Error,
/// and throw that instead.</summary>
/// <param name="log"></param>
/// <param name="that">the condition</param>
/// <param name="exception">a function that will generate the exception</param>
/// <param name="delayedArgs">will be evaluated only if <paramref name="that"/> is false,
/// then passed as args to <see cref="ILogger.Error"/></param>
/// <exception cref="System.Exception"><paramref name="exception"/></exception>
/// <returns><paramref name="that"/></returns>
public static bool EnsureElseThrow(this ILogger log, bool that, Func<Exception> exception, Func<object[]> delayedArgs)
{
try {
if (that) return that;
var ex = exception();
log.Error(ex,messageWasFalse,delayedArgs());
throw ex;
}
catch (Exception e) { log.Error(e,messageThrew); throw;}
}
const string messageThisThrew = "EnsureElseLogAndThrow({@this},) threw during evaluation.";
const string messageThisWasFalse = "EnsureElseLogAndThrow({@this},) was false.";
const string messageThrew = "EnsureElseLogAndThrow threw during evaluation.";
const string messageWasFalse = "EnsureElseLogAndThrow was false.";
static readonly string nl = Environment.NewLine;
static readonly Exception FailedToEvaluateOrGetExceptionException
= new Exception("Failed to evaluate an assertion and failed to evaluate the exception to describe it");
}
}
using Xunit;
namespace LogAssert.Tests
{
public class LogAssertExtensionTests
{
[Theory]
[InlineData(null, null)]
[InlineData(null, 1, null)]
[InlineData("", 1, null)]
[InlineData("", "1", null)]
[InlineData("message", null)]
[InlineData("message", 1, null)]
[InlineData("message", "1", null)]
[InlineData(" ", null)]
public void LogAssertPoorFormatDoesntThrowButDoesIncludeMessage(string msg, params object[] args)
{
var actual=LogAssert.PoorFormat(msg, args);
if(!string.IsNullOrEmpty(msg)) Assert.StartsWith(msg,actual);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment