Partager via


Système de logs simple

Conseil / Astuce

Vous pouvez télécharger l’exemple de cet article à partir de GitHub.

La journalisation simple d’Entity Framework Core (EF Core) peut être utilisée pour obtenir facilement des journaux lors du développement et du débogage d’applications. Cette forme de journalisation nécessite une configuration minimale et aucun package NuGet supplémentaire.

Conseil / Astuce

EF Core s’intègre également à Microsoft.Extensions.Logging, qui nécessite plus de configuration, mais est souvent plus adapté à la journalisation dans les applications de production.

Paramétrage

Les journaux EF Core sont accessibles à partir de n’importe quel type d’application grâce à LogTo lors de la configuration d’une instance DbContext. Cette configuration est généralement effectuée dans un remplacement de DbContext.OnConfiguring. Par exemple:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine);

Alternativement, LogTo peut être appelé comme partie de AddDbContext ou lors de la création d’une instance DbContextOptions à passer au constructeur DbContext.

Conseil / Astuce

OnConfiguring est toujours appelé lorsque AddDbContext est utilisé ou qu’une instance DbContextOptions est passée au constructeur DbContext. Cela permet d’appliquer la configuration de contexte, quel que soit le mode de construction de DbContext.

Diriger les fichiers journal

Journalisation dans la console

LogTo nécessite un Action<T> délégué qui accepte une chaîne. EF Core va appeler ce délégué avec une chaîne pour chaque message de log généré. Il incombe ensuite au délégué de faire quelque chose avec le message donné.

La Console.WriteLine méthode est souvent utilisée pour ce délégué, comme indiqué ci-dessus. Cela entraîne l’écriture de chaque message de journal dans la console.

Journalisation dans la fenêtre de débogage

Debug.WriteLine peut être utilisé pour envoyer la sortie à la fenêtre Débogage dans Visual Studio ou d’autres IDE. La syntaxe lambda doit être utilisée dans ce cas, car la Debug classe est compilée hors des builds de mise en production. Par exemple:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(message => Debug.WriteLine(message));

Journalisation dans un fichier

L’écriture dans un fichier nécessite la création d’un StreamWriter ou d’un élément similaire pour le fichier. La WriteLine méthode peut ensuite être utilisée comme dans les autres exemples ci-dessus. N’oubliez pas de vous assurer que le fichier est fermé correctement en libérant le flux d'écriture lorsque le contexte est supprimé. Par exemple:

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();
}

Conseil / Astuce

Envisagez d’utiliser Microsoft.Extensions.Logging pour la journalisation des fichiers dans des applications de production.

Obtention de messages détaillés

Données sensibles

Par défaut, EF Core n’inclut pas les valeurs des données dans les messages d’exception. Cela est dû au fait que ces données peuvent être confidentielles et peuvent être révélées en production si une exception n’est pas gérée.

Toutefois, connaître les valeurs de données, en particulier pour les clés, peut être très utile lors du débogage. Cela peut être activé dans EF Core en appelant EnableSensitiveDataLogging(). Par exemple:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableSensitiveDataLogging();

Exceptions de requête détaillées

Pour des raisons de performances, EF Core n’encapsule pas chaque appel pour lire une valeur du fournisseur de base de données dans un bloc try-catch. Toutefois, cela entraîne parfois des exceptions difficiles à diagnostiquer, en particulier lorsque la base de données retourne une valeur NULL lorsqu’elle n’est pas autorisée par le modèle.

Activer EnableDetailedErrors entraînera EF à introduire ces blocs "try-catch" et fournira ainsi des erreurs plus détaillées. Par exemple:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableDetailedErrors();

Filtrage

Niveaux de journalisation

Chaque message de journal EF Core est affecté à un niveau défini par l’énumération LogLevel . Par défaut, la journalisation simple de EF Core inclut chaque message du niveau Debug ou supérieur. LogTo peut recevoir un niveau minimal plus élevé pour filtrer certains messages. Par exemple, passer Information entraîne un ensemble minimal de journaux limité à l’accès à la base de données et à certains messages de maintenance.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

Messages spécifiques

Chaque message de journal est affecté à un EventId. Ces ID sont accessibles à partir de la CoreEventId classe ou de la RelationalEventId classe pour les messages relationnels spécifiques. Un fournisseur de base de données peut également avoir des ID spécifiques au fournisseur dans une classe similaire. Par exemple, SqlServerEventId pour le fournisseur SQL Server.

LogTo peut être configuré pour enregistrer uniquement les messages associés à un ou plusieurs ID d’événement. Par exemple, pour journaliser uniquement les messages lorsque le contexte est initialisé ou est éliminé :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });

Catégories de message

Chaque message de journal est affecté à une catégorie d’enregistreur d’événements hiérarchique nommée. Les catégories sont les suivantes :

Catégorie Messages
Microsoft.EntityFrameworkCore Tous les messages EF Core
Microsoft.EntityFrameworkCore.Database Toutes les interactions de base de données
Microsoft.EntityFrameworkCore.Database.Connection Utilisations d’une connexion de base de données
Microsoft.EntityFrameworkCore.Database.Command Utilisations d’une commande de base de données
Microsoft.EntityFrameworkCore.Database.Transaction Utilisations d’une transaction de base de données
Microsoft.EntityFrameworkCore.Update Enregistrement d’entités, à l’exclusion des interactions de base de données
Microsoft.EntityFrameworkCore.Model Toutes les interactions de modèle et de métadonnées
Microsoft.EntityFrameworkCore.Model.Validation Validation du modèle
Microsoft.EntityFrameworkCore.Query Requêtes, à l’exclusion des interactions de base de données
Microsoft.EntityFrameworkCore.Infrastructure Événements généraux, tels que la création de contexte
Structure Microsoft.EntityFrameworkCore.Scaffolding Ingénierie inverse de base de données
Microsoft.EntityFrameworkCore.Migrations Migrations
Microsoft.EntityFrameworkCore.ChangeTracking Interactions de suivi des modifications

LogTo peut être configuré pour enregistrer uniquement les messages d’une ou plusieurs catégories. Par exemple, pour journaliser uniquement les interactions de base de données :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });

Notez que la DbLoggerCategory classe fournit une API hiérarchique pour rechercher une catégorie et évite la nécessité de chaînes de code dur.

Étant donné que les catégories sont hiérarchiques, cet exemple utilisant la Database catégorie inclut tous les messages pour les sous-catégories Database.Connection, Database.Commandet Database.Transaction.

Filtres personnalisés

LogTo permet à un filtre personnalisé d’être utilisé pour les cas où aucune des options de filtrage ci-dessus n’est suffisante. Par exemple, pour enregistrer un message au niveau Information ou supérieur, ainsi que des messages pour l’ouverture et la fermeture d’une connexion :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(
            Console.WriteLine,
            (eventId, logLevel) => logLevel >= LogLevel.Information
                                   || eventId == RelationalEventId.ConnectionOpened
                                   || eventId == RelationalEventId.ConnectionClosed);

Conseil / Astuce

Le filtrage à l’aide de filtres personnalisés ou l’une des autres options présentées ici est plus efficace que le filtrage dans le LogTo délégué. Cela est dû au fait que si le filtre détermine que le message ne doit pas être journalisé, le message de journal n’est même pas créé.

Configuration pour des messages spécifiques

L’API EF Core ConfigureWarnings permet aux applications de modifier ce qui se passe lorsqu’un événement spécifique est rencontré. Cela peut être utilisé pour :

  • Modifier le niveau du journal auquel l’événement est journalisé
  • Ignorer complètement la journalisation de l’événement
  • Lever une exception lorsque l’événement se produit

Modification du niveau de journalisation pour un événement

L'exemple précédent a utilisé un filtre personnalisé pour enregistrer chaque message à LogLevel.Information ainsi que pour deux événements définis pour LogLevel.Debug. La même chose peut être obtenue en changeant le niveau du journal des deux événements Debug à 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);

Supprimer la journalisation d’un événement

De la même façon, un événement individuel peut être supprimé de l'enregistrement. Cela est particulièrement utile pour ignorer un avertissement qui a été examiné et compris. Par exemple:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
        .LogTo(Console.WriteLine);

Organiser un événement

Enfin, EF Core peut être configuré pour lever pour un événement donné. Cela est particulièrement utile pour modifier un avertissement en une erreur. (En effet, c’était l’objectif original de ConfigureWarnings la méthode, donc le nom.) Par exemple:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
        .LogTo(Console.WriteLine);

Contenu et mise en forme des messages

Le contenu par défaut de LogTo est formaté sur plusieurs lignes. La première ligne contient des métadonnées de message :

  • Préfixe LogLevel à quatre caractères
  • Horodatage local mis en forme pour la culture actuelle
  • Dans le formulaire EventId qui peut être copié/collé pour obtenir le membre à partir de CoreEventId ou l’une des autres EventId classes, ainsi que la valeur d’ID brute
  • Catégorie d’événement, comme décrit ci-dessus.

Par exemple:

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.

Ce contenu peut être personnalisé en passant des valeurs depuis DbContextLoggerOptions, comme illustré dans les sections suivantes.

Conseil / Astuce

Envisagez d’utiliser Microsoft.Extensions.Logging pour plus de contrôle sur la mise en forme du journal.

Utilisation de l’heure UTC

Par défaut, les horodatages sont conçus pour la consommation locale en cours de débogage. Utilisez DbContextLoggerOptions.DefaultWithUtcTime plutôt des horodatages UTC indépendants de la culture, mais conservez tout le même. Par exemple:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithUtcTime);

Cet exemple montre comment mettre en forme les journaux suivants :

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.

Journalisation sur une seule ligne

Parfois, il est utile d’obtenir exactement une ligne par message de journal. Cela peut être activé par DbContextLoggerOptions.SingleLine. Par exemple:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);

Cet exemple montre comment mettre en forme les journaux suivants :

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.

Autres options de contenu

D’autres indicateurs DbContextLoggerOptions peuvent être utilisés pour réduire la quantité de métadonnées incluses dans le journal. Cela peut être utile en combinaison avec la journalisation en une seule ligne. Par exemple:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);

Cet exemple montre comment mettre en forme les journaux suivants :

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.

Déplacement d’EF6

La journalisation simple d’EF Core diffère d’Database.Log EF6 de deux manières importantes :

  • Les messages de journal ne sont pas limités aux interactions de base de données uniquement
  • La journalisation doit être configurée au moment de l’initialisation du contexte

Pour la première différence, le filtrage décrit ci-dessus peut être utilisé pour limiter les messages enregistrés.

La deuxième différence est une modification intentionnelle pour améliorer les performances en ne générant pas de messages de journal lorsqu’ils ne sont pas nécessaires. Toutefois, il est toujours possible d’obtenir un comportement similaire à EF6 en créant une Log propriété sur votre DbContext , puis en l’utilisant uniquement lorsqu’elle a été définie. Par exemple:

public Action<string> Log { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(s => Log?.Invoke(s));