Journalisation en C# et .NET

.NET prend en charge la journalisation haute performance structurée via l’API ILogger pour faciliter la supervision du comportement des applications et le diagnostic des problèmes. Les journaux peuvent être écrits dans différentes destinations en configurant différents fournisseurs de journalisation. Les fournisseurs de journalisation de base sont intégrés et de nombreux fournisseurs tiers sont également disponibles.

Bien démarrer

Ce premier exemple montre les principes de base, mais il convient seulement à une application console triviale. Dans la section suivante, vous voyez comment améliorer le code en prenant en compte la mise à l’échelle, les performances, la configuration et les modèles de programmation classiques.

using Microsoft.Extensions.Logging;

using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

L’exemple précédent :

  • Crée un objet ILoggerFactory. ILoggerFactory stocke toute la configuration qui détermine l’emplacement où les messages de journal sont envoyés. Dans le cas présent, vous configurez le fournisseur de journalisation de la console afin que les messages de journal soient écrits dans la console.
  • Crée un ILogger avec une catégorie nommée « Program ». La catégorie est une string associée à chaque message consigné par le ILogger. Elle est utilisée pour regrouper les messages de journalisation ayant la même classe (ou catégorie) lors de la recherche ou du filtrage des journaux.
  • Appelle LogInformation pour journaliser un message au niveau Information. Le niveau de journal indique la gravité de l’événement journalisé et est utilisé pour filtrer les messages de journal moins importants. L’entrée de journal inclut également un modèle de message"Hello World! Logging is {Description}." et une paire clé-valeur Description = fun. Le nom de clé (ou l’espace réservé) provient du mot à l’intérieur des accolades dans le modèle et la valeur provient de l’argument de méthode restant.

Ce fichier projet pour cet exemple inclut deux packages NuGet :

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
  </ItemGroup>

</Project>

Conseil

Tous les exemples de code source de journalisation sont disponibles dans l’Explorateur d’exemples pour téléchargement. Pour plus d’informations, consultez Parcourir les exemples de code : Journalisation dans .NET.

Journalisation dans une application non triviale

Plusieurs modifications de l’exemple précédent sont à envisager pour la journalisation dans un scénario moins trivial :

using Microsoft.Extensions.Logging;

internal partial class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger("Program");
        LogStartupMessage(logger, "fun");
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "Hello World! Logging is {Description}.")]
    static partial void LogStartupMessage(ILogger logger, string description);
}
  • La pratique recommandée pour les noms de catégorie de journal est d’utiliser le nom complet de la classe qui crée le message de journal. Ceci permet de relier les messages de journalisation au code qui les a produits et offre un bon niveau de contrôle lors du filtrage des journaux. CreateLogger accepte un Type pour faciliter ce nommage.
using Microsoft.Extensions.Logging;

internal class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger<Program>();
        logger.LogInformation("Hello World! Logging is {Description}.", "fun");
    }
}
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;

using ILoggerFactory factory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddOtlpExporter();
    });
});
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

Intégration avec des hôtes et une injection de dépendances

Si votre application utilise l’injection de dépendances (DI) ou un hôte comme WebApplication d’ASP.NET ou un hôte générique, vous devez utiliser des objets ILoggerFactory et ILogger depuis le conteneur d’injection de dépendances au lieu de les créer directement.

Obtenir un ILogger depuis l’injection de dépendances

Cet exemple obtient un objet ILogger dans une application hébergée en utilisant les API minimales d’ASP.NET :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ExampleHandler>();

var app = builder.Build();

var handler = app.Services.GetRequiredService<ExampleHandler>();
app.MapGet("/", handler.HandleRequest);

app.Run();

partial class ExampleHandler(ILogger<ExampleHandler> logger)
{
    public string HandleRequest()
    {
        LogHandleRequest(logger);
        return "Hello World";
    }

    [LoggerMessage(LogLevel.Information, "ExampleHandler.HandleRequest was called")]
    public static partial void LogHandleRequest(ILogger logger);
}

L’exemple précédent :

  • Création d’un service singleton appelé ExampleHandler et mappage des requêtes web entrantes pour exécuter la fonction ExampleHandler.HandleRequest.
  • La ligne 8 définit un constructeur principal pour ExampleHandler, une fonctionnalité ajoutée dans C# 12. L’utilisation du constructeur C# de style plus ancien fonctionnerait également bien, mais nécessite un peu plus de code.
  • Le constructeur définit un paramètre de type ILogger<ExampleHandler>. ILogger<TCategoryName> dérive de ILogger et indique la catégorie de l’objet ILogger. Le conteneur d’injection de dépendances localise un ILogger avec la catégorie correcte et la fournit comme argument du constructeur. S’il n’existe pas encore de ILogger avec cette catégorie, le conteneur d’injection de dépendances la crée automatiquement à partir du ILoggerFactory dans le fournisseur du service.
  • Le paramètre logger reçu dans le constructeur a été utilisé pour la journalisation dans la fonction HandleRequest.

ILoggerFactory fourni par l’hôte

Les générateurs d’hôtes initialisent la configuration par défaut, puis ajoutent un objet configuré ILoggerFactory au conteneur d’injection de dépendances de l’hôte lors de la génération de l’hôte. Avant la génération de l’hôte, vous pouvez ajuster la configuration de journalisation via HostApplicationBuilder.Logging, WebApplicationBuilder.Logging ou des API similaires sur d’autres hôtes. Les hôtes appliquent également la configuration de journalisation depuis des sources de configuration par défaut comme appsettings.json et des variables d’environnement. Pour plus d’informations, consultez Configuration dans .NET.

Cet exemple part du précédent pour personnaliser le ILoggerFactory fourni par WebApplicationBuilder. Il ajoute OpenTelemetry comme fournisseur de journalisation qui transmet les journaux sur OTLP (protocole OpenTelemetry) :

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter());
builder.Services.AddSingleton<ExampleHandler>();
var app = builder.Build();

Créer un ILoggerFactory avec l’injection de dépendances

Si vous utilisez un conteneur d’injection de dépendances sans hôte, utilisez AddLogging pour configurer et ajouter ILoggerFactory au conteneur.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// Add services to the container including logging
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole());
services.AddSingleton<ExampleService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();

// Get the ExampleService object from the container
ExampleService service = serviceProvider.GetRequiredService<ExampleService>();

// Do some pretend work
service.DoSomeWork(10, 20);

class ExampleService(ILogger<ExampleService> logger)
{
    public void DoSomeWork(int x, int y)
    {
        logger.LogInformation("DoSomeWork was called. x={X}, y={Y}", x, y);
    }
}

L’exemple précédent :

  • Création d’un conteneur de service d’injection de dépendances contenant un ILoggerFactory configuré pour écrire sur la console
  • Ajout d’un singleton ExampleService au conteneur
  • Création d’une instance de ExampleService du conteneur d’injection de dépendances qui a également créé automatiquement un ILogger<ExampleService> à utiliser comme argument du constructeur.
  • Appel de ExampleService.DoSomeWork qui a utilisé le ILogger<ExampleService> pour consigner un message sur la console.

Configuration de la journalisation

La configuration de la journalisation est définie dans le code ou via des sources externes, comme des fichiers de configuration et des variables d’environnement. Quand elle est possible, l’utilisation d’une configuration externe est avantageuse, car elle peut être changée sans regénérer l’application. Cependant, certaines tâches, comme la définition des fournisseurs de journalisation, peuvent être configurées seulement à partir du code.

Configurer la journalisation sans code

Pour les apps qui utilisent un hôte, la configuration de la journalisation est généralement fournie par la section "Logging" des fichiers appsettings.{Environment}.json. Pour les applications qui n’utilisent pas d’hôte, les sources de configuration externe sont au lieu de cela configurées explicitement ou configurées dans le code.

Le fichier appsettings.Development.json suivant est généré par les modèles de service Worker .NET :

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Dans le code JSON précédent :

  • Les catégories de niveau de journal "Default", "Microsoft" et "Microsoft.Hosting.Lifetime" sont spécifiées.
  • La valeur "Default" est appliquée à toutes les catégories qui ne sont pas spécifiées, ce qui rendra toutes les valeurs par défaut pour toutes les catégories "Information". Vous pouvez remplacer ce comportement en spécifiant une valeur pour une catégorie.
  • La catégorie "Microsoft" s’applique à toutes les catégories qui commencent par "Microsoft".
  • La catégorie "Microsoft" journalise au niveau de journalisation Warning et aux niveaux supérieurs.
  • La catégorie "Microsoft.Hosting.Lifetime" est plus précise que la catégorie "Microsoft". La catégorie "Microsoft.Hosting.Lifetime" journalise donc au niveau "Information" et aux niveaux supérieurs.
  • Aucun fournisseur de journaux n’est spécifié. LogLevel s’applique donc à tous les fournisseurs de journalisation activés, à l’exception de Windows EventLog.

La propriété Logging peut avoir des propriétés LogLevel et des propriétés de fournisseur de journaux. La propriété LogLevel spécifie le niveau de journalisation minimal pour les catégories sélectionnées. Dans le code JSON précédent, les niveaux de journalisation Information et Warning sont spécifiés. LogLevel indique le niveau de gravité du journal, qui peut varier de 0 à 6 :

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5 et None = 6.

Quand LogLevel est spécifié, la journalisation est activée pour les messages au niveau spécifié et aux niveaux supérieurs. Dans le code JSON précédent, la catégorie Default est journalisée pour Information et niveaux supérieurs. Par exemple, les messages Information, Warning, Error et Critical sont journalisés. Si aucun n’est LogLevel spécifié, la journalisation est définie par défaut sur le niveau Information. Pour plus d’informations, consultez Niveaux de journalisation.

Une propriété de fournisseur peut spécifier une propriété LogLevel. Le LogLevel indiqué sous un fournisseur spécifie les niveaux à journaliser pour ce fournisseur, et remplace les paramètres de journalisation ne concernant pas le fournisseur. Considérez le fichier appsettings.json suivant :

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.Hosting": "Trace"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
}

Les paramètres de Logging.{ProviderName}.LogLevel remplacent les paramètres de Logging.LogLevel. Dans le code JSON précédent, le niveau de journalisation par défaut du fournisseur Debug est défini sur Information :

Logging:Debug:LogLevel:Default:Information

Le paramètre précédent spécifie le niveau de journalisation Information pour chaque catégorie Logging:Debug:, sauf Microsoft.Hosting. Lorsqu’une catégorie spécifique est listée, celle-ci remplace la catégorie par défaut. Dans le code JSON précédent, les catégories Logging:Debug:LogLevel"Microsoft.Hosting" et "Default" remplacent les paramètres de Logging:LogLevel

Le niveau de journalisation minimal peut être spécifié pour tous les éléments suivants :

  • Certains fournisseurs : par exemple, Logging:EventSource:LogLevel:Default:Information
  • Certaines catégories : par exemple, Logging:LogLevel:Microsoft:Warning
  • Tous les fournisseurs et toutes les catégories : Logging:LogLevel:Default:Warning

Les journaux situés en dessous du niveau minimal ne seront pas :

  • Passés au fournisseur.
  • Journalisés ou affichés.

Pour supprimer tous les journaux, spécifiez LogLevel.None. LogLevel.None a une valeur de 6, ce qui est supérieur à LogLevel.Critical (5).

Lorsqu’un fournisseur prend en charge les étendues de journal, IncludeScopes indique si elles sont activées. Pour plus d’informations, consultez Étendues de journalisation.

Le fichier appsettings.json suivant contient des paramètres pour tous les fournisseurs intégrés :

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft.Extensions.Hosting": "Warning",
                "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"
            }
        }
    }
}

Dans l’exemple précédent :

  • Les catégories et les niveaux ne sont pas des valeurs suggérées. L’exemple est fourni pour afficher tous les fournisseurs par défaut.
  • Les paramètres de Logging.{ProviderName}.LogLevel remplacent les paramètres de Logging.LogLevel. Par exemple, le niveau dans Debug.LogLevel.Default remplace le niveau dans LogLevel.Default.
  • L’alias de chaque fournisseur est utilisé. Chaque fournisseur définit un alias qui peut être utilisé dans la configuration à la place du nom de type complet. Les alias de fournisseurs intégrés sont les suivants :
    • Console
    • Debug
    • EventSource
    • EventLog
    • AzureAppServicesFile
    • AzureAppServicesBlob
    • ApplicationInsights

Définir le niveau de journalisation à l’aide de la ligne de commande, de variables d’environnement et d’autres méthodes de configuration

Le niveau de journalisation peut être défini par l’un des fournisseurs de configuration. Par exemple, vous pouvez créer une variable d’environnement persistante nommée Logging:LogLevel:Microsoft avec la valeur Information.

Créez et assignez une variable d’environnement persistante, en fonction de la valeur de niveau de journal.

:: Assigns the env var to the value
setx "Logging__LogLevel__Microsoft" "Information" /M

Dans une nouvelle instance de l’invite de commandes, lisez la variable d’environnement.

:: Prints the env var value
echo %Logging__LogLevel__Microsoft%

Le paramètre d’environnement précédent est conservé dans l’environnement. Pour tester les paramètres lors de l’utilisation d’une application créée avec les modèles de service Worker .NET, utilisez la commande dotnet run dans le répertoire du projet après l’affectation de la variable d’environnement.

dotnet run

Conseil

Après avoir défini une variable d’environnement, redémarrez votre environnement de développement intégré (IDE) pour vous assurer que les variables d’environnement nouvellement ajoutées sont disponibles.

Dans Azure App Service, sélectionnez Nouveau paramètre d’application dans la page Paramètres > Configuration. Les paramètres d’application Azure App Service sont :

  • Chiffrés au repos et transmis sur un canal chiffré.
  • Exposés en tant que variables d’environnement.

Pour plus d’informations sur la définition de valeurs de configuration .NET à l’aide de variables d’environnement, consultez Variables d’environnement.

Configurer la journalisation avec du code

Pour configurer la journalisation dans du code, utilisez l’API ILoggingBuilder. Vous pouvez y accéder depuis différents endroits :

Cet exemple montre comment définir le fournisseur de journalisation de la console et plusieurs filtres.

using Microsoft.Extensions.Logging;

using var loggerFactory = LoggerFactory.Create(static builder =>
{
    builder
        .AddFilter("Microsoft", LogLevel.Warning)
        .AddFilter("System", LogLevel.Warning)
        .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
        .AddConsole();
});

ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogDebug("Hello {Target}", "Everyone");

Dans l’exemple précédent, AddFilter est utilisé pour ajuster le niveau de journalisation qui est activé pour différentes catégories. AddConsole est utilisé pour ajouter le fournisseur de journalisation de la console. Par défaut, les journaux avec la gravité Debug ne sont pas activés, mais comme la configuration a ajusté les filtres, le message de débogage « Hello Everyone » s’affiche sur la console.

Mode d’application des règles de filtre

À la création d’un objet ILogger<TCategoryName>, l’objet ILoggerFactory sélectionne une seule règle à appliquer à cet enregistrement d’événements par fournisseur. Tous les messages écrits par une instance ILogger sont filtrés selon les règles sélectionnées. La règle la plus précise qui peut être appliquée à chaque paire catégorie/fournisseur est sélectionnée parmi les règles disponibles.

L’algorithme suivant est utilisé pour chaque fournisseur quand un objet ILogger est créé pour une catégorie donnée :

  • Sélectionnez toutes les règles qui correspondent au fournisseur ou à son alias. Si aucune correspondance n’est trouvée, sélectionnez toutes les règles avec un fournisseur vide.
  • À partir du résultat de l’étape précédente, sélectionnez les règles ayant le plus long préfixe de catégorie correspondant. Si aucune correspondance n’est trouvée, sélectionnez toutes les règles qui ne spécifient pas de catégorie.
  • Si plusieurs règles sont sélectionnées, prenez la dernière.
  • Si aucune règle n’est sélectionnée, utilisez LoggingBuilderExtensions.SetMinimumLevel(ILoggingBuilder, LogLevel) pour spécifier le niveau de journalisation minimale.

Catégorie de journal

Quand un objet ILogger est créé, une catégorie est spécifiée. Cette catégorie est incluse dans tous les messages de journal créés par cette instance de ILogger. La chaîne de la catégorie est arbitraire, mais la convention est d’utiliser le nom complet de la classe. Par exemple, dans une application avec un service défini comme l’objet suivant, la catégorie peut être "Example.DefaultService" :

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger<DefaultService> _logger;

        public DefaultService(ILogger<DefaultService> logger) =>
            _logger = logger;

        // ...
    }
}

SSi une catégorisation plus poussée est souhaitée, la convention consiste à utiliser un nom hiérarchique en ajoutant une sous-catégorie au nom de classe complet, et à spécifier explicitement la catégorie à l’aide de LoggerFactory.CreateLogger :

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger _logger;

        public DefaultService(ILoggerFactory loggerFactory) =>
            _logger = loggerFactory.CreateLogger("Example.DefaultService.CustomCategory");

        // ...
    }
}

L’appel de CreateLogger avec un nom fixe peut être utile si vous l’utilisez dans plusieurs méthodes afin que les événements puissent être organisés par catégorie.

ILogger<T> équivaut à appeler CreateLogger avec le nom de type complet T.

Log level

Le tableau suivant liste les valeurs LogLevel, la méthode d’extension de commodité Log{LogLevel}, ainsi que des suggestions d’utilisation :

LogLevel Valeur Méthode Description
Trace 0 LogTrace Contiennent les messages les plus détaillés. Ces messages peuvent contenir des données d’application sensibles. Ils sont désactivés par défaut et ne doivent pas être activés dans un environnement de production.
Déboguer 1 LogDebug Pour le débogage et le développement. À utiliser avec prudence dans un environnement de production en raison du volume élevé de messages.
Information 2 LogInformation Effectue le suivi du flux général de l’application. Peut avoir une valeur à long terme.
Avertissement 3 LogWarning Pour les événements anormaux ou inattendus. Comprend généralement des erreurs ou des conditions qui n’entraînent pas l’échec de l’application.
Error 4 LogError Fournit des informations sur des erreurs et des exceptions qui ne peuvent pas être gérées. Ces messages indiquent un échec de l’opération ou de la demande en cours, et non l’échec de l’application.
Critical 5 LogCritical Fournit des informations sur des échecs qui nécessitent un examen immédiat. Exemples : perte de données, espace disque insuffisant.
Aucun 6 Spécifie qu’aucun message ne doit être écrit.

Dans le tableau précédent, LogLevel est listé du plus faible niveau de gravité au plus élevé.

Le premier paramètre de la méthode Log (LogLevel) indique le niveau de gravité du journal. Au lieu d’appeler Log(LogLevel, ...), la plupart des développeurs appellent les méthodes d’extension Log{LogLevel}. Les méthodes d’extension Log{LogLevel}appellent la méthode Log et spécifient le LogLevel. Par exemple, les deux appels de journalisation suivants ont un fonctionnement équivalent et produisent le même journal :

public void LogDetails()
{
    var logMessage = "Details for log.";

    _logger.Log(LogLevel.Information, AppLogEvents.Details, logMessage);
    _logger.LogInformation(AppLogEvents.Details, logMessage);
}

AppLogEvents.Details est l’ID d’événement et est implicitement représenté par une valeur de constante Int32. AppLogEvents est une classe qui expose diverses constantes d’identificateur nommées et qui s’affiche dans la section ID d’événement du journal.

Le code suivant crée des journaux Information et Warning :

public async Task<T> GetAsync<T>(string id)
{
    _logger.LogInformation(AppLogEvents.Read, "Reading value for {Id}", id);

    var result = await _repository.GetAsync(id);
    if (result is null)
    {
        _logger.LogWarning(AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
    }

    return result;
}

Dans le code précédent, le premier paramètre Log{LogLevel} (AppLogEvents.Read) est l’ID de l’événement de journal. Le deuxième paramètre est un modèle de message contenant des espaces réservés pour les valeurs d’argument fournies par les autres paramètres de méthode. Les paramètres de méthode sont expliqués dans la section Modèle de message plus loin dans cet article.

Configurez le niveau de journal approprié et appelez les méthodes Log{LogLevel} appropriées pour contrôler la quantité de sortie du journal écrite sur un support de stockage particulier. Exemple :

  • En production :
    • La journalisation aux niveaux Trace ou Debug produit un volume élevé de messages de journal détaillés. Pour contrôler les coûts et ne pas dépasser les limites de stockage des données, journalisez les messages de niveau Trace et Debug dans un magasin de données à haut volume et à faible coût. Il est conseillé de limiter Trace et Debug à certaines catégories.
    • La journalisation aux niveaux allant de Warning à Critical doivent produire moins de messages de journal.
      • Les coûts et les limites de stockage ne posent généralement pas problème.
      • Peu de journaux permettent une plus grande flexibilité au niveau du choix du magasin de données.
  • Lors du développement :
    • Réglez sur Warning.
    • Ajoutez les messages Trace ou Debug lorsque vous résolvez des problèmes. Pour limiter la sortie, définissez Trace ou Debug uniquement pour les catégories en cours d’investigation.

Le code JSON suivant définit Logging:Console:LogLevel:Microsoft:Information :

{
    "Logging": {
        "LogLevel": {
            "Microsoft": "Warning"
        },
        "Console": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        }
    }
}

ID d’événement de log

Chaque journal peut spécifier un identificateur d’événement, le EventId est une structure avec un Id et des propriétés en lecture seule facultatives Name. L’exemple de code source utilise la classe AppLogEvents pour définir les ID d’événement :

using Microsoft.Extensions.Logging;

internal static class AppLogEvents
{
    internal static EventId Create = new(1000, "Created");
    internal static EventId Read = new(1001, "Read");
    internal static EventId Update = new(1002, "Updated");
    internal static EventId Delete = new(1003, "Deleted");

    // These are also valid EventId instances, as there's
    // an implicit conversion from int to an EventId
    internal const int Details = 3000;
    internal const int Error = 3001;

    internal static EventId ReadNotFound = 4000;
    internal static EventId UpdateNotFound = 4001;

    // ...
}

Conseil

Pour plus d’informations sur la conversion d’un int en un EventId, consultez Opérateur EventId.Implicit(Int32 en EventId).

Un ID d’événement associe un jeu d’événements. Par exemple, tous les journaux liés à la lecture des valeurs d’un référentiel peuvent être 1001.

Le fournisseur de journalisation peut journaliser l’ID d’événement dans un champ ID, dans le message de journalisation, ou pas du tout. Le fournisseur Debug n’affiche pas les ID d’événements. Le fournisseur Console affiche les ID d’événements entre crochets après la catégorie :

info: Example.DefaultService.GetAsync[1001]
      Reading value for a1b2c3
warn: Example.DefaultService.GetAsync[4000]
      GetAsync(a1b2c3) not found

Certains fournisseurs de journalisation stockent l’ID d’événement dans un champ, ce qui permet de filtrer en fonction de l’ID.

Modèle de message de journal

Chaque API de journalisation utilise un modèle de message. Ce dernier peut contenir des espaces réservés pour lesquels les arguments sont fournis. Utilisez des noms et non des nombres pour les espaces réservés. L’ordre des espaces réservés, pas leurs noms, détermine quels paramètres sont utilisés pour fournir leurs valeurs. Dans le code suivant, les noms de paramètres sont hors séquence dans le modèle de message :

string p1 = "param1";
string p2 = "param2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

Le code précédent crée un message de journal avec les valeurs des paramètres dans la séquence :

Parameter values: param1, param2

Notes

Soyez attentif lorsque vous utilisez plusieurs espaces réservés dans un seul modèle de message, car ils sont basés sur des ordinaux. Les noms ne sont pas utilisés pour aligner les arguments sur les espaces réservés.

Cette approche permet aux fournisseurs de journalisation d’implémenter une journalisation sémantique ou structurée. Les arguments proprement dits, et pas seulement le modèle de message mis en forme, sont transmis au système de journalisation. Cela permet aux fournisseurs de journalisation de stocker les valeurs des paramètres sous forme de champs. Tenez compte de la méthode d’enregistreur d’événements suivante :

_logger.LogInformation("Getting item {Id} at {RunTime}", id, DateTime.Now);

Par exemple, lorsque vous journalisez des événements vers le Stockage Table Azure :

  • Chaque entité de table Azure peut avoir des propriétés ID et RunTime.
  • Les tables avec des propriétés simplifient les requêtes qui sont exécutées sur des données journalisées. Par exemple, vous pouvez rechercher tous les journaux compris dans une plage RunTime spécifique, sans avoir besoin d’analyser le délai d’expiration du message texte.

Mise en forme du modèle de message de journal

Les modèles de messages de journal prennent en charge la mise en forme des espaces réservés. Les modèles sont libres de spécifier n’importe quel format valide pour l’argument de type donné. Prenons l’exemple de le modèle de message d’enregistreur d’événements Information suivant :

_logger.LogInformation("Logged on {PlaceHolderName:MMMM dd, yyyy}", DateTimeOffset.UtcNow);
// Logged on January 06, 2022

Dans l’exemple précédent, l’instance DateTimeOffset est le type qui correspond au PlaceHolderName dans le modèle de message d’enregistreur d’événements. Ce nom peut être n’importe quel nom, car les valeurs sont basées sur des ordinaux. Le format MMMM dd, yyyy est valide pour le type DateTimeOffset.

Pour plus d’informations sur la mise en forme de DateTime et DateTimeOffset, consultez Chaînes de format de date et d’heure personnalisées.

Exemples

Les exemples suivants montrent comment mettre en forme un modèle de message à l’aide de la syntaxe d’espace réservé {}. En outre, un exemple d’échappement de la syntaxe d’espace réservé {} s’affiche avec sa sortie. Enfin, l’interpolation de chaîne avec des espaces réservés de création de modèles est également affichée :

logger.LogInformation("Number: {Number}", 1);               // Number: 1
logger.LogInformation("{{Number}}: {Number}", 3);           // {Number}: 3
logger.LogInformation($"{{{{Number}}}}: {{Number}}", 5);    // {Number}: 5

Conseil

  • Dans la plupart des cas, vous devez utiliser la mise en forme du modèle de message de journal pour la journalisation. L’utilisation de l’interpolation de chaîne peut entraîner des problèmes de performances.
  • La règle d’analyse du code CA2254 : Le modèle doit être une expression statique vous avertit des endroits où vos messages de journal n’utilisent pas la mise en forme appropriée.

Enregistrer des exceptions

Les méthodes d’enregistreur d’événements ont des surcharges qui prennent un paramètre d’exception :

public void Test(string id)
{
    try
    {
        if (id is "none")
        {
            throw new Exception("Default Id detected.");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(
            AppLogEvents.Error, ex,
            "Failed to process iteration: {Id}", id);
    }
}

La journalisation des exceptions dépend du fournisseur.

Niveau de journalisation par défaut

Si le niveau de journalisation par défaut n’est pas défini, la valeur sera Information.

Par exemple, considérez l’application de service Worker suivante :

  • Créé avec les modèles Worker .NET.
  • appsettings.json and appsettings.Development.json supprimés ou renommés.

Avec la configuration précédente, l’accès à la page de confidentialité ou d’accueil produit de nombreux messages Trace, Debug et Information de catégorie Microsoft.

Le code suivant définit le niveau de journalisation par défaut lorsque celui-ci n’est pas défini dans la configuration :

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

await host.RunAsync();

fonction Filter

Une fonction de filtre est appelée pour tous les fournisseurs et toutes les catégories auxquels aucune règle n’est affectée dans la configuration ou dans le code :

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddFilter((provider, category, logLevel) =>
{
    return provider.Contains("ConsoleLoggerProvider")
        && (category.Contains("Example") || category.Contains("Microsoft"))
        && logLevel >= LogLevel.Information;
});

using IHost host = builder.Build();

await host.RunAsync();

Le code précédent affiche les journaux de console lorsque la catégorie contient Example ou Microsoft, et si le niveau de journalisation est Information ou supérieur.

Étendues de journal

Une étendue regroupe un ensemble d’opérations logiques. Ce regroupement permet de joindre les mêmes données à tous les journaux créés au sein d’un ensemble. Par exemple, chaque journal créé dans le cadre du traitement d’une transaction peut contenir l’ID de la transaction.

Une étendue :

Les fournisseurs suivants prennent en charge les étendues :

Utilisez une étendue en incluant les appels de l’enregistrement d’événements dans un bloc using :

public async Task<T> GetAsync<T>(string id)
{
    T result;
    var transactionId = Guid.NewGuid().ToString();

    using (_logger.BeginScope(new List<KeyValuePair<string, object>>
        {
            new KeyValuePair<string, object>("TransactionId", transactionId),
        }))
    {
        _logger.LogInformation(
            AppLogEvents.Read, "Reading value for {Id}", id);

        var result = await _repository.GetAsync(id);
        if (result is null)
        {
            _logger.LogWarning(
                AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
        }
    }

    return result;
}

Le JSON suivant active les étendues pour le fournisseur Console :

{
    "Logging": {
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Warning",
                "Default": "Information"
            }
        },
        "LogLevel": {
            "Default": "Debug"
        }
    }
}

Le code suivant active les étendues pour le fournisseur Console :

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.IncludeScopes = true);

using IHost host = builder.Build();

await host.RunAsync();

Créer des journaux dans Main

Le code suivant journalise dans Main en obtenant une instance ILogger à partir d’une injection de dépendances après avoir créé l’hôte :

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using IHost host = Host.CreateApplicationBuilder(args).Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Host created.");

await host.RunAsync();

Le code précédent s’appuie sur deux packages NuGet :

Son fichier projet se présente comme suit :

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
  </ItemGroup>

</Project>

Aucune méthode d’enregistreur d’événements asynchrone

La journalisation doit être suffisamment rapide par rapport au coût du code asynchrone en matière de performances. Si un magasin de données de journalisation est lent, n’écrivez pas de données directement dedans. Écrivez les messages de journal dans un magasin rapide, puis déplacez-les vers le magasin lent. Par exemple, lorsque vous vous connectez à SQL Server, ne le faites pas directement dans une méthode Log, puisque les méthodes Log sont synchrones. Ajoutez plutôt de façon synchronisée des messages de journal à une file d’attente en mémoire, puis configurez un traitement en arrière-plan afin d’extraire les messages de la file d’attente et d’effectuer le travail asynchrone d’envoi des données vers SQL Server.

Modifier les niveaux de journalisation dans une application en cours d’exécution

L’API de journalisation ne permet pas de modifier les niveaux de journalisation pendant l’exécution d’une application. Toutefois, certains fournisseurs de configuration sont capables de recharger la configuration, ce qui agit immédiatement sur la configuration de la journalisation. Par exemple, le fournisseur de configuration de fichier recharge la configuration de journalisation par défaut. Si la configuration est modifiée dans le code pendant l’exécution d’une application, celle-ci peut appeler IConfigurationRoot.Reload pour mettre à jour sa configuration de journalisation.

Packages NuGet

Les interfaces ILogger<TCategoryName> et ILoggerFactory ainsi que les implémentations sont incluses dans la plupart des kits SDK .NET comme référence de package implicite. Elles sont également disponibles explicitement dans les packages NuGet suivants lorsqu’elles ne sont pas référencées implicitement :

Pour plus d’informations sur les références de package implicites du Kit de développement logiciel (SDK) .NET, consultez le Kit de développement logiciel (SDK) .NET : table vers espace de noms implicite.

Voir aussi