Shuffleco.de

Lost in shuffled code

May 16, 2017 - 5 minute read - .Net C# WCF NLog

zentralisiertes Logging mit NLog und WCF

1. Die Grundidee

Ich möchte mich etwas näher mit NLog beschäftigen und habe dabei folgenden erdachten UseCase: Mehrere Anwendungen in einer Domäne nutzen NLog zum Loggen von Fehlern und dergleichen. Ich möchte nun die Logs an zentraler Stelle Sichten und auswerten können.

Eine Datenbank soll die Logs schnell durchsuchbar machen und basis für weitergehende Auswertungen sein. Anwendungsstabilität, Laufzeit, Nutzungsdauer oder so sind da mögliche Metriken.

Ein Web Dashboard soll auf einen Blick den Stand der “Lage” wiedergeben und einlaufende Meldungen in Echtzeit darstellen. Das Dashboard soll zunächt mal eine Tabelle der letzten 10 Fehlermeldungen und ein Liniendiagramm enthalten, das die Anzahl der Fehlermeldungen nach Typ auf einer Zeitachse aufträgt.

NLog uebersicht

Nlog bietet von Haus aus die Möglichkeit zu verschienden ‘Targets’ genannten Endpunkten zu loggen. Neben den üblichen Verdächtigen wie Loggen in die Console oder in ein LogFile unterstützt NLog auch das Loggen zu einem LogReceiverService genannten Target. Dieses Target verfügt über eine Schnittstelle, die als ServiceContract eines WCF Services dienen kann. Doch dazu unten mehr.

Als Framework für die WebApp habe ich mich für Microsofts SignalR entschieden, einfach weil ich damit bisher noch nichts gemacht habe, die Specs scheinen mir aber für meinen Use-Case zu passen.

2. Datenbank + Dienst

Da ich die Logmeldungen in einer Datenbank ablegen möchte, richte ich die erst einmal ein. Die Attribute orientieren sich an den von Nlog vorgegebenen Daten. Hierfür gibt es hierfür spezielle Layout Renderer. Das sind einfache Makros die später in der Nlog.config zu den Attributen gemappt werden.

Als Basis für den Dienst verwende ich einen verwalteten Windows Service. Folgende NuGet Pakete installiere ich:

    PM > Install-Package Nlog
    PM > Install-Package Nlog.config
    PM > Install-Package MySql.Data

Nach der Installation verfügt das Projekt über eine NLog.Config, in der ich die Datenbank als Logtarget eintragen kann.

Hier hinterlege ich neben dem Connectionstring auch den Commandtext enthalten, mit dem die Logs in die DB eingefügt werden. Hierfür ist innerhalb des <target> Elements ein <commandtext> Element vorgesehen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<target name="database" xsi:type="Database" 
        dbProvider="MySql.Data.MySqlClient.MySqlConnection, MySql.Data"
        connectionString="server=localhost;Database=nlog;user id=****;password=****">
        <commandText>
          insert into nlog.log (
          Application, Date, Level, Message,
          Logger, CallSite, Exception
          ) values (
          @Application, @Date, @Level, @Message,
          @Logger, @Callsite, @Exception
          );
        </commandText>
        <parameter name="@exception" layout="${exception:tostring}" />
        ...

Ein weiteres LogTarget in einer FallbackGroup sorgt dafür das keine Logs verloren gehen, sollte die Verbindung zur Datenbank einmal fehlschlagen. Diesen Mechanismus werde ich später auch in den Clients anwenden. Auch dort kann dann das klassische Logging in eine Datei alls Fallback für einen nicht erreichbaren LogServer dienen.

vereinfachte Konfiguration

Ohne viel in der app.config herumzuschrauben, nutze ich die vereinfachte Konfiguration für WCF, die ab .Net Framework 4.6.1 möglich ist. Hier wird einfach das <service> Element in der Konfiguration nicht mehr vorausgesetz. Danke Microsoft!

If you do not add a section or add any endpoints in a section and your service does not programmatically define any endpoints, then a set of default endpoints are automatically added to your service, one for each service base address …

Somit ist ist die Konfiguration für einen funktionierenen Dienst mit wenigen Zeilen Code erledigt. Der ServiceTyp “typeof(NLogServer)” ist die Klasse, die die Serviceschnittstelle Implementiert. Diese ist durch Nlog vorgegeben. Die beiden Uris sind die base addresses, für die das WCF Framework die Endpunkte definieren wird.

Manuell könnte man die Konfiguration zum Beispiel vornehmen indem man innerhalb der Klasse NLogServer die Methode Configure Implementiert, diese wird vom Framework automatisch aufgerufen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static void Configure(ServiceConfiguration config)
{
    ServiceEndpoint se = new ServiceEndpoint(
       new ContractDescription("ILogReceiverServer"), 
       new BasicHttpBinding(), 
       new EndpointAddress("")
    );
    se.Behaviors.Add(new MyEndpointBehavior());
    config.AddServiceEndpoint(se);
    config.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
}

Intuitiver fand ich aber den Ansatz die Endpunkte und Konfiguration auf Default zu belassen und Änderungen ggfs. in der app.config zu überschreiben. Ich gebe also 2 Addressen an und verlasse mich im wesentlichen auf das Framework.

1
2
3
4
5
6
_host = new ServiceHost(
    typeof(NLogServer),
    new Uri("http://localhost:9999"), 
    new Uri("net.tcp://localhost:9998")
);
_host.Open();

Die beiden URis könnte ich später für je einen anderen Zweck nutzen, den TCP Endpoint für Applikationen innerhalb des LANs, der http Endpoint könnte apäter externen Instanzen als Logtarget dienen.

http://localhost:9999

net.tcp://localhost:9998

Um die Metadaten innerhalb der Umgebung (angenommen das ist kein Sicherheitsrisiko) zu veröffentlichen, muss ich eine Einstellung in der App.Config Datei vornehmen um das Verhalten des WCF Dienstes meinem Wunsch entsprechend anzupassen. In der Default Konfiguration ist die Infoseite auch aktiviert und nachdem der Service gestartet wurde, kann ich im Browser schon sehen ob alles läuft.

http://localhost:9999

zeigt mir die Infoseite des Dienstes an. Erfolg!

wcf_browser.png

3. Nlog in den Applikationen konfigurieren

Die Applikationen, die NLog bereits nutzen müssen in der vorhandenen config erweitert werden. Das bestehende Log wird um den Service als Target erweitert, innerhalb einer FallbackGroup gekapselt: Die Logrule kann entsprechend angepasst werden um nur Events mit speziellem Level an den Service zu senden. In diesem Fall möchte ich ab Debug aufwärts alles an den WCF dienst senden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<targets>
    <target xsi:type="FallbackGroup" name="loggingtarget">
         <target xsi:type="LogReceiverService"
            name="NLogServer"
            endpointAddress="http://localhost:9999"
            useBinaryEncoding="false"
            clientId="demo1"
            includeEventProperties="true">
         </target>
      <target xsi:type="File" fileName="log.txt"  name="fileLogger"/>
    </target>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="loggingtarget" />
</rules>

Das ist schon alles! Um das Setup zu testen schreibe ich eine Konsolen-Applikation das nichts tut ausser zu Loggen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static void Main(string[] args)
{
 logger.Trace("trace" );
 logger.Debug("debug");
 logger.Info("info");
 logger.Warn("warning");
 logger.Error("error" );
 logger.Fatal("fatal error");
 logger.Log(LogLevel.Info, " informational ");
 logger.Log(LogLevel.Info,"fooo!");
}

Die erste Meldung wird gemäß unserer Rule nicht an den Service gesendet. Die anderen kann ich kurze Zeit später kann ich in der Datenbank des WCF Service finden:

Datenbank

Das war einfacher als gedacht. Ich denke das es durchaus Sinn macht sich nicht auf die Webservice Variante zu verlassen, sondern immer zusätzlich ein Fallback zu konfigurieren. Die in NLog eingebauten Mechanismen und Routingfunktionen helfen hierbei und sind Flexibel.

Die SignalR WebApp ist noch nicht fertig, die werde ich bei Gelegenheit Nachbloggen :)

May 15, 2017 - 5 minute read - .Net C#

Self hosted Services mit Katana

Ich bin ja ein Fan von REST Apis, Ich mag die Lesbarkeit der Aufrufe und die Tatsache das man keinen State verwaltet. Ich kann diverse Api calls schnell mit Postman testen. An der Microsoft Implementierung MVC REST Api mag ich zudem, das es eine klare Trennung der Verantwortlichkeiten zumindest unterstützt.

Das Mittel der Wahl um leichtgewichtige RESTful Apis auf einem Windows System zu hosten ist meiner Meinung nach derzeit AspNet Katana, das OWIN basierte Dienste bauen und ohne IIS Hosten kann.

Wie ist das ganze einzurichten?

Zunächst einmal habe ich mir als Basis einen das Windows Service Template hergenommen:

Projekt anlegen

Die Klasse Program wird den Service instanzieren.

Program.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main()
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
        {
            new OwinListener()
        };
        ServiceBase.Run(ServicesToRun);
    }
}

als nächstes Installieren wir das Owin.Selfhost Package über den Nuget Paketmanager.

1
PM> Install-Package Microsoft.Owin.SelfHost 

OwinListener wird von ServiceBase abgeleitet und überschreibt die Lifecycle Methoden OnStart() und OnStop() um Setup und TearDown Aufgaben auszuführen. In meinem Fall verwende ich Sie um die WebApi zu initialisieren. Diese Methoden werden dann später vom ServiceControlManager aufgerufen um den Service zu steuern.

OwinListener.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public partial class OwinListener : ServiceBase
{
    private IDisposable _service;
    private ILogger _logger;

    public OwinListener()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
    	StartApi();
    }

    private void StartApi()
    {
    	var options = new StartOptions();
    	options.Urls.Add("http://localhost:9000");
    	options.Urls.Add("http://127.0.0.1:9000");
    	options.Urls.Add($"http://{Environment.MachineName}:9000");
    	try
    	{
    	    _service = WebApp.Start<OwinStartUp>(options);
    	}
    	catch (Exception ex)
    	{
            _logger.Log(ex);
            throw;
    	}
    }

    protected override void OnStop()
    {
        _service.Dispose();
    }
    
    /// <summary>
    /// Klasse für lokales debuggen (unter Projekteigenschaften als Startobjekt festlegen)
    /// </summary>
    public class LocalDebug
    {
        static void Main()
        {
            var myService = new OwinListener();
            while (true)
            {
                myService.OnStart(null);
                System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
                break;
            }
        }
    }
}

Die WebApi Help Pages von Microsoft funktionieren derzeit noch nicht mit Katana, ich habe aber hier DalSoft Help Pages ein Projekt gefunden, das hervorragend mit Katana funktioniert, und wie im Original Razor Templates für die Api Dokumentation verwendet.

1
PM> Install-Package DalSoft.WebApi.HelpPage 

Für das Exception Handling verwende ich einfach eine winzige Middleware: Hier kommen nur die Ausnahmen an, die weiter unten im Stack auftreten.

GlobalExceptionMiddleware.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class GlobalExceptionMiddleware : OwinMiddleware
{
    public GlobalExceptionMiddleware(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        try
        {
            await Next.Invoke(context);
        }
        catch (Exception ex)
        {
            //Todo: Handle Exceptions der nachfolgenden Komponenten
        }
    }
}

Für die Authentifizierung habe ich zu demozwecken eine BasicAuth implementiert, auch Asp.Net Identity oder Active Directory Auth ist hier natürlich denkbar.

AuthenticationMiddleware.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class AuthenticationMiddleware : OwinMiddleware
{
    public AuthenticationMiddleware(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        var request = context.Request;
        var response = context.Response;

        response.OnSendingHeaders(state => {
            var resp = (OwinResponse)state;
            if (resp.StatusCode == 401)
                resp.Headers.Set("WWW-Authenticate", "Basic");
        }, response);
        
        var header = request.Headers.Get("Authorization");

        if (!string.IsNullOrWhiteSpace(header))
        {
            var authHeader = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(header);

            if ("Basic".Equals(authHeader.Scheme,StringComparison.OrdinalIgnoreCase))
            {
                string parameter = Encoding.UTF8.GetString(Convert.FromBase64String(authHeader.Parameter));
                var parts = parameter.Split(':');

                string userName = parts[0];
                string password = parts[1];

                if (userName == "bar")
                {
                    if (password == "foo")
                    {
                        var claims = new[] { new Claim(ClaimTypes.Name, "bar") };
                        var identity = new ClaimsIdentity(claims, "Basic");
                        request.User = new ClaimsPrincipal(identity);
                    }
                }
            }
        }
        
        await Next.Invoke(context);
    }
}

Füge alles zusammen

die Klasse OwinStartUp enthält nun den Code der die Middleware einbindet. Unsere Middlewarekomponenten sind nun also:

  • WebApi
  • Help Pages
  • Exception Handling
  • Authentication

Die Reihenfolge, in der die Komponenten eingebunden werden, ist wichtig. Schließlich wollen wir zum Beispiel zuerst sicherstellen das der Nutzer Authentifiziert ist, bevor er API Controller Methoden aufrufen kann. Die Default-Konfiguration gleicht der Standard WepApi Config und enthält ein Routentemplate, das greift wenn nicht mit AttributeRouting in den API Controllern gearbeitet wird. Ich bevorzuge allerdings das explizite setzen von Routen im Controller und wende daher zusätzlich

config.MapHttpAttributeRoutes();

in der Konfiguration an.

OwinStartup.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class OwinStartUp
{
    public void Configuration(IAppBuilder appBuilder)
    {
        //Web API 
        var config = new HttpConfiguration();
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "{controller}/{id}",
            defaults: new {id = RouteParameter.Optional}
        );
        
        //Exception handling
        appBuilder.Use(typeof(GlobalExceptionMiddleware));

        //help pages
        appBuilder.UseWebApiHelpPage(config);

        //auth Middleware einbinden
        appBuilder.Use(typeof(AuthenticationMiddleware));
        config.EnsureInitialized();

        //Reihenfolge hier wichtig! (auth vor Api) 
        appBuilder.UseWebApi(config);
    }        
}

Sind diese Schritte abgeschlossen, kann es mit der Implementierung der API Controller weitergehen, Wie vorher beschrieben bevorzuge ich das Attribute Routing.

Über jeder Methode gebe ich also an auf welches HTTP Verb Sie reagieren soll. Im Beispiel 2 einfache Methoden, die bei einem Http POST an die URLs

http://<localhost:9000/owin/Start

http://<localhost:9000/owin/Stop

reagieren, und ein HTTP 200 OK mit etwas Text im Body antworten.

Das Attribute

[Authorize]

über dem Controller bewirkt nun das unsere AuthenticationMiddleware greift und nur authorisierte Benutzer Zugriff auf das API erhalten. Alle anderen werden lediglich ein HTTP 403 zu sehen bekommen.

DemoController.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[Authorize]
public class DemoController : ApiController
{
    [HttpPost]
    [Route("Owin/Start")]
    public IHttpActionResult Start()
    {
        return Ok("Start");
    }

    [HttpPost]
    [Route("Owin/Stop")]
    public IHttpActionResult Stop()
    {
        return Ok("Stop");
    }
}

Zusammengefasst:

Mit nur wenigen einfachen Klassen haben wir einen schönen Windows Service mit REST interface, einfacher Authentifizierung und automatischer API Dokumentation erstellt.

Viel Spass beim Implementieren und Happy ‘REST’ ing!

Apr 15, 2017 - 1 minute read - octopress hugo blog

migriert

ich mag ja die Geschwindigkeit von statischen html Seiten sehr, daher bleibe ich auch dabei. Jetzt habe ich die alten octopress einträge nach Hugo, einem in GO geschreibenen HTML generator migriert.

May 17, 2013 - 2 minute read - bash linux

automounter

Ein Problem das ich Zuhause immer habe wenn ich meine Workstation boote:

Meine Musik und die Filme liegen auf dem Mediaserver im Wohnzimmer. Damit ich nicht immer hinrennen muss um die Kiste von Hand zu booten, habe ich dieses Script geschrieben, das den HTPC bootet, und meine Medienfreigabe automatisch mountet.

Benötigt weden die IP des Hosts und die MAC Adresse seiner Netzwerkkarte. wakeonlan bootet den Host dann und nachdem man ihm etwas Zeit gegeben hat zum Hochfahren, wird über gvfs-mount die Freigabe ins Dateisystem eingehängt.

automounter.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#HTPC Autoboot / Automount
#ShuffleCode, 16.05.2013

#!/bin/sh

#--------------
HTPC="192.168.0.103"
HTPC_MAC="00:1f:c6:85:71:aa"
COUNT=1

ON=$(ping -c 1 $HTPC | grep received | cut -d "," -f 2 | cut -d " " -f 2)
echo "teste"
if [ $ON == "1" ]
then
	notify-send "MEDIACENTER ist an, Mounte Musik"
	gvfs-mount sftp://media@$HTPC
else
	notify-send "MEDIACENTER ist aus, boote jetzt"
	wakeonlan $HTPC_MAC
	notify-send "warte auf $HTPC..."
	#WAIT FOR HTPC to BOOT
	TEST="0"
	while [ $TEST == "0" ]
	do
		TEST=$(ping -c $COUNT $HTPC | grep received | cut -d "," -f 2 | cut -d " " -f 2)
	done
	sleep 20
	gvfs-mount sftp://media@$HTPC
	notify-send "MEDIACENTER ist an, Mounte Musik"
fi

May 17, 2013 - 1 minute read - linux bash vlc streaming

VLC HomeNetwork RTP Streaming

Um von einem Rechner der einen Webstream empfängt, die Daten an andere Audioclients im Lan weiterzugeben: VLC Multistreaming!

    cvlc -vvv <Stream Adresse> --sout '#duplicate{dst=rtp{dst=<Ziel-IP>},dst=display{delay=500}'

Delay an Verbindungsgeschwindigkeit anpassen, bei mir ist alles zer-W-Lan’t, daher 500ms Delay auf der Wiedergebenden Maschine.

Werden weitere Clients eingebunden, einfach ein weiteres

    dst=rtp{dst=<Ziel-IP>}

anfügen.