Astuce
|
private static readonly ILog log = LogManager.GetLogger(typeof(Program));
|
Logging Levels
- FATAL
- ERROR
- WARN
- INFO
- DEBUG
|
The default value for the root logger is DEBUG. |
Layout
|
<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.
|
// 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);
}
}
|
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
|
<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
|
<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.
|
ILog log = LogManager.GetLogger("MyLogger");
// log seulement dans les appenders (MyAppender) du logger MyLogger
log.Error("Message");
|
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{}
|
<!-- pour le nom d'un fichier -->
<file type="log4net.Util.PatternString" value="%property{logFilePath}" />
|
|
ThreadContext.Properties["logFilePath"] = "MonFichierDeLog.log";
XmlConfigurator.Configure();
|
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
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 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
|
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
|
<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.
|
# 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
|
|
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!
|
|
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. |
|
<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
|
<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
|
<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>
|
|
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;
}
}
}
}
|
|
var errorFlagAppender = LogManager.GetRepository().GetAppenders().OfType<ErrorFlagAppender>();
if (errorFlagAppender.Any(e => e.ErrorOccurred))
{ }
else if (errorFlagAppender.Any(e => e.WarningOccured))
{ }
|
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>
|
Permet d'afficher la location de l'appelant plutôt que celle du wrapper.
|
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>
|
|
// 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
|
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);
|
Permet d'avoir une autre configuration pour une sous-partie de l'application comme un module.
|
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