Skip to content

Log4Net

Kota edited this page Sep 21, 2018 · 24 revisions

Nuget: install-package log4net
Link: https://logging.apache.org/log4net/
For Java: log4j
Common Logging Framework: https://github.com/net-commons/common-logging

Basic configuration

// Configure
log4net.Config.BasicConfigurator.Configure();

// Get a Logger
var log = log4net.LogManager.GetLogger(typeof(Program));

// Log : Default is Console Log
log.Info("Hello from log4net!");

Introduction

  • Logging Event: can be stored anywhere (more than one storage)
    • Storage: Application Event Log (Appender) , SQLServer Database (Appender), Local Text File (Appender)
    • Log Levels: DEBUG, INFO, WARN, ERROR, FATAL

Configuration

Xml Configuration

- By default it looks for app.config / web.config for XML Path
<configuration>
  <configSections>
    <section name="log4net" type="log4net.config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections> 

  <log4net>
    <appender name="Console" type="log4net.Appender.ConsoleAppender" >
      <layout type="log4net.Layout.SimpleLayout" />
    </appender>
    <appender name="File" type="log4net.Appender.FileAppender" >
      <file value="GuiApplicationLog.txt" />
      <layout type="log4net.Layout.SimpleLayout" />
    </appender>

    <root>
      <level value="All" />
      <appender-ref ref="Console" />
    </root>
  </log4net>
</configuration>

Code Based - requires compilation

var layout = new SimpleLayout();
layout.ActivateOptions();

var appender = new ConsoleAppender();
appender.Layout = layout;
appender.ActivateOptions();

var hierarchy = (Hierarchy)LogManager.GetRepository();
Logger root = hierarchy.Root;
root.Level = log4net.Core.Level.All;

BasicConfiguration.Configure(appender);

// Usage
ILog log = LogManager.GetLogger(typeof(Program));
log.Info("Info!");

Common Config Issues

  • (No error and No log so ensure below points)
  • Forgetting to Initialize log4net.Config.<basicConfigurator>.Configure();
  • Ensure Logging Level is not OFF <level value="All" />
  • Look for Configuration errors in debugger output window of Visual Studio
  • Make sure Layout is set for Appenders <layout type="log4net.Layout.SimpleLayout" />
  • Make sure Appenders is set for Loggers <appender-ref ref="Console" />

Log Level, Appenders - Hierarchy

  • Log Level Order: Debug > Info > Warn > Error > Fatal
    • At root Levl
     <root>
        <level value="All" />
        <appender-ref ref="Console" />
      </root>  
    
    • Logger Object Properties
    <logger name="Sample.Program" >
      <level value="INFO" />
        <appender-ref ref="File" />        // prints twice on Console. AS the root one also gets copied
        <appender-ref ref="Console" />
    </logger>
    

Appenders

  • Decides where to store the log message. File, Console, DB, Events etc...
  • Two appenders of same type can also be used by multiple refs (also can be done using Forwarding Appender)
  • Common Properties: Level, Layout, Filter
  • Console Appender
  • Colored Console Appender
<appender name="Console" type="log4net.Appender.ColoredConsoleAppender" >
  <target value="Console.Out" />
  <mapping>
    <level value="DEBUG" />
    <foreColor value="Green" />
  </mapping>
  <mapping>
      <level value="ERROR" />
      <foreColor value="Red, HighIntensity" />
    </mapping>
    <mapping>
      <level value="FATAL" />
      <foreColor value="Red" />
      <backColor value="White" />
    </mapping>
    <layout type="log4net.Layout.SimpleLayout" />
</appender>
  • Debug Appender (works on release binaries too :( )
    • Sends output to [System.Diagnostics.Debug.Write]
  • Trace Appender
    • Sends output to [System.Diagnostics.Trace.Write]
  • EventLog Appender (requires Administrator Access)
    • To show in Event Logs (eventvwr)
    • Properties: Log Name (default-Application), Application Name (Source), Category, EventId, Mapping
  • File Appender (Not to use - as the logs will increase)
    • Properties: Path, Encoding, AppendToFile (false-rewrites after app restart), LocakingModel (Exclusive(default), Minimal, InterProcess)
  • Rolling File Appender
    • Same as File Appender but creates new Log file once it reaches certain size / after certain time. Also no of files can be configured (deletes the old log files if count increases)
    • +Properties: RollingStyle (size, date), MaxFileSize, MaxFileSizeRollBackups, CountDirection, PreserveLogFileExtension
  • ADO.NET Appender (Buffering)
    • ConnectionString, ConnectionStringName, AppSettingsKey (connectionstringtouse), ConnectionType, ReconnectOnError,
    • CommandType, CommandText, UseTransactions, Parameters (Name, DBType, Size)
  • ASP.NET Trace Appender
    • P: Category. DEBUG, INFO => System.Web.Trace.Write | WARN, ERROR, FATAL => System.Web.Trace.Warn
  • Remoting Appender (Buffering)
    • P: Sink (Sink URL, IRemoteLoggingSink)
  • Telnet Appender
    • Port (TCP/IP - default 23)
  • UDP Appender
    • Encoding, LocalPort, RemoteAddress, RemotePort
  • SMTP and SMTPPickupDir Appender (Buffering) (Emails)
    • SmtpHost, Port, Authentication, Username, Password, EnableSSL, Other(From, To, CC/BCC, Priority, ReplyTo, Subject)
  • SMTPPickupDir Appender: Stores Email formatted Logs for later (Buffering)
    • PickupDir, From, To, Subject
  • Forwarding Appender (Forwards message to other Appender)
    • Appender-ref
    • Can be set with a different Threshold than To Appender
  • Buffering Appender (Buffering)
    • Appender-ref
    • Collects message till buffer is reached and forwards to other Appender

Layouts

  • Appenders : Helps in Storage
  • Layouts: Helps in formatting the content. Contains Header, Footer
  • Simple Layout
    • Console doesnt shows header & footer, File appender shows
<appender> 
  <layout type="log4net.Layout.SimpleLayout">
    <header value="==== BEGIN LOG =====&#13;&#10;" >
    <footer value="==== END LOG =====&#13;&#10;" >
  </layout>
</appender>
  • XML Layout (Prefix, Encode etc..)
  <layout type="log4net.Layout.XmlLayout">
  • Pattern Layout
    // %<> is called as format specifier
    LOG:[%logger]%-5level-%message-%newline 
    LOG:[MyApp.Program]DEBUG-Program Starting
    
    Example Setup
    <layout type="log4net.Layout.PatternLayout" >
      <conversionPattern value="%timestamp [%thread] %level %logger - %message%newline%exception" />
    </layout>
    
    • Format Modifier => %number %5level means the no of characters are limited to 5 character %-5level means the minimum no of characters is 5 character %.5level means the no of characters is limited to 5 character and excess is truncated from beginning
  • Raw Layout
    • RawTimeStampLayout , RawUtcTimeStampLayout

Log Event Context

  • Custom Logging properties. Ex: CPU Utilization, No of threads etc..
log4net.GlobalContext.Properties["WindowTitle"] = Process.GetCurrentProcess().MainWindowTitle;
logger.Info("Info log");
  • Accesing this property in Layout. Ex: Pattern Layout %property{WindowTitle}
  • Property Contexts
  • Context Property Stacks
ThreadContext.Properties["name"] = "A"
ThreadContext.Properties["name"] = "B"   // overrides

=> %property{name} gets "B"

ThreadContext.Stacks["name"].Push("A")
ThreadContext.Stacks["name"].Push("B")

=> %property{name} gets both "A" and "B"
  • Properties can be of any type: string, new Object();. Conversion calls ToString() on that object for logging

Filters

  • Filters log message before it reaches the appender
  • Filters can be chained (in order of setup)
  • Types
    • Level Match Filter. LevelToMatch, AcceptOnMatch
<appender>
  <filter type="log4net.Filter.LevelMatchFilter" >
    <levelToMatch value="ERROR" />
    <acceptOnMatch value="true" />
  </filter>
  <filter type="log4net.Filter.DenyAllFilter"  />
</appender>
  • Level Range Filter. LevelMin, LevelMax, AcceptOnMatch
  • Logger Match Filter. LoggerToMatch, AcceptOnMatch String.StartsWith()
  • String Match Filter. RegexToMatch, StringToMatch, AcceptOnMatch
    • RegexToMatch takes 1st priority if both regex & string is used
  • Property Match Filter. Key, RegexToMatch, StringToMatch, AcceptOnMatch
    • Can be Property on Global/Thread/LogicalThread Contexts
  • Deny All Filter <filter type="log4net.Filter.DenyAllFilter" />
  • Filters can forward to another Filter OR Reject the logs. ( Accept / Deny / Neutral )
    • LevelRangeFilter denies message if it doesn't match based on config (acceptOnMatch)

Effective Logging (Logging Mantras)

  • Logging Code is Still Code
  • Logging uses Resources
    • Lossy Logging | Lossy Appender are Buffering Appender
      • Keeps the logs in the buffer. New logs deletes the old logs and stores in the buffer. Once there is a FATAL (can be configured - LogLevel/Exception/Time) occurs, the logs are processed and stored
      • Properties: Lossy, BufferSize, LevelEvaluator, ExceptionEvaluator, TimeEvaluator
        <evaluator type="log4net.Core.LevelEvaluator" >
          <threshold value="ERROR" />
        </evaluator>
      
  • Log Now
  • Tips
    • Surround values with [] so that if it's null it will show the square braces
    • Logging is not Exception Handling
    • Logging Unhandled Exceptions
    AppDomain.CurrentDomain.UnhandledException += LogUnhandledException;
    
    void LogUnhandledException(object sender, UnhandledExceptionEventArgs e) {
      Log.Error(
        String.Format("A {0}fatal Unhandled exception occured", e.IsTerminating ? string.Empty : "non-" ),
        e.ExceptionObject as Exception
      );
    }
    

Better Code

  • Generics
class LogManager 
{
  static readonly ILogManager _logManager;
  static LogManager()
  {
    log4net.Config.XmlConfigurator.Configure(new FileInfo("log4net.config"));
    _logManager = new LogManager();
  }

  public static ILogger GetLogger<T>()
  {
    return _logManager.GetLogger(typeof(T));
  }
}
// usage
var logger = LogManager.GetLogger<Program>();
  • Extension methods :: Log what is required
// Existing :: In client code
if(Log.IsDebugEnabled)
{
  Log.DebugFormat("Value is {0}", value);
}

// NEW
static class LoggerExtensions
{
  public static void Debug(this ILogger log, Func<string> getMessage)
  {
    if(! log.IsDebugEnabled)
    {
      return;
    }

    var logMessage = getMessage();
    log.Debug(logMessage);
  }
}
// usage
private static Ilogger Log = LogManager.GetLogger<Program>();
Log.Debug(() => String.Format("Value is {0}", value));
  • Extension methods :: Logger On-demand
static class GenericLoggingExtensions
{
  public static ILogger Log<T>(this T thing)
  {
    var log = LogManager.GetLogger<T>();
    return log;
  }
}
// usage
this.Log().Debug("Debugging!");
  • Disposable Activity Pattern
static class LogActivityExtensions
{
  static IDisposable Activity(this ILogger log, string format, params object[] args) 
  {
    return new LoggingActivity(log, String.Format(format, args));
  }
}

class LoggingActivity : IDisposable
{
  private readonly ILogger _log, string _activityName, IDisposable _scope;

  public LoggingActivity(ILogger log, string activityName)
  {
    _log = log;
    _activityName = activityName;

    _scope = _log.PushActivity(activityName);
    log.DebugFormat(">> Entering activity [{0}]", activityName);
  }
  public void Dispose()
  {
    _log.DebugFormat(">> Leavingactivity [{0}]", _activityName);
    _scope.Dispose();
  }
}
//LoggerAdapter .cs
public bool IsDebugEnabled { get { return _log.IsDebugEnabled; } }
public IDisposable PushActivity(string activityName)
{
  return ThreadContext.Stacks["activity"].Push(activityName);
}

// usage
this.Log().Debug("Preparing to Run.");
using (this.Log().Activity("Run))
{
  this.Log().Debug("We are running now.");
}

// Patter Layout
<conversionPattern value="%level [Current Activity: %property{activity}] - %message%newline" />

// output
DEBUG [Current Activity: <null>] - Preparing to Run.
DEBUG [Current Activity: Run] - >> Entering activity [Run]
DEBUG [Current Activity: Run] - We are running now.
DEBUG [Current Activity: Run] - << Leaving activity [Run]
  • Object Patterns (Decorator)
    • Decorating an actual object to Log info and then proceed with its normal behavior
  • PostSharp
    • using log4net, PostSharp.Aspects, PostSharp.Extensibility
    • [MulticastAttributeUsage(MulticastTragets.Method ......)], [AttributeUsage(AttributeTargets.Assembly)]
    • LogMethodAspect : OnMethodBoundaryAspect
    • Implement: OnEntry() { .... logging ... }, OnExit(), OnException()
    • Use it [assembly: LogMethodAspect] namespace { ... // logs every method call (entry,exit,exception) ... }

Extending Log4Net

  • Custom Layout : inherit LayoutSkeleton
class SerializableLogEvent
{
  ......
}

class JsonLayout : LayoutSkeleton
{
  private JavaScriptSerializer _serializer;
  
  public override void ActivateOptions()
  {
    _serializer = new JavaScriptSerializer();
  }

  public override void Format(TextWriter writer, LoggingEvent loggingEvent)
  {
    var logEvent = new SerializableLogEvent(loggingEvent);
    var json = _serializer.Serialize(logEvent);
    writer.WriteLine(json);
  }
}
  • Custom Filter : inherit FilterSkeleton (Accept / Deny / Neutral)
class QueryStringfilter : FilterSkeleton
{
  public override FilerDecision Decide(LoggingEvent loggingEvent)
  {
    if (null == HttpContext.Current)
    {
      return FilterDecision.Neutral;
    }
    
    if (!HttpContext.Current.Request.QueryString.Get("log"))
    {
      return FilterDecision.Neutral;
    }
    
    var logQueryStringValue = HttpContext.Current.Request.QueryString.Get("log");
    var values = new[] {"1", "yes", "true"};
    if (values.Contains(logQueryStringValue.ToLowerInvariant()))
    {
      return FilterDecision.Accept;
    }

    return FilterDecision.Deny;
  }
}
  • Custom Appender : AppenderSkeleton, BufferingAppenderSkeleton (this.BufferSize, Append => SendBuffer())
using Twilio;

public class TwilioAppender : AppenderSkeleton
{
    TwilioRestclient _twilio;

    public string AccountSid { get; set; }
    public string AuthToken { get; set; }
    public string From { get; set; }
    public string To { get; set; }

    public override void ActivatOptions()
    {
        _twilio = new TwilioRestclient(AccountSid, AuthToken);
        base.ActivatOptions();
    }

    protected override void Append(LoggingEvent loggingEvent)
    {
        var message = this.RenderLoggingEvent(loggingEvent);
        _twilio.SendSmsMessage(From, To, message);
    }
}
// In config
<appender name="Twilio" type="namespace.TwilioAppender, namespace" >
  <accountSid value="AccountSid" />
  <authToken value="authToken" />
  <from value="143" />
  <to value="864" />
  <filter />
  <layout />
</appender>
  • Custom Object Renderer (like Layouts but focuses on specific type of objects) : IObjectRenderer
public class RequestRenderer : IObjectRenderer
{
    public void RenderObject(RendererMap rendererMap, object obj, TextWriter writer)
    {
        HttpRequestBase requestBase = obj as HttpRequestBase;
        if (null == requestBase)
        {
            return;
        }

        var renderedRequest = FormatRequest(requestBase);
        writer.WriteLine(renderedRequest);
    }

    private static string FormatRequest()
    {
        var renderedRequest = String.Format(
            "([{0}] [{1}] from browser [{2}])",
            request.HttpMethod,
            request.RawUrl,
            request.Browser.Browser
        );
        return  renderedRequest;
    }
}
// config
<log4net>
  <appender>
    <layout />
  </appender>

  <renderer 
    renderingClass="nampespace.RequestRenderer, namespace" 
    renderedClass="System.Web.HttprequestBase"  />
</log4net>
  • Custom Plugins : PluginSkeleton
public class AutoregisterRenderersPlugin : PluginSkeleton
{
    public AutoregisterRenderersPlugin() : base("AutoregisterRenderers")
    {
    }

    public override void Attach(log4net.Repository.ILoggerRepository repository)
    {
        var renderMap = log4net.LogManager.GetRepository().RendererMap;

        var renderers = from assembly in AppDomain.CurrentDomain.GetAssemblies()
                        from type in assembly.GetTypes()
                        from attr in type.GetCustomAttributes(typeof(RendersAttribute), true)
                        let renderAttr = attr as RendersAttribute
                        let renderer = Activator.CreateInstance(type) as IObjectrenderer
                        select new {RenderType = renderAttr.rendersType, Renderer= renderer };

        renderers.ToList().ForEach( r = renderMap.Put(r,RenderType, r.Renderer) );

        base.Attach(repository);
    }

    public override void Shutdown()
    {
        bsae.Shutdown();
    }
}
class RendersAttribute : Attribute
{
  ...
}

[Renders(typeof(HttpRequestBase))]
class RequestRenderer : IObjectRenderer
{
  ...
}
// usage :: Register as below in startup
[assembly: log4net.Config.Plugin(typeof(namespace.AutoregisterRenderersPlugin))]
namespace 
{
  class Application
  {
      protected void Application_Start(){}
  }
}
// config - no need to add renderers

Interesting Reads:

Fix attribute of Log4Net

Clone this wiki locally