Avril 2016
Volume 31 numéro 4
Cet article a fait l'objet d'une traduction automatique.
L’essentiel de .NET : journalisation avec .NET Core
Par Mark Michaelis
Dans le numéro de février, je me permets dans la nouvelle configuration API inclus dans la plate-forme .NET Core 1.0 nouvellement créée (voir bit.ly/1OoqmkJ). (Je suppose que la plupart des lecteurs ont entendu parler récemment renommé Core .NET 1.0, qui a été précédemment appelé .NET Core 5 et la partie de la plate-forme ASP.NET 5 [voir bit.ly/1Ooq7WI].) Dans cet article, j'ai utilisé afin d'Explorer l'API Microsoft.Extensions.Configuration de test unitaire. Dans cet article j'adopter une approche similaire, sauf avec Microsoft.Extensions.Logging. La différence principale dans mon approche est que je suis le test à partir d'un fichier de .NET 4.6 CSPROJ plutôt qu'un projet ASP.NET Core. Cela met l'accent sur le fait que le .NET Core est disponible, vous pouvez envisager d'utiliser immédiatement, même si vous n'avez pas migré vers les projets ASP.NET principale.
Journalisation ? Pourquoi diable devons-nous une nouvelle infrastructure de journalisation ? Nous avons déjà NLog, Log4Net, Loggr, Serilog et le Microsoft.Diagnostics.Trace/Debug/TraceSource intégrés, n'en citer que quelques-uns. En fait, le fait qu'il existe de nombreuses infrastructures de journalisation est en fait un des facteurs qui rendent Microsoft.Extensions.Logging pertinentes. En tant que développeur face à la multitude de choix, vous risquez de sélectionner un savoir que vous devrez peut-être basculer vers un autre plus tard. Par conséquent, vous êtes probablement tenté d'écrire votre propre wrapper d'API de journalisation qui appelle quelle infrastructure de journalisation particulier, vous ou votre entreprise choisit cette semaine. De même, vous pouvez utiliser une infrastructure de journalisation particulière dans votre application, pour découvrir que l'une des bibliothèques que vous mettez à profit utilise un autre, vous devez écrire un écouteur qui accepte les messages à partir d'un à l'autre à l'origine.
Ce que Microsoft fournit avec Microsoft.Extensions.Logging étant ce wrapper tout le monde n'ait pas à écrire leurs propres. Ce wrapper fournit un ensemble d'API qui sont ensuite transmis à un fournisseur de votre choix. Et bien, tandis que Microsoft inclut des fournisseurs pour des opérations comme la Console (Microsoft.Extensions.Logging.Console), le débogage (Microsoft.Extensions.Logging.Debug), le journal des événements (Microsoft.Extensions.Logging.EventLog) et le TraceSource (Microsoft.Estensions.Logging.TraceSource), il a collaboré également avec les différentes équipes de framework de journalisation (y compris des tiers, tels que NLog, Serilog, Loggr, Log4Net, etc.) afin qu'il existe trop Microsoft.Extensions.Logging compatibles fournisseurs.
Démarrage
La racine de l'enregistrement de l'activité commence par une fabrique de journal, comme indiqué dans Figure 1.
Figure 1 utilisation Microsoft.Extensions.Logging
public static void Main(string[] args = null)
{
ILoggerFactory loggerFactory = new LoggerFactory()
.AddConsole()
.AddDebug();
ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation(
"This is a test of the emergency broadcast system.");
}
Comme indiqué dans le code, pour commencer, vous instanciez un Microsoft.Extensions.Logging.LoggerFactory, qui implémente ILoggerFactory dans le même espace de noms. Ensuite, vous spécifiez les fournisseurs que vous souhaitez utiliser en tirant parti de la méthode d'extension de ILoggerFactory. Dans Figure 1, j'utilise spécifiquement Microsoft.Extensions.Logging.ConsoleLoggerExtensions.AddConsole et Microsoft.Extensions.Logging.DebugLoggerFactoryExtensions.AddDebug. (Bien que les classes se trouvent dans l'espace de noms Microsoft.Extensions.Logging, elles sont réellement trouvées dans les packages Microsoft.Extensions.Logging.Console et Microsoft.Extensions.Logging.Debug NuGet, respectivement.)
Les méthodes d'extension sont des raccourcis pratiques simplement la manière dont plus général ajouter un provider—ILoggerFactory.AddProvider (ILoggerProvider fournisseur). Le raccourci est que la méthode AddProvider nécessite une instance de module fournisseur d'informations : probablement dont le constructeur requiert une expression de filtre de niveau de journalisation de l'un, tandis que les méthodes d'extension fournissent des valeurs par défaut pour ces expressions. Par exemple, la signature du constructeur de ConsoleLoggerProvider est :
public ConsoleLoggerProvider(Func<string, LogLevel, bool> filter,
bool includeScopes);
Le premier paramètre est une expression de prédicat qui vous permet de définir si un message s'affiche dans la sortie en fonction de la valeur du texte connecté et le niveau de journal.
Par exemple, vous pouvez appeler AddProvider avec une instance spécifique de ConsoleLoggerProvider qui a été construite à partir d'un filtre de tous les messages plus élevées (plus importante) à LogLevel.Information :
loggerFactory.AddProvider(
new ConsoleLoggerProvider(
(text, logLevel) => logLevel >= LogLevel.Verbose , true));
(Chose intéressante, contrairement aux méthodes d'extension qui retournent un ILoggerFactory, AddProvider retourne void, empêchant la syntaxe de type FLUIDE illustrée Figure 1.)
Il est important d'être informé que, malheureusement, il existe certains incohérence entre les modules fournisseurs d'informations une niveau de journalisation de valeur élevée comme étant plus ou moins importante. Un niveau de journal 6 indique une erreur critique s'est produite, ou il est simplement un message de diagnostic détaillé ? Microsoft.Extensions.Logging.LogLevel utilise des valeurs élevées pour indiquer une priorité plus élevée avec la déclaration d'enum LogLevel suivante :
public enum LogLevel
{
Debug = 1,
Verbose = 2,
Information = 3,
Warning = 4,
Error = 5,
Critical = 6,
None = int.MaxValue
}
Par conséquent, à l'instanciation d'un ConsoleLoggerProvider qu'écritures messages uniquement lorsque le logLevel > = LogLevel.Verbose, vous excluez uniquement les messages au niveau du débogage d'être écrit dans la sortie.
Notez que vous pouvez ajouter plusieurs fournisseurs à la fabrique de journaux, voire plusieurs fournisseurs du même type. Par conséquent, si j'ajoute un appel à ILoggerFactory.AddProvider à Figure 1, un appel à ILogger.LogInformation afficher un message sur la console à deux reprises. Le premier fournisseur de console (celle ajoutée en AddConsole) affiche par défaut les rien LogLevel.Information ou une version ultérieure. Toutefois, un appel ILogger.LogVerbose apparaît une seule fois en tant que seul le deuxième fournisseur (ajouté via la méthode AddProvider) vous éviterez correctement filtrés.
Modèles de journalisation
En tant que Figure 1 présente, la racine de toutes les journalisations commence par une fabrique de journal à partir de laquelle vous pouvez demander un ILogger via la méthode ILoggerFactory.CreateLogger < T >. Le type générique T dans cette méthode consiste à identifier la classe dans laquelle le code s'exécute, il est possible d'écrire le nom de classe dans laquelle le journal écrit des messages. En d'autres termes, en appelant loggerFactory.CreateLogger < programme >, vous essentiellement établir un journal spécifique à la classe Program afin que chaque fois qu'un message est écrit, il est également possible d'écrire le contexte d'exécution comme se trouvant dans la classe Program. Par conséquent, la console de sortie de Figure 1 est :
info: SampleWebConsoleApp.Program[0]
This is a test of the emergency broadcast system.
Ce résultat est basé sur les éléments suivants :
- « info » résulte du fait qu'il s'agit d'un appel de méthode LogInformation.
- « SampleWebConsoleApp.Program » est déterminé à partir de T.
- « [0] » est l'ID d'événement : une valeur n'a pas spécifier pour la valeur par défaut 0.
- « Il s'agit d'un test de la diffusion d'urgence système. » est l'argument de messages transmis à LogInformation.
Étant donné que la valeur programme indique au niveau de la classe de contexte, vous souhaiterez probablement instancier une instance de journal différente pour chaque classe à partir de laquelle vous souhaitez enregistrer. Par exemple, si le programme crée et appelle une instance de classe de contrôleur, vous pourrez avoir une nouvelle instance de l'enregistreur d'événements au sein de la classe de contrôleur qui a été créée par un autre appel de méthode où T est désormais contrôleur :
loggerFactory.CreateLogger<Controller>()
Comme vous pouvez le remarquer, cela nécessite un accès à la même instance de fabrique de journal sur lequel les fournisseurs ont été précédemment configurées. Et, bien qu'il soit possible, que vous pouvez transmettre l'enregistreur d'événements instance factory dans chaque classe à partir de laquelle vous souhaitez exécuter la journalisation, elle deviendrait rapidement une plaie serait de début pour la refactorisation.
La solution consiste à enregistrer un ILoggerFactory statique unique comme une propriété statique qui est disponible pour toutes les classes lorsque vous instanciez l'instance de ILoggger de leur objet spécifique. Par exemple, vous pouvez envisager une classe statique ApplicationLogging qui inclut une instance ILoggerFactory statique :
public static class ApplicationLogging
{
public static ILoggerFactory LoggerFactory {get;} = new LoggerFactory();
public static ILogger CreateLogger<T>() =>
LoggerFactory.CreateLogger<T>();
}
Le problème évident dans une telle classe est que le LoggerFactory est thread-safe. Et, en tant que la méthode AddProvider illustrée Figure 2 présente, il s'agit.
Figure 2 l'implémentation Microsoft.Extensions.Logging.LoggerFactory AddProvider
public void AddProvider(ILoggerProvider provider)
{
lock (_sync)
{
_providers = _providers.Concat(new[] { provider }).ToArray();
foreach (var logger in _loggers)
{
logger.Value.AddProvider(provider);
}
}
}
Étant donné que seules les données dans l'instance de ILogger sont déterminées à partir du type générique T, vous pourriez faire remarquer que chaque classe peut avoir un ILogger statique même d'étendre les objets de chaque classe. Toutefois, en supposant que la programmation standard de tout en garantissant la sécurité des threads de tous les membres statiques, une telle approche nécessiterait de contrôle d'accès concurrentiel dans l'implémentation de ILogger (qui n'existait pas par défaut), et susceptible de provoquer un goulot d'étranglement important en tant que des verrous sont effectuées et publié. Pour cette raison, la recommandation, est en fait, d'avoir une instance de ILogger individuelle pour chaque instance d'une classe. Le résultat, par conséquent, est une propriété ILogger sur chaque classe pour laquelle vous souhaitez prendre en charge la journalisation (voir Figure 3).
Figure 3 Ajout d'une Instance de ILogger à chaque objet nécessitant une journalisation
public class Controller
{
ILogger Logger { get; } =
ApplicationLogging.CreateLogger<Controller>();
// ...
public void Initialize()
{
using (Logger.BeginScopeImpl(
$"=>{ nameof(Initialize) }"))
{
Logger.LogInformation("Initialize the data");
//...
Logger.LogInformation("Initialize the UI");
//...
}
}
}
Étendues de présentation
Fréquemment, fournisseurs prennent en charge le concept de « portée » où vous pouvez se connecter (par exemple) comment votre code parcourt une chaîne d'appel. Poursuite de l'exemple, si le programme appelle une méthode sur une classe de contrôleur, que classe instancie ensuite sa propre instance de journal avec son propre contexte de type T. Cependant, au lieu de simplement afficher un contexte de message d'informations : SampleWebConsoleApp.Program[0] suivi d'informations : SampleWebConsoleApp.Controller[0], vous souhaitez peut-être connecter ce contrôleur programme appelé et peut-être même inclure les noms de méthode. Pour ce faire, vous activez le concept de portée dans le fournisseur. Figure 3 fournit un exemple de la méthode Initialize via l'appel de Logger.BeginScopeImpl.
Une classe de programme qui peut-être sembler un peu similaire à l'aide du modèle de journalisation tout en exploitant l'activation de l'étendue entraîne Figure 4.
Figure 4 implémentation mise à jour de programme
public class Program
{
static ILogger Logger { get; } =
ApplicationLogging.CreateLogger<Program>();
public static void Main(string[] args = null)
{
ApplicationLogging.LoggerFactory.AddConsole(true);
Logger.LogInformation(
"This is a test of the emergency broadcast system.");
using (Logger.BeginScopeImpl(nameof(Main)))
{
Logger.LogInformation("Begin using controller");
Controller controller = new Controller();
controller.Initialize();
Logger.LogInformation("End using controller");
}
Logger.Log(LogLevel.Information, 0, "Shutting Down...", null, null);
}
}
La sortie de Figure 3 associés Figure 4 est indiqué dans Figure 5.
Figure 5 sortie Console avec des étendues inclus
info: SampleWebConsoleApp.Program[0]
This is a test of the emergency broadcast system.
info: SampleWebConsoleApp.Program[0]
=> Main
Begin using controller
info: SampleWebConsoleApp.Controller[0]
=> Main => Initialize
Initialize the data
info: SampleWebConsoleApp.Controller[0]
=> Main => Initialize
Initialize the UI
info: SampleWebConsoleApp.Program[0]
=> Main
End using controller
info: SampleWebConsoleApp.Program[0]
Shutting down...
Notez la façon dont l'étendue se déroule automatiquement afin d'inclure plus Initialize ou principal. Cette fonctionnalité est fournie par le fait que BeginScopeImpl retourne une instance de IDisposable qui se déroule automatiquement l'étendue lorsque l'instruction using appelle Dispose.
Utilisation d'un fournisseur tiers
Pour libérer certaines infrastructures tierces journalisation plus importants, Microsoft collaboré avec ses développeurs et garantie il existe des fournisseurs pour chacun. Sans indiquer une préférence, envisagez comment connecter le Framework NLog, comme dans Figure 6.
Figure 6 configuration NLog comme un fournisseur Microsoft.Extensions.Logging
[TestClass]
public class NLogLoggingTests
{
ILogger Logger {get;}
= ApplicationLogging.CreateLogger<NLogLoggingTests>();
[TestMethod]
public void LogInformation_UsingMemoryTarget_LogMessageAppears()
{
// Add NLog provider
ApplicationLogging.LoggerFactory.AddNLog(
new global::NLog.LogFactory(
global::NLog.LogManager.Configuration));
// Configure target
MemoryTarget target = new MemoryTarget();
target.Layout = "${message}";
global::NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(
target, global::NLog.LogLevel.Info);
Logger.LogInformation(Message);
Assert.AreEqual<string>(
Message, target.Logs.FirstOrDefault<string>());
}
}
La majeure partie de ce code est connue pour ceux qui connaissent NLog. Tout d'abord, j'ai instancier et configurer une cible de NLog de type NLog.Targets.MemoryTarget. (Il existe de nombreuses cibles NLog et chacune peut être identifié et configuré dans le fichier de configuration NLog, outre l'utilisation de code de configuration, comme indiqué dans Figure 6.) Notez que bien que similaire, la disposition est attribuée une valeur littérale de ${message}, pas une valeur de chaîne interpolée.
Une fois ajouté à la LoggerFactory et configuré, le code est identique à tout autre code de fournisseur.
Gestion des exceptions
Bien sûr, une des raisons plus courantes se consiste à enregistrer lorsqu'une exception est levée, plus précisément, lorsque l'exception est gérée plutôt à nouveau levée ou lorsque l'exception est totalement non gérée (voir bit.ly/1LYGBVS). Comme vous l'imaginez, Microsoft.Extensions.Logging a des méthodes spécifiques pour la gestion d'une exception. La plupart de ces méthodes sont implémentées dans Microsoft.Extensions.Logging.LoggerExtensions en tant que méthodes d'extension ILogger. Et bien, c'est à partir de cette classe que chaque méthode spécifique à un niveau de journal particulier (ILogger.LogInformation, ILogger.LogDebug, ILogger.LogCritical, etc.) est implémentée. Par exemple, si vous souhaitez consigner un message LogLevel.Critical concernant une exception (par exemple avant d'arrêter correctement l'application), appelez :
Logger.LogCritical(message,
new InvalidOperationException("Yikes..."));
Un autre aspect important de journalisation et de gestion des exceptions est que la journalisation, en particulier lors de la gestion des exceptions, ne doit pas lever une exception. Si une exception est levée lorsque vous vous connectez, sans doute le message ou une exception jamais écrire dans et pourrait potentiellement inaperçues entièrement, quel que soit leur importance. Malheureusement, la implementation—Microsoft.Extensions.Logging.Logger—has l'emploi ILogger pas ces exceptions, si une exception se produit, le code appelant doit traiter, et ce, chaque fois que Logger.LogX est appelée. Une approche générale pour résoudre ce consiste à encapsuler éventuellement enregistreur d'événements afin d'intercepter l'exception. Toutefois, vous pouvez mettre en œuvre vos propres versions de ILogger et ILoggerFactory (voir bit.ly/1LYHq0Q pour obtenir un exemple). Étant donné que .NET Core est open source, vous pourrez même cloner la classe et volontairement implémenter la gestion des exceptions dans vos propres implémentations LoggerFactory et ILogger.
Synthèse
J'ai commencé par demander, « pourquoi sur terre serait nous voulons encore une autre infrastructure de journalisation dans .NET? » J'espère maintenant il est clair. La nouvelle infrastructure crée une couche d'abstraction ou un wrapper qui vous permet d'utiliser le framework de journalisation souhaité en tant que fournisseur. Cela garantit que vous disposez d'une flexibilité maximale dans votre travail en tant que développeur. En outre, même s'il est uniquement disponible avec .NET Core, faisant référence à des packages .NET Core NuGet comme Microsoft.Extensions.Logging pour un projet Visual Studio .NET 4.6 standard n'est pas un problème.
Mark Michaelisest le fondateur de IntelliTect, où il sert de son poste d'architecte en chef technique et un formateur. Depuis près de deux décennies, il a été MVP Microsoft et un directeur régional Microsoft depuis 2007. Michaelis fait plusieurs logiciels conception révision équipes Microsoft, notamment c#, Microsoft Azure, SharePoint et Visual Studio ALM. Il participe à des conférences de développeurs et a écrit de nombreux ouvrages, y compris sa plus récente, « Essential c# 6.0 (5e édition) » (itl.tc/EssentialCSharp). Contactez-le sur Facebook à facebook.com/Mark.Michaelis, sur son blog à l'adresse IntelliTect.com/Mark, sur Twitter : @markmichaelis ou par courrier électronique à mark@IntelliTect.com.
Je remercie les experts techniques IntelliTect suivants d'avoir relu cet article : Kevin Bost, Chris Finlayson et Michael Stokesbary