Wednesday, 17 May 2017

UWP MetroLog using JsonPostTarget & HTTP Server

Introduction

This blog more deals about the UWP MetroLog which is a lightweight logging framework designed for Windows Store & UWP Apps and more you can know about it from the github.

Usage

Using this log framework we can use several targets to write the log and for that we have some documents to look in the internet and in the documentation in the official location. But for JSON Target we have very limited support when i am writing this post and using JsonPostTarget we can stream log messages back to an HTTP/HTTPS end-point of our own design.

Code

So here I have some UWP application code to showcase how we can use the Json Target logging.
Add the dependencies in project.json to have Metro log as  "MetroLog": "1.0.1"  , in future the version may be change and also you can get that from MetroLog from NuGet Package.

Note : Enable Private Networks (Client & Server) or Internet (Client & Server) capabilities in the Package.appxmanifest in the project.

In the code we have two major modules:
  • HTTP Server
  • Logger
HTTP Server will use Windows.Networking.Sockets.StreamSocketListener to receive the socket input from the JsonPostTarget log format.

Logger section will be used to initialize the MetroLog with JsonPostTarget with the url to post and log level to be save and the threshold as shown below.


using MetroLog;
using MetroLog.Targets;
using System;

public class JsonLogger
    {
        public void InitializeJsonLog(int listnerport)
        {
            var configuration = new LoggingConfiguration();
            string ul = string.Format("http://localhost:{0}/WriteLog", listnerport);
            var url = new Uri(ul);
            var jsonPostTarget = new JsonPostTarget(1, url);
            configuration.AddTarget(LogLevel.Trace, LogLevel.Fatal, jsonPostTarget);
            configuration.IsEnabled = true;
            LogManagerFactory.DefaultConfiguration = configuration;
        }
    }

HTTP Server has 2 methods to Start & Stop the socket listener and a private methods to format the content and to save the log message in the local folder.


using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using Windows.Networking.Sockets;
using Windows.Storage;

public class HTTPServer : IDisposable
    {
        private int port;
        private readonly StreamSocketListener listener;
        SemaphoreSlim sSlim;

        public HTTPServer(int listnerport)
        {
            this.listener = new StreamSocketListener();
            port = listnerport;
            this.listener.ConnectionReceived += (s, e) => ProcessRequestAsync(e.Socket);
            sSlim = new SemaphoreSlim(1);
        }

        public async void StartServerAsync()
        {
            await listener.BindServiceNameAsync(port.ToString());
        }

        public async void StopServerAsync()
        {
            await listener.CancelIOAsync();
            Dispose();
        }

        public void Dispose()
        {
            this.listener.Dispose();
        }
        
        private void ProcessRequestAsync(StreamSocket socket)
        {
            string content;
            try
            {
                using (var input = socket.InputStream)
                {
                    using (var reader = new StreamReader(input.AsStreamForRead()))
                    {
                        var requestHeader = reader.ReadLine();
                        content = ParseRequest(reader, new Dictionary<string, string>());
                    }
                }
                if (!string.IsNullOrWhiteSpace(content))
                    WriteToLog(content);                
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private string ParseRequest(StreamReader reader, Dictionary<string, string> Headers)
        {
            bool finishedParsingHeaders = false;
            while (true)
            {
                string line = "";
                if (!finishedParsingHeaders)
                    line = reader.ReadLine();
                else
                {
                    int contentLength = Headers.ContainsKey("Content-Length") ? int.Parse(Headers["Content-Length"]) : 0;
                    if (contentLength > 0)
                    {
                        char[] byteContent = new char[contentLength];
                        reader.ReadBlock(byteContent, 0, contentLength);

                        line = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(byteContent));
                        return line;
                    }
                    break;
                }

                if (String.IsNullOrWhiteSpace(line))               
                    finishedParsingHeaders = true;              
                else
                {
                    if (!finishedParsingHeaders)
                    {
                        var splitHeader = line.Split(new char[] { ':' }, 2);
                        Headers.Add(splitHeader[0].Trim(), splitHeader[1].Trim());
                    }
                }
            }
            return string.Empty;
        }

        private async void WriteToLog(string Content)
        {
            await sSlim.WaitAsync();
            try
            {
                var filename = String.Format("Log - {0:yyyyMMdd}.log", DateTime.Now);
                var logfilename = ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.OpenIfExists).GetAwaiter().GetResult();
                await FileIO.AppendTextAsync(logfilename, Content);
            }           
            finally
            {
                sSlim.Release();
            }
        }
    }

From the application first you have to start the HTTP Server and then log some message and check the log file content in the local storage folder for the file with log content existence.
The log location will be in C:\Users\<user>\AppData\Local\Packages\<AppName>\LocalState\

Main Page code in Application :

The below is the sample code to initialize the log and HTTP Server.


public sealed partial class MainPage : Page
    {
        private HttpServer.HTTPServer httpServer;
        private int listerport = 8085;
        public MainPage()
        {
            this.InitializeComponent();
            this.Loaded += MainPage_Loaded;
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            httpServer = new HttpServer.HTTPServer(listerport);
            new Logger.JsonLogger().InitializeJsonLog(listerport);
        }

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            httpServer.StartServerAsync();
        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {
            httpServer.StopServerAsync();
        }

        private void btnLog_Click(object sender, RoutedEventArgs e)
        {
            var logger = LogManagerFactory.DefaultLogManager.GetLogger(typeof(MainPage));
            logger.Debug("test debug");
            logger.Info("test info");
            logger.Warn("test warn");
            logger.Error("error message", new Exception("test exception"));
            logger.Fatal("fatal message", new Exception("teat fatal exception"));
        }
    }

Below is app UI screenshot for the above code :


Procedure:
  1. Run the application.
  2. Click Start button to start the HTTP Server.
  3. Click the Click Me To Log Sample button to write some sample log information.
  4. Click Stop button to stop the HTTP Server.
  5. Now check the log file in the above mentioned location.


Also you can download the source code from here.

Thanks for Reading!! Happy Coding!!