Ever wonder why your logs look like alphabet soup when you’re trying to debug at 2 AM? The default Serilog output format dumps everything into a generic template that’s either too noisy or missing the exact detail you need. You can customize the outputTemplate to show timestamps your way, filter out unnecessary fields, and highlight what actually matters for your workflow. This guide shows you working examples for console output, file logging, JSON formats, and advanced expressions so you can build logs that match how you troubleshoot, not how a framework decided logs should look.
Complete Configuration Examples with Working Code

Serilog output format gets controlled through the outputTemplate configuration. You can set it in appsettings.json for declarative config or programmatically in code when you need dynamic scenarios.
{
"Serilog": {
"MinimumLevel": "Information",
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/app-.txt",
"rollingInterval": "Day",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Properties}{NewLine}{Exception}"
}
}
]
}
}
The outputTemplate uses placeholders to control which fields show up in logs. You’ve got @t or {Timestamp} for the timestamp with optional format specifiers like yyyy-MM-dd HH:mm:ss. Then @l or {Level} shows the log level, where u3 gives you three letter uppercase format like INF or WRN. The @m or {Message} contains the rendered log message, with lj forcing literal JSON rendering to prevent double escaping. @x or {Exception} includes exception details with stack traces. @p or {Properties} outputs all enriched properties as key value pairs. The WriteTo array configures multiple sinks at once, letting you do different formatting for console output versus file output.
using Serilog;
var logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console(
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
)
.WriteTo.File(
path: "logs/app-.txt",
rollingInterval: RollingInterval.Day,
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] {Level} | {SourceContext} | {Message:lj}{NewLine}{Properties:j}{NewLine}{Exception}"
)
.CreateLogger();
logger.Information("Application started at {StartTime}", DateTime.UtcNow);
Programmatic configuration gives you full control when you need dynamic decisions based on environment variables or runtime conditions. Use code based setup when you need to apply conditional logic, switch formatters based on deployment environment, or integrate with dependency injection containers. The configuration chain can combine multiple sinks with completely different formatters. A concise console sink for local debugging alongside a detailed file sink for troubleshooting production issues. Configuration files work better for operations teams who need to adjust log verbosity without redeploying, while code gives developers precise control over complex formatting scenarios.
Practical Formatting Examples for Common Scenarios

Each example below includes the complete configuration, expected output, and specific use cases where the pattern works best.
- Minimal console output for local debugging
outputTemplate: "{Timestamp:HH:mm:ss} {Message:lj}{NewLine}{Exception}"
Output: 14:23:45 User logged in successfully
Use this when you’re running the app locally and want clean, readable output without log levels cluttering your console. Perfect for quick iteration cycles where you just need to see what’s happening.
- Structured file logging with timestamps
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] {Message:lj}{NewLine}{Properties:j}{NewLine}{Exception}"
Output: [2024-01-15 14:23:45.123] [INF] User logged in successfully
{"UserId": "12345", "SessionId": "abc-def"}
File based logging where you need precise timestamps and structured properties for later analysis. The millisecond precision helps correlate events that happen in quick succession.
- Production ready format for log aggregation
outputTemplate: "{Timestamp:o} {Level} {SourceContext} {Message:lj} {Properties:j}{NewLine}{Exception}"
Output: 2024-01-15T14:23:45.1234567+00:00 Information MyApp.Services.UserService User logged in successfully {"UserId": "12345"}
When you’re shipping logs to CloudWatch, Elasticsearch, or Splunk. The ISO 8601 timestamp with timezone offset (o format) ensures correct time handling across regions, and all data on one line makes parsing straightforward.
- Detailed exception logging
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message:lj}{NewLine}Context: {SourceContext}{NewLine}{Exception}{NewLine}---"
Output: 2024-01-15 14:23:45 [Error] Failed to process payment
Context: MyApp.Payment.PaymentProcessor
System.InvalidOperationException: Payment gateway timeout
---
Debugging complex issues where you need full context and stack traces. The separator line makes it easy to scan through log files and spot where each error begins.
- Custom property formatting
logger.Information("Request completed in {ElapsedMs}ms for {UserId}", 234, "user123");
outputTemplate: "{Timestamp:HH:mm:ss} {Message:lj} | Elapsed: {ElapsedMs}ms | User: {UserId}{NewLine}"
Output: 14:23:45 Request completed in 234ms for user123 | Elapsed: 234ms | User: user123
When you’ve got specific metrics or context that need to appear in a consistent position for quick scanning. The custom property placeholders let you pull specific values out of the structured data.
- Compact format for high volume logging
outputTemplate: "{Timestamp:HH:mm:ss.ff} {Level:u1} {Message:l}{NewLine}"
Output: 14:23:45.12 I User login
Log volume is high and storage is a concern. Level:u1 shows only the first letter (I, W, E), and Message:l omits properties from the rendered message. You lose context but save significant space in high throughput scenarios.
JSON Output Formatting with Built-in Serilog Formatters

Built-in JSON formatters produce machine readable structured output that log aggregation tools can parse and index without custom configuration.
JsonFormatter
JsonFormatter provides the standard structure with Timestamp, Level, and MessageTemplate as root properties that can’t be changed through settings. All extra data gets stored in a Properties collection rather than at the top level. The formatter keeps the template string (like “User {UserId} logged in”) instead of the rendered message, which means you see the pattern but not the final output. Configuration file support makes it simple to enable: "formatter": "Serilog.Formatting.Json.JsonFormatter" in appsettings.json.
CompactJsonFormatter
CompactJsonFormatter simplifies the structure with shorter property names. @t for timestamp, @mt for message template, @l for level, @x for exception. This reduces text volume compared to JsonFormatter, making logs easier to read and cheaper to store. The main downside is the absence of a fully rendered message, so you need to mentally combine the template with properties to understand what happened. Use this when you’re shipping to a system that’ll reconstruct messages from templates, or when storage costs matter more than immediate readability.
RenderedCompactJsonFormatter
RenderedCompactJsonFormatter includes all data in the message from the start, eliminating the intermediate template step. You get both @mt (the template) and @m (the fully rendered message), so “User {UserId} logged in” becomes “User user123 logged in” right in the log entry. This costs a bit more storage but saves time when you’re scanning logs manually or building dashboards that display messages directly.
JsonFormatter with renderMessage Flag
Setting the renderMessage flag to true must happen in code, not configuration files. Use new JsonFormatter(renderMessage: true) when calling WriteTo methods. This combines the standard JsonFormatter structure with rendered output, giving you both the template for structured parsing and the message for readability. It’s a middle ground when you want the familiar JsonFormatter schema but need human readable messages without reconstructing them from properties.
Choose formatters based on your destination and priorities. CompactJsonFormatter works great for cost sensitive high volume scenarios. RenderedCompactJsonFormatter fits debugging workflows where you read logs directly. Standard JsonFormatter suits tools that expect specific property names, and the renderMessage variant helps when both humans and machines need to read the same logs.
Advanced Formatting with Serilog Expressions

Serilog.Expressions provides an embeddable mini language that turns templates into tiny programs capable of producing a wide range of text formats beyond what standard outputTemplate allows.
ExpressionTemplate goes beyond simple property substitution by supporting SQL style operators, function calls, and conditional logic. While standard outputTemplate can only insert property values, ExpressionTemplate can manipulate them. Comparing strings, extracting substrings, or completely omitting fields based on runtime conditions. The expression language uses familiar operators like =, <>, like, and is null, with string literals in single quotes instead of placeholders.
Property manipulation through functions lets you clean up verbose output. The Substring() function can shorten SourceContext names by stripping namespace prefixes: Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1) turns “MyApp.Services.UserService” into just “UserService”. This keeps logs readable without losing the ability to filter by namespace when you need it. String operations, date arithmetic, and property access give you programming level control without writing a custom formatter.
Conditional formatting includes or excludes properties based on log level or other criteria. The expression if @l = 'Information' then undefined else @l omits the level property for Information level events, reducing redundancy when 95% of your logs are informational. You can apply this pattern to any field. Skip timestamps in local development, omit exception details for non error logs, or hide sensitive properties based on environment flags. The if/then/else syntax reads naturally and handles complex logic chains.
The spread operator (..) flattens nested properties and merges objects at the top level. Instead of wrapping custom fields in a Properties object, {..@p} spreads all properties as individual JSON fields. This makes logs more compatible with systems that expect flat schemas and improves queryability in tools like CloudWatch Insights. Use ExpressionTemplate when you need specific transformations that’d require a full custom formatter but don’t want the implementation overhead.
Implementing Custom ITextFormatter for Output Control

Custom ITextFormatter implementation becomes necessary when you need complete control over output structure beyond what templates and built-in formatters provide. Specialized binary formats, custom compression, or integration with proprietary logging systems.
using Serilog.Events;
using Serilog.Formatting;
using System.IO;
public class CustomFormatter : ITextFormatter
{
public void Format(LogEvent logEvent, TextWriter output)
{
output.Write($"{logEvent.Timestamp:yyyy-MM-dd HH:mm:ss.fff} ");
output.Write($"[{logEvent.Level}] ");
output.Write(logEvent.RenderMessage());
if (logEvent.Properties.Count > 0)
{
output.Write(" | ");
foreach (var property in logEvent.Properties)
{
output.Write($"{property.Key}={property.Value} ");
}
}
if (logEvent.Exception != null)
{
output.WriteLine();
output.Write($"Exception: {logEvent.Exception}");
}
output.WriteLine();
}
}
The Format method receives a LogEvent containing all log data and a TextWriter for output. Access timestamp through logEvent.Timestamp, level through logEvent.Level, and the rendered message via RenderMessage(). Properties live in the Properties dictionary as LogEventPropertyValue objects, and exceptions are available through the Exception property. You control exactly how each element gets serialized, positioned, and formatted.
Custom formatters give you precise control over escaping, delimiters, and property ordering that templates can’t provide. You can implement custom JSON schemas, CSV output for data analysis tools, or binary protocols for high throughput scenarios. The interface is simple enough that most implementations stay under 50 lines of code.
Performance considerations matter when logs ship at high volume. ExpressionTemplate is less efficient than the heavily optimized CompactJsonFormatter but offers low effort customization. Custom ITextFormatter implementations sit somewhere in the middle. Faster than expressions if you avoid reflection and allocations, but slower than built-in formatters unless you really optimize. Justify the complexity only when built-in options can’t meet your requirements, and always benchmark in realistic conditions before deploying custom formatters to production.
Common Formatting Patterns for Timestamps, Levels, and Exceptions

Timestamps, log levels, and exceptions are the most commonly customized elements because they directly affect log readability and usefulness in different scenarios.
Timestamp Formatting Options
Standard .NET DateTime format strings control timestamp appearance in outputTemplate. The “o” specifier produces ISO 8601 format with timezone offset: {Timestamp:o} outputs 2024-01-15T14:23:45.1234567+10:00. For human readable logs, use {Timestamp:yyyy-MM-dd HH:mm:ss.fff} which shows 2024-01-15 14:23:45.123. When milliseconds don’t matter, {Timestamp:HH:mm:ss} gives you compact time only output like 14:23:45.
UTC versus local time handling depends on your Timestamp property. Serilog captures timestamps in DateTimeOffset, preserving timezone information. Adding zzz to your format string includes the offset: {Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} shows 2024-01-15 14:23:45.123 +10:00. For logs shipping to centralized systems, always use UTC based formats or include timezone offsets so events correlate correctly across regions.
{Timestamp:o} // 2024-01-15T14:23:45.1234567+10:00
{Timestamp:yyyy-MM-dd HH:mm:ss} // 2024-01-15 14:23:45
{Timestamp:HH:mm:ss.fff} // 14:23:45.123
Log Level Display Variations
Log level representation ranges from full names to single characters depending on readability versus space tradeoffs. {Level} outputs the complete name: Information, Warning, Error. The u3 specifier creates three letter uppercase abbreviations: {Level:u3} shows INF, WRN, ERR. For extreme compactness, u1 gives single letters: {Level:u1} produces I, W, E.
Format specifiers control padding and alignment for consistent column width. {Level:u3} already pads to three characters, but you can add width specifiers: {Level,5} pads to five characters for right alignment. This keeps columns visually aligned when scanning logs.
Conditional level display reduces redundancy. Using Serilog.Expressions: if @l = 'Information' then undefined else @l completely omits the level property for Information level logs, since they represent normal operation and don’t need the visual marker that warnings and errors do.
{Level} // Information
{Level:u3} // INF
{Level:u1} // I
Exception Formatting
Exception formatting with the @x or {Exception} placeholder includes stack traces and inner exceptions automatically. Position determines readability. Inline exceptions crowd the message line, while {Message}{NewLine}{Exception} puts exceptions on a new line for easier scanning.
{Message:lj}{NewLine}{Exception} // Exception on separate line
{Timestamp} {Level} {Message:lj} | {Exception} // Inline exception
Combining these formatting patterns creates effective log output for different scenarios. Development might use {Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception} for clean console output, while production uses {Timestamp:o} {Level} {SourceContext} {Message:lj} {Exception} for structured aggregation.
Plain Text Console and File Formatting Patterns

Plain text formats excel during development and debugging because you can read them directly without parsing tools, and they work everywhere. Terminal windows, text editors, tail commands, grep searches.
Common patterns follow a timestamp level message structure that matches how developers mentally scan logs. The timestamp comes first so you can correlate events chronologically, level indicates severity at a glance, and the message contains the actual information. Putting exceptions on a new line keeps them readable without breaking the flow of normal log entries.
Color coding and themes improve console output readability by using terminal colors to highlight different log levels. Serilog’s console sink supports themes like Literate and Code that colorize output automatically. Errors show in red, warnings in yellow, information in default colors. This visual distinction helps you spot problems immediately when watching logs stream during debugging sessions.
| Pattern | Output Example | Use Case |
|---|---|---|
| {Timestamp:HH:mm:ss} {Message:lj} | 14:23:45 User logged in | Minimal local debugging with just time and message |
| {Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj} | 14:23:45 [INF] User logged in | Standard console output with severity indicators |
| [{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] {Level} {Message:lj}{NewLine}{Exception} | [2024-01-15 14:23:45.123] Information User logged in | Detailed file logging with full timestamps and exception support |
| {Timestamp:HH:mm:ss} {Message:lj}{NewLine}{Properties:j} | 14:23:45 User logged in {“UserId”: “123”, “SessionId”: “abc”} |
Structured properties for debugging while keeping message clean |
| {Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception} | 2024-01-15 14:23:45 [ERR] UserService Payment failed System.TimeoutException |
Multi line format for deep troubleshooting with full context |
Structured Properties and Log Event Enrichment

Structured properties transform logs from plain text into queryable data by capturing values as separate fields instead of concatenating them into message strings.
The difference between structured logging and string concatenation is fundamental. Writing logger.Information($"User {userId} logged in") bakes the userId into the message text. Writing logger.Information("User {UserId} logged in", userId) keeps UserId as a separate property that log aggregators can index and query. You can then filter for all logs where UserId equals a specific value, build metrics on unique user counts, or correlate events across different log messages. None of which works with concatenated strings.
The destructuring operator (@) serializes complex objects into structured form. logger.Information("Order placed {@Order}", orderObject) captures the entire order object as nested properties rather than calling ToString(). The @ prefix tells Serilog to serialize the object’s properties, creating queryable fields for Order.Id, Order.Total, Order.Items and anything else in the object structure. Without @, you’d just see the type name.
Enrichers like FromLogContext, WithMachineName, and WithThreadId automatically add contextual data to every log event. FromLogContext lets you attach properties within a scope using LogContext.PushProperty(), useful for adding request IDs or user context that applies to multiple log statements. WithMachineName adds the server name to every log, helping identify which instance in a cluster generated the event. WithThreadId includes the thread number, critical for debugging concurrency issues.
Enriched properties appear differently depending on output format. Plain text templates include them through the {Properties} placeholder, which dumps all extra properties as key value pairs. JSON formatters add them as separate fields at the top level or in a Properties object. Using {Properties:j} in templates forces JSON formatting even in plain text output, making properties more readable when they contain complex nested data.
- Machine Name Enricher adds MachineName property identifying which server generated the log, essential in distributed systems
- Thread ID Enricher includes ThreadId property for debugging multi threaded operations and race conditions
- Environment Enricher adds EnvironmentName property (Development, Staging, Production) for filtering logs by deployment environment
- Process ID Enricher includes ProcessId property to distinguish logs when multiple app instances run on the same machine
- Correlation ID Enricher adds CorrelationId property for tracing requests across service boundaries in microservice architectures
Optimizing Output Format for Different Environments

Development needs readable console output with colors and full context, while production demands structured JSON that ships efficiently to log aggregation systems and minimizes storage costs.
The performance implications of different formatters become significant at high log volumes. CompactJsonFormatter is heavily optimized for throughput and produces smaller output than standard JsonFormatter or plain text with verbose templates. ExpressionTemplate trades performance for flexibility. It’s noticeably slower than built-in formatters but faster than implementing and maintaining custom formatter code. When you’re logging thousands of events per second, formatter overhead directly impacts application performance and can cause backpressure in log pipelines.
Environment variables or configuration switches let you change formats without code changes. Store the outputTemplate in appsettings.json with environment specific configuration files (appsettings.Development.json, appsettings.Production.json), or read environment variables to choose formatters dynamically. This keeps development environments human friendly while production environments stay machine optimized.
Balancing verbosity with storage and processing costs matters in production. Every property you log costs storage space, network bandwidth to ship logs, and processing time in aggregation tools. Detailed timestamps with millisecond precision, full stack traces, and verbose property collections help debugging but can double or triple log volume. Production logging should capture enough context to troubleshoot issues but skip enrichers and properties that don’t provide value. Lowering the minimum log level to Warning or Error in production eliminates the overwhelming number of informational messages that consume space without helping identify problems.
| Environment | Recommended Format | Reasoning |
|---|---|---|
| Local Development | Plain text console with colors and minimal fields: {Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj} | Quick readability during active coding, colors help spot errors immediately, time only stamps since date is obvious |
| Containerized Development | RenderedCompactJsonFormatter to console | Works with docker logs command, includes rendered messages for readability, machine parseable if needed |
| Staging | CompactJsonFormatter with full timestamps and enrichers | Matches production format for realistic testing, includes extra context for troubleshooting integration issues |
| Production | CompactJsonFormatter with minimal enrichers, Warning level minimum | Optimized for high volume and low storage cost, structured for log aggregation, reduced noise from informational logs |
Troubleshooting Common Output Formatting Issues

Configuration mistakes in Serilog output formatting usually produce logs that are missing expected fields, include unwanted noise, or don’t appear in the format you specified.
Validate your output format by writing test logs and checking that all expected fields appear in the correct positions. Log a message with known property values, then verify the output contains those values in the expected format. If you’re using JSON formatters, pipe the output through a JSON validator to catch syntax issues. For plain text, check that placeholders resolve to actual values rather than appearing as literal text in the output.
-
Missing properties in output. You forgot to include the placeholder in outputTemplate. Add {PropertyName} for plain text or ensure JSON formatters include the property in their schema. Check that the property name matches exactly, including case.
-
Properties appearing in wrong format. Incorrect format specifier on the placeholder. Review the format string after the colon in {Timestamp:yyyy-MM-dd} and verify it matches .NET format syntax. Common mistake: using invalid format codes or forgetting the colon.
-
Extra noise from ActivityTrackingOptions. Serilog includes activity tracking fields like SpanId and TraceId by default in some configurations. Disable with
.Enrich.WithProperty("ActivityTrackingOptions", ActivityTrackingOptions.None)or set to specific values you need. -
Timestamps in wrong timezone or format. Your format string doesn’t include timezone offset, or you’re using local time when you meant UTC. Add zzz to format string for offset, or ensure Timestamp values use DateTimeOffset with correct timezone.
-
Custom properties not appearing. Enricher isn’t configured or LogContext isn’t being used. Add
.Enrich.FromLogContext()to logger configuration and verify you’re callingLogContext.PushProperty()before logging, or check that enrichers are registered in the WriteTo chain. -
Formatter not being used. Incorrect sink configuration where you specified a formatter but the sink ignores it. Check that you’re passing the formatter to the correct Args section in appsettings.json or as a parameter in code. Some sinks require specific argument names like “formatter” versus “outputTemplate”.
-
Exception details not showing. Missing {Exception} or @x placeholder in your template. Add it at the end of your outputTemplate, usually after {NewLine} so exceptions don’t crowd the message line. Verify exception objects are actually being logged with the log call.
Final Words
Getting your serilog output format right makes the difference between logs you can actually use and logs you ignore.
Start with the configuration examples that match your environment. Copy the appsettings.json or programmatic setup, adjust the outputTemplate to show what you need, and test it with a few log statements.
If plain text works for now, use it. When you need structured data for aggregation, switch to CompactJsonFormatter. When neither fits, ExpressionTemplate gives you the flexibility without building a custom formatter from scratch.
Your logs should help you ship faster and debug production issues in minutes, not hours. Pick a format, keep it consistent, and move on to building.
FAQ
What is the difference between outputTemplate and built-in JSON formatters in Serilog?
The outputTemplate provides full control over plain text log formatting using property placeholders like @t, @l, @m, @x, and @p, while built-in JSON formatters like CompactJsonFormatter produce machine-readable structured JSON output. OutputTemplate is best for human-readable console and file logs, whereas JSON formatters optimize for log aggregation tools.
How do I configure outputTemplate in appsettings.json for Serilog?
OutputTemplate configuration in appsettings.json is set within the WriteTo array under the Serilog section, specifying the outputTemplate property for each sink. The template uses placeholders like @t for timestamp, @l for level, @m for message, and @x for exception to control which fields appear.
When should I use CompactJsonFormatter versus JsonFormatter?
CompactJsonFormatter is preferred when you need simpler structure with shorter property names (@t, @mt, @l, @x) and reduced verbosity for storage efficiency. JsonFormatter stores extra data in the Properties collection and supports configuration files, making it suitable when you need standard root properties like Timestamp, Level, and MessageTemplate.
Can I configure different output formats for console and file sinks simultaneously?
Multiple sinks can be configured simultaneously with different outputTemplates, allowing logs to go to file sink locally with detailed formatting and console sink in containers with minimal formatting. Each sink in the WriteTo array accepts its own outputTemplate parameter for independent format control.
What is the renderMessage flag in JsonFormatter and when do I use it?
The renderMessage flag in JsonFormatter combines standard JSON structure with fully rendered messages, but must be configured programmatically in code rather than through configuration files. Use this flag when you need both structured properties for querying and complete rendered messages for immediate readability in log aggregation tools.
How do I format timestamps in Serilog output using ISO 8601 format?
Timestamps in Serilog outputTemplate can be formatted with standard .NET DateTime format strings like {Timestamp:o} for ISO 8601 format including timezone offset. The @t placeholder supports format specifiers such as yyyy-MM-dd HH:mm:ss or HH:mm:ss.fff for custom timestamp displays.
What are structured properties and how do they appear in log output?
Structured properties are values captured as queryable fields rather than concatenated strings, added by including placeholders in log messages and referenced in outputTemplate. In plain text formats they appear in the {Properties} placeholder, while in JSON formatters they become separate fields for better querying in log aggregators.
When should I implement custom ITextFormatter instead of using outputTemplate?
Custom ITextFormatter implementation is necessary when you need complete control over output serialization beyond what outputTemplate or ExpressionTemplate can provide. Implement ITextFormatter for complex formatting logic, custom property rendering, or specialized serialization requirements that built-in formatters cannot handle.
How do I reduce log verbosity for production environments?
Production log verbosity is reduced by lowering the MinimumLevel configuration, using CompactJsonFormatter for shorter output, and applying conditional logic to exclude redundant properties like Information-level indicators. Multiple sinks allow verbose local debugging while maintaining compact production formats.
Why are my custom properties not appearing in the formatted output?
Custom properties fail to appear when the enricher is not configured in the logger setup or when the outputTemplate lacks the {Properties} placeholder for plain text formats. For JSON formatters, custom enrichments are stored in the Properties collection rather than at the top level.
What is ExpressionTemplate and when should I use it over standard outputTemplate?
ExpressionTemplate is a programmable mini-language from Serilog.Expressions that supports conditional logic, property manipulation functions like Substring(), and SQL-style operators for advanced formatting. Use ExpressionTemplate when you need dynamic formatting logic beyond basic placeholder substitution, accepting slightly lower performance than CompactJsonFormatter.
How do I format exceptions to include full stack traces in Serilog output?
Exception formatting uses the @x placeholder in outputTemplate to include full stack traces and inner exceptions in the log output. Position the @x placeholder at the end of your template, typically on a new line for readability in plain text formats.
Can I use different log formats for development versus production environments?
Different log formats for development and production are achieved using environment variables or configuration sections to switch between human-readable console output with colors for development and structured JSON for production log aggregation. Configure environment-specific appsettings files to control formatter selection automatically.
What is the spread operator in Serilog Expressions and how does it work?
The spread operator (..) in Serilog Expressions merges objects and arrays, flattening nested properties into the output format for simpler structure. Use the spread operator when recreating CompactJsonFormatter output with expression templates or when combining enriched properties.
How do I add color coding to console output in Serilog?
Color coding in console output is enabled through console themes when configuring the console sink, providing visual distinction between log levels for improved readability during development. Console themes are applied programmatically using the theme parameter in WriteTo.Console() configuration.
