• entries
    359
  • comments
    237
  • views
    188610

It's big, It's heavy, It's wood!

Sign in to follow this  

84 views

The C# logging code is fixed up kinda nice now. I've never had to release code for public consumption and don't work professionally, so I'd be interested in your folks' feedback on readability, format, and stuff I might be missing in my ignorance. Actual functional feedback is always cool too.


So, the previous incarnation of the logger was based around the idea that logging is a strategy. Something you provide to modules who can then spew info while ignoring what the strategy does under the hood. Decorators would then add common logging info, or otherwise modify the message from the module's message into the actual logging string.

The new incarnation foregoes a bit of the decoration idea. Previously, things would inherit from a common interface to handle new and differing decorations. This lead to lots of little classes, which had a bit in common. The new version uses a more C++ style functor practice for specifying the decoration to common formatting code.

Thus the current logging library consists of three main concepts:

Logger - A sink, which dumps the log message to an actual log target. A file is the common target, but stdout, an in-game console, or nowhere are other likely Loggers.

Decorator - A modifier, which formats extra text onto the inital log message, usually by simple appending or pre-fixing.

Generator - A functor to generate the content for a Decorator to format. Current memory usage, the date, or the calling thread are the sort of things generated to make more useful log info.


The common usage is for a module to reference a Logger interface, either by property or by constructor. This allows for module specific logs, a global log (albeit referenced multiple times), or specific decoration for each module.

This is example usage of the classes, and a good example of per-module decoration.

using System;
using System.Collections.Generic;
using System.Text;
using rms.Util.Logging;
using System.Threading;

public class LogTestThread {
protected Logger Log;
public readonly string Name;
public void Main() {
Log = new PrefixDecorator(Log, new ThreadLabelGenerator(Name));

for (int x = 0; x < 5; ++x) {
Log.Log(Name + " here.\n");
}
}
public static implicit operator ThreadStart(LogTestThread rhs) { return (rhs.Main); }

public LogTestThread(string name, Logger log) {
this.Log = log;
this.Name = name;
}
}

class Program {
static void Main(string[] args) {
Thread a, b, c;
ThreadSafeLogToFile GlobalLog = ThreadSafeLogToFile.CreateOverwriting("test.log");
Logger LocalLog = new PrefixDecorator(GlobalLog, new ThreadLabelGenerator("[Main]"));
LocalLog = new PrefixDecorator(LocalLog, new TimeGenerator());

a = new Thread(new LogTestThread("[a]", GlobalLog));
b = new Thread(new LogTestThread("=b=", GlobalLog));
c = new Thread(new LogTestThread("CCC", GlobalLog));

LocalLog.Log("Pre-Thread Message\n");
c.Start();
b.Start();
a.Start();
LocalLog.Log("Post-Thread Message\n");


}
}




The resultant log looks like (depending on how the threads actually get run):

[Main] - 7/12/2006 1:50:38 PM - Pre-Thread Message
CCC - CCC here.
CCC - CCC here.
CCC - CCC here.
CCC - CCC here.
CCC - CCC here.
[a] - [a] here.
[a] - [a] here.
[a] - [a] here.
[a] - [a] here.
[a] - [a] here.
[Main] - 7/12/2006 1:50:38 PM - Post-Thread Message
=b= - =b= here.
=b= - =b= here.
=b= - =b= here.
=b= - =b= here.
=b= - =b= here.




I still need to add some of the more common info gathering into generators, but this is more or less the library at current state. Constructive criticism, questions welcome.

using System;
using System.Text;
using System.IO;

namespace rms {
namespace Util {
namespace Logging {

//
// rms.Util.Logging
//
// A logging structure based on decorated strategies.
//
//
// Modules are expected to implement their own logging strategy which may be
// (possibly decorated) references to a common 'global' logger.
//

//
///
/// Basic logging interface.
///

//
public abstract class Logger {
public delegate Logger LogFunction(StringBuilder Msg);
public delegate Logger CopiedStringLogFunction(string Msg);

//
///
/// Common delimiter between decorations.
///

//
public static string Delimiter = " - ";

//
///
/// Submit message for logging.
///

/// The message to be logged.
/// Returns this, suitable for chaining.
//
public abstract Logger Log(StringBuilder Msg);
public Logger Log(string Msg) { return (Log(new StringBuilder(Msg))); }

public static implicit operator LogFunction(Logger rhs) { return (rhs.Log); }
public static implicit operator CopiedStringLogFunction(Logger rhs) { return (rhs.Log); }
}


//
///
/// Logging interface holding common decoration code.
///

//
public abstract class DecoratingLogger : Logger {
protected Logger WrappedLog;
public abstract override Logger Log(StringBuilder Msg);
public DecoratingLogger(Logger LogToWrap)
: base() {
if (LogToWrap == null) {
WrappedLog = new DevNull();
} else {
WrappedLog = LogToWrap;
}
}
}

//
///
/// A Log that leads to nowhere.
///

//
public class DevNull : Logger {
public override Logger Log(StringBuilder Msg) {
return (this);
}
}


//
///
/// A Log to broadcast a message to multiple concrete logs.
///

//
public class BroadcastLog : Logger {

public event Logger.CopiedStringLogFunction Loggers;
public override Logger Log(StringBuilder Msg) {
if (Loggers != null) { Loggers(Msg.ToString()); }
return (this);
}
}

//
///
/// Abstraction to send messages to an arbitrary stream.
///

//
public sealed class LogToStream : Logger, IDisposable {

private TextWriter LogStream;
public override Logger Log(StringBuilder Msg) {
if (LogStream != null) { LogStream.Write(Msg); LogStream.Flush(); }
return (this);
}
public LogToStream(TextWriter logStream)
: base() {
this.LogStream = logStream;
//LogStream.AutoFlush = true;
}

private void Dispose(bool Disposing) {
if (Disposing && LogStream != null) {
LogStream.Close();
}
}

public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}


//
///
/// Standard Logger for writing to file.
///

//
public class LogToFile : Logger, IDisposable {

protected LogToStream LogStream;
public override Logger Log(StringBuilder Msg) {
LogStream.Log(Msg);
return (this);
}
protected LogToFile(TextWriter logStream)
: base() {
LogStream = new LogToStream(logStream);
}

///
/// Create a logger by overwriting specified file.
///

/// File to log to.
/// Created Logger
public static LogToFile CreateOverwriting(string Filename) {
return (new LogToFile(new StreamWriter(Filename)));

}

///
/// Create a logger by appending to specified file.
///

/// File to log to.
/// Created Logger
public static LogToFile CreateAppending(string Filename) {
return (new LogToFile(new StreamWriter(Filename, true)));
}

public virtual void Dispose() {
LogStream.Dispose();
}

}

//
///
/// ThreadSafe File Logger.
///

//
public class ThreadSafeLogToFile : LogToFile, IDisposable {
//
// Note: Changed to use TextWriter rather than StreamWriter,
// meaning Synchronized is available to us.
//
/*public new Logger Log(StringBuilder Msg) {
lock (LogStream) {
base.Log(Msg);
}
return (this);
}*/

protected ThreadSafeLogToFile(TextWriter logStream)
: base(TextWriter.Synchronized(logStream)) {

}

///
/// Create a logger by overwriting specified file.
///

/// File to log to.
/// Created Logger
public static new ThreadSafeLogToFile CreateOverwriting(string Filename) {
return (new ThreadSafeLogToFile(new StreamWriter(Filename)));
}

///
/// Create a logger by appending to specified file.
///

/// File to log to.
/// Created Logger
public static new ThreadSafeLogToFile CreateAppending(string Filename) {
return (new ThreadSafeLogToFile(new StreamWriter(Filename, true)));
}

public override void Dispose() {
base.Dispose();
}

}

//
///
/// Logger to Console.Out.
///

//
public sealed class LogToConsole : Logger {

private LogToStream LogStream;
public override Logger Log(StringBuilder Msg) {
LogStream.Log(Msg);
return (this);
}
public LogToConsole()
: base() {
LogStream = new LogToStream(Console.Out);
}
}

//
///
/// Common code for functor log decoration
///

//
public abstract class FunctorDecorator : DecoratingLogger {
public delegate string MsgGenerator();
private static string EmptyGenerator() { return (""); }
protected MsgGenerator DecoratingFunctor;

protected FunctorDecorator(Logger wrappedLog, MsgGenerator decoratingFunctor)
: base(wrappedLog) {
if (decoratingFunctor == null) {
this.DecoratingFunctor = EmptyGenerator;
} else {
this.DecoratingFunctor = decoratingFunctor;
}
}
}

//
///
/// Decorator which prefixes generated data to the log message.
///

//
public class PrefixDecorator : FunctorDecorator {

public override Logger Log(StringBuilder Msg) {
WrappedLog.Log(Msg.Insert(0, Logger.Delimiter).Insert(0, DecoratingFunctor()));
return (this);
}

public PrefixDecorator(Logger wrappedLog, MsgGenerator prefixFunctor) : base(wrappedLog, prefixFunctor) { }
}

//
///
/// Decorator which suffixes generated data to the log message.
///

//
public class SuffixDecorator : FunctorDecorator {
public override Logger Log(StringBuilder Msg) {
WrappedLog.Log(Msg.Append(Logger.Delimiter).Append(DecoratingFunctor()));
return (this);
}

public SuffixDecorator(Logger wrappedLog, MsgGenerator suffixFunctor) : base(wrappedLog, suffixFunctor) { }

}

//
///
/// Common code for Generators to be used with the FunctorDecorators.
///

//
public abstract class DecorationGenerator {
public abstract string Gen();
public static implicit operator FunctorDecorator.MsgGenerator(DecorationGenerator rhs) { return (rhs.Gen); }
}

//
///
/// Generates current memory usage.
///

//
public class MemoryGenerator : DecorationGenerator {
public static string Prefix = "mem: ";
protected bool Convert;
public override string Gen() {
if (!Convert) {
return (Prefix + GC.GetTotalMemory(false).ToString());
} else {
// TODO: move this to common code.
long rtn = GC.GetTotalMemory(false);
string suffix = "B";
if (rtn > 1024) {
rtn = rtn / 1024;
suffix = "K";
if (rtn > 1024) {
rtn = rtn / 1024;
suffix = "M";
if (rtn > 1024) {
rtn = rtn / 1024;
suffix = "G";
}
}
}
return (Prefix + rtn + suffix);
}
}
//
///
///
///

/// Flag for using short format.
public MemoryGenerator(bool convert) {
Convert = convert;
}
public MemoryGenerator() : this(false) { }

}

//
///
/// Prefix a label to the log message.
///

//
public class LabelPrefix : DecoratingLogger {
public readonly string Label;
public override Logger Log(StringBuilder Msg) {
WrappedLog.Log(Msg.Insert(0, Label));
return (this);
}
public LabelPrefix(Logger wrappedLog, string label)
: base(wrappedLog) {
this.Label = label;
}
}

//
///
/// Generate the current time as a log decoration.
///

//
public class TimeGenerator : DecorationGenerator {
private static TimeGenerator CommonInstance = new TimeGenerator();
//
///
/// Return a common shared instance of a TimeGenerator.
///

//
public TimeGenerator Common {
get {
return (CommonInstance);
}
}
public override string Gen() {
return (System.DateTime.Now.ToString());
}
}

//
///
/// Generate a thread static label for log message source.
///

//
public class ThreadLabelGenerator : DecorationGenerator {
[ThreadStatic]
public static string Label;
public override string Gen() {
return (Label);
}
public ThreadLabelGenerator() {
if (Label == null) {
Label = "";
}
}
public ThreadLabelGenerator(string label) {
Label = label;
if (Label == null) {
Label = "";
}
}
}
}
}
}


Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now