Mittwoch, 27. Oktober 2010

Logging in dot.net

Einführung
Aktuell bin ich als Entwicklungsleiter in einem C# Umfeld eingesetzt. Bei einer Client/ServerApplikation galt es, UseCases zu monitoren und die Laufzeit zu messen. Dabei interessiert besonders
  • Anzahl und Dauer der Netzwerkrequests vom Client zum Server
  • Art, Anzahl und Dauer der abgesetzten SQL-Statements am Server
  • Dauer des Netzwerkresponses vom Server zum Client
und natürlich das selektive Einschalten von Logging für zu überwachende Teilbereiche, wie ich es von Java (sprich von log4j [1]) gewohnt bin.

Der Client loggte bereits mittels der Logging-Implementierung von dot.net, nämlich Systems.Diagnostic.TraceSource. Der Server hatte kein explizites Logging, intern wurden jedoch einige Bibliotheken verwendet, die auf unterschiedliche Arten loggen. Verwendet wurden
  • als Job Scheduler Quartz.net, verwendet Common.Logging [3]
  • als ORM NHibernate, verwendet log4net [2], die dot.net Variante von log4j [1]
  • zur Netzwerkkommunikation WCF, verwendet die Systems.Diagnostic.TraceSource
Die Aufgabenstellung lautet daher:
  • Einführen von Logging auf Client und Serverseite
  • Harmonisierung der bestehenden Frameworks
  • Zusammenfassung der Logs von Client und Server, um die gewünschten Fakten zu ermitteln

Common.Logging
Common.Logging ist kein eigenes Logging Framework, sondern eine Abstraktion (eine "Bridge") von unterschiedlichen Frameworks. Common.Logging erlaubt die konfigurierbare Wahl des Logging-Frameworks, welches die Ausgabe schlussendlich wahrnimmt. Geschaffen wurde die Bibliothek nach dem Vorbild von Commons.Logging (man beachte das "s"), welches dieselbe Aufgabe in der Java-Welt wahrnimmt.

Eine Bibliothek sollte mich durch ihre Verwendung nicht  nötigen, andere Frameworks einzusetzen, die gänzlich andere Aspekte  abdecken, in diesem Falle Logging. Daher sollten Bibliotheken immer Common.Logging benutzen. D.h. an dieser Stelle ist Kritik an NHibernate angebracht, dass log4net verwendet wird. Ein entsprechender Issue, der die Umstellung auf Common.Logging fordert, existiert bereits in Jira, bitte fleissig voten [4].

Der eigene Code hingegen kann sehr wohl direkt ein spezifisches Logging-Framework verwenden, allerdings wird damit ein späterer, möglicher Wechsel auf ein alternatives Logging-Framework unnötig erschwert. Daher macht es Sinn, dass auch innerhalb des eigenen Codes Common.Logging verwendet wird.

Common.Logging bringt zusätzlich noch Adaptoren mit, die die Logausgabe von Logging-Frameworks "einfangen" und nach Common.Logging überführen. Somit kann man sehr leicht die Logging-Ausgaben unterschiedlicher Frameworks "harmonisieren".

Im Folgenden schauen wir uns an, wie wir die oben genannten Anforderungen über log4net und TraceSource abwickeln können.

Variante 1: Loggen über log4net
Die beteiligten Komponenten werden wie folgt verdrahtet:
Sowohl Client als auch Server verwenden Common.Logging, damit muss der rote Teil der Grafik konfiguriert werden. An dieser Stelle soll nicht angeführt werden, wie Common.Logging oder Log4Net zu konfigurieren ist. Dazu sei auf die entsprechenden Homepages verwiesen. Anmerken möchte ich jedoch, dass die Konfiguration von Log4Net sowohl in einem eigenen config-File als auch in der app.config passieren kann. Am Server ist auf jeden Fall das eigene config-File anzustreben, da Log4Net dieses überwachen kann, bei einer Änderung die Datei neu einliest und man damit die Konfiguration zur Laufzeit ändern kann. So kann im Bedarfsfall eine Komponente näher untersucht werden, ohne dass der Server restartet werden muss.

Anbei die Teile der app.config, die notwendig sind, um TraceSource nach Common.Logging über zu führen:
<!-- ************ System.Diagnostics Konfiguration ************ -->
<system.diagnostics>

  <!-- Listener für Weiterleitung der Ausgaben von Trace und TraceSource nach Common.Logging registrieren -->
  <!-- Der Wert für initializeData gibt an, welche Levels von Trace und TraceSource weiter gegeben werden, -->
  <!-- siehe System.Diagnostics.TraceEventType -->
  <sharedListeners>
    <add name="Diagnostics"
         type="Common.Logging.Simple.CommonLoggingTraceListener, Common.Logging"
         initializeData="DefaultTraceEventType=Information; LoggerNameFormat={listenerName}.{sourceName}">
    </add>
  </sharedListeners>

  <!-- Bestehende Behandlung von Trace (mit clear) deaktivieren und Listener für Weiterleitung der Ausgaben -->
  <!-- von Trace in Common.Logging aktivieren. Damit wird Trace ausschließlich in Common.Logging überführt. -->
  <trace>
    <listeners>
      <clear />
      <add name="Diagnostics" />
    </listeners>
  </trace>

  <!-- Bestehende Behandlung von TraceSource (mit clear) deaktivieren und Listener für Weiterleitung der Ausgaben -->
  <!-- von TraceSource in Common.Logging aktivieren. Damit wird TraceSource ausschließlich in Common.Logging überführt. -->
  <!-- Eine Eigenheit von TraceSource ist, dass alle Sources (= Logger in der Log4Net-Sprache) -->
  <!-- einzeln zu Listenern (= Appendern in der Log4Net-Sprache) zugewiesen werden müssen.-->
  <sources>
    <source name="System.ServiceModel" switchValue="All" propagateActivity="true">
      <listeners>
        <clear />
        <add name="Diagnostics" />
      </listeners>
    </source>
  </sources>
</system.diagnostics>

Der erste Block definiert einen Listener (=Appender in der Log4x-Sprache), der das Logging von TraceSource entgegen nimmt und nach Common.Logging überführt. Der zweite Block sorgt dafür, dass System.Trace-Aufrufe nach Common.Logging umgeleitet werden. Hier ist zu beachten, dass ein Default-Listener registriert ist (Log-Window in Visual Studio), welcher mit clear entfernt werden kann. Der dritte Block definiert, dass genau eine Source (= Logger in der Log4x-Sprache), nämlich "System.ServiceModel" mit dem oben definierten Listener verdrahtet wird. Diese Source nimmt die Protokollierung der WCF-Kommunikation vor. Alternativ kann Beginn und Ende der Kommunikation über Aspekte geloggt werden, was zu weniger und sinnvolleren Logging-Outputs führt.

Die Harmonisierung der unterschiedlichen Log-Quellen funktionierte auf diese Art und Weise, wie sieht's nun mit dem Zusammenführen der Outputs von Client und Server aus? Dies kann sehr einfach mit einem Tool wie NetLogClient [5] erfolgen, welches sowohl vom Client als auch vom Server den Output von definierten Loggern über UDP entgegen nimmt und diese zeitlich geordnet visualisiert (NetLogClient ist der C#-Counterpart zu chainsaw).

Variante 2: Loggen über TraceSource
Die beteiligten Komponenten werden wie folgt verdrahtet:

Client als auch Server verwenden System.Diagnostic.TraceSource, der rote Teil der Grafik muss konfiguriert werden. Auch hier schauen wir uns nur die relevanten Teile an.

Zuerst muss Log4Net nach TraceSource übergeführt werden, in der Konfiguration von Log4Net ist folgendes vorzusehen.
<!-- A1 is a TraceAppender and uses PatternLayout -->
<!-- Siehe http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html -->
<!-- # %date Zeitpunkt in ISO8601DateFormat {ISO8601} oder absolut=ohne Date-Info {ABSOLUTE} -->
<!-- # %thread Thread name -->
<!-- # %level   Used to output the level of the logging event. -->
<!-- # %logger  Used to output the logger of the logging event. -->
<!-- # %ndc  NDC (nested diagnostic context) associated with the thread that generated the logging event -->
<!-- # %message message -->
<!-- # %newline platform dependent line separator character or characters -->
<appender name="A1" type="log4net.Appender.TraceAppender">
  <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date{ABSOLUTE} [%thread] %-5level %logger %ndc - %message%newline"/>
  </layout>
</appender>

Zweitens ist Common.Logging zu konfigurieren:
<!-- ************ Common.Logging Konfiguration ************ -->
<common>
  <logging>
    <!-- Common.Logging nach Diagnostic.Trace überführen. -->
    <factoryAdapter type="Common.Logging.Simple.TraceLoggerFactoryAdapter, Common.Logging">
      <arg key="level" value="DEBUG"/>
      <arg key="showLogName" value="true"/>
      <arg key="showDataTime" value="true"/>
      <arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff"/>
    </factoryAdapter>
  </logging>
</common>

Diese Lösung hat den Nachteil, dass drei unterschiedliche Layouts vorhanden sind, die an jeweils unterschiedlichen stellen anzupassen sind, um über die gesamte Architektur einheitliche Layouts zu erhalten. Die Harmonisierung ist damit abgehandelt, die Zusammenführung funktioniert über den Microsoft Service Trace Viewer. Für diesen sind sowohl auf der Client- als auch auf der Serverseite XML-Logs zu erstellen, die dann importiert werden müssen. Zusammengehörige Requests werden in beiden Varianten durch dieselbe Activity-ID identifiziert.

Bewertung
Wenn man sich die Konfiguration der beiden Varianten ansieht (und mit Reflector ein bisschen in TraceSource rumschnüffelt), dann merkt man, dass Log4Net und TraceSource unterschiedliche Konzepte verfolgen.

Bei der Implementierung von Log4Net wurde das Observer- und das Mediator-Pattern verwendet. Jeder Logger kennt nur seinen Mediator, was zu leichtgewichtigen Logger-Objekten führt. Deshalb wird beim Einsatz von Log4Net auch fast immer das logger-per-class pattern [6] angewandt.

TraceSource verwendet zwar auch das Observer-, nicht aber das Mediator-Pattern. Deshalb muss für jede Source angegeben werden, welcher Listener den Output erhält. Auch werden Log-Levels nicht vererbt. Dies ist ein Grund, warum Log4Net-Jünger jammern, wenn sie mit TraceSource in Berührung kommen. Tatsächlich steht dahinter ein ganz anderes Konzept: die Logger - Entschuldigung, die Sources sind schwergewichtig, daher wird ein source-per-componente pattern angewandt (Sorry, dazu kenne ich keinen externen Link). Dies erklärt auch, warum die Source nicht über ihren Klassennamen inkl. Namespace von einem zentralen Buffer (Factory) geholt wird, sondern einfach mit new() erzeugt wird. Innerhalb der Komponente müssen die entsprechenden Klassen sehen wie die Source geteilt wird.

Die korrelierende Auswertung mit Microsoft Service Trace Viewer gefällt, schnell war der Wunsch da, auch SQL-Statements in diesem vorzufinden. Gewählt und umgesetzt wurde daher eine Mischvariante aus den obigen zwei Varianten:
Für den Menschen bestimmte Log-Dateien werden über Log4Net erstellt (Variante 1). WCF wird aber nicht integriert, sondern bleibt eigenständig. Für die SQL-Statements aus NHibernate wird ein weiterer, nicht additiver Logger eingerichtet (Variante 2), der nach TraceSource loggt. Die korrelierten Auswertungen inkl. SQL können mit Microsoft Service Trace Viewer betrachtet werden. Auch der UDP-Logger kann weiterhin eingesetzt werden. Anbei noch ein Bild des Service Trace Viewers.



Das Thema Logging ist damit aber noch nicht erschöpft, diesen Beitrag möchte ich aber an dieser Stelle beenden. Bitten möchte ich um Voting/Comments von drei Fehlern, die wir im Rahmen der Einführung gefunden haben, nämlich den obig erwähnten Punkt [4], dann noch unnötige Referenzen bei Log4Net [7] und einen Fehler bei der Codeausgabe von Common.Logging [8].

Euer
CL@coopXarch

Links

Freitag, 22. Oktober 2010

Software Architektur – Versuch einer Definition

Liebe Leser,

inspiriert durch unzählige Artikel, erlebte Diskussionen, Vorträge, etc. wollte ich meine ganz persönliche Definition von Software Architektur in diesem Beitrag zum Besten geben. Anfänglich dachte ich an eine glanzvolle, plakative und prägnante Formulierung. Umso mehr ich darüber reflektiert habe, desto weiter habe ich mich von diesem Ziel entfernen müssen. Es gibt viele Definitionen, wovon einige ganz brauchbar sind und andere besser gleich wieder vergessen werden sollten. Von den brauchbaren betrachten alle immer nur einen Aspekt des Themas. Darum möchte ich die für mich relevanten Gedanken zur Definition von Software Architektur in Folge kurz erläutern.

Immer wieder hört man auf die Frage nach der Software Architektur eines konkreten Produktes Antworten, die sich auf die eingesetzten Technologien oder Frameworks beziehen. Eventuell wurden sogar Architekturentscheidungen rein auf Basis der Technologien getroffen. Manchmal bekommt man auch als Antwort, dass keine dedizierte Architektur existiere. Bei Ersterem ist aus meiner Sicht der Architekturansatz viel zu kurz gegriffen. Was alles in eine Architekturbetrachtung einfließen sollte, werde ich in Kürze ausführen. Die zweite Aussage ist schlicht weg falsch. Es gibt immer eine Architektur. Ob diese gut oder schlecht ist, geplant oder im Laufe der Umsetzung zustande gekommen ist, das sei dahingestellt. Extremisten unter den Verfechtern von agilen Vorgehensmodellen vertreten die Meinung, dass sich die Architektur durch Refactoring ergibt. Andere glauben daran, dass sich ein Teammitglied im Sinne der Gruppendynamik die Rolle des Architekten auferlegen wird und dann die Architektur definiert. Ich bin zwar auch ein Anhänger von agilem Vorgehen, teile dennoch nicht diese Überzeugungen.

Worum dreht sich eine Architektur?
Eine Architektur kann man aus mehreren Sichten betrachten. Hier herrscht keine Einigkeit, aber oft werden fachliche Architektur, Anwendungsarchitektur, technologische Architektur und Systemarchitektur unterschieden. Es obliegt dem Architekten in Verbindung mit dem konkreten Fall, welche Sichten für die Umsetzung betrachtet werden sollten. Wichtig ist an dieser Stelle, dass es sich nicht um eine eindimensionale Drehscheibe handelt und man immer auch eine technologieneutrale konzeptionelle Sicht vornehmen muss. Eine einfach aufbauende Herangehensweise würde ausgehend von einer konzeptionellen Architektur in eine Anwendungsarchitektur überführen, bei welcher die Konzepte verwendet werden um die fallspezifische Domäne zu beschreiben. Daran geknüpft würde man dann ein Technologiemapping vornehmen und erhält als Ergebnis eine gesamtheitliche Architektur.

Wie steht es um das Profil eines guten Software Architekten?
Eine knackige Aussage, welche ich einmal zu der Primärtätigkeit eines Software Architekten gehört habe, lautet: "ein Software Architekt erstellt eine Blaupause und die Dokumentation, wie man zu deren Umsetzung kommt". Dem kann ich mich voll anschließen. In meinen Augen entwirft ein guter Architekt ein System passend zu den von ihm selbst vorgegebenen Technologien in enger Anlehnung an den Kunden bzw. das Projekt (Produkt).

"An architect always implements"
Ich bin grundsätzlich davon überzeugt, dass diese Aussage unumstößlich ist. Ich habe sogar schon Jobangebote gesehen, wo dieser Satz – ein wenig ausgeschmückt – direkt in den Anforderungen an den Bewerber enthalten war. Ein Software Architekt hat nicht zwangsläufig Produktcode beizusteuern, aber er muss schon über die eingesetzten Technologien Bescheid wissen und diese auch ausprobiert haben und kennen, wenn gleich er lediglich auf einer Spielwiese experimentiert hat.

Ein Software Architekt muss auch eine kommunikative Persönlichkeit sein. Er hat immerhin die Aufgabe mit relativ vielen Stakeholdern zu interagieren und die Architektur zu beschreiben bzw. diese zu vermitteln.

Architektur vs. Design
Architektur kann man als strategisches Design umschreiben, bei welchem sowohl die nicht funktionalen Anforderungen, als auch die Anforderungen mit systemweitem Einfluss in die fachliche Domäne eingeführt werden. Dem gegenüber steht das taktische Design, welches mehr oder weniger den Rest bis hin zu einer Featureumsetzung abbildet.

Abschließend möchte ich noch eine Aussage von Ralph Johnson anführen, der in einer Mailingliste einmal zum Abschluss einer Diskussion über die offizielle IEEE Definition von Architektur folgenden Satz geschrieben hat: "Architecture is about the important stuff". Er hat sich darauf bezogen, dass eine Architektur auf den für die Entwicklung wichtigsten Dingen basieren sollte, welche das auch immer im konkreten Fall sein mögen. In Anlehnung daran sei es mir gestattet darauf hinzuweisen, dass Architekten keine gottähnlichen, allwissenden, alles könnenden Wesen sind, sondern im tiefsten Inneren Softwareentwickler mit hoffentlich viel Erfahrung und Weitblick.

Euer
JL@coopXarch

Montag, 18. Oktober 2010

Java in the Cloud - Erfahrungen beim Deployen einer Applikation mit der Spring "CloudFoundry"

Liebe Leser!

Gestern habe ich erstmals eine Java Applikation in der Cloud gestartet. Das Spring Service CloudFoundry stellt einen vollständigen Spring Stack zur Verfügung:
  • Apache
  • Spring tc Server (das ist der Spring Enterprise Tomcat) oder Tomcat 5.5
  • mySQL.

CloudFoundry verwendet als Träger-Cloud-Service Amazon EC2 und bietet also einen Platform as a Service (PaaS) Aufsatz auf das Amazon Infrastructure as a Service (IaaS) Angebot.

Soweit - so gut. Was waren nun die Fallstricke?
  • Zuerst muss man einen Amazon Web Services Account anlegen.Wenn man danach in CloudFoundry seinen Amazon Access Key validieren will, schlägt das fehl. Warum? 
    • Nun, man muss sich zuvor bei Amazon für EC2 anmelden, dann funktioniert auch die Validierung. Danach kann man sich die notwendigen Key Pairs von CloudFoundry generieren lassen.
  • Beim Deployen einer Applikation wird das Eingabefeld "Web Server - Public IP" angezeigt. Sobald man hier etwas einfüllt, schlägt die Feldvalidierung immer fehl. Was tun? 
    • Nun, das Feld einfach freilassen, dann wird die Applikation deployed und man gelangt über einen angebotenen Link auf die deployte Applikation in der Cloud - Voila!
Weiterführende Probleme, die mir bei meinem ersten Test aufgefallen sind:
  • Applikations-Logfiles
    • Beim Tracen von Problemen ist es essentiell, ein "tail -f" auf die Applikationslogfiles mitlaufen zu lassen. Fraglich ist, wie das über CloudFoundry geht bzw. ob so etwas Feines wie bei Hudson (CI Server) angeboten wird - hier kann man einem Build in Echtzeit im Web beobachten. Alternative: Ich nehme an, man kann auch auf den Amazon Server direkt verbinden und die Logfiles dort auswerten.
  • Daten in der Datenbank
    • Falls die Instanz gestoppt wird - aus welchem Grund auch immer - wie kommt man zu den Daten in der Datenbank? Vermutung: Wie oben direkt auf die Amazon Instanz verbinden und einen Export machen - ist das auch automatisierbar?
  • Deployment einer Version mit minimaler Downtime
    • Das Deployen einer Mini Applikation hat ca. 4 Minuten gedauert. Wenn ich eine neue Version deployen will (und vorausgesetzt, es ist keine Änderung der Datenstruktur mit Datenmigration notwendig), wie halte ich die Downtime so gering als möglich? So wie ich es verstanden habe, kann man nicht einfach den Apache auf einen neu deployten Stack umschalten bzw. habe ich dazu noch keine Informationen gefunden. Wahrscheinlich muss man dazu wieder auf die Amazon Instanz gehen und das Procedere händisch ausführen.
Ad Kosten: Soweit ich gesehen habe, routet CloudFoundry - das übrigens noch im BETA Stadium ist - einfach die Amazon kosten durch und verrechnet keinen Aufschlag. Die Frage ist, ob mit der Verfügbarkeit von VMforce das CloudFoundry Service nicht eingestellt werden wird. VMWare tut sich ja mit Salesforce.com zusammen, um nicht Amazon das Geschäft mit dem IaaS zuschanzen zu müssen.

Euer 
JWR@coopXarch.

Montag, 11. Oktober 2010

Ich bau mir einen Cluster oder "Die Geschichte des BossBlade"...

In der Vergangenheit habe ich mich auch mit dem Bau von Cluster Systemen beschäftigt und den "BossBlade" entwickelt - damals waren Blade Server noch in aller Munde und die Cloud noch nicht erfunden. Ich dachte mir, das könnte Euch interessieren - im Folgenden also der Werbetext des BossBlade "Produkts"...

Der "BossBlade" ist ein komplett auf Open Source basierender, hochausfallsicherer, skalierbarer Shared Nothing Cluster ohne Single Point of Failure: HA HTTP/HTTPS Load Balancer, HA JBoss Cluster mit Cache Synchronization und Farming, HA Datenbank Cluster (PostgreSQL, mySQL, etc.).

Bis jetzt waren hochausfallssichere, skalierbare Applikationsserver-Cluster grossen Unternehmen vorbehalten, die die beträchtlichen Summen für Serverhardware und Speichersubsysteme (RAID, SAN, NAS) und Lizenzen für Betriebssystem, Clustersoftware, Datenbanken, Applikationsserver, Web Server, Netwerkmanagementsoftware nebst Wartungsverträgen aufbringen konnten. Diese Servercluster sind die Basis für Enterprise Java Softwaresysteme (J2EE), die sich durch zentralisierte Architektur, hohe Wartbarkeit und Flexibilität und bedarfsgerechte Skalierbarkeit auszeichnen. In kleinen und mittleren Unternehmen regiert bis heute oft ein Flickwerk an unterschiedlichen Applikationen in verschiedenen Skript- und Programmiersprachen auf verschiedenen Plattformen ohne zentrales Software-Architektur-Konzept. Mit dem BossBlade steht nun erstmals auch kleinen und mittleren Firmen eine vollständig auf Open Source Software basierende, skalierbare Infrastruktur für Enterprise Software zu Verfügung, die kostengünstig und stabil ist und mit dem Unternehmen wächst. Die Migration und Zentralisierung der historischen Softwarelösungen auf das BossBlade bringt Kostenvorteile bei der Weiterentwicklung und Wartung der Software und erhöht die Stabilität, Verfügbarkeit und Skalierbarkeit des Systems. Durch die Shared Nothing Technologie (keine einfach ausgelegten gemeinsamen Komponenten - z.B. externes RAID) kann das BossBlade auf zwei Serverräume aufgeteilt werden, um die Systemverfügbarkeit bei Strom- und Leitungsausfall bzw. anderen physischen Einflüssen zu garantieren.

Der "BossBlade" - Links die Säule der 2 Webserver, mittig die Applikationsserver, rechts die Datenbankserver - soviel zur anschaulichen Darstellung von "Tiers" - die Ausfallsicherheit im Testaufbau ist natürlich durch die Verwendung einer Steckerleiste und eines Switches begrenzt ;-)


JWR@coopXarch.