Steve Wellens

Programming in the .Net environment

Sponsors

Links

January 2012 - Posts

Log4net: Log to a JavaScript Console

While lurking and skulking in the shadows of various technical .Net sites, I've noticed many developers discussing log4net in their blogs and posts; log4net is an extremely popular tool for logging .Net Applications. So, I decided to try it out. After the initial complexities of getting it up and running, I was suitably impressed. Could it be…logging is fun? Well, I don't know if I'd go that far…at least I'd never admit it.

One of the great features of log4net is how easy it is to route the logs to multiple outputs. Here is an incomplete list of outputs…or 'Appenders' in the log4net vernacular:

AdoNetAppender Logs to databases
ConsoleAppender Logs to a console window
EventLogAppender Logs to Windows event log
FileAppender Logs to a file
RollingFileAppender Logs to multiple files
SmtpAppender Logs to an email address
TraceAppender Logs to the .Net trace system
AspNetTraceAppender Logs to the Asp.Net page trace
MemoryAppender Logs to a memory buffer
Etc., etc., etc.

Wow.

I've been doing a lot of work with jQuery/JavaScript and it dawned on me that seeing server side logging strings in a JavaScript Console could be useful.

So I wrote a log4net JavaScript Console Appender. Strings logged at the server will show up in the browser's console window. Note: For IE, you need to have the "Developer Tools" window active.

I'm not going to describe how to setup log4net in an Asp.Net web site; there are many step-by-step tutorials around. But I'll give you some hints:

  • Each step must be followed or it will not work (duh).
  • Putting the log4net settings in a separate configuration file is more complicated than having the settings in web.config: Start with the settings in web.config.
  • The name in this line of code: LogManager.GetLogger("MyLogger");
    ...refers to this section in the configuration file: <logger name="MyLogger">

I built the Appender and a test Asp.Net site in .Net Framework 4.0.  Here's the jsConsoleAppender.cs file:

using System;
using System.Collections.Generic;
using System.Text;
using log4net;
using log4net.Core;
using log4net.Appender;
using log4net.Layout;
using System.Web;
using System.Web.UI;
 
namespace log4net.Appender
{
    // log4net JSConsoleAppender
    // Writes log strings to client's javascript console if available
 
    public class JSConsoleAppender : AppenderSkeleton
    {
        // each JavaScript emitted requires a unique id, this counter provides it
        private int m_IDCounter = 0;
 
        // what to do if no HttpContext is found
        private bool m_ExceptionOnNoHttpContext = true;
        public bool ExceptionOnNoHttpContext
        {
            get { return m_ExceptionOnNoHttpContext; }
            set { m_ExceptionOnNoHttpContext = value; }
        }
 
        // The meat of the Appender
        override protected void Append(LoggingEvent loggingEvent)
        {
            // optional test for HttpContext, set in config file.
            // default is true
            if (ExceptionOnNoHttpContext == true)
            {
                if (HttpContext.Current == null)
                {
                    ErrorHandler.Error("JSConsoleAppender: No HttpContext to write javascript to.");
                    return;
                }
            }
 
            // newlines mess up JavaScript...check for them in the pattern
            PatternLayout Layout = this.Layout as PatternLayout;
 
            if (Layout.ConversionPattern.Contains("%newline"))
            {
                ErrorHandler.Error("JSConsoleAppender: Pattern may not contain %newline.");
                return;
            }
 
            // format the Log string
            String LogStr = this.RenderLoggingEvent(loggingEvent);
 
            // single quotes in the log message will mess up our JavaScript
            LogStr = LogStr.Replace("'", "\\'");
 
            // Check if console exists before writing to it
            String OutputScript = String.Format("if (window.console) console.log('{0}');", LogStr);
 
            // This sends the script to the bottom of the page
            Page page = HttpContext.Current.CurrentHandler as Page;
            page.ClientScript.RegisterStartupScript(page.GetType(), m_IDCounter++.ToString(), OutputScript, true);
        }
 
        // There is no default layout
        override protected bool RequiresLayout
        {
            get { return true; }
        }
    }
}

From the Asp.Net test application, here's the web.config file. In the pattern for the JSConsoleAppender, I added the word SERVER: to differentiate the lines from client logging. Note there are two other Appenders in the log…just for fun!

<?xml version="1.0"?>
<configuration>
 
  <!--BEGIN log4net configuration-->
  <configSections >
    <section name="log4net"
             type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  </configSections>
  <log4net>
 
    <appender name="LogFileAppender"
              type="log4net.Appender.FileAppender">
      <param  name="File" value="C:\Log4Net.log"/>
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern"  value="%d %-5p %c %m%n"/>
      </layout>
    </appender>
 
    <appender name="TraceAppender"
              type="log4net.Appender.TraceAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date  %-5level [%property{NDC}] - %message%newline" />
      </layout>
    </appender>
 
    <appender name="JSConsoleAppender"
              type="log4net.Appender.JSConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <!--Note JSConsoleAppender cannot have %newline-->
        <conversionPattern value="SERVER: %date %-5level %logger:  %message  SRC: %location" />
      </layout>
    </appender>
 
    <logger name="MyLogger">
      <level value="ALL" />
      <appender-ref ref="LogFileAppender"  />
      <appender-ref ref="TraceAppender"  />
      <appender-ref ref="JSConsoleAppender"  />
    </logger>
  </log4net>
  <!--END log4net configuration-->
 
  <system.web>
    <compilation debug="true"
                 targetFramework="4.0"/>
  </system.web>
</configuration>

Here's the default.aspx file from the test program, I added a bit of JavaScript and jQuery:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Log4Net Test</title>
    <script src="Scripts/jquery-1.7.js" type="text/javascript"></script>
    <script type="text/javascript">
 
        $(document).ready(DocReady);
 
        function DocReady()
        {
            if (window.console) 
                console.log("CLIENT: Doc Ready!");
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <h5>Log4Net Test</h5>
        <asp:Button ID="ButtonLog" runat="server" Text="Do Some Logging!" 
            onclick="ButtonLog_Click" />   
    </div>
    </form>
</body>
</html>

Here's the default.cs file from the test program with some logging strings:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using log4net;
 
public partial class _Default : System.Web.UI.Page
{
    private static readonly ILog log = LogManager.GetLogger("MyLogger");
 
    protected void Page_Load(object sender, EventArgs e)
    {
        log.Info("Page_Load!");
    }
 
    protected void ButtonLog_Click(object sender, EventArgs e)
    {
        log.Info("Soft kitty, warm kitty");
        log.Warn("Little ball of fur");
        log.Error("Happy kitty, sleepy kitty");
        log.Fatal("Purr, purr, purr.");
    }
}

And finally, here are the three output logs:

From the JSConsoleAppender (IE Developer Tools), it includes a client log call with the server log calls:

From the TraceAppender, the Visual Studio Output window:

 

From the LogFileAppender (using Notepad):

You can download the solution/projects/code  here.

I hope someone finds this useful.

Steve Wellens

More Posts