LoggerMessage
Learn high-performance logging with LoggerMessage.Define.
LoggerMessage.Define
Every LogInformation("...") call allocates a string at runtime. In hot paths this is costly. LoggerMessage.Define compiles the message template once and returns a reusable delegate.
Example: private static readonly Action<ILogger, string> _greet = LoggerMessage.Define<string>(LogLevel.Information, 0, "Merhaba {User}");. Then _greet(logger, "Ayşe") does not re-parse the template on each call; it runs a ready delegate.
Exercise High-performance logging with LoggerMessage
Define a non-generic ILogger interface (void LogInformation(string message)) and an ILogger<T> : ILogger interface. ConsoleLogger<T> implements ILogger<T> and calls Console.WriteLine("[INF] " + message). In a static LoggerMessage class, DefineInformation(string message) returns Action<ILogger, string>; the returned delegate replaces the {Arg} placeholder with the second argument and calls LogInformation. In Main, create a ConsoleLogger<Program>, get the delegate via DefineInformation("Merhaba {Arg}"), and call greet(logger, "Ayşe").
Show example solution
Define interface ILogger { void LogInformation(string message); } and interface ILogger<T> : ILogger { }. Write class ConsoleLogger<T> : ILogger<T> { public void LogInformation(string message) => Console.WriteLine("[INF] " + message); }. Write static class LoggerMessage { public static Action<ILogger, string> DefineInformation(string message) => (logger, arg) => logger.LogInformation(message.Replace("{Arg}", arg)); }. In Main: var logger = new ConsoleLogger<Program>(); var greet = LoggerMessage.DefineInformation("Merhaba {Arg}"); greet(logger, "Ayşe");
Example solution:
using System;
public interface ILogger
{
void LogInformation(string message);
}
public interface ILogger<T> : ILogger { }
public class ConsoleLogger<T> : ILogger<T>
{
public void LogInformation(string message) =>
Console.WriteLine("[INF] " + message);
}
public static class LoggerMessage
{
public static Action<ILogger, string> DefineInformation(string message) =>
(logger, arg) => logger.LogInformation(message.Replace("{Arg}", arg));
}
class Program
{
static void Main()
{
var logger = new ConsoleLogger<Program>();
var greet = LoggerMessage.DefineInformation("Merhaba {Arg}");
greet(logger, "Ayşe");
}
}
Reuse the delegate
The delegate from Define is stored in a static readonly field. So it is built once at startup, and each call uses the same object. Template parsing happens a single time.
In hot paths (say, logs inside loops) this gives a measurable speedup. In rarely called spots a direct LogInformation is fast enough; over-engineering should be avoided.
Test yourself
-
Complete the Define call
var greet = LoggerMessage.____("Merhaba {User}");Show answer
var greet = LoggerMessage.DefineInformation("Merhaba {User}"); -
Why prefer LoggerMessage.Define?
- To write shorter code
- To color logs
- To capture only errors
- To avoid allocating a string on every call
Show answer
To avoid allocating a string on every call — Correct. Define compiles the template once, avoiding repeated allocation.
-
Match LoggerMessage concepts
- Define
- Method that compiles the template
- Action<ILogger, T>
- Returned delegate
- static readonly
- Stores the delegate
- {Arg}
- Placeholder