Fundamentals - Logging and monitoring - Logging

In this article

Logging providers

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole();

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();
var builder = WebApplication.CreateBuilder();
builder.Host.ConfigureLogging(logging =>
{
    logging.ClearProviders();
    logging.AddConsole();
});

Create logs

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        _logger.LogInformation("About page visited at {DT}", 
            DateTime.UtcNow.ToLongTimeString());
    }
}

Configure logging

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}
{
  "Logging": {
    "LogLevel": { // All providers, LogLevel applies to all the enabled providers.
      "Default": "Error", // Default logging, Error and higher.
      "Microsoft": "Warning" // All Microsoft* categories, Warning and higher.
    },
    "Debug": { // Debug provider.
      "LogLevel": {
        "Default": "Information", // Overrides preceding LogLevel:Default setting.
        "Microsoft.Hosting": "Trace" // Debug:Microsoft.Hosting category.
      }
    },
    "EventSource": { // EventSource provider
      "LogLevel": {
        "Default": "Warning" // All categories of EventSource provider.
      }
    }
  }
}
{
  "Logging": {
    "LogLevel": { // No provider, LogLevel applies to all the enabled providers.
      "Default": "Error",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Warning"
    },
    "Debug": { // Debug provider.
      "LogLevel": {
        "Default": "Information" // Overrides preceding LogLevel:Default setting.
      }
    },
    "Console": {
      "IncludeScopes": true,
      "LogLevel": {
        "Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
        "Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
        "Microsoft.AspNetCore.Mvc.Razor": "Error",
        "Default": "Information"
      }
    },
    "EventSource": {
      "LogLevel": {
        "Microsoft": "Information"
      }
    },
    "EventLog": {
      "LogLevel": {
        "Microsoft": "Information"
      }
    },
    "AzureAppServicesFile": {
      "IncludeScopes": true,
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "AzureAppServicesBlob": {
      "IncludeScopes": true,
      "LogLevel": {
        "Microsoft": "Information"
      }
    },
    "ApplicationInsights": {
      "LogLevel": {
        "Default": "Information"
      }
    }
  }
}

Log in Program.cs

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Logger.LogInformation("Adding Routes");
app.MapGet("/", () => "Hello World!");
app.Logger.LogInformation("Starting the app");
app.Run();
var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/Test", async (ILogger<Program> logger, HttpResponse response) =>
{
    logger.LogInformation("Testing logging in Program.cs");
    await response.WriteAsync("Testing");
});

app.Run();
using Microsoft.Extensions.Logging.Console;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddSimpleConsole(i => i.ColorBehavior = LoggerColorBehavior.Disabled);

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/Test", async (ILogger<Program> logger, HttpResponse response) =>
{
    logger.LogInformation("Testing logging in Program.cs");
    await response.WriteAsync("Testing");
});

app.Run();

Set log level by command line, environment variables, and other configuration

set Logging__LogLevel__Microsoft=Information
dotnet run
setx Logging__LogLevel__Microsoft Information /M
"Logging": {
  "Console": {
    "LogLevel": {
      "Microsoft.Hosting.Lifetime": "Trace"
    }
  }
}
setx Logging__Console__LogLevel__Microsoft.Hosting.Lifetime Trace /M

Note When configuring environment variables with names that contain . (periods) in macOS and Linux, consider the "Exporting a variable with a dot (.) in it" question on Stack Exchange and its corresponding accepted answer.

How filtering rules are applied

Logging output from dotnet run and Visual Studio

Log category

public class PrivacyModel : PageModel
{
    private readonly ILogger<PrivacyModel> _logger;

    public PrivacyModel(ILogger<PrivacyModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        _logger.LogInformation("GET Pages.PrivacyModel called.");
    }
}
public class ContactModel : PageModel
{
    private readonly ILogger _logger;

    public ContactModel(ILoggerFactory logger)
    {
        _logger = logger.CreateLogger("TodoApi.Pages.ContactModel.MyCategory");
    }

    public void OnGet()
    {
        _logger.LogInformation("GET Pages.ContactModel called.");
    }

Log level

LogLevel Value Method Description
Trace 0 LogTrace Contain the most detailed messages. These messages may contain sensitive app data. These messages are disabled by default and should not be enabled in production.
Debug 1 LogDebug For debugging and development. Use with caution in production due to the high volume.
Information 2 LogInformation Tracks the general flow of the app. May have long-term value.
Warning 3 LogWarning For abnormal or unexpected events. Typically includes errors or conditions that don't cause the app to fail.
Error 4 LogError For errors and exceptions that cannot be handled. These messages indicate a failure in the current operation or request, not an app-wide failure.
Critical 5 LogCritical For failures that require immediate attention. Examples: data loss scenarios, out of disk space.
None 6 Specifies that a logging category shouldn't write messages.
[HttpGet]
public IActionResult Test1(int id)
{
    var routeInfo = ControllerContext.ToCtxString(id);

    _logger.Log(LogLevel.Information, MyLogEvents.TestItem, routeInfo);
    _logger.LogInformation(MyLogEvents.TestItem, routeInfo);

    return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id);
        return NotFound();
    }

    return ItemToDTO(todoItem);
}
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/Privacy
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint '/Privacy'
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[3]
      Route matched with {page = "/Privacy"}. Executing page /Privacy
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[101]
      Executing handler method DefaultRP.Pages.PrivacyModel.OnGet - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[102]
      Executed handler method OnGet, returned result .
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[103]
      Executing an implicit handler method - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[104]
      Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[4]
      Executed page /Privacy in 74.5188ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint '/Privacy'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 149.3023ms 200 text/html; charset=utf-8
{
  "Logging": {      // Default, all providers.
    "LogLevel": {
      "Microsoft": "Warning"
    },
    "Console": { // Console provider.
      "LogLevel": {
        "Microsoft": "Information"
      }
    }
  }
}

Log event ID

public class MyLogEvents
{
    public const int GenerateItems = 1000;
    public const int ListItems     = 1001;
    public const int GetItem       = 1002;
    public const int InsertItem    = 1003;
    public const int UpdateItem    = 1004;
    public const int DeleteItem    = 1005;

    public const int TestItem      = 3000;

    public const int GetItemNotFound    = 4000;
    public const int UpdateItemNotFound = 4001;
}
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id);
        return NotFound();
    }

    return ItemToDTO(todoItem);
}
info: TodoApi.Controllers.TodoItemsController[1002]
      Getting item 1
warn: TodoApi.Controllers.TodoItemsController[4000]
      Get(1) NOT FOUND

Log message template

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id);
        return NotFound();
    }

    return ItemToDTO(todoItem);
}
var apples = 1;
var pears = 2;
var bananas = 3;

_logger.LogInformation("Parameters: {Pears}, {Bananas}, {Apples}", apples, pears, bananas);
Parameters: 1, 2, 3
_logger.LogInformation("Getting item {Id} at {RequestTime}", id, DateTime.Now);

Log exceptions

[HttpGet("{id}")]
public IActionResult TestExp(int id)
{
    var routeInfo = ControllerContext.ToCtxString(id);
    _logger.LogInformation(MyLogEvents.TestItem, routeInfo);

    try
    {
        if (id == 3)
        {
            throw new Exception("Test exception");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(MyLogEvents.GetItemNotFound, ex, "TestExp({Id})", id);
        return NotFound();
    }

    return ControllerContext.MyDisplayRouteInfo();
}

Default log level

var builder = WebApplication.CreateBuilder();
builder.Logging.SetMinimumLevel(LogLevel.Warning);

Filter function

var builder = WebApplication.CreateBuilder();
builder.Logging.AddFilter((provider, category, logLevel) =>
{
    if (provider.Contains("ConsoleLoggerProvider")
        && category.Contains("Controller")
        && logLevel >= LogLevel.Information)
    {
        return true;
    }
    else if (provider.Contains("ConsoleLoggerProvider")
        && category.Contains("Microsoft")
        && logLevel >= LogLevel.Information)
    {
        return true;
    }
    else
    {
        return false;
    }
});

ASP.NET Core categories

Category Notes
Microsoft.AspNetCore General ASP.NET Core diagnostics.
Microsoft.AspNetCore.DataProtection Which keys were considered, found, and used.
Microsoft.AspNetCore.HostFiltering Hosts allowed.
Microsoft.AspNetCore.Hosting How long HTTP requests took to complete and what time they started. Which hosting startup assemblies were loaded.
Microsoft.AspNetCore.Mvc MVC and Razor diagnostics. Model binding, filter execution, view compilation, action selection.
Microsoft.AspNetCore.Routing Route matching information.
Microsoft.AspNetCore.Server Connection start, stop, and keep alive responses. HTTPS certificate information.
Microsoft.AspNetCore.StaticFiles Files served.
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Trace",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Log scopes

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    TodoItem todoItem;
    var transactionId = Guid.NewGuid().ToString();
    using (_logger.BeginScope(new List<KeyValuePair<string, object>>
        {
            new KeyValuePair<string, object>("TransactionId", transactionId),
        }))
    {
        _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

        todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            _logger.LogWarning(MyLogEvents.GetItemNotFound, 
                "Get({Id}) NOT FOUND", id);
            return NotFound();
        }
    }

    return ItemToDTO(todoItem);
}

Built-in logging providers

Console

Debug

Event Source

dotnet-trace tooling

Find the PID for the process that has the same name as the app's assembly.

dotnet-trace ps

When using a PowerShell command shell, enclose the --providers value in single quotes ('): dotnet-trace collect -p {PID} --providers 'Microsoft-Extensions-Logging:{Keyword}:{Provider Level} :FilterSpecs=" {Logger Category 1}:{Category Level 1}; {Logger Category 2}:{Category Level 2}; ... {Logger Category N}:{Category Level N}"'

On non-Windows platforms, add the -f speedscope option to change the format of the output trace file to speedscope. The following table defines the Keyword:

Keyword Description
1 Log meta events about the LoggingEventSource. Doesn't log events from ILogger.
2 Turns on the Message event when ILogger.Log() is called. Provides information in a programmatic (not formatted) way.
4 Turns on the FormatMessage event when ILogger.Log() is called. Provides the formatted string version of the information.
8 Turns on the MessageJson event when ILogger.Log() is called. Provides a JSON representation of the arguments.

The following table lists the provider levels: | Provider Level | Description | | -------------- | ----------- | | 0 | LogAlways | | 1 | Critical | | 2 | Error | | 3 | Warning | | 4 | Informational | | 5 | Verbose |

The parsing for a category level can be either a string or a number:

Category named value Numeric value
Trace 0
Debug 1
Information 2
Warning 3
Error 4
Critical 5

The provider level and category level:

If no FilterSpecs are specified then the EventSourceLogger implementation attempts to convert the provider level to a category level and applies it to all categories.

Provider Level Category Level
Verbose(5) Debug(1)
Informational(4) Information(2)
Warning(3) Warning(3)
Error(2) Error(4)
Critical(1) Critical(5)

If FilterSpecs are provided, any category that is included in the list uses the category level encoded there, all other categories are filtered out. The following examples assume:

Consider the following command: dotnet-trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:5

The preceding command:

Consider the following command: dotnet-trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:5:\"FilterSpecs=*:5\"

The preceding command:

The following command captures debug messages because category level 1 specifies Debug. dotnet-trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:5:\"FilterSpecs=*:1\"

The following command captures debug messages because category specifies Debug. dotnet-trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:5:\"FilterSpecs=*:Debug\"

FilterSpecs entries for {Logger Category} and {Category Level} represent additional log filtering conditions. Separate FilterSpecs entries with the ; semicolon character. Example using a Windows command shell: dotnet-trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:2:FilterSpecs=\"Microsoft.AspNetCore.Hosting*:4\"

The preceding command activates:

dotnet-trace collect -p {PID} 
    --providers Microsoft-Extensions-Logging:{Keyword}:{Provider Level}
        :FilterSpecs=\"
            {Logger Category 1}:{Category Level 1};
            {Logger Category 2}:{Category Level 2};
            ...
            {Logger Category N}:{Category Level N}\"
Keyword Description
1 Log meta events about the LoggingEventSource. Doesn't log events from ILogger.
2 Turns on the Message event when ILogger.Log() is called. Provides information in a programmatic (not formatted) way.
4 Turns on the FormatMessage event when ILogger.Log() is called. Provides the formatted string version of the information.
8 Turns on the MessageJson event when ILogger.Log() is called. Provides a JSON representation of the arguments.
Provider Level Description
0 LogAlways
1 Critical
2 Error
3 Warning
4 Informational
5 Verbose
Category named value Numeric value
Trace 0
Debug 1
Information 2
Warning 3
Error 4
Critical 5
Provider Level Category Level
Verbose(5) Debug(1)
Informational(4) Information(2)
Warning(3) Warning(3)
Error(2) Error(4)
Critical(1) Critical(5)
dotnet-trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:5
dotnet-trace collect -p %PID%  --providers Microsoft-Extensions-Logging:4:5:\"FilterSpecs=*:5\"
dotnet-trace collect -p %PID%  --providers Microsoft-Extensions-Logging:4:5:\"FilterSpecs=*:1\"
dotnet-trace collect -p %PID%  --providers Microsoft-Extensions-Logging:4:5:\"FilterSpecs=*:Debug\"
dotnet-trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:2:FilterSpecs=\"Microsoft.AspNetCore.Hosting*:4\"

Perfview

Windows EventLog

"Logging": {
  "EventLog": {
    "LogLevel": {
      "Default": "Information"
    }
  }
}
var builder = WebApplication.CreateBuilder();
builder.Logging.AddEventLog(eventLogSettings =>
{
    eventLogSettings.SourceName = "MyLogs";
});

Azure App Service

using Microsoft.Extensions.Logging.AzureAppServices;

var builder = WebApplication.CreateBuilder();
builder.Logging.AddAzureWebAppDiagnostics();
builder.Services.Configure<AzureFileLoggerOptions>(options =>
{
    options.FileName = "azure-diagnostics-";
    options.FileSizeLimit = 50 * 1024;
    options.RetainedFileCountLimit = 5;
});
builder.Services.Configure<AzureBlobLoggerOptions>(options =>
{
    options.BlobName = "log.txt";
});

Azure log streaming

Azure Application Insights

Third-party logging providers

No asynchronous logger methods

Change log levels in a running app

ILogger and ILoggerFactory

Apply log filter rules in code

using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Logging.Debug;

var builder = WebApplication.CreateBuilder();
builder.Logging.AddFilter("System", LogLevel.Debug);
builder.Logging.AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information);
builder.Logging.AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Trace);

Automatically log scope with SpanId, TraceId, ParentId, Baggage, and Tags.

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddSimpleConsole(options =>
{
    options.IncludeScopes = true;
});

builder.Logging.Configure(options =>
{
    options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
                                       | ActivityTrackingOptions.TraceId
                                       | ActivityTrackingOptions.ParentId
                                       | ActivityTrackingOptions.Baggage
                                       | ActivityTrackingOptions.Tags;
});
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Create a custom logger

Additional resources

Ref: Logging in .NET Core and ASP.NET Core