Structured logging
Learn structured logging and placeholders.
Structured logging
Structured logging writes the message not as a flat string but with named placeholders: logger.LogInformation("User {User} signed in", user). The logger parses the placeholders; the values are stored as fields.
Benefit: log sinks (Serilog, Elasticsearch) make fields queryable. Searching for all events where "User=Ayşe" is easy. With a flat string only full-text search would work. Placeholder names should be meaningful and consistent.
Placeholder syntax
A placeholder is {Name}; the name starts with a capital (PascalCase). Values bind in order: LogInformation("{User} {Action}", user, action). Here {User} takes the first argument, {Action} the second.
Note: $"User {user}" differs from LogInformation("User {User}", user). The former pre-formats the string (structure is lost); the latter passes the template to the logger. For structured logging keep the template separate.
Exercise Apply structured logging
Define an ILogger<T> interface with void LogInformation(string message, params object[] args). ConsoleLogger<T> implements it: each {...} placeholder is replaced in order by the next argument, then Console.WriteLine("[INF] " + message) is called. In Main, create a ConsoleLogger<Program> and call LogInformation("Kullanıcı {User} {Action} yaptı", "Ayşe", "giriş").
Show example solution
Define interface ILogger<T> { void LogInformation(string message, params object[] args); }. Write class ConsoleLogger<T> : ILogger<T> { public void LogInformation(string message, params object[] args) { int i = 0; while (i < args.Length) { int s = message.IndexOf('{'); if (s < 0) break; int e = message.IndexOf('}', s); if (e < 0) break; message = message.Substring(0, s) + args[i] + message.Substring(e + 1); i++; } Console.WriteLine("[INF] " + message); } }. In Main: var l = new ConsoleLogger<Program>(); l.LogInformation("Kullanıcı {User} {Action} yaptı", "Ayşe", "giriş");
Example solution:
using System;
public interface ILogger<T>
{
void LogInformation(string message, params object[] args);
}
public class ConsoleLogger<T> : ILogger<T>
{
public void LogInformation(string message, params object[] args)
{
int i = 0;
while (i < args.Length)
{
int s = message.IndexOf('{');
if (s < 0) break;
int e = message.IndexOf('}', s);
if (e < 0) break;
message = message.Substring(0, s) + args[i] + message.Substring(e + 1);
i++;
}
Console.WriteLine("[INF] " + message);
}
}
class Program
{
static void Main()
{
var logger = new ConsoleLogger<Program>();
logger.LogInformation("Kullanıcı {User} {Action} yaptı", "Ayşe", "giriş");
}
}
Test yourself
-
Match structured logging terms
- {User}
- Named placeholder
- params object[] args
- Placeholder values
- message template
- String containing placeholders
- field
- Queryable field
-
Complete the structured message
logger.LogInformation("Kullanıcı ____ giriş yaptı", user);Show answer
logger.LogInformation("Kullanıcı {User} giriş yaptı", user); -
What is the main gain of structured logging?
- Lower memory use
- Making fields queryable
- More colorful output
- Shorter code
Show answer
Making fields queryable — Correct. Named placeholders turn values into queryable fields.