Ecriture de log c# multi-thread et multi-process

Soyez le premier à donner votre avis sur cette source.

Vue 25 095 fois - Téléchargée 1 029 fois

Description

Bonjour, c'est ma première publication, merci d'être indulgent :)

Ce code permet d'utiliser la gestion d'erreur C# (System.Diagnostics.Trace.TraceError(); TraceWarning() ... ou System.Diagnostics.Debug.WriteLine();)
et de rattacher le tout à un logger qui ecrit les erreurs dans un fichier de log.
L'intéret est d'avoir des codes dans mes librairies qui ne depend pas systématiquement d'un code de gestion d'erreur et ecriture de log.

2eme point : je voulais un system de log facile à intégrer genre un objet static à l'appli, quelques instructions dans un constructeur et ca roule tout seul.
public static MyLogListener monLogListener;
static void Main() {
string logPath = "C:\\log.txt";
monLogListener = new MyLogListener(logPath);
System.Diagnostics.Trace.Listeners.Add(monLogListener);
System.Diagnostics.Trace.AutoFlush = true;

Je voulais aussi avoir comme fonctionnalités :
- un system d'écriture ROBUSTE : supporte l'ecriture multiple depuis plusieurs threads ou application sur un même fichier en même temps
- limitation de la taille du fichier (derniers logs ecrasé)
- ecriture des logs non blocante pour l'appli
- ecriture des logs inversées (le log le plus récent en début de fichier et pas à la fin)
- Ecriture de la date
- Ecriture du message des exceptions passés en argument à TraceError()
- un system de notification qui indique qu'une erreur a été detecté (pour notifier l'utilisateur par exemple).

Voila je vous laisse tester et reprendre ce qui vous intéresse. En esperant que ce code vous sera utile.

Source / Exemple :


/**

  • Auteur : Tyrann
  • Projet : ---
  • Module:
  • MyLogListener.cs
  • Sujet:
  • Creation d'une class Listener permettant d'écrire très simplement des logs dans un fichier text
  • L'ecriture est inversé (+facile à lire), dernier log ecrit en debut du fichier
  • La taille du fichier de log est limité à 1 Mo par defaut ou modifiable la propriété MaxLogSize
  • Autre implemetation utile :
  • - un system d'écriture ROBUSTE : supporte l'ecriture multiple depuis plusieurs threads ou application sur un même fichier en même temps
  • - limitation de la taille du fichier (derniers logs ecrasé)
  • - ecriture des logs non blocante pour l'appli
  • - ecriture des logs inversées (le log le plus récent en début de fichier et pas à la fin)
  • - Ecriture de la date & heure du log
  • - Ecriture du message des exceptions passés en argument à TraceError()
  • - un system de notification qui indique qu'une erreur a été detecté (pour notifier l'utilisateur par exemple).
  • Des qu'une exception est recu,
  • => la valeur ExceptionDetected = true
  • => LastException contient la derniere exception recu
  • => LastErrorMsg contient le derniere message recu
  • => un evenement ExceptionDetectedEvent peut être envoyé (exemple affiche une bulle de message sur les erreurs)
*
  • Il est aussi possible de l'utiliser + simplement ainsi :
  • MyLogListener log = new MyLogListener("C:\\log.txt");
  • log.WriteLine(message);
*
  • Pour l'utiliser le logger dans votre code
  • Lors de l'initalisation dans votre application (class main ou constructeur fenetre appli)
  • ==========================================
  • // object globale
  • public static MyLogListener monLogListener;
  • //constructeur de l'appli
  • public Form1() {
  • MyLogListener myLister = new MyLogListener("Log.txt");
  • myLister.MaxLogSize = 0;// 1000000;
  • myLister.WriteDateInfo = true;
  • myLister.ErrorDetectedEvent += myLister_ErrorDetectedEvent;
  • Trace.Listeners.Add(myLister);
  • Trace.AutoFlush = true;
*
  • // la fonction de traitement de l'évenement
  • static void monLogListener_ErrorDetectedEvent(object sender, EventArgs e)
  • {
  • //recuperation du message de l'erreur
  • string msgErr = monLogListener.LastErrorMsg;
  • //recuperation du message de l'exception
  • Exception ex = monLogListener.LastException;
  • string msgEx = "";
  • if (ex != null) msgEx = ex.Message;
  • MessageBox.Show("Hoo une erreur est survenue, son message : " + msgErr + ".\r\n Exeption msg = " + msgEx);
  • }
* *
  • Là ou vous voulez mettre un log
  • =================================
  • try{System.IO.Directory.CreateDirectory("");}
  • catch (Exception err)
  • {
  • System.Diagnostics.Trace.WriteLine("message avec Trace.WriteLine()");
  • System.Diagnostics.Trace.TraceError("message avec Trace.TraceError");
  • System.Diagnostics.Trace.TraceWarning("message avec Trace.TraceWarning");
  • System.Diagnostics.Trace.TraceInformation("message avec Trace.TraceInformation");
  • System.Diagnostics.Trace.WriteLine(err);
  • System.Diagnostics.Trace.TraceError("exception avec Trace.WriteLine()", err);
  • System.Diagnostics.Trace.TraceWarning("exception avec Trace.WriteLine()", err);
  • System.Diagnostics.Trace.TraceInformation("exception avec Trace.TraceInformation", err);
  • System.Diagnostics.Debug.WriteLine(err);
  • }
Autre exemple complet : namespace TestLog { static class Program { public static MyLogListener monLogListener; [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); //initialisation du logger string logPath = "C:\\log.txt"; monLogListener = new MyLogListener(logPath); monLogListener.MaxLogSize = 0;// 1000000; monLogListener.WriteDateInfo = true; System.Diagnostics.Trace.Listeners.Add(monLogListener); System.Diagnostics.Trace.AutoFlush = true; Application.Run(new MaFenetre()); } } public partial class MaFenetre : Form { public MaFenetre() { InitializeComponent(); //ecoute evenement qd erreur Program.monLogListener.ErrorDetectedEvent += monLogListener_ErrorDetectedEvent; } static void monLogListener_ErrorDetectedEvent(object sender, EventArgs e) { //recuperation du message de l'erreur string msgErr = Program.monLogListener.LastErrorMsg; //recuperation du message de l'exception Exception ex = Program.monLogListener.LastException; string msgEx = ""; if (ex != null) msgEx = ex.Message; MessageBox.Show("Hoo une erreur est survenue, son message : " + msgErr + ".\r\n Exeption msg = " + msgEx); } private void button1_Click(object sender, EventArgs e) { // Exemple d'implementation d'erreur try { System.IO.Directory.CreateDirectory(""); } catch (Exception err) { System.Diagnostics.Trace.TraceError("Ho l'erreur",err); } } private void button2_Click(object sender, EventArgs e) { Application.Run(new Form1()); } } }
  • /
using System; using System.Diagnostics; using System.IO; using System.Threading; using System.Collections; namespace TestLog { public class MyLogListener : TraceListener { #region objets, variable privé //surveillance rapprocher du fichier de log private FileSystemWatcher watcher = null; //chemin complet du fichier de log private string _logPath; // object pour le lock concernant l'ecriture dans le fichier private Object fileLock = new Object(); // flag si messagebox deja affiché encas d'erreur (histoire de pas en avoir 15000 private bool alreadyMsgBox = false; //Pile FIFO qui contient les message de log a écrire dans un fichier private static Stack StackDeLogAEcrire; #endregion #region Propriétés /// <summary> /// Chemin vers le fichier de log /// </summary> public string LogPath { set { _logPath = value; string fileName = Path.GetFileName(_logPath); if (Path.GetExtension(_logPath) == string.Empty) throw new Exception("Bad log filename, extension require!"); string directoryLogPath = Path.GetDirectoryName(_logPath); // on vérifie que le répertoire existe if (directoryLogPath != string.Empty) if (!Directory.Exists(directoryLogPath)) Directory.CreateDirectory(directoryLogPath); } get { return _logPath; } } private int _maxLogInWait; /// <summary> /// Limitateur de la pile de message en attente (element de securité), à laisser à 50 /// car plus la taille de message en attente augmente plus le logger prend des ressources systems et nuie à l'ensemble /// Mettre 0 pour desactiver cette protection (pour debuggage par exemple). /// </summary> public int MaxLogInWait { set { _maxLogInWait = value; } get { return _maxLogInWait; } } private long _maxLogSize; /// <summary> /// Taille max du fichier de log (en oct). Mettre 0 desactive la limitation /// </summary> public long MaxLogSize { set { _maxLogSize = value; } get { return _maxLogSize; } } private bool _showFatalErrorInMessageBox; /// <summary> /// Active l'affichage d'erreur fatal dans une boite de message /// il est conseillé de désactiver cette propriete pour des processus serveurs /// </summary> public bool ShowFatalErrorInMessageBox { set { _showFatalErrorInMessageBox = value; } get { return _showFatalErrorInMessageBox; } } private bool _WriteDateInfo; /// <summary> /// Indique si la date et l'heure est ecrite lors de chaque ecriture de log /// </summary> public bool WriteDateInfo { set { _WriteDateInfo = value; } get { return _WriteDateInfo; } } private bool _indicateDate; /// <summary> /// Indique si la date et l'heure est ecrite lors de chaque ecriture de log /// </summary> public bool IndicateDate { set { _indicateDate = value; } get { return _indicateDate; } } private bool _IsErrorDetected = false; /// <summary> /// Indique si une exception a été recu /// </summary> public bool IsErrorDetected { private set { _IsErrorDetected = value; } get { return _IsErrorDetected; } } private Exception _LastException = null; /// <summary> /// Pointe sur la derniere exception recu /// </summary> public Exception LastException { private set { _LastException = value; } get { return _LastException; } } private string _LastErrMsg = null; /// <summary> /// Indique le dernier message d'erreur recu /// </summary> public string LastErrorMsg { private set { _LastErrMsg = value; } get { return _LastErrMsg; } } #endregion #region Methodes publiques /// <summary> /// Constructeur /// </summary> /// /// <param name="logPath"> /// [in] Spécifie le chemin du fichier de log /// </param> public MyLogListener(string logPath) { if ((logPath == null) || (logPath == string.Empty)) throw new Exception("The path of the log file is required."); LogPath = logPath; MaxLogSize = 1000000; //1 Mo; IndicateDate = true; WriteDateInfo = true; StackDeLogAEcrire = new Stack(); ShowFatalErrorInMessageBox = true; MaxLogInWait = 50; //lance le detecteur de changement sur le fichier de logs - cf.WriteInFicThreadStart() watcher = new FileSystemWatcher(); watcher.Path = System.IO.Path.GetDirectoryName(this.LogPath); // surveille que notre fichier. watcher.Filter = System.IO.Path.GetFileName(this.LogPath); // "*.txt"; // surveille les changements dernier acces, dernieère ecriture, renommage et repertoire watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName; //| NotifyFilters.DirectoryName; // Add event handlers. watcher.Changed += new FileSystemEventHandler(watcher_Changed); watcher.Created += new FileSystemEventHandler(watcher_Changed); watcher.Deleted += new FileSystemEventHandler(watcher_Changed); watcher.Renamed += new RenamedEventHandler(watcher_Changed); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) { //si de type information et ecriture log info non demandé : on sort if (eventType == TraceEventType.Information) if (this.WriteDateInfo == false) return; if (message == string.Empty) message = ""; if (eventType == TraceEventType.Error) { //envoi l'evenement RaiseExceptionDetectedEvent(message, null); } message = " Type : " + eventType.ToString() + " - message : " + message + "\r\n"; WriteLine(message); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message, params object[] args) { //si de type information et ecriture log info non demandé : on sort if (eventType == TraceEventType.Information) if (this.WriteDateInfo == false) return; if (args.Length > 0) { if (args[0] is Exception) { Exception ex = (Exception)args[0]; if (message == string.Empty) message = ""; if (eventType == TraceEventType.Error) { //envoi de l'evenement RaiseExceptionDetectedEvent(message, ex); } //formatage du message string messageErr = " Type : " + eventType.ToString() + " - message : " + message + "\r\n"; messageErr += String.Format("EXCEPTION type : {0} \r\n Message d'erreur: {1} \r\n Origine : {2} \r\n", ex.GetType().ToString(), ex.Message, ex.StackTrace); WriteLine(messageErr); } } } /// <summary> /// Evénement indiquant la reception d'une erreur provenant de Trace.TraceError /// </summary> public event EventHandler ErrorDetectedEvent; // Methode pour envoyer un evenement SearchContactEvent protected virtual void RaiseExceptionDetectedEvent(string messageCourt, Exception ex) { this.LastException = ex; this.LastErrorMsg = messageCourt; this.IsErrorDetected = true; EventHandler eventHandler = ErrorDetectedEvent; if (eventHandler != null) eventHandler(this, new EventArgs()); } public override void WriteLine(string message, string cate) { WriteInFic(message + "\r\n"); } public override void WriteLine(string message) { Write(message + "\r\n"); } public override void Write(string message) { if (this.IndicateDate == true) message = DateTime.Now.ToString() + ":" + DateTime.Now.Millisecond.ToString() + " -> " + message; WriteInFic(message); } public override void WriteLine(object o) { base.WriteLine(o); //renvoi au pere, object non traité } #endregion #region Methodes privées private void WriteInFic(string message) { bool runThread = false; lock (StackDeLogAEcrire) { //si la pile de message est vide on lancera le thread d'ecriture if (StackDeLogAEcrire.Count == 0) runThread = true; StackDeLogAEcrire.Push(message); } if (runThread) { //lancement du thread Thread WriteThread = new Thread(WriteInFicThreadStart); WriteThread.Start(); } } private void WriteInFicThreadStart() { //Fonction coeur //Cette fonction est lancé dans un thread car elle permet l'ecriture inversé des log dans un fichier //l'opération consiste à // 1- Prise de controle du fichier de log // 2- recopie de son contenu dans un fichier temporaire // 3- ecriture du message // 4- ajoute les ancien log present dans le fichier temporaire try { //verrouillage en cas de multi-thread/multi acces interne appli (à priori inutile mais bon y a le mauvais developpeur et le BON developpeur, le mauvais fait plein de bug et le bon ... aussi :)) lock (fileLock) { long tailleFic = 0; // compteur de taille du fichier pour appliquer le limitateur de taille des logs //si un fichier de log existe, on faudra rajouter son contenu apres notre message de log string oldFilePath = this.LogPath + ".old"; bool bRecopieNedd = false; if (File.Exists(this.LogPath)) bRecopieNedd = true; //1- tentative de prise de controle du fichier de log //Permet de prend en compte le cas de plusieurs appli/process accèdant au même fichier de log FileStream fsw = null; try { fsw = new FileStream(this.LogPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); } catch { //if (watcher == null) //{ //} // commence la surveillance. if (watcher.EnableRaisingEvents == false) watcher.EnableRaisingEvents = true; return; } //2- recopie de son contenu dans un fichier temporaire if (File.Exists(oldFilePath)) File.Delete(oldFilePath); if(bRecopieNedd) File.Copy(this.LogPath, oldFilePath); //3- ecriture du message //ouverture du flux en ecriture System.IO.StreamWriter sw = new StreamWriter(fsw); //ecriture du message lock (StackDeLogAEcrire) { while (StackDeLogAEcrire.Count > 0) { string message = (string)StackDeLogAEcrire.Pop(); sw.Write(message); tailleFic += message.Length; //1car = 1oct } } //4- ajoute les ancien log present dans le fichier temporaire if (bRecopieNedd) { using (StreamReader sr = new StreamReader(oldFilePath)) { String line; while ((line = sr.ReadLine()) != null) { tailleFic += line.Length; //1car = 1oct //si taille max defini des log est atteint on arrete la recopie if (tailleFic < this.MaxLogSize || this.MaxLogSize == 0) sw.WriteLine(line); else break; } } //suppression du fichier temporaire File.Delete(oldFilePath); } //relachement du flux et fichier sw.Close(); fsw.Close(); //si entre temps il y a des nouveaux element à ecrire on relance le thread lock (StackDeLogAEcrire) { if (StackDeLogAEcrire.Count > 0) { //Protection : Limitation de la pile et capacité de traitement du logger //si autant de message en attente = ralentissement machine, process ... pas forcement judicieux if (StackDeLogAEcrire.Count > MaxLogInWait && MaxLogInWait != 0) { StackDeLogAEcrire.Clear(); StackDeLogAEcrire.Push("==== DEPASSEMENT DU NOMBRE DE MESSAGE QUE LE GESTIONNAIRE DE LOG PEUT TRAITER !!! ==="); StackDeLogAEcrire.Push("==== CERTAINS LOGS N'ONT PAS ETE ECRIT. ==="); } Console.WriteLine("Le thread d'eriture a ete relance"); Thread WriteThread = new Thread(WriteInFicThreadStart); WriteThread.Start(); } } } } catch { //si une erreur -- affichage de l'erreur dans une box (si propriété autorisée) if (this.ShowFatalErrorInMessageBox) { if (!this.alreadyMsgBox) { System.Windows.Forms.MessageBox.Show("Erreur Ecriture de log impossible!", System.Reflection.Assembly.GetEntryAssembly().FullName, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); alreadyMsgBox = true; } } } } private void watcher_Changed(object source, FileSystemEventArgs e) { // l'etat du fichier de log a changer, on relance l'ecriture // arrete la surveillance. if (watcher.EnableRaisingEvents == true) watcher.EnableRaisingEvents = false; WriteInFicThreadStart(); } #endregion } }

Codes Sources

A voir également

Ajouter un commentaire

Commentaires

Messages postés
2
Date d'inscription
jeudi 26 juillet 2007
Statut
Membre
Dernière intervention
19 août 2015

Très utile, m'a fait gagner un temps précieux. Merci!
Messages postés
29
Date d'inscription
mardi 7 janvier 2003
Statut
Membre
Dernière intervention
26 mars 2009

Je trouve l'idée très bonne.

Simplement un commentaire:
Au lieu de définir une variable statique globale, tu peux créer un Singleton qui n'oblige pas l'instanciation à se faire dans le Main et n'oblige pas la déclaration Static.
La méthode getInstance() du Singleton peut renvoyer une interface ILog qui propose la méthode Write() principalement.

Donc, si tu fais Log.GetInstance().Write("mon info"); , cela écrira disons par défaut dans la sortie standard d'erreur, sans rien paramétrer avant.
La classe se suffit à elle même : déclaration, initialisation par défaut.

Un avantage est que le Singleton pourrait très bien dans le futur lire ses paramètres dans un fichier de configuration externe, qui pourrait contenir le nom du fichier par défaut, la chaîne de connexion BDD, etc. Ce qui n'oblige pas l'application en cours à paramétrer le type de Log désiré.
Rien n'empêche d'exposer des méthodes qui permettent de paramétrer depuis le Main() en plus, comme tu le fais actuellement.

Un autre avantage est que tu peux faire ta cuisine interne dans le Singleton : tu peux instancier une classe qui implémente ILog, et donc déporter la méthode Write() dans d'autres classes : "class LogWriterToFile : ILog", ou LogWriterToDefaultOutput, LogWriterToDB, etc... et dans la méthode Write() de la classe Log, tu peux appeler :

// --- Singleton (constructeur privé...)
class Log : ILog
{

// -- Par défaut sortie standard
private _writer = new LogWriterToDefaultOutput();

(...)

public ILog GetInstance()
{

}

public void Write(...)
{
_writer.Write(...);
}

}

A mon sens cette approche permet également beaucoup de souplesse : possibilité de chaîner les appels, d'utiliser le Design Pattern Proxy, le Design Pattern "chaîne de responsabilité" (si l'on admet qu'une info de succès doit être à l'écran, une erreur dans un fichier, et une info critique à la fois dans un fichier et envoyée par mail)...

Le Singleton peut aussi stocker une liste de ILog à appeler dans la méthode Write...

Le message c'est juste que travailler avec Singleton + interface autorise toutes les modifications de structure dans la façon dont le Log est géré derrière.
- Le Singleton sert de façade et d'accesseur global
- L'interface sert à pouvoir implémenter librement le code où l'on veut.

Enfin c'est un avis personnel.
Messages postés
1
Date d'inscription
samedi 6 octobre 2007
Statut
Membre
Dernière intervention
22 avril 2009

C'est sympa comme code.
Une remarque quand même.
Quand il y a des chaines multiples à concaténer, il est préférable d'utiliser la méthode string.concat
Exemple :
string messageErr = " Type : " + eventType.ToString() + " - message : "+ message + "\r\n";
devient
string messageErr = string.Concat(" Type : {0} - message : {1}\r\n",eventType.ToString(),message).

C'est plus propre et plus lisible ;)
Messages postés
2
Date d'inscription
lundi 9 mars 2009
Statut
Membre
Dernière intervention
11 mars 2009

Je débute cela m'aide énormément, merci pour ton travail
Messages postés
3
Date d'inscription
dimanche 10 novembre 2002
Statut
Membre
Dernière intervention
5 mars 2008

Super génial ton code : BRAVO.

Par contre, étant ultra débutant en C#, je ne comprend pas cette partie du code (void RaiseExceptionDetectedEvent) :
EventHandler eventHandler = ErrorDetectedEvent;
if (eventHandler != null)
eventHandler(this, new EventArgs());

Si tu pouvais m'expliquer, ca serait super sympa (surtout la première ligne) ;)
Afficher les 11 commentaires

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.