Simple Logging
Tip
You can download this article's sample from GitHub.
Entity Framework Core (EF Core) simple logging can be used to easily obtain logs while developing and debugging applications. This form of logging requires minimal configuration and no additional NuGet packages.
Tip
EF Core also integrates with Microsoft.Extensions.Logging, which requires more configuration, but is often more suitable for logging in production applications.
Configuration
EF Core logs can be accessed from any type of application through the use of LogTo when configuring a DbContext instance. This configuration is commonly done in an override of DbContext.OnConfiguring. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
Alternately, LogTo
can be called as part of AddDbContext or when creating a DbContextOptions instance to pass to the DbContext
constructor.
Tip
OnConfiguring is still called when AddDbContext is used or a DbContextOptions instance is passed to the DbContext constructor. This makes it the ideal place to apply context configuration regardless of how the DbContext is constructed.
Directing the logs
Logging to the console
LogTo
requires an Action<T> delegate that accepts a string. EF Core will call this delegate with a string for each log message generated. It is then up to the delegate to do something with the given message.
The Console.WriteLine method is often used for this delegate, as shown above. This results in each log message being written to the console.
Logging to the debug window
Debug.WriteLine can be used to send output to the Debug window in Visual Studio or other IDEs. Lambda syntax must be used in this case because the Debug
class is compiled out of release builds. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(message => Debug.WriteLine(message));
Logging to a file
Writing to a file requires creating a StreamWriter or similar for the file. The WriteLine method can then be used as in the other examples above. Remember to ensure the file is closed cleanly by disposing the writer when the context is disposed. For example:
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();
}
Tip
Consider using Microsoft.Extensions.Logging for logging to files in production applications.
Getting detailed messages
Sensitive data
By default, EF Core will not include the values of any data in exception messages. This is because such data may be confidential, and could be revealed in production use if an exception is not handled.
However, knowing data values, especially for keys, can be very helpful when debugging. This can be enabled in EF Core by calling EnableSensitiveDataLogging(). For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableSensitiveDataLogging();
Detailed query exceptions
For performance reasons, EF Core does not wrap each call to read a value from the database provider in a try-catch block. However, this sometimes results in exceptions that are hard to diagnose, especially when the database returns a NULL when not allowed by the model.
Turning on EnableDetailedErrors will cause EF to introduce these try-catch blocks and thereby provide more detailed errors. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
Filtering
Log levels
Every EF Core log message is assigned to a level defined by the LogLevel enum. By default, EF Core simple logging includes every message at Debug
level or above. LogTo
can be passed a higher minimum level to filter out some messages. For example, passing Information
results in a minimal set of logs limited to database access and some housekeeping messages.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Specific messages
Every log message is assigned an EventId. These IDs can be accessed from the CoreEventId class or the RelationalEventId class for relational-specific messages. A database provider may also have provider-specific IDs in a similar class. For example, SqlServerEventId for the SQL Server provider.
LogTo
can be configured to only log the messages associated with one or more event IDs. For example, to log only messages for the context being initialized or disposed:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });
Message categories
Every log message is assigned to a named hierarchical logger category. The categories are:
Category | Messages |
---|---|
Microsoft.EntityFrameworkCore | All EF Core messages |
Microsoft.EntityFrameworkCore.Database | All database interactions |
Microsoft.EntityFrameworkCore.Database.Connection | Uses of a database connection |
Microsoft.EntityFrameworkCore.Database.Command | Uses of a database command |
Microsoft.EntityFrameworkCore.Database.Transaction | Uses of a database transaction |
Microsoft.EntityFrameworkCore.Update | Saving entities, excluding database interactions |
Microsoft.EntityFrameworkCore.Model | All model and metadata interactions |
Microsoft.EntityFrameworkCore.Model.Validation | Model validation |
Microsoft.EntityFrameworkCore.Query | Queries, excluding database interactions |
Microsoft.EntityFrameworkCore.Infrastructure | General events, such as context creation |
Microsoft.EntityFrameworkCore.Scaffolding | Database reverse engineering |
Microsoft.EntityFrameworkCore.Migrations | Migrations |
Microsoft.EntityFrameworkCore.ChangeTracking | Change tracking interactions |
LogTo
can be configured to only log the messages from one or more categories. For example, to log only database interactions:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });
Notice that the DbLoggerCategory class provides a hierarchical API for finding a category and avoids the need to hard-code strings.
Since categories are hierarchical, this example using the Database
category will include all messages for the subcategories Database.Connection
, Database.Command
, and Database.Transaction
.
Custom filters
LogTo
allows a custom filter to be used for cases where none of the filtering options above are sufficient. For example, to log any message at level Information
or above, as well as messages for opening and closing a connection:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(
Console.WriteLine,
(eventId, logLevel) => logLevel >= LogLevel.Information
|| eventId == RelationalEventId.ConnectionOpened
|| eventId == RelationalEventId.ConnectionClosed);
Tip
Filtering using custom filters or using any of the other options shown here is more efficient than filtering in the LogTo
delegate. This is because if the filter determines the message should not be logged, then the log message is not even created.
Configuration for specific messages
The EF Core ConfigureWarnings API allows applications to change what happens when a specific event is encountered. This can be used to:
- Change the log level at which the event is logged
- Skip logging the event altogether
- Throw an exception when the event occurs
Changing the log level for an event
The previous example used a custom filter to log every message at LogLevel.Information
as well as two events defined for LogLevel.Debug
. The same can be achieved by changing the log level of the two Debug
events to Information
:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(
b => b.Log(
(RelationalEventId.ConnectionOpened, LogLevel.Information),
(RelationalEventId.ConnectionClosed, LogLevel.Information)))
.LogTo(Console.WriteLine, LogLevel.Information);
Suppress logging an event
In a similar way, an individual event can be suppressed from logging. This is particularly useful for ignoring a warning that has been reviewed and understood. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
.LogTo(Console.WriteLine);
Throw for an event
Finally, EF Core can be configured to throw for a given event. This is particularly useful for changing a warning into an error. (Indeed, this was the original purpose of ConfigureWarnings
method, hence the name.) For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
.LogTo(Console.WriteLine);
Message contents and formatting
The default content from LogTo
is formatted across multiple lines. The first line contains message metadata:
- The LogLevel as a four-character prefix
- A local timestamp, formatted for the current culture
- The EventId in the form that can be copy/pasted to get the member from CoreEventId or one of the other
EventId
classes, plus the raw ID value - The event category, as described above.
For example:
info: 10/6/2020 10:52:45.581 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Blogs" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
"Name" INTEGER NOT NULL
);
dbug: 10/6/2020 10:52:45.582 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committing transaction.
dbug: 10/6/2020 10:52:45.585 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committed transaction.
This content can be customized by passing values from DbContextLoggerOptions, as shown in the following sections.
Tip
Consider using Microsoft.Extensions.Logging for more control over log formatting.
Using UTC time
By default, timestamps are designed for local consumption while debugging. Use DbContextLoggerOptions.DefaultWithUtcTime to use culture-agnostic UTC timestamps instead, but keep everything else the same. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithUtcTime);
This example results in the following log formatting:
info: 2020-10-06T17:55:39.0333701Z RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Blogs" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
"Name" INTEGER NOT NULL
);
dbug: 2020-10-06T17:55:39.0333892Z RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committing transaction.
dbug: 2020-10-06T17:55:39.0351684Z RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committed transaction.
Single line logging
Sometimes it is useful to get exactly one line per log message. This can be enabled by DbContextLoggerOptions.SingleLine. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);
This example results in the following log formatting:
info: 10/6/2020 10:52:45.723 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT, "Name" INTEGER NOT NULL);
dbug: 10/6/2020 10:52:45.723 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committing transaction.
dbug: 10/6/2020 10:52:45.725 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committed transaction.
Other content options
Other flags in DbContextLoggerOptions can be used to trim down the amount of metadata included in the log. This can be useful in conjunction with single-line logging. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);
This example results in the following log formatting:
2020-10-06T17:52:45.7320362Z -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT, "Name" INTEGER NOT NULL);
2020-10-06T17:52:45.7320531Z -> Committing transaction.
2020-10-06T17:52:45.7339441Z -> Committed transaction.
Moving from EF6
EF Core simple logging differs from Database.Log in EF6 in two important ways:
- Log messages are not limited to only database interactions
- The logging must be configured at context initialization time
For the first difference, the filtering described above can be used to limit which messages are logged.
The second difference is an intentional change to improve performance by not generating log messages when they are not needed. However, it is still possible to get a similar behavior to EF6 by creating a Log
property on your DbContext
and then using it only when it has been set. For example:
public Action<string> Log { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(s => Log?.Invoke(s));