77using System . Runtime . InteropServices ;
88using System . Runtime . CompilerServices ;
99using Celeste . Mod . Core ;
10+ using Celeste . Mod . Helpers ;
1011using System . Globalization ;
1112
1213namespace Celeste . Mod {
@@ -105,34 +106,78 @@ private static bool shouldLog(string tag, LogLevel level) {
105106 /// <param name="str">The string / message to log.</param>
106107 public static void Verbose ( string tag , string str )
107108 => Log ( LogLevel . Verbose , tag , str ) ;
109+
110+ /// <inheritdoc cref="Verbose(string,string)"/>
111+ public static void Verbose ( string tag ,
112+ [ InterpolatedStringHandlerArgument ( nameof ( tag ) ) ] LogInterpolatedStringHandler < LogLevelConstTypes . Verbose > str )
113+ {
114+ if ( str . ShouldLog )
115+ LogUnchecked ( LogLevel . Verbose , tag , str . ToStringAndClear ( ) ) ;
116+ }
117+
108118 /// <summary>
109119 /// Log a string to the console and to log.txt, using <see cref="LogLevel.Debug"/>
110120 /// </summary>
111121 /// <param name="tag">The tag, preferably short enough to identify your mod, but not too long to clutter the log.</param>
112122 /// <param name="str">The string / message to log.</param>
113123 public static void Debug ( string tag , string str )
114124 => Log ( LogLevel . Debug , tag , str ) ;
125+
126+ /// <inheritdoc cref="Debug(string,string)"/>
127+ public static void Debug ( string tag ,
128+ [ InterpolatedStringHandlerArgument ( nameof ( tag ) ) ] LogInterpolatedStringHandler < LogLevelConstTypes . Debug > str )
129+ {
130+ if ( str . ShouldLog )
131+ LogUnchecked ( LogLevel . Debug , tag , str . ToStringAndClear ( ) ) ;
132+ }
133+
115134 /// <summary>
116135 /// Log a string to the console and to log.txt, using <see cref="LogLevel.Info"/>
117136 /// </summary>
118137 /// <param name="tag">The tag, preferably short enough to identify your mod, but not too long to clutter the log.</param>
119138 /// <param name="str">The string / message to log.</param>
120139 public static void Info ( string tag , string str )
121140 => Log ( LogLevel . Info , tag , str ) ;
141+
142+ /// <inheritdoc cref="Info(string,string)"/>
143+ public static void Info ( string tag ,
144+ [ InterpolatedStringHandlerArgument ( nameof ( tag ) ) ] LogInterpolatedStringHandler < LogLevelConstTypes . Info > str )
145+ {
146+ if ( str . ShouldLog )
147+ LogUnchecked ( LogLevel . Info , tag , str . ToStringAndClear ( ) ) ;
148+ }
149+
122150 /// <summary>
123151 /// Log a string to the console and to log.txt, using <see cref="LogLevel.Warn"/>
124152 /// </summary>
125153 /// <param name="tag">The tag, preferably short enough to identify your mod, but not too long to clutter the log.</param>
126154 /// <param name="str">The string / message to log.</param>
127155 public static void Warn ( string tag , string str )
128156 => Log ( LogLevel . Warn , tag , str ) ;
157+
158+ /// <inheritdoc cref="Warn(string,string)"/>
159+ public static void Warn ( string tag ,
160+ [ InterpolatedStringHandlerArgument ( nameof ( tag ) ) ] LogInterpolatedStringHandler < LogLevelConstTypes . Warn > str )
161+ {
162+ if ( str . ShouldLog )
163+ LogUnchecked ( LogLevel . Warn , tag , str . ToStringAndClear ( ) ) ;
164+ }
165+
129166 /// <summary>
130167 /// Log a string to the console and to log.txt, using <see cref="LogLevel.Error"/>
131168 /// </summary>
132169 /// <param name="tag">The tag, preferably short enough to identify your mod, but not too long to clutter the log.</param>
133170 /// <param name="str">The string / message to log.</param>
134171 public static void Error ( string tag , string str )
135172 => Log ( LogLevel . Error , tag , str ) ;
173+
174+ /// <inheritdoc cref="Error(string,string)"/>
175+ public static void Error ( string tag ,
176+ [ InterpolatedStringHandlerArgument ( nameof ( tag ) ) ] LogInterpolatedStringHandler < LogLevelConstTypes . Error > str )
177+ {
178+ if ( str . ShouldLog )
179+ LogUnchecked ( LogLevel . Error , tag , str . ToStringAndClear ( ) ) ;
180+ }
136181
137182 /// <summary>
138183 /// Log a string to the console and to log.txt
@@ -141,6 +186,14 @@ public static void Error(string tag, string str)
141186 /// <param name="str">The string / message to log.</param>
142187 public static void Log ( string tag , string str )
143188 => Verbose ( tag , str ) ;
189+
190+ /// <inheritdoc cref="Log(string,string)"/>
191+ public static void Log ( string tag ,
192+ [ InterpolatedStringHandlerArgument ( nameof ( tag ) ) ] LogInterpolatedStringHandler < LogLevelConstTypes . Verbose > str )
193+ {
194+ if ( str . ShouldLog )
195+ LogUnchecked ( LogLevel . Verbose , tag , str . ToStringAndClear ( ) ) ;
196+ }
144197
145198 /// <summary>
146199 /// Log a string to the console and to log.txt
@@ -150,24 +203,40 @@ public static void Log(string tag, string str)
150203 /// <param name="str">The string / message to log.</param>
151204 public static void Log ( LogLevel level , string tag , string str ) {
152205 if ( shouldLog ( tag , level ) ) {
153- lock ( locker ) {
154- string now = DateTime . Now . ToString ( CultureInfo . InvariantCulture ) ;
155- string logLevel = level . FastToString ( ) ;
156-
157- if ( ! ColorizedLogging ) {
158- Console . WriteLine ( $ "({ now } ) [Everest] [{ logLevel } ] [{ tag } ] { str } ") ;
159- return ;
160- }
206+ LogUnchecked ( level , tag , str ) ;
207+ }
208+ }
209+
210+ /// <inheritdoc cref="Log(LogLevel,string,string)"/>
211+ public static void Log ( LogLevel level , string tag ,
212+ [ InterpolatedStringHandlerArgument ( nameof ( level ) , nameof ( tag ) ) ] LogInterpolatedStringHandler str )
213+ {
214+ if ( str . ShouldLog )
215+ LogUnchecked ( level , tag , str . ToStringAndClear ( ) ) ;
216+ }
161217
162- const string colorReset = "\x1b [0m" ;
163- const string colorFaint = "\x1b [2m" ;
164- string colorLevel = level . GetAnsiEscapeCodeForLevel ( ) ;
165- string colorText = level . GetAnsiEscapeCodeForText ( ) ;
218+ /// <summary>
219+ /// Logs a string to the console and to log.txt, without checking whether the LogLevel should be logged,
220+ /// only for internal use.
221+ /// </summary>
222+ private static void LogUnchecked ( LogLevel level , string tag , string str ) {
223+ lock ( locker ) {
224+ string now = DateTime . Now . ToString ( CultureInfo . InvariantCulture ) ;
225+ string logLevel = level . FastToString ( ) ;
166226
167- outWriter . WriteLine ( $ " { colorFaint } ( { now } ) [Everest] { colorReset } { colorLevel } [ { logLevel } ] [ { tag } ] { colorText } { str } { colorReset } " ) ;
168- logWriter . WriteLine ( $ "({ now } ) [Everest] [{ logLevel } ] [{ tag } ] { str } ") ;
169- logWriter . Flush ( ) ;
227+ if ( ! ColorizedLogging ) {
228+ Console . WriteLine ( $ "({ now } ) [Everest] [{ logLevel } ] [{ tag } ] { str } ") ;
229+ return ;
170230 }
231+
232+ const string colorReset = "\x1b [0m" ;
233+ const string colorFaint = "\x1b [2m" ;
234+ string colorLevel = level . GetAnsiEscapeCodeForLevel ( ) ;
235+ string colorText = level . GetAnsiEscapeCodeForText ( ) ;
236+
237+ outWriter . WriteLine ( $ "{ colorFaint } ({ now } ) [Everest] { colorReset } { colorLevel } [{ logLevel } ] [{ tag } ] { colorText } { str } { colorReset } ") ;
238+ logWriter . WriteLine ( $ "({ now } ) [Everest] [{ logLevel } ] [{ tag } ] { str } ") ;
239+ logWriter . Flush ( ) ;
171240 }
172241 }
173242
@@ -277,6 +346,87 @@ internal static bool TryEnableWindowsVTSupport() {
277346 }
278347 return false ;
279348 }
349+
350+ /// <summary>
351+ /// Interpolated String Handler used by the logger to avoid interpolating the string if the message won't get logged anyway.
352+ /// </summary>
353+ [ InterpolatedStringHandler ]
354+ public ref struct LogInterpolatedStringHandler
355+ {
356+ DefaultInterpolatedStringHandler _handler ;
357+
358+ public bool ShouldLog { get ; }
359+
360+ public LogInterpolatedStringHandler ( int literalLength , int formattedCount , LogLevel level , string tag , out bool shouldLog )
361+ {
362+ ShouldLog = shouldLog = Logger . shouldLog ( tag , level ) ;
363+
364+ if ( shouldLog )
365+ _handler = new DefaultInterpolatedStringHandler ( literalLength , formattedCount ) ;
366+ }
367+
368+ internal string ToStringAndClear ( ) => _handler . ToStringAndClear ( ) ;
369+
370+ public void AppendLiteral ( string txt ) => _handler . AppendLiteral ( txt ) ;
371+
372+ public void AppendFormatted < T > ( T value ) => _handler . AppendFormatted ( value ) ;
373+
374+ public void AppendFormatted < T > ( T value , string format ) => _handler . AppendFormatted ( value ) ;
375+
376+ public void AppendFormatted < T > ( T value , int alignment ) => _handler . AppendFormatted ( value , alignment ) ;
377+
378+ public void AppendFormatted < T > ( T value , int alignment , string format ) => _handler . AppendFormatted ( value , alignment ) ;
379+
380+ public void AppendFormatted ( scoped ReadOnlySpan < char > value ) => _handler . AppendFormatted ( value ) ;
381+
382+ public void AppendFormatted ( scoped ReadOnlySpan < char > value , int alignment , string format = null ) =>
383+ _handler . AppendFormatted ( value , alignment , format ) ;
384+
385+ public void AppendFormatted ( string value ) => _handler . AppendFormatted ( value ) ;
386+
387+ public void AppendFormatted ( string value , int alignment , string format = null ) => _handler . AppendFormatted ( value , alignment , format ) ;
388+ }
389+
390+ // Unfortunately, it is impossible to pass constants as a parameter to InterpolatedStringHandlers afaik,
391+ // so we need a different way to pass the log level - const generics seem like the only way unfortunately.
392+ /// <inheritdoc cref="LogInterpolatedStringHandler"/>
393+ [ InterpolatedStringHandler ]
394+ public ref struct LogInterpolatedStringHandler < TLevel > where TLevel : struct , IConst < LogLevel >
395+ {
396+ DefaultInterpolatedStringHandler _handler ;
397+
398+ public bool ShouldLog { get ; }
399+
400+ public LogInterpolatedStringHandler ( int literalLength , int formattedCount , string tag , out bool shouldLog )
401+ {
402+ shouldLog = Logger . shouldLog ( tag , TLevel . Value ) ;
403+ ShouldLog = shouldLog ;
404+
405+ if ( shouldLog )
406+ _handler = new DefaultInterpolatedStringHandler ( literalLength , formattedCount ) ;
407+ }
408+
409+ internal string ToStringAndClear ( ) => _handler . ToStringAndClear ( ) ;
410+
411+ public void AppendLiteral ( string txt ) => _handler . AppendLiteral ( txt ) ;
412+
413+ public void AppendFormatted < T > ( T value ) => _handler . AppendFormatted ( value ) ;
414+
415+ public void AppendFormatted < T > ( T value , string format ) => _handler . AppendFormatted ( value ) ;
416+
417+ public void AppendFormatted < T > ( T value , int alignment ) => _handler . AppendFormatted ( value , alignment ) ;
418+
419+ public void AppendFormatted < T > ( T value , int alignment , string format ) => _handler . AppendFormatted ( value , alignment ) ;
420+
421+ public void AppendFormatted ( scoped ReadOnlySpan < char > value ) => _handler . AppendFormatted ( value ) ;
422+
423+ public void AppendFormatted ( scoped ReadOnlySpan < char > value , int alignment , string format = null ) =>
424+ _handler . AppendFormatted ( value , alignment , format ) ;
425+
426+ public void AppendFormatted ( string value ) => _handler . AppendFormatted ( value ) ;
427+
428+ public void AppendFormatted ( string value , int alignment , string format = null ) => _handler . AppendFormatted ( value , alignment , format ) ;
429+ }
280430 }
281431 public enum LogLevel {
282432 Verbose ,
@@ -322,4 +472,31 @@ internal static string GetAnsiEscapeCodeForLevel(this LogLevel level) {
322472 } ;
323473 }
324474 }
475+
476+ public static class LogLevelConstTypes {
477+ public struct Verbose : IConst < LogLevel >
478+ {
479+ public static LogLevel Value => LogLevel . Verbose ;
480+ }
481+
482+ public struct Debug : IConst < LogLevel >
483+ {
484+ public static LogLevel Value => LogLevel . Debug ;
485+ }
486+
487+ public struct Info : IConst < LogLevel >
488+ {
489+ public static LogLevel Value => LogLevel . Info ;
490+ }
491+
492+ public struct Warn : IConst < LogLevel >
493+ {
494+ public static LogLevel Value => LogLevel . Warn ;
495+ }
496+
497+ public struct Error : IConst < LogLevel >
498+ {
499+ public static LogLevel Value => LogLevel . Error ;
500+ }
501+ }
325502}
0 commit comments