Adam's blog formerly game development, now just casual madness

Logging

There’s really nothing much to say about logging. I just thought, I’d share some code that might spare you some time writing it yourself. Basically a Log class. Create an instance for each separate Log type (e.g. Game, Core, Editor, …) and assign n ILogOutput instances to each of them. There are ILogOutput classes for System.Console or a TextWriter in general (any Stream).

The Log class comes with some static properties holding different Logs I set up. They all write only to the Console and use a different background color and log prefix. PushIndent and PopIndent is intended to be used for logging across the a function call hierarchy. Just call PushIndent before calling a function and PopIndent afterwards if you want its log entries to be indented. There are also some utility methods to write a specific log only once or only once in a specified time period. You will need to adjust out the WriteTimed methods as I’m using my engines timer for them. Replace it with some global clock and you’re fine – or just comment them out for a quick test.

Anyway, here is the code:

From Log.cs:

public class Log
{
	private	static	Log	logGame		= new Log(new ConsoleLogOutput("[Game]   ", ConsoleColor.DarkGray));
	private	static	Log	logCore		= new Log(new ConsoleLogOutput("[Core]   ", ConsoleColor.DarkBlue));
	private	static	Log	logEditor	= new Log(new ConsoleLogOutput("[Editor] ", ConsoleColor.DarkMagenta));

	public static Log Game
	{
		get { return logGame; }
	}
	public static Log Core
	{
		get { return logCore; }
	}
	public static Log Editor
	{
		get { return logEditor; }
	}

	private HashSet<string>				onceWritten = new HashSet<string>();
	private	Dictionary<string,float>	timedLast	= new Dictionary<string,float>();
	private	List<ILogOutput>			strOut		= null;
	private	int							indent		= 0;

	public Log(params ILogOutput[] output)
	{
		this.strOut = new List<ILogOutput>(output);
	}

	public void RegisterOutput(ILogOutput writer)
	{
		this.strOut.Add(writer);
	}
	public void UnregisterOutput(ILogOutput writer)
	{
		this.strOut.Remove(writer);
	}

	public void Reset()
	{
		this.onceWritten.Clear();
		this.timedLast.Clear();
	}

	public void PushIndent()
	{
		this.indent++;
	}
	public void PopIndent()
	{
		this.indent--;
	}

	private void Write(LogMessageType type, string msg)
	{
		foreach (ILogOutput log in this.strOut)
		{
			log.Write(type, msg, this.indent);
		}
	}

	public void Write(string format, params object[] obj)
	{
		this.Write(LogMessageType.Message, String.Format(format, obj));
	}
	public void WriteWarning(string format, params object[] obj)
	{
		this.Write(LogMessageType.Warning, String.Format(format, obj));
	}
	public void WriteError(string format, params object[] obj)
	{
		this.Write(LogMessageType.Error, String.Format(format, obj));
	}

	public void WriteOnce(string format, params object[] obj)
	{
		string t = String.Format(format, obj);
		if (!this.onceWritten.Contains(t))
		{
			this.onceWritten.Add(t);
			this.Write(LogMessageType.Message, t);
		}
	}
	public void WriteWarningOnce(string format, params object[] obj)
	{
		string t = String.Format(format, obj);
		if (!this.onceWritten.Contains(t))
		{
			this.onceWritten.Add(t);
			this.Write(LogMessageType.Warning, t);
		}
	}
	public void WriteErrorOnce(string format, params object[] obj)
	{
		string t = String.Format(format, obj);
		if (!this.onceWritten.Contains(t))
		{
			this.onceWritten.Add(t);
			this.Write(LogMessageType.Error, t);
		}
	}

	public void WriteTimed(int delayMs, string timerId, string format, params object[] obj)
	{
		float last;
		if (!this.timedLast.TryGetValue(timerId, out last) || Time.MainTimer - last > delayMs)
		{
			this.timedLast[timerId] = Time.MainTimer;
			this.Write(LogMessageType.Message, String.Format(format, obj));
		}
	}
	public void WriteWarningTimed(int delayMs, string timerId, string format, params object[] obj)
	{
		float last;
		if (!this.timedLast.TryGetValue(timerId, out last) || Time.MainTimer - last > delayMs)
		{
			this.timedLast[timerId] = Time.MainTimer;
			this.Write(LogMessageType.Warning, String.Format(format, obj));
		}
	}
	public void WriteErrorTimed(int delayMs, string timerId, string format, params object[] obj)
	{
		float last;
		if (!this.timedLast.TryGetValue(timerId, out last) || Time.MainTimer - last > delayMs)
		{
			this.timedLast[timerId] = Time.MainTimer;
			this.Write(LogMessageType.Error, String.Format(format, obj));
		}
	}
}

public enum LogMessageType
{
	Message,
	Warning,
	Error
}

public interface ILogOutput
{
	void Write(LogMessageType type, string msg, int indent);
}

From TextWriterLogOutput.cs:

public class TextWriterLogOutput : ILogOutput
{
	private	string		prefix	= null;
	private	TextWriter	writer	= null;

	public TextWriterLogOutput(TextWriter writer, string prefix = null)
	{
		this.writer = writer;
		this.prefix = prefix;
	}

	public virtual void Write(LogMessageType type, string msg, int indent)
	{
		string[] lines = msg.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
		for (int i = 0; i < lines.Length; i++)
		{
			if (i == 0)
			{
				switch (type)
				{
					case LogMessageType.Message:
						lines[i] = this.prefix + "Info:    " + new string(' ', indent * 4) + lines[i];
						break;
					case LogMessageType.Warning:
						lines[i] = this.prefix + "Warning: " + new string(' ', indent * 4) + lines[i];
						break;
					case LogMessageType.Error:
						lines[i] = this.prefix + "ERROR:   " + new string(' ', indent * 4) + lines[i];
						break;
				}
			}
			else
			{
				lines[i] = this.prefix + "         " + new string(' ', indent * 4) + lines[i];
			}
			writer.WriteLine(lines[i]);
		}
	}
}

From ConsoleLogOutput.cs:

public class ConsoleLogOutput : TextWriterLogOutput, ILogOutput
{
	private	ConsoleColor	bgColor;

	public ConsoleLogOutput(string prefix = null, ConsoleColor bgColor = ConsoleColor.Black) : base(Console.Out, prefix)
	{
		this.bgColor = bgColor;
	}

	public override void Write(LogMessageType type, string msg, int indent)
	{
		ConsoleColor clrBg = Console.BackgroundColor;
		ConsoleColor clrFg = Console.ForegroundColor;

		Console.BackgroundColor = this.bgColor;
		if (type == LogMessageType.Warning)		Console.ForegroundColor = ConsoleColor.Yellow;
		else if (type == LogMessageType.Error)	Console.ForegroundColor = ConsoleColor.Red;
		else									Console.ForegroundColor = ConsoleColor.Gray;

		base.Write(type, msg, indent);

		Console.ForegroundColor = clrFg;
		Console.BackgroundColor = clrBg;
	}
}

69 of 74