Log4net

De Banane Atomic
Version datée du 12 mars 2023 à 09:33 par Nicolas (discussion | contributions)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigationAller à la recherche

Astuce

Cs.svg
private static readonly ILog log = LogManager.GetLogger(typeof(Program));

Logging Levels

  1. FATAL
  2. ERROR
  3. WARN
  4. INFO
  5. DEBUG
The default value for the root logger is DEBUG.

Layout

Xml.svg
<layout type="log4net.Layout.XMLLayout" />

<layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date - %location%newline%message%newline%exception%newline" />
</layout>

XMLLayout et LocationInfo

Par défaut XMLLayout ne logue pas les LocationInfo.

Csharp.svg
// forcer XMLLayout a loguer les LocationInfo.
var xmlLayout = new XMLLayout(true);

// créer une variante de RollingFileAppender avec un XMLLayout qui logue les LocationInfo
public class RollingFileAppenderXmlLayoutLocation : RollingFileAppender
{
    public RollingFileAppenderXmlLayoutLocation() : base()
    {
        Layout = new XmlLayout(true);
    }
}

PatternLayout - Conversion Patterns

Pattern Exemple
%date 2014-01-15 12:00:00,000
%date{ABSOLUTE} 12:00:00,000
%date{DATE} 15 Jan 2014 12:00:00,000
%date{ISO8601} 2014-01-15 12:00:00,000
%date{MMMM dd, yyyy HH:mm:ss, fff} January 15, 2014 12:00:00,000
%level DEBUG
%location Namespace.Class.Method(c:\chemin\vers\le\fichier.cs:ligne)
%message Le message à loguer.
%exception Le message de l'exception suivit de la stack trace.
%exception{message} Le message de l'exception.
%newline saut de ligne
Il ne peut y avoir qu'un Conversion Patterns par appender.
Pour pouvoir appliquer un Conversion Patterns par level, il faut créer plusieurs Conversion Patterns et ajouter des filters.

Filter

Xml.svg
<appender ...
    <!-- match un level particulier -->
    <filter type="log4net.Filter.LevelMatchFilter">
        <levelToMatch value="ERROR"/>
    </filter>
    <!-- match un range de levels -->
    <filter type="log4net.Filter.LevelRangeFilter">
        <levelMax value="FATAL"/>
        <levelMin value="ERROR"/>
    </filter>

Logger

Xml.svg
<logger name="MyLogger" additivity="false">
    <level value="ALL" />
    <appender-ref ref="MyAppender" />
</logger>
<appender name="MyAppender" type="log4net.Appender.ColoredConsoleAppender">
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message%newline"/>
    </layout>
</appender>

Si Additivity à false, pas d'héritage des appenders parents.

Csharp.svg
ILog log = LogManager.GetLogger("MyLogger");
// log seulement dans les appenders (MyAppender) du logger MyLogger
log.Error("Message");

Logger hierarchy

  • root
    • loggerName1
      • loggerName1.loggerName2

Par défaut les logueurs ont le paramètre additivity à true, ils héritent donc de root et de leurs parents.
Ainsi l'appel au logueur loggerName1.loggerName2, fera aussi appel au logueur loggerName1 et à tous les logeurs définis dans root, si additivity est non-définis ou à true.

%property{}

Xml.svg
<!-- pour le nom d'un fichier -->
<file type="log4net.Util.PatternString" value="%property{logFilePath}" />
Csharp.svg
ThreadContext.Properties["logFilePath"] = "MonFichierDeLog.log";
XmlConfigurator.Configure();

Context

Scope Type Description
Global log4net.GlobalContext The global context is shared by all threads in the current AppDomain. This context is thread safe for use by multiple threads concurrently.
Thread log4net.ThreadContext The thread context is visible only to the current managed thread.
Logical Thread log4net.ThreadLogicalContext The logical thread context is visible to a logical thread. Logical threads can jump from one managed thread to another. For more details see the .NET API System.Runtime.Remoting.Messaging.CallContext.
Event log4net.Core.LoggingEvent Each event captures the current contextual state at the time the event is generated. Contextual data can be set on the event itself. This context is only visible to the code generating the event itself.

Class Library et Console Application

Console Application

  • Référence à log4net
  • AssemblyInfo.cs[assembly: log4net.Config.XmlConfigurator(Watch = true)]
  • App.config → log4net XML configuration

Class Library

  • Référence à log4net

Appenders

Exemple: ColoredConsoleAppender

configSections doit être le premier sous-noeud de configuration
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, Log4net"/>
    </configSections>
	
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>

    <log4net>
        <root>
            <!-- Définit le niveau minimal à logguer, exclut donc les DEBUG -->
            <level value="INFO" />
            <appender-ref ref="ColoredConsoleAppender" />
            <appender-ref ref="ColoredConsoleAppenderError" />
        </root>

        <!-- appender pour les DEBUG to WARN -->
        <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender" >
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%message%newline"/>
            </layout>
            <filter type="log4net.Filter.LevelRangeFilter">
                <levelMax value="WARN"/>
                <levelMin value="DEBUG"/>
            </filter>
            <mapping>
                <level value="WARN" />
                <foreColor value="Yellow" />
            </mapping>
            <mapping>
                <level value="INFO" />
                <foreColor value="Green" />
            </mapping>
        </appender>

        <!-- appender pour les ERROR to FATAL -->
        <appender name="ColoredConsoleAppenderError" type="log4net.Appender.ColoredConsoleAppender" >
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%date{ISO8601} %level%newline  %location%newline  %message%newline"/>
            </layout>
            <filter type="log4net.Filter.LevelRangeFilter">
                <levelMax value="FATAL"/>
                <levelMin value="ERROR"/>
            </filter>
            <mapping>
                <level value="ERROR" />
                <foreColor value="Red" />
            </mapping>
            <mapping>
                <level value="FATAL" />
                <foreColor value="Red, HighIntensity" />
            </mapping>
        </appender>
    </log4net>
</configuration>
AssemblyInfo.cs
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
Program.cs
using log4net;
using log4net.Config;

namespace MonNamespace
{
    class Program
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(Program));

        static void Main(string[] args)
        {
            // on peut aussi charger la configuration de cette façon
            XmlConfigurator.Configure();

            log.Debug("DEBUG");
            log.Info("INFO");
            log.Warn("WARN");
            log.Error("ERROR");
            log.Fatal("FATAL");
        }
    }
}

Appender custom

Xml.svg
<appender name="MonAppender" type="MyNamespace.MyAppender, MyAssembly">
Il faut spécifier l'assemblage si l'appender se trouve dans un assemblage différent.
L'assembly des custom appender doit se trouver dans le même dossier que l’exécutable, sinon il ne sera pas chargé.

Appender pour WPF

Logging Display and WPF

Csharp.svg
public class NotifyAppender : AppenderSkeleton, INotifyPropertyChanged
{
    private static string _notification;
    public string Notification
    {
        get
        {
            return _notification; ;
        }
        set
        {
            if (_notification != value)
            {
                _notification = value;
                OnPropertyChanged("Notification");
            }
        }
    }

    protected override void Append(LoggingEvent loggingEvent)
    {
        var writer = new StringWriter(CultureInfo.InvariantCulture);
        Layout.Format(writer, loggingEvent);
        Notification += writer.ToString();
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
ViewModel.cs
public NotifyAppender Appender
{
    get
    {
        return LogManager.GetRepository().GetAppenders().FirstOrDefault(a => a is NotifyAppender) as NotifyAppender;
    }
}
View.xaml
<GroupBox Header="Log">
    <ScrollViewer>
        <TextBlock Text="{Binding Path=Appender.Notification}" />
    </ScrollViewer>
</GroupBox>
App.config
<appender name="NotifyAppender" type="MyNamespace.NotifyAppender, MyAssembly">
  <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date - %location%newline%message%newline%exception%newline" />
  </layout>
</appender>

EventLogAppender

Xml.svg
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender" >
    <!--<param name="LogName" value="Application" />-->
    <param name="ApplicationName" value="My Windows EventLog Source" />
    <param name="EventId" value="1000" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date - %location%newline%message" />
    </layout>
</appender>
Si «My Windows EventLog Source» n'existe pas, log4net va tenter de le créer.
Pour cela il lui faut les droits d'administration, dans le cas contraire rien ne sera logué.
Il faut donc créer la source à la main avec les droits d'administration.
Powershell.svg
# Création de la source "My Windows EventLog Source" dans le log Application
new-eventlog -LogName Application -source "My Windows EventLog Source"
# Suppression de la source "My Windows EventLog Source"
remove-eventlog -source "My Windows EventLog Source"
# Tester si la source a bien été crée
Get-EventLog -LogName Application | Where-Object {$_.Source -eq "My Windows EventLog Source"} | Select-Object Source -first 1
Csharp.svg
if(!EventLog.SourceExists("My Windows EventLog Source"))
{
    // Création de la source "My Windows EventLog Source" dans le log Application
    EventLog.CreateEventSource("My Windows EventLog Source", "Application");
    // An event log source should not be created and immediately used.
    // Ce code doit être exécuté dans l'installeur!
Dos.svg
eventcreate /ID 1000 /L APPLICATION /T INFORMATION /SO "My Windows EventLog Source" /D "Un message"
Par défaut, log4net fixe l'EventId à 0, ce qui pose problème.
Il faut donc fixer l'EventId entre 1 et 1000.

RollingFileAppender

Xml.svg
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <!-- Chemin vers le fichier (Par défaut le chemin de l’exécutable) -->
    <file value="Application.log" />

    <!-- Le système de fichier tournant est basé sur la taille -->
    <rollingStyle value="Size" />
    <!-- Nombre max de backups (Application.log.1, Application.log.2, ...) -->
    <maxSizeRollBackups value="-1" />
    <!-- Taille max d'un fichier de log -->
    <maximumFileSize value="5MB" />

    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date - %location%newline%message%newline%exception%newline" />
    </layout>

    <!-- Options -->

    <!-- Par défaut log4net lock le fichier et bloque l'accès -->
    <!-- avec cette option, log4net lock, écrit puis unlock pour chaque opération d'écriture. Impacte les performances. -->
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />

    <!-- true (défaut) ajoute à la suite du fichier, false écrase tous au lancement de l'application -->
    <appendToFile value="true" />

    <!-- Ordre d'incrément des fichiers tournant:
         CountDirection < 0 (défaut) → *.log.1 plus récent, renomme tous les backups à chaque rotation
         CountDirection > 0 → *.log.5 plus récent -->
    <countDirection value="1"/>

    <!-- true if always should be logged to the same file, otherwise false. -->
    <staticLogFileName value="true" />
</appender>

ColoredConsoleAppender

Xml.svg
<appender name="MonColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
    <mapping>
        <level value="ALL" />
        <foreColor value="Yellow" />
        <backColor value="Red, HighIntensity" />
    </mapping>
            
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%location%newline%message%newline%newline" />
    </layout>
</appender>

ColoredConsoleAppender Class

AdoNetAppender

Xml.svg
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
    <bufferSize value="100" />
    <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <connectionString value="My connection string" />
    <commandText value="INSERT INTO [dbo].[ReportCreator_Log] ([Date],[Level],[Location],[Message],[Exception]) VALUES (@log_date, @log_level, @location, @message, @exception)" />
    <parameter>
        <parameterName value="@log_date" />
        <dbType value="DateTime" />
        <layout type="log4net.Layout.RawTimeStampLayout" />
    </parameter>
    <parameter>
        <parameterName value="@log_level" />
        <dbType value="String" />
        <size value="50" />
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%level" />
        </layout>
    </parameter>
    <parameter>
        <parameterName value="@location" />
        <dbType value="String" />
        <size value="2000" />
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%location" />
        </layout>
    </parameter>
    <parameter>
        <parameterName value="@message" />
        <dbType value="String" />
        <size value="4000" />
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%message" />
        </layout>
    </parameter>
    <parameter>
        <parameterName value="@exception" />
        <dbType value="String" />
        <size value="2000" />
        <layout type="log4net.Layout.ExceptionLayout" />
    </parameter>
</appender>
Tsql.svg
CREATE TABLE [mon_schema].[ma_table_de_log] (
	[Id] [int] IDENTITY (1, 1) NOT NULL,
	[Date] [datetime] NOT NULL,
	[Level] [varchar] (50) NOT NULL,
        [Location] [varchar] (2000) NULL,
	[Message] [varchar] (4000) NOT NULL,
	[Exception] [varchar] (2000) NULL
);

ErrorFlagAppender

App.config
<logger name="MonLogger">
    <appender-ref ref="ErrorFlagAppender" />
</logger>
<appender name="ErrorFlagAppender" type="MonNamespace.ErrorFlagAppender,MonAssembly">
</appender>
ErrorFlagAppender.cs
namespace MonNamespace
{
    /// <summary>
    /// A custom appender to know if at leat an error has been logged.
    /// </summary>
    class ErrorFlagAppender : AppenderSkeleton
    {
        /// <summary>
        /// Gets or sets a value indicating whether an error occurred.
        /// </summary>
        /// <value>
        ///   <c>true</c> if an error occurred; otherwise, <c>false</c>.
        /// </value>
        public bool ErrorOccurred { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether a warning occured.
        /// </summary>
        /// <value>
        ///   <c>true</c> if a warning occured; otherwise, <c>false</c>.
        /// </value>
        public bool WarningOccured { get; set; }

        protected override void Append(LoggingEvent loggingEvent)
        {
            if (loggingEvent.Level == Level.Error || loggingEvent.Level == Level.Fatal)
            {
                ErrorOccurred = true;
            }
            else if (loggingEvent.Level == Level.Warn)
            {
                WarningOccured = true;
            }
        }
    }
}
Csharp.svg
var errorFlagAppender = LogManager.GetRepository().GetAppenders().OfType<ErrorFlagAppender>();
if (errorFlagAppender.Any(e => e.ErrorOccurred))
{ }
else if (errorFlagAppender.Any(e => e.WarningOccured))
{ }

Debugger log4net

App.config
<!-- afficher dans la console les données de debug -->
<appSettings>
    <add key="log4net.Internal.Debug" value="true"/>
</appSettings>

<!-- écrire les données de debug dans un fichier -->
<system.diagnostics>
    <trace autoflush="true">
        <listeners>
            <add name="textWriterTraceListener"
                 type="System.Diagnostics.TextWriterTraceListener"
                 initializeData="log4net.log" />
        </listeners>
    </trace>
</system.diagnostics>

Wrapper

Permet d'afficher la location de l'appelant plutôt que celle du wrapper.

Csharp.svg
public class MyLogWrapper
{
    private static readonly ILog log = LogManager.GetLogger("MyLogger");
    // fonctionne aussi avec LogManager.GetLogger(typeof(MyLogWrapper));

    public void LogMessage(string message)
    {
        // location → MyLogWrapper.LogMessage
        log.Error(message);
        // location → Program.Main
        log.Logger.Log(typeof(MyLogWrapper), Level.Error, message, null);
    }
}

class Program
{
    static void Main(string[] args)
    {
        var mlw = new MyLogWrapper();
        mlw.LogMessage("test");

Config comme Embedded Resource

Créer un fichier log4net.config, le définir comme Embedded Resource.

log4net.config
<?xml version="1.0" encoding="utf-8" ?>

<log4net>
...
</log4net>
Csharp.svg
// chargement de la config
var assembly = Assembly.GetExecutingAssembly();
// assembly.GetManifestResourceNames() permet d'obtenir la liste des noms des ressources
var resourceName = "MonNamespace.log4net.config";

using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
    XmlConfigurator.Configure(stream);
}

Configuration dans le code

Csharp.svg
var appender = new RollingFileAppender();
var layout = new PatternLayout(@"%date %-5level %message%newline");
appender.Layout = layout;
appender.File = "c:\log.txt";
appender.AppendToFile = true;
appender.MaximumFileSize = "1MB";
appender.MaxSizeRollBackups = 0; //no backup files
appender.Threshold = Level.Error;
appender.ActivateOptions();                       
log4net.Config.BasicConfigurator.Configure(appender);

Repositories

Permet d'avoir une autre configuration pour une sous-partie de l'application comme un module.

Cs.svg
ILoggerRepository repository = LogManager.CreateRepository("MonRepository");
XmlConfigurator.Configure(repository, new FileInfo("C:\Folder\otherConfig.config"));

// récupérer le logger du repository
var log = LogManager.GetLogger("MonRepository", typeof(Program));
La création de repository ne peut se faire que via le code.

Installation

Déploiement avec NuGet:
click-droit sur References → Manage NuGet Packages... → Online