Skip to content

Commit 33ad712

Browse files
Merge pull request #959 from JaThePlayer/perf/avoid-logging-string-interpolation
Avoid string interpolation in the Logger if the message won't get logged anyway
2 parents a178563 + 383ca5f commit 33ad712

File tree

2 files changed

+204
-15
lines changed

2 files changed

+204
-15
lines changed

Celeste.Mod.mm/Mod/Everest/Logger.cs

Lines changed: 192 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Runtime.InteropServices;
88
using System.Runtime.CompilerServices;
99
using Celeste.Mod.Core;
10+
using Celeste.Mod.Helpers;
1011
using System.Globalization;
1112

1213
namespace 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
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Celeste.Mod.Helpers;
2+
3+
/// <summary>
4+
/// Represents a constant value, to be used for generic methods by structs implementing this interface.
5+
/// </summary>
6+
public interface IConst<out T>
7+
{
8+
/// <summary>
9+
/// The value of this constant.
10+
/// </summary>
11+
public static abstract T Value { get; }
12+
}

0 commit comments

Comments
 (0)