Logging is an essential part of every application, it helps us understand what’s happening under the hood, trace errors, monitor performance and debug issues.
When it comes to data access, logging can be especially valuable.
You can see what queries are being executed, how long they take and whether they're behaving as expected.
If you're using Entity Framework Core, you can easily log and inspect everything with a powerful feature called Simple Logging.
Simple Logging
Simple Logging is a lightweight way to see how your LINQ queries are translated into raw SQL.
It doesn’t require any advanced configuration or third-party packages, just a few lines of setup.
Use cases for Simple Logging:
- Inspect the SQL queries generated by EF Core
- Monitor query performance locally
- Debug issues with incorrect or inefficient queries
It's a great tool for learning, debugging and optimizing during development.
NOTE: EF Core also integrates with Microsoft.Extensions.Logging, which requires more configuration, but is often more suitable for logging in production applications.
Simple Logging Configuration
Enabling Simple Logging in EF Core is straightforward. All you need to do is configure logging when setting up your DbContext.
EF Core logs are accessible from any type of application by using the LogTo method when configuring a DbContext instance. Based on your preferences and requirements, you can log to:
- Console
- Debug window
- File
The LogTo method requires an Action
Logging to the Console
Logging directly to the console is one of my personal favorites. Simply pass Console.WriteLine to the LogTo method, and EF Core will write each log message to the console.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.LogTo(Console.WriteLine);
Logging to the Debug Window
Just like console logging, you can direct EF Core log messages to the Debug window in your IDE using Debug.WriteLine.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.LogTo(message => Debug.WriteLine(message));
Lambda syntax must be used in this case because the Debug class is compiled out of release builds.
Logging to a File
Logging to a file requires a bit more setup, as you'll need to create a StreamWriter to write logs to a file.
Once the stream is set up, you can pass its WriteLine method to LogTo, just like in the previous examples:
private readonly StreamWriter _logStream = new StreamWriter("mylog.txt", append: true);
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.LogTo(_logStream.WriteLine);
public override void Dispose()
{
base.Dispose();
_logStream.Dispose();
}
public override async ValueTask DisposeAsync()
{
await base.DisposeAsync();
await _logStream.DisposeAsync();
}
NOTE: It's important to properly dispose of the StreamWriter when the DbContext is disposed.
Sensitive Data
By default, EF Core excludes data values such as parameters and keys from messages and logs:
Executed DbCommand (12ms) [Parameters=[@p0='?' (DbType = Guid), @p1='?', @p2='?', @p3='?' (DbType = Decimal)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Products" ("Id", "Description", "Name", "Price")
VALUES (@p0, @p1, @p2, @p3);
This is a security conscious choice, as such information may be sensitive and shouldn't be exposed.
However, when debugging, having access to these values can be incredibly useful. To include this information in logs, you can explicitly enable it using EnableSensitiveDataLogging():
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder
.LogTo(Console.WriteLine)
.EnableSensitiveDataLogging();
Executed DbCommand (30ms) [Parameters=[@p0='c8485bd1-dcb7-42a0-b0d7-518a379c02c6', @p1='string' (Nullable = false), @p2='string' (Nullable = false), @p3='2'], CommandType='Text', CommandTimeout='30']
INSERT INTO "Products" ("Id", "Description", "Name", "Price")
VALUES (@p0, @p1, @p2, @p3);
NOTE: Only enable sensitive data logging in debugging environments.
Detailed query exceptions
EF Core does not wrap every value read from the database in a try-catch block by default.
To improve error messages in such cases, you can enable EnableDetailedErrors(). This instructs EF Core to wrap individual reads in try-catch blocks and provide more context when exceptions occur:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
Filtering
Every EF Core log message is associated with a severity level from the LogLevel enum.
By default, EF Core logs all messages at Debug level and above, however, you can reduce the noise by specifying a higher minimum log level.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
For more granular control, LogTo also supports a custom filtering function.
This allows you to specify exactly which messages should be logged based on both LogLevel and EventId:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder
.LogTo(
Console.WriteLine,
(eventId, logLevel) => logLevel >= LogLevel.Information
|| eventId == RelationalEventId.ConnectionOpened
|| eventId == RelationalEventId.ConnectionClosed);
Conclusion
EF Core's Simple Logging is a powerful yet lightweight feature that gives you deep insight into how your application interacts with the database.
Whether you're inspecting generated SQL, debugging query issues, or optimizing performance, it provides valuable transparency with minimal configuration.
With features like EnableSensitiveDataLogging, EnableDetailedErrors, and flexible log filtering, you can fine-tune your logging experience to match your development needs.
If you want to check out examples I created, you can find the source code here:
Source CodeI hope you enjoyed it, subscribe and get a notification when a new blog is up!
