Logging:LogLevel

Set minimum log levels per category and per provider in configuration, predict which rule applies, and change levels per environment without a rebuild.

Signature
"Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }  |  Logging__LogLevel__Default=Warning
  • Added in .NET ASP.NET Core 1.0

Parameters

ParameterTypeDescription
$DefaultoptionalLogLevel= InformationRule for categories no other rule matches.
$<category prefix>optionalLogLevelRule for categories that start with this text, such as Microsoft.AspNetCore or Orders.Payments. The longest matching prefix wins. A * wildcard is allowed once.
$Logging:<Provider>:LogLevel:<category>optionalLogLevelRule for one provider only (Console, Debug, EventSource, EventLog, or a provider alias). Beats the general rules for that provider.

Return value

LogLevelFilter rules: for each logger category and provider, the minimum LogLevel that is written. Messages below it are dropped before formatting.

Errors

Nothing. An invalid level name fails binding with InvalidOperationException when the logger factory is first built. An unknown category or provider name is accepted silently.

Changelog

  • PHP ASP.NET Core 2.0Filter rules (LoggerFilterOptions) bound from the Logging section and applied by the logger factory to every provider.

The Logging section is bound to LoggerFilterOptions. Every rule has three parts: a provider (or none), a category (or none) and a minimum level. A logger's category is the full type name for ILogger<OrdersService>, or the string passed to CreateLogger. Levels are Trace (0), Debug, Information, Warning, Error, Critical and None (6, turns a category off). Without any rule the minimum is Information.

How a rule is chosen

  1. Rules for the specific provider (Logging:Console:LogLevel) are preferred over rules with no provider (Logging:LogLevel).
  2. Among those, the rule with the longest category prefix that matches wins. Default is the rule with no category.
  3. Only one rule applies; levels are not combined.

Matching is a plain, case-insensitive string prefix, not a namespace match. A rule for Orders also matches a category called OrdersReporting. Write rules for a namespace that no unrelated category starts with, for example Orders.Payments rather than Orders when an OrdersReporting namespace also exists.

CategoryTypical production levelWhy
DefaultInformationYour own business events
Microsoft.AspNetCoreWarningPer-request Information logs (request starting/finished, endpoint executing) are high volume
Microsoft.Hosting.LifetimeInformationKeeps "Now listening on" and "Application started"
Microsoft.EntityFrameworkCore.Database.CommandWarning (Information to see SQL)Every SQL command is an Information event
System.Net.Http.HttpClientWarningEach outgoing call logs several Information lines

Changing levels

CreateBuilder loads appsettings.json with reload on change and the logger factory listens for it, so editing the file on a VM changes levels within seconds. In containers the file is in the image, so teams set Logging__LogLevel__Default=Debug and restart one pod. For changes without a restart, mount a ConfigMap as a directory (say /config, not over /app/appsettings.json: that needs subPath, and subPath mounts never update), add it with builder.Configuration.AddJsonFile("/config/logging.json", optional: true, reloadOnChange: true), and set DOTNET_USE_POLLING_FILE_WATCHER=true, because the ConfigMap update is a symlink swap the default watcher often misses. Otherwise plan on a rollout restart. The production failure modes: Debug left on after an incident multiplies log volume and the bill; "Microsoft": "Warning" hides the lifetime messages people grep for during deploys; and Microsoft.EntityFrameworkCore.Database.Command at Information with EnableSensitiveDataLogging writes parameter values, including personal data, to the log store. A message below the threshold costs almost nothing only if you use message templates or LoggerMessage; an interpolated string is built before the level check.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Silence per-request Microsoft.AspNetCore Information logs in production while keeping Microsoft.Hosting.Lifetime startup messages
  • See the SQL EF Core sends for a slow orders query by raising Microsoft.EntityFrameworkCore.Database.Command to Information on one replica
  • Write only warnings to the console in a container but keep Information for an OpenTelemetry exporter via provider-specific rules

Frequently asked questions

What is the default log level if nothing is configured?

Information. The templates also add Microsoft.AspNetCore at Warning in appsettings.json; without that rule every request writes several Information lines.

How do I set a category with dots from an environment variable?

Keep the dots and replace each colon with a double underscore: Logging__LogLevel__Microsoft.AspNetCore=Warning. Linux allows dots in variable names; some shells do not let you export them directly, but Kubernetes and Docker do.

Can I change the level without restarting?

Yes if the setting comes from a reloadable source: CreateBuilder loads appsettings.json with reload on change and the logger factory applies new rules. Environment variables are read once at startup.

More examples

Production appsettings.json

This is the usual production baseline: your code at Information, framework chatter at Warning, lifetime events kept. <code>Orders.Payments</code> at Debug is a temporary rule for an incident; remove it after, because nothing will remind you. The console formatter writes JSON lines that log collectors parse without regexes. There is deliberately no <code>Console:LogLevel</code> block: a provider-level <code>Default</code> beats every general category rule for that provider, so it would put the framework back at Information on the console.

Example · json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.EntityFrameworkCore.Database.Command": "Warning",
      "System.Net.Http.HttpClient": "Warning",
      "Orders.Payments": "Debug"
    },
    "Console": {
      "FormatterName": "json"
    }
  }
}

Turning SQL logging on for one pod

Environment variable names may contain dots, and the double underscore becomes the colon, so this sets <code>Logging:LogLevel:Microsoft.EntityFrameworkCore.Database.Command</code>. One extra replica with verbose logs limits the volume and cost while you reproduce the issue.

Example · yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-api-debug
spec:
  replicas: 1
  selector:
    matchLabels:
      app: orders-api
      track: debug
  template:
    metadata:
      labels:
        app: orders-api
        track: debug
    spec:
      containers:
        - name: api
          image: registry.example.com/orders-api:1.8.2
          env:
            - name: Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command
              value: Information
            - name: Logging__LogLevel__Default
              value: Debug

Templates keep filtered logs cheap

The category is the full name of <code>ShippingEndpoint</code>. Filtering happens inside <code>Log</code>, after the arguments are evaluated. The <code>LoggerMessage</code> source generator saves formatting and boxing when the level is off, but the caller still evaluates its arguments, so an expensive argument such as <code>BuildCarrierPayload(id)</code> needs an <code>IsEnabled</code> guard either way.

Example · csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/orders/{id:int}/ship", (int id, ILogger<ShippingEndpoint> logger) =>
{
    // Bad: the string is built even when Debug is filtered out.
    // logger.LogDebug($"Shipping order {id} at {DateTime.UtcNow:O}");

    // Good: formatting happens only if Debug is enabled for this category.
    logger.LogDebug("Shipping order {OrderId}", id);

    if (logger.IsEnabled(LogLevel.Trace))
    {
        logger.LogTrace("Carrier payload {Payload}", BuildCarrierPayload(id));
    }

    return TypedResults.Accepted($"/orders/{id}/shipment");
});

app.Run();

static string BuildCarrierPayload(int orderId) => $"{{\"order\":{orderId},\"carrier\":\"DHL\"}}";

public sealed class ShippingEndpoint;

Discussion

  • Be the first to comment on this lesson.