log4net Levels im laufenden Betrieb ändern

3. Juli 2013

div class=”articleAbstract”>

Entwicklerteams, die Logging schon zur Entwicklungszeit ausgiebig nutzen, haben es sehr leicht Fehler in der laufenden Produktionsumgebung zu analysieren. Man muss nur kurz den Loglevel hochsetzen, wenn – ja wenn – man Zugriff auf die Konfigurationsdatei hat. In großen Produktionsumgebungen ist dies üblicherweise nicht erlaubt.

Mit ein bisschen Programmierung kann man die Loglevels für log4net im laufenden Betrieb auf einer Admin-Seite anzeigen und ein- bzw. ausschalten (toggeln). Ein exemplarisches Userinterface – hier als ASP.NET User Control realisiert – sieht im Ergebnis wie folgt aus:

LogLevelToggler
ASP.NET Beispiel-Userinterface

Funktionsweise (Business Logic)

LogLevels

In log4net kann ein Logger genau einen Level haben. In der obigen Grafik zum Userinterface ist der Level für den Logger “MyApp.Log1” auf “Error” gesetzt. Dadurch ergibt sich automatisch, dass die Level “Info” und “Debug” deaktiviert und “Fatal” aktiviert sind.

  • Aktivieren: Wird ein Level von deaktiviert in aktiviert geändert, so wird der Level direkt am Logger gesetzt. Beispiel: Klicken von “IsDebug” des Loggers “MyApp.Log1” setzt den Level “Debug” des Loggers.
  • Deaktivieren: Wird ein Level deaktiviert, so wird der nächste höhere Level aktiviert. Beispiel: Klicken auf “IsError” des Loggers “MyApp.Log1” setzt den Level des Loggers auf “Fatal”.

Somit kann jede gewünschte Logging-Stufe direkt ein- und ausgeschaltet werden.

Defining Logger

Mit dem Interface ILog der Logger können die Informationen zu IsFatal, IsError etc. ermittelt werden. Theoretisch kann für jeden Logger ein eigener LogLevel definiert werden. In der Praxis wird man sich jedoch darauf beschränken den Level nur für den Root-Logger oder wenige andere anwendungsrelevante Logger zu setzen, deren Werte sich auf alle Kinder vererben. Die Vererbung wird im Userinterface in der Spalte “Defining Logger” angezeigt. Beispiel: Wird der Level für “MyApp.Log1.Sub11” geändert, so ändert sich auch der geerbte Wert für den Logger “MyApp.Log1.Sub11.Sub111

Code

Der Code besteht aus zwei Methoden. Die erste Methode GetLoggerInformation() ermittelt den Zustand aller Logger. Die zweite Methode ToggleLogLevel() schaltet den Zustand eines LogLevels anhand der vorgestellten Business Logic um.

   1: namespace Sdx.Logging

   2: {

   3:     using log4net;

   4:     using log4net.Core;

   5:     using System.Linq;

   6:     using System.Collections.Generic;

   7:  

   8:     /// <summary>

   9:     /// Helper class, to retrieve all loggers and toggle levels

  10:     /// </summary>

  11:     public class LogLevelToggler

  12:     {

  13:         /// <summary>

  14:         /// Retrieve current Logger information

  15:         /// </summary>

  16:         /// <returns></returns>

  17:         public List<LoggerInformation> GetLoggerInformation()

  18:         {

  19:             var result = new List<LoggerInformation>();

  20:  

  21:             // if you like to care about repositories, feel free to modify

  22:             // get ILog interfaces of all loggers

  23:             foreach (var repository in LogManager.GetAllRepositories())

  24:             {

  25:                 var loggers = repository.GetCurrentLoggers();

  26:                 foreach (var l in loggers)

  27:                 {

  28:                     // Add Logging information

  29:                     var li = new LoggerInformation

  30:                         {

  31:                             Name = l.Name,

  32:                             Repository =  repository.Name,

  33:                             IsFatal = l.IsEnabledFor(Level.Fatal),

  34:                             IsError = l.IsEnabledFor(Level.Error),

  35:                             IsInfo = l.IsEnabledFor(Level.Info),

  36:                             IsDebug = l.IsEnabledFor(Level.Debug),

  37:                         };

  38:                     result.Add(li);

  39:  

  40:                     // enrich hierarchie information

  41:                     var hierarchieLogger = l as log4net.Repository.Hierarchy.Logger;

  42:                     if (hierarchieLogger != null)

  43:                     {

  44:                         // get defining logger for level

  45:                         while (hierarchieLogger.Level == null && hierarchieLogger.Parent != null)

  46:                         {

  47:                             hierarchieLogger = hierarchieLogger.Parent;

  48:                         }

  49:                         li.DefiningLogger = hierarchieLogger.Name;

  50:                     }

  51:                     else

  52:                     {

  53:                         // fallback

  54:                         li.DefiningLogger = "-- unknown --";

  55:                     }

  56:                 }

  57:             }

  58:             return result;

  59:         }

  60:  

  61:         /// <summary>

  62:         /// Supported Levels

  63:         /// </summary>

  64:         public static readonly Dictionary<string, Level> LevelMap = new Dictionary<string, Level>

  65:         {

  66:             {"IsOff", Level.Off },

  67:             {"IsFatal", Level.Fatal},

  68:             {"IsError", Level.Error},

  69:             {"IsInfo", Level.Info},

  70:             {"IsDebug", Level.Debug},

  71:             {"IsAll", Level.All},

  72:         };

  73:  

  74:  

  75:         /// <summary>

  76:         /// Toggle level for logger 

  77:         /// </summary>

  78:         /// <param name="loggerName">name of logger</param>

  79:         /// <param name="selectedLevel">level to toggle</param>

  80:         public void ToggleLogLevel(string loggerName, Level selectedLevel)

  81:         {

  82:             // get Logger interface

  83:             var logger = log4net.LogManager.GetLogger(loggerName);

  84:             if (logger != null)

  85:             {

  86:                 // get hierachical logger

  87:                 var hl = logger.Logger as log4net.Repository.Hierarchy.Logger;

  88:                 if (hl != null)

  89:                 {

  90:                     if (hl.EffectiveLevel <= selectedLevel)

  91:                     {

  92:                        

  93:                         // Disable Selected Level by selecting Level + 1

  94:                         // eg. disable INFO by setting level to ERROR

  95:                         hl.Level = LogLevelToggler.LevelMap.TakeWhile(l => l.Value != selectedLevel).Last().Value; 

  96:                     }

  97:                     else

  98:                     {

  99:                         // Enable Selected Level

 100:                         hl.Level = selectedLevel;

 101:                     }

 102:                 }

 103:             }

 104:         }

 105:     }

 106: }

Business Logic: LogLevelToggler

   1: namespace Sdx.Logging

   2: {

   3:     /// <summary>

   4:     /// Display object

   5:     /// </summary>

   6:     public class LoggerInformation

   7:     {

   8:         // Name of Logger

   9:         public string Name { get; set; }

  10:         public string Repository { get; set; }

  11:  

  12:         // Levels

  13:         public bool IsFatal { get; set; }

  14:         public bool IsError { get; set; }

  15:         public bool IsInfo { get; set; }

  16:         public bool IsDebug { get; set; }

  17:         

  18:         // Name of logger, defining the Level

  19:         public string DefiningLogger { get; set; }

  20:     }

  21: }

Value Object: LoggerInformation

Ausblick

Das zugehörige ASP.NET Control zum bequemen Einbinden in eigene Projekte habe ich im folgenden Beitrag veröffentlicht. Eine Einbindung in eine Admin-Seite (!) ist dann schnell durchgeführt. Viel Spaß mit dem kleinen Helferlein.