Azure File Storage

Azure File Storage stellt ein Samba-Netzwerklaufwerk mit REST API zur Verfügung. Primär ist es dazu gedacht, verschiedenen Azure-Virtual-Machines (VMs) oder Azure-Services ein gemeinsames Dateiverzeichnis zur Verfügung zu stellen.

Zur Erinnerung: Wird in Azure eine neue VM-Instanz instanziiert (z. B. für ein OS-Update oder bei Skalierungsbedarf), so hat sie per Default nur lokalen Speicher. Dateien, die ich nur lokal schreibe, sind durch andere Instanzen nicht lesbar. Die Instanzen müssen somit miteinander kommunizieren (z. B. per Queue, File-Storage, Table-Storage, Datenbank, Services), um einen gemeinsamen Zustand zu repräsentieren.

Nutzung als Netzlaufwerk

Mit einem “net use” bzw. “Netzlaufwerk verbinden” kann ich aus beliebigen File-Storage Instanzen ein Laufwerk mounten. Das geht netterweise auch von zu Hause aus. Wer also ein paar GB/TB (wahlweise geo-) redundanten Speicherplatz braucht oder bequem auf die Daten der Azure-Instanzen zugreifen möchte, kann hier gerne für kleines Geld zugreifen. Innerhalb der Azure-Welt wird Samba 2 und Samba 3 unterstützt. Von zu Hause aus (d. h. über das Internet) wird nur Samba 3 angeboten – das ist dann End-To-End verschlüsselt.

Am Rande: Fritz!Box-Nutzer sollten wissen, dass das Filtern von Netbios ausgeschaltet sein sollte (hat mich 2h gekostet). In den USA scheint das Protokoll durch den ein oder anderen Provider gesperrt zu sein. Prinzipiell wird nur TCP-Port 445 Outgoing benötigt.

Nutzung als REST-API

Alle Filezugriffe können auch per REST API durchgeführt werden (à la Azure-Blob-Storage).

Die Schnittstelle ist für höherwertige Services gedacht, die keinen direkten Zugriff auf die VM bieten. Mit Azure-Web-Apps erwirbt man beispielsweise nur das Recht, eine Anwendung in einem Shared-IIS zu hosten, aber nicht die Möglichkeit, die darunterliegende VM zu administrieren. Daten, die die Web-Anwendung dauerhaft in das Filesystem schreibt, müssen somit in den zentralen Speicher geschrieben werden.

Kurzum: Wenn die Web-App, anstelle von Filezugriffen, die REST API aufruft, werden die Daten ins zentrale File-Share geschrieben. Für administrative Zwecke kann man das Laufwerk dann wieder schnell in den eigenen Rechner mounten. Nett.

Limitierungen

Azure File Storage kennt keine Rechte – der Zugriff ist immer Vollzugriff. Die Authentifizierung erfolgt mit dem Namen der Instanz und einem – von zwei – Access-Keys, der bei der Erstellung des File Storage erzeugt wird bzw. auf Knopfdruck geändert werden kann. Weitere Einschränkungen listet Microsoft unter Features Not Supported By the Azure File Service auf.

Nutzung im Unternehmen

Neben den Diensten bietet sich der Azure-File-Storage durchaus für geschlossene Benutzergruppen an, die Daten austauschen wollen. Als Sicherheitsmechanismus gibt es nur die Möglichkeit, von Zeit zu Zeit den Key auszutauschen.

Fazit

 Für eine Azure-Migration einer Webanwendung ist der Azure-File-Storage ein sehr brauchbarer Service, den ich schon erfolgreich einsetzen konnte. Microsoft bietet ähnliche Dienste wie Azure Blobs und Azure Data Disks an, deren Einsatz von dem jeweiligen Anwendungsszenario abhängen. Nicht nur mir persönlich fehlt das “echte” Rechtemanagement sehr… ich denke mal, das wird schon noch.

Hackathon mit AngularJS

Mit dem “Hackathon im November” durften sich auch eingefleischte C# Programmierer einmal mit Javascript/AngularJS auseinandersetzen. Hier ein kleiner Erfahrungsbericht.

Motivation

Ein rasend schneller Hochgeschwindigkeitszug aus neuen Technologien rauscht derzeit in Form von Javascript/CSS Bibliotheken und Frameworks durch das Internet. Wer sich den Spaß macht nach diesen Begriffen zu suchen, wird für jedes vergangene Jahr unterschiedliche Top 10 Listen finden. Auslöser sind Mobile Web Apps  und Single Page Applications  die in diversen Projekten zum Einsatz kommen.

Hackathon – Technologie zum Anfassen

Jeder Mitarbeiter soll die Chance haben auch außerhalb des jeweils aktuellen Projektes in neue Technologien hinein zu schnuppern. So trafen sich Mitte November einige Kollegen zum AngularJS-Hackathon. Das ist eine Veranstaltungsform ganz nach dem Geschmack von Techies: Eine kleine Aufgabenstellung, Rechner raus und unter fachkundiger Anleitung (Thx to Nico) ausprobieren wie sich die Technologie anfühlt.
Witzigerweise waren vorwiegend Verfechter von streng getypten Programmiersprachen anwesend, die sich somit in die Niederungen begaben um Javascript zu schreiben. Das stimme so nicht ganz, denn niemand schreibt "Javascript" – wir schrieben "AngularJS".

Aufgabenstellung:

Entwickelt haben wir eine Single Page Application im Kontext von Showtime – einem Referenzszenario  für komplexe Business Szenarien. Nico hatte hierzu ein Grundgerüst (Solution-Struktur) bereitgestellt, das gleich von Anfang an ein sinnvolles Arbeiten ermöglicht.

Themen:

Für nur einen Tag sind wir recht erfolgreich durch unsere Themenblöcke durchgekommen. Angewendet wurden:

  • Model, View, Controller und Scope in Angular JS
  • Routing, Modules
  • One Way, Two Way Data Binding
  • dazwischen immer mal wieder ein paar Javascript Grundlagen
  • Entwickeln von Master/Detail-Seiten
  • Bereitstellung eines Service, Promises
  • Anbindung der ShowTimeServices mit Logon
  • Direktiven

Ergebnis:

Für mich ist die bereitgestellte Solution Gold wert. Es stellt eine saubere Projektstruktur bereit, mit der ich nach diesem Tag auch auf Anhieb umgehen kann. Danke Nico für deine Geduld die vielen Fragen zu beantworten – vor allem wenn es mal wieder um die Unterschiede C#/Javascript ging (und warum das in JS nicht genau so funktionieren kann ;)). Ein sehr wertvoller Tag.

Flurfunk Links:

Stoppwörter mit LINQ

Kleine aber feine Problemlösungen machen Spaß und immer öfters ist – zumindest bei mir – LINQ daran beteiligt. Heute war es die Problemstellung der “Stoppwörter”, die mir wieder einmal zeigten, wie gut das Konzept der LINQ-Abfragen funktioniert. 

Die Aufgabenstellung besteht darin, aus einer Liste von Strings diejenigen ohne Stoppwörter herauszufinden.

Beispiel:

Wortliste : "Das", "ist", "ein Stop1 mit", "ein", "-em Stop2 als", "Stopptext"

Stoppliste: "Stop1", "Stop2"

Ergebnis  : "Das", "ist", "ein", "Stopptext"

Die offensichtliche Lösung mit zwei verschachtelten Schleifen ist in diesem Fall nicht besonders schwer zu schreiben. Ein Blick auf die Problemstellung zeigt, dass zwei Mengen miteinander verarbeitet werden, was für mich ein guter Indikator für den Einsatz von LINQ ist.

LINQ 2 Objects

LINQ 2 Objects führt Abfragen auf Objekten im Speicher aus. Die Lösung für das Problem sieht dabei wie folgt aus:

Suche in der Wortliste words.Where()
Wörter, l =>
für die in der Stoppliste kein Stoppwort s =>
gefunden werden kann !stopWords.Where(…).Any()
das im Wort enthalten ist l.Contains(s)

   1: var words = new List<string> { 

   2:   "Das", "ist", "ein Stop1 mit", "ein", "-em Stop2 als", "Stopptext"};

   3: var stopWords = new List<string> { "Stop1", "Stop2" };

   4:  

   5: // Nur Texte ohne Stoppwörter ermitteln

   6: var result = words.Where(

   7:     l => !stopWords.Where(s => l.Contains(s)).Any()

   8: );

 
Eine gute Kommentierung von komplexen LINQ-Statements ist unumgänglich. “It was hard to write so it should be hard to read” ist kein verlässliches Prinzip in der Softwareentwicklung.

LINQ to Entities

LINQ to Entities kann über das Entity Framework Abfragen an den SQL-Server weitergeben, so dass sie dort ausgeführt werden und den Client entlasten. Ich war gespannt wie LINQ to Entities die Abfrage in SQL umsetzt. Hierzu habe ich zwei Tabellen “Words”, “StopWords” erzeugt, die jeweils die Wörter und Stoppwörter enthalten. Die Abfrage wird jeweils um die Selektion auf die Wort-Spalte erweitert:

   1: var dbWords = db.Words.Select(w => w.Word);

   2: var dbStopWords = db.StopWord.Select(w => w.StopWord1);

   3: var result = dbWords.Where(

   4:     l => !dbStopWords.Where(s => l.Contains(s)).Any()

   5: );

Das Ergebnis setzt LINQ to Entities perfekt um

   1: SELECT 

   2: [Extent1].[Word] AS [Word]

   3: FROM [dbo].[Words] AS [Extent1]

   4: WHERE  NOT EXISTS (SELECT 

   5:     1 AS [C1]

   6:     FROM [dbo].[StopWord] AS [Extent2]

   7:     WHERE (CHARINDEX([Extent2].[StopWord], [Extent1].[Word])) > 0

   8: )

Anmerkung: Ein Index auf die Wörter-Spalten ist für eine brauchbare Performance Pflicht.

Fazit

Zwei verschachtelte Schleifen mit Vergleichen und Zuordnungen können mit einem LINQ-Einzeiler ersetzt werden und ich habe mit Zufriedenheit festgestellt, dass LINQ to Entities die Anfragen hervorragend in SQL umsetzt.

Somit hoffe ich, dass zukünftig LINQ noch viel mehr eingesetzt wird. Es führt in der Regel zu kurzem und verständlichen Code.

Hurra – die Windows Azure PRIVATE Cloud ist da

… und keiner hat`s gemerkt.

MS hat die PRIVATE Cloud heimlich, still und leise in Form von Windows Azure Pack bereitgestellt. Zudem sind im Optimalfall keine neuen Lizenzen nötig, da vorhandene Lizenzen genutzt werden können:

… is available to Microsoft customers at no additional cost. Once installed in your datacenter, the Windows Azure Pack integrates with System Center and Windows Server to help provide a self-service portal for managing services
 

"Kann ich  Windows Azure in meinem Rechenzentrum betreiben?"

Das war – gefühlt – die am meisten gestellte Frage im Jahr 2008 bei der Vorstellung von Windows Azure auf der PDC in Los Angeles. Die Frage kann seit kurzem bejaht werden: Mit dem Windows Azure Pack gibt Microsoft auch Endkunden und Hostern die Möglichkeit eine eigene Windows-Azure-Cloud fern von NSA und Co den eigenen Mitarbeitern und Kunden bereitzustellen.

Windows Azure Pack stellt die Laufzeitumgebung für Dienste und Anwendung bereit, die auch in der Public Cloud lauffähig sind. Laut Technet-Blog:

build an application once and then deploy and operate it in any Microsoft Cloud – private, hosted or public.

Mit Windows Azure Pack
  • legen Administratoren die Ressourcen (CPU, Speicherplatz, Netzwerk etc) für die Cloud fest
  • werden vollautomatisierte Prozesse zum Betrieb und Überwachung definiert
  • werden automatisch Nutzungsabrechnungen erstellt
  • werden Mandanten verwaltet
  • werden Kombinationen aus Ressourcen und Services den Mandanten zur Verfügung gestellt

Die Administratoren legen Dienste und Prozesse (!) der Cloud fest. Sie definieren welcher Mandant (Kunde/Abteilung/Bereich) Ressourcen in einem bestimmten Umfang nutzen darf. Der jeweilige Nutzer (z.B. Anwendungsverantwortlicher) bedient sich selber an den automatisierten Prozessen. Windows Azure Pack liefert die Tools für die Administratoren wie auch die Tools für die Nutzer.

Das Whitepaper definiert nachfolgende Funktionalitäten für Windows Azure (bzw. Details im Technet) die für das Windows Azure Pack angeboten werden:

Somit sind viele Services von Windows Azure im Windows Azure Pack (noch) nicht enthalten. Die weitere Planung hat MS bislang offen gelassen.

Die Chance für Hoster, Enterprise Kunden und Dienstleister

Eigentlich hätte die Nachricht als "Big Bang" 3 Wochen lang die IT-Presse beschäftigen müssen. Microsoft bietet Hosting-Anbieter die Chance neue Geschäftsfelder und Kunden im wachsenden Markt des Hybrid-Cloud-Computing zu erobern. Enterprise-Kunden bekommen die Möglichkeit eine Rechenzentrumstechnologie mit hochautomatisierten Prozessen in Eigen- oder Fremdregie nach eigenen Vorgaben in die eigene Infrastruktur zu integrieren. Kleine Dienstleister können Clouds für spezifische Anforderungen z.B. der Sicherheit oder Verfügbarkeit konzipieren und implementieren. Der Windows-Azure-Markt, den Microsoft bislang nur alleine eroberte kann nun durch alle Partner vorangetrieben werden.

Und Microsoft?

Leider gab es bislang nur technische Ankündigungen zu hören oder sehen. Dass hier eine neue Schlüsseltechnologie für Hoster, Enterprise Kunden und Dienstleister bereitgestellt wird kann man ruhig deutlicher kommunizieren. Ich halte den Windows Azure Pack für eine großen Schritt in die richtige Richtung.

ASP.NET Control zum Ändern von log4net-Loglevels

Im nachfolgenden wird ein ASP.NET User Control vorgestellt, mit dessen Hilfe man log4net Loglevels im laufenden Betrieb ändern kann. Dies ist überaus hilfreich um in einer Produktionsumgebung “mal schnell” den LogLevel für spezifische Logger zu erhöhen. Die Funktionsweise habe ich schon in
log4net Levels im laufenden Betrieb ändern
. vorgestellt.  Das Control kann schnell in eine Admin-Seite eingebunden werden und stellt sich wie folgt dar:

LogLevelToggler

LogLevelTogglerControl

Das UserControl zeigt die Daten in einem GridView an. Auf Rocket-Science wird der Einfachheit halber verzichtet und die Daten als One-Way-Binding ohne DataSource realisiert.

   1: <%@ Control  Language="C#" AutoEventWireup="true" 

   2:              CodeBehind="LogLevelTogglerControl.ascx.cs" 

   3:              Inherits="WebAppSystemInfo.LogLevelTogglerControl" %>

   4: <asp:GridView runat="server" ID="gridView" AutoGenerateColumns="false">

   5: <Columns>

   6:   <asp:BoundField ReadOnly="true" HeaderText="Logger" DataField="Name" />

   7:   <asp:BoundField ReadOnly="true" HeaderText="Repository" DataField="Repository" />

   8:   <asp:TemplateField HeaderText="IsFatal" ItemStyle-HorizontalAlign="Center">

   9:   <ItemTemplate>

  10:     <asp:ImageButton runat="server" ImageUrl='<%# GetImage(Eval("IsFatal")) %>' OnCommand="ImgLevel_Command" CommandArgument="IsFatal" CommandName='<%#Eval("Name") %>' />

  11:   </ItemTemplate>

  12:   </asp:TemplateField>

  13:   <asp:TemplateField HeaderText="IsError" ItemStyle-HorizontalAlign="Center">

  14:   <ItemTemplate>

  15:     <asp:ImageButton runat="server" ImageUrl='<%# GetImage(Eval("IsError"))  %>' OnCommand="ImgLevel_Command" CommandArgument="IsError" CommandName='<%#Eval("Name") %>'/>

  16:   </ItemTemplate>

  17:   </asp:TemplateField>

  18:   <asp:TemplateField HeaderText="IsInfo" ItemStyle-HorizontalAlign="Center">

  19:   <ItemTemplate>

  20:     <asp:ImageButton runat="server" ImageUrl='<%# GetImage(Eval("IsInfo")) %>' OnCommand="ImgLevel_Command" CommandArgument="IsInfo" CommandName='<%#Eval("Name") %>'/>

  21:   </ItemTemplate>

  22:   </asp:TemplateField>

  23:   <asp:TemplateField HeaderText="IsDebug" ItemStyle-HorizontalAlign="Center">

  24:   <ItemTemplate>

  25:     <asp:ImageButton runat="server" ImageUrl='<%# GetImage(Eval("IsDebug")) %>' OnCommand="ImgLevel_Command" CommandArgument="IsDebug" CommandName='<%#Eval("Name") %>'/>

  26:   </ItemTemplate>

  27:   </asp:TemplateField>

  28:     <asp:BoundField ReadOnly="True" HeaderText="Defining Logger" DataField="DefiningLogger"/>

  29: </Columns>

  30: </asp:GridView>

ASPX Code: LogLevelTogglerControl.aspx

Im Code-Behind nutzt das Control die Funktionalität des LogLevelToggler.

   1: namespace Sdx.Logging

   2: {

   3:     using System.Web.UI.WebControls;

   4:  

   5:     public partial class LogLevelTogglerControl : System.Web.UI.UserControl

   6:     {

   7:         /// <summary>

   8:         /// Ermittlung des Image

   9:         /// </summary>

  10:         /// <param name="value"></param>

  11:         /// <returns></returns>

  12:         protected string GetImage(object value)

  13:         {

  14:             var state = (bool)value;

  15:             if (state) return "~/Images/Security_Shields_Complete_and_ok_32xLG_color.png";

  16:             return "~/Images/Security_Shields_Critical_32xLG_color.png";

  17:         }

  18:  

  19:         /// <summary>

  20:         /// Ermittelt alle Logger und bindet dieses an das GridView

  21:         /// </summary>

  22:         public override void DataBind()

  23:         {

  24:             base.DataBind();

  25:             var lds = new LogLevelToggler();

  26:             var dataSource = lds.GetLoggerInformation();

  27:             gridView.DataSource = dataSource;

  28:             gridView.DataBind();

  29:         }

  30:  

  31:         /// <summary>

  32:         /// Toggelt den Level

  33:         /// </summary>

  34:         /// <param name="sender"></param>

  35:         /// <param name="e"></param>

  36:         protected void ImgLevel_Command(object sender, CommandEventArgs e)

  37:         {

  38:             // toggle level

  39:             var lm = new LogLevelToggler();

  40:             var rowName = (string)e.CommandArgument;

  41:             var level = LogLevelToggler.LevelMap[rowName];

  42:             lm.ToggleLogLevel(e.CommandName, level);

  43:             // rebind

  44:             this.DataBind();

  45:         }

  46:     }

  47: }

Code-Behind: LogLevelTogglerControl.aspx.cs

Nutzung

Das Control muss jetzt nur noch auf der gewünschten Admin-Seite eingebunden werden.

   1: <%@ Register TagPrefix="ctrl" TagName="LogLevelTogglerControl" Src="~/LogLevelTogglerControl.ascx" %>

   2:  

   3: <ctrl:LogLevelTogglerControl ID="logLevelToggler" runat="server" />

Bitte das Databinding im Codebehind nicht vergessen. Sonst sieht man nichts.

   1: protected void Page_Load(object sender, EventArgs e)

   2: {

   3:     if (! IsPostBack)

   4:     {

   5:         logLevelToggler.DataBind();

   6:     }

   7: }

Fazit

Mit dem vorgestellten Code hat man eine einfache Copy-Paste-Vorlage um LogLevels im laufenden Betrieb umschalten zu können.

Ein ausdrücklicher Gruß geht an mein altes Münchner “Fritten-Buden-Team” wo ich dieses nützliche Prinzip erstmalig gesehen habe.

log4net Levels im laufenden Betrieb ändern

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.