Editor Prism logging utility. Wraps a Logger to emit info/warn/error/trace, provides Guard/Try helpers that swallow exceptions and optionally report Diagnostics, and a Timed scope for measuring durations.
namespace Editor.Prism.Core;
/// <summary>
/// The Prism logger, plus the swallow-and-report helpers that make fault isolation a one-liner.
/// <para>
/// The rule everywhere in Prism: a subsystem that throws must never take the editor, the document or
/// the rest of the compile with it. Wrap the risky call in <see cref="Guard(string, Action)"/> or
/// <see cref="Try(string, Action, DiagnosticSink, string, GraphRef?)"/> and carry on.
/// </para>
/// </summary>
public static class PrismLog
{
static readonly Logger s_log = new( PrismConstants.ProductName );
/// <summary>Set true to emit trace-level chatter from the compiler and the compile service.</summary>
public static bool Verbose { get; set; }
/// <summary>Log an informational message.</summary>
public static void Info( object message ) => s_log.Info( message );
/// <summary>Log a warning.</summary>
public static void Warn( object message ) => s_log.Warning( message );
/// <summary>Log an error.</summary>
public static void Error( object message ) => s_log.Error( message );
/// <summary>Log an exception with context.</summary>
public static void Error( Exception exception, object message ) => s_log.Error( exception, $"{message}" );
/// <summary>Log a trace message. Suppressed unless <see cref="Verbose"/> is set.</summary>
public static void Trace( object message )
{
if ( !Verbose ) return;
s_log.Trace( message );
}
/// <summary>
/// Run an action, logging and swallowing any exception. Returns true when it completed.
/// Use where there is no diagnostic sink to report into — UI callbacks, event handlers, disposal.
/// </summary>
public static bool Guard( string what, Action action )
{
try
{
action?.Invoke();
return true;
}
catch ( Exception e )
{
s_log.Error( e, $"{what} failed" );
return false;
}
}
/// <summary>Run a function, logging and swallowing any exception. Returns the fallback on failure.</summary>
public static T Guard<T>( string what, Func<T> func, T fallback = default )
{
try
{
return func is null ? fallback : func();
}
catch ( Exception e )
{
s_log.Error( e, $"{what} failed" );
return fallback;
}
}
/// <summary>
/// Run an action, converting any exception into a diagnostic on <paramref name="sink"/> as well as
/// a log entry. Returns true when it completed. This is the fault-isolation primitive used by the
/// node emitter, the serializer and every validator.
/// </summary>
public static bool Try( string what, Action action, DiagnosticSink sink,
string code = DiagnosticCode.NodeEmitFailed, GraphRef? graph = null )
{
try
{
action?.Invoke();
return true;
}
catch ( Exception e )
{
s_log.Error( e, $"{what} failed" );
sink?.Report( new Diagnostic( DiagnosticSeverity.Error, code, $"{what} failed: {e.Message}",
e.ToString(), null, graph ) );
return false;
}
}
/// <summary>
/// Run a function, converting any exception into a diagnostic. Returns false and leaves
/// <paramref name="value"/> at its default when the call threw.
/// </summary>
public static bool Try<T>( string what, Func<T> func, out T value, DiagnosticSink sink,
string code = DiagnosticCode.NodeEmitFailed, GraphRef? graph = null )
{
try
{
value = func is null ? default : func();
return true;
}
catch ( Exception e )
{
s_log.Error( e, $"{what} failed" );
sink?.Report( new Diagnostic( DiagnosticSeverity.Error, code, $"{what} failed: {e.Message}",
e.ToString(), null, graph ) );
value = default;
return false;
}
}
/// <summary>
/// Time a block and trace how long it took. Disposing the returned scope writes the entry.
/// </summary>
public static IDisposable Timed( string what ) => new TimedScope( what );
sealed class TimedScope : IDisposable
{
readonly string _what;
readonly long _start;
public TimedScope( string what )
{
_what = what;
_start = System.Diagnostics.Stopwatch.GetTimestamp();
}
public void Dispose()
{
if ( !Verbose ) return;
var ms = ( System.Diagnostics.Stopwatch.GetTimestamp() - _start ) * 1000.0 /
System.Diagnostics.Stopwatch.Frequency;
s_log.Trace( $"{_what} took {ms:0.00} ms" );
}
}
}