Editor/Prism/Core/PrismLog.cs

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.

File Access
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" );
		}
	}
}