項目中要實現寫日志Logging,但客戶不想用Log4net,說不想用任何第三方組件,好吧,我們自己寫一個簡單的記日志的組件吧。但要實現下面的幾個功能:
- 可以用FileAppender的方式記日志
- 線程安全,支持多個線程同時Append同一日志文件
- 支持FileRolling,設置大小自動分卷
我們知道是Log4net是線程安全的,雖然也有人說不是進程安全的。我們自己實現,要支持多個線程同時Append同一日志文件,需要加鎖。這里我們用並發隊列ConcurrentQueue來實現(.NET Framework 4.0 及以上),思路就是當線程請求寫日志,首先把請求放入隊列,然后批量一次寫入。實現最高性能。由於寫入的瓶頸在IO上,而文件無需頻繁打開,異步寫入的方式自然性能高很多。缺點是機器掉電的話,隊列里面的日志請求丟失。具體實現是當請求寫日志的時候隊列入隊Queue.Enqueue,然后每隔幾秒鍾批量寫入日志,出隊(while (Queue.TryDequeue(out entry)))。
ILogger.cs

1
namespace YourCompany.Logging
2 {
3 public interface ILogger
4 {
5 // write custom log
6 void Write( string serviceName, string loggingIdentity, LogMessageDirection direction, string message);
7 void Write( string serviceName, string loggingIdentity, string message);
8 // write general log
9 void GlobalWrite( string message, LogMessageCategory category);
10 }
11
12 public enum LogMessageCategory { Info, Success, Warning, Error };
13 public enum LogMessageDirection { None, InBound, OutBound };
14 public enum LogMessageOption { None, LogMessageAsRaw, LogMessageAsText, LogMessageAsRawAndText };
15 }
2 {
3 public interface ILogger
4 {
5 // write custom log
6 void Write( string serviceName, string loggingIdentity, LogMessageDirection direction, string message);
7 void Write( string serviceName, string loggingIdentity, string message);
8 // write general log
9 void GlobalWrite( string message, LogMessageCategory category);
10 }
11
12 public enum LogMessageCategory { Info, Success, Warning, Error };
13 public enum LogMessageDirection { None, InBound, OutBound };
14 public enum LogMessageOption { None, LogMessageAsRaw, LogMessageAsText, LogMessageAsRawAndText };
15 }
Logger.cs

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
namespace YourCompany.Logging
{
public sealed class Logger
{
internal static ILogger Current
{
get
{
return OperationContext.Current.LogContext;
}
}
// Every period of 15s, flush logs entry in queue to really write to file.
// Do not dispose the Timer here.
private static System.Threading.Timer LoggingQueueTimer = new Timer(timerCallback, null, 0 /* start immediately */,
Int32.Parse(ConfigurationManager.AppSettings[Constants.Configuration.WritLogQueueCheckPeriod]));
// The concurrent logs writing queue, logs will actually be written until DoGlobalWrite() method is called or timer checker found items in queue.
internal static ConcurrentQueue<LogEntry> LoggingQueue = new ConcurrentQueue<LogEntry>();
private static void timerCallback( object o)
{
DoGlobalWrite();
}
internal static void Write(LogMessageDirection direction, IMessage message)
{
Write(direction, new IMessage[] { message });
return;
}
internal static void Write(LogMessageDirection direction, IMessage[] messages)
{
if (OperationContext.Current != null)
{
try
{
switch (OperationContext.Current.LoggingOption)
{
case LogMessageOption.LogMessageAsText:
if (messages.Length > 1)
{
StringBuilder builder = new StringBuilder();
foreach (IMessage message in messages)
{
builder.Append(message.ToString());
}
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, builder.ToString());
}
else
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, messages[ 0].ToString());
}
break;
case LogMessageOption.LogMessageAsRaw:
if (messages.Length > 1)
{
using (MemoryStream buffer = new MemoryStream())
{
foreach (IMessage message in messages)
{
byte[] data = message.ToArray();
buffer.Write(data, 0, data.Length);
}
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, BitConverter.ToString(buffer.ToArray()).Replace( " - ", string.Empty));
}
}
else
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, BitConverter.ToString(messages[ 0].ToArray()).Replace( " - ", string.Empty));
}
break;
case LogMessageOption.LogMessageAsRawAndText:
if (messages.Length > 1)
{
using (MemoryStream buffer = new MemoryStream())
{
foreach (IMessage message in messages)
{
byte[] data = message.ToArray();
buffer.Write(data, 0, data.Length);
}
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, BitConverter.ToString(buffer.ToArray()).Replace( " - ", string.Empty));
}
StringBuilder builder = new StringBuilder();
foreach (IMessage message in messages)
{
builder.Append(message.ToString());
}
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, builder.ToString());
}
else
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, BitConverter.ToString(messages[ 0].ToArray()).Replace( " - ", string.Empty));
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, messages[ 0].ToString());
}
break;
default:
// nop
break;
}
}
catch
{
}
}
return;
}
public static void Write( string message)
{
if (OperationContext.Current != null)
{
try
{
if (OperationContext.Current.LoggingOption != LogMessageOption.None)
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, message);
}
}
catch
{
}
}
return;
}
public static void Write( string format, params object[] arg)
{
if (OperationContext.Current != null)
{
try
{
if (OperationContext.Current.LoggingOption != LogMessageOption.None)
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, string.Format(format, arg));
}
}
catch
{
}
}
return;
}
internal static void Write(LogMessageDirection direction, string format, params object[] arg)
{
if (OperationContext.Current != null)
{
try
{
if (OperationContext.Current.LoggingOption != LogMessageOption.None)
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, string.Format(format, arg));
}
}
catch
{
}
}
return;
}
public static void DoGlobalWrite()
{
if (OperationContext.Current != null &&
OperationContext.Current.GlobalTraceIsOn)
{
try
{
LogEntry entry = null;
while (LoggingQueue.TryDequeue( out entry))
{
if(entry != null)
{
// Current.GlobalWrite(string.Format("From id:{0}. {1}", OperationContext.Current.Identity, message));
Current.GlobalWrite(entry.Message, entry.Category);
}
}
}
catch
{
#if DEBUG
throw;
#endif
}
}
return;
}
public static void GlobalWrite( string message, LogMessageCategory category)
{
try
{
// If app.config / web.config settings WriteWarningLogs=False, then don't write warning entry
// to get a better performance if there're a large amout of warnings in the system.
if (category == LogMessageCategory.Warning)
{
var cfg = ConfigurationManager.AppSettings[Constants.Configuration.WriteWarningLogs];
if (!String.IsNullOrEmpty(cfg) && !Boolean.Parse(cfg))
return;
}
// If app.config / web.config settings WriteInfoLogs=False, then don't write warning entry
// to get a better performance if there're a large amout of warnings in the system.
if (category == LogMessageCategory.Info)
{
var cfg = ConfigurationManager.AppSettings[Constants.Configuration.WriteInfoLogs];
if (!String.IsNullOrEmpty(cfg) && !Boolean.Parse(cfg))
return;
}
LoggingQueue.Enqueue( new LogEntry(String.Empty, String.Empty, LogMessageDirection.None, message,
category));
}
catch
{
#if DEBUG
throw;
#endif
}
return;
}
public static void GlobalWrite( string format, params object[] arg)
{
if (OperationContext.Current != null &&
OperationContext.Current.GlobalTraceIsOn)
{
try
{
// Current.GlobalWrite(string.Format("From id:{0}. {1}", OperationContext.Current.Identity, string.Format(format, arg)));
Current.GlobalWrite( string.Format(format, arg), LogMessageCategory.Info);
}
catch
{
}
}
return;
}
}
#region "Internal Methods"
public sealed class OperationContext : IDisposable
{
void IDisposable.Dispose()
{
using ( this.MessageBuffer) { }
return;
}
public OperationContext(IConfigurationSite site, bool isRemotingContext)
{
this.SerialHeaderAvailable = false;
this.Site = site;
this.IsRemotingContext = isRemotingContext;
this.Identity = site.Identity;
this.ServiceName = site.ServiceName;
this.ProtocolEncoding = site.ProtocolEncoding;
this.LoggingOption = site.LoggingOption;
this.MessageBuffer = new MemoryStream();
if (isRemotingContext)
{
this.ChannelContext = (CallContext)RemotingServices.Unmarshal(site.CallContextObj as ObjRef);
}
else
{
this.ChannelContext = site.CallContextObj as CallContext;
}
if (site.LoggingEnabled)
{
if (isRemotingContext)
{
this.LogContext = (ILogger)RemotingServices.Unmarshal(site.LogContextObj as ObjRef);
}
else
{
this.LogContext = site.LogContextObj as ILogger;
}
}
else
{
this.LogContext = null;
}
this.QueueResponses = new Queue<IMessage>();
this.Notifiers = this.Site.Notifiers;
this.MessageFilter = null;
this.GlobalTraceIsOn = site.GlobalTraceIsOn;
}
internal bool IsCancelSignaled
{
get { return this.Site.IsCancelSignaled; }
}
string sponsorGroup;
internal string SponsorGroup
{
get { return this.sponsorGroup; }
set
{
this.sponsorGroup = value;
this.Site.SponsorName = value;
}
}
internal string SponsorName
{
get;
set;
}
string callerIdentity;
internal string CallerIdentity
{
get { return this.callerIdentity; }
set
{
this.callerIdentity = value;
this.Site.CallerIdentity = value;
}
}
internal string TID
{
get;
set;
}
internal string SerialNo
{
get;
set;
}
internal CallChangedEventArg CreateCallChangedEventArg(CallChangedState state, string message)
{
CallChangedEventArg arg = new CallChangedEventArg(state, message);
FillDefaultValues(arg);
return arg;
}
internal EventArg CreateEventArg()
{
EventArg arg = new EventArg();
FillDefaultValues(arg);
return arg;
}
internal void FillDefaultValues(EventArg arg)
{
// arg.ProtocolKind = this.ChannelContext.ProtocolKind;
arg.ServiceName = this.ServiceName;
arg.SponsorName = string.IsNullOrEmpty( this.SponsorName) ? OperationContext.CompanyName : this.SponsorName;
arg.CallerIdentity = this.CallerIdentity;
arg.TID = this.TID;
arg.SerialNo = this.SerialNo;
return;
}
internal readonly IConfigurationSite Site;
internal readonly string Identity;
internal readonly bool IsRemotingContext;
internal readonly string ServiceName;
internal readonly CallContext ChannelContext;
internal readonly ILogger LogContext;
internal Encoding ProtocolEncoding;
internal readonly LogMessageOption LoggingOption;
internal MemoryStream MessageBuffer;
internal bool SerialHeaderAvailable;
internal Func<IMessage, bool> MessageFilter;
internal readonly Queue<IMessage> QueueResponses;
internal ReadOnlyCollection< object> Notifiers;
internal bool GlobalTraceIsOn;
// [ThreadStatic]
public static OperationContext Current;
internal static string CompanyName = string.Empty;
};
[Serializable]
public class CallChangedEventArg : EventArg
{
public readonly CallChangedState State;
public readonly string Message;
public CallChangedEventArg(CallChangedState state, string message)
{
this.State = state;
this.Message = message;
}
}
[Flags]
public enum CallChangedState
{
None = 0,
Connected = 1,
Disconnected = 2,
Success = 4,
Error = 8
};
[Serializable]
public class EventArg
{
string serviceName = string.Empty;
public string ServiceName
{
get { return this.serviceName; }
internal set { this.serviceName = value; }
}
string sponsorName = string.Empty;
public string SponsorName
{
get { return this.sponsorName; }
internal set { this.sponsorName = value; }
}
string callerIdentity = string.Empty;
public string CallerIdentity
{
get { return this.callerIdentity; }
internal set { this.callerIdentity = value; }
}
string tid = string.Empty;
public string TID
{
get { return this.tid; }
internal set { this.tid = value; }
}
string sn = string.Empty;
public string SerialNo
{
get { return this.sn; }
internal set { this.sn = value; }
}
}
public class ConfigurationSite : IConfigurationSite
{
public string OutputPath { get; set; }
public string SponsorName { get; set; }
public string CallerIdentity { get; set; }
public string ConnectionString { get; set; }
public bool GlobalTraceIsOn { get; set; }
public string ServiceName { get; set; }
public string Identity { get; set; }
public object CallContextObj { get; set; }
public object LogContextObj { get; set; }
public Encoding ProtocolEncoding { get; set; }
public bool IsCancelSignaled { get; set; }
public bool LoggingEnabled { get; set; }
public LogMessageOption LoggingOption { get; set; }
public ReadOnlyCollection< object> Notifiers { get; set; }
}
public interface IConfigurationSite
{
string OutputPath { get; }
string SponsorName { get; set; }
string CallerIdentity { get; set; }
string ConnectionString { get; }
bool GlobalTraceIsOn { get; }
string ServiceName { get; }
string Identity { get; }
object CallContextObj { get; }
object LogContextObj { get; }
Encoding ProtocolEncoding { get; }
bool IsCancelSignaled { get; }
bool LoggingEnabled { get; }
LogMessageOption LoggingOption { get; }
ReadOnlyCollection< object> Notifiers { get; }
}
internal class RollingDefaultTraceListener : DefaultTraceListener
{
long rollSizeKB;
int maxNumberOfRollingLogs;
RollingFlatFile logger;
public RollingDefaultTraceListener( long rollSizeKB, int maxNumberOfRollingLogs)
{
this.rollSizeKB = rollSizeKB;
this.maxNumberOfRollingLogs = maxNumberOfRollingLogs;
}
public new string LogFileName
{
get { return base.LogFileName; }
set
{
base.LogFileName = value;
if ( this.logger == null)
{
string fileName = Path.GetFileName(value);
string path = Path.Combine(Path.GetDirectoryName(value), Environment.MachineName + " _ " + fileName);
GlobalLoggers.CreateLogger(fileName, Encoding.UTF8, path, this.rollSizeKB, this.maxNumberOfRollingLogs);
// move the default log file to another location
Trace.Listeners.Remove( " Default ");
Trace.Listeners.Add( this);
this.logger = GlobalLoggers.Get(fileName);
}
}
}
public override void Write( string message)
{
WriteLine(message);
return;
}
public override void WriteLine( string message)
{
if ( this.logger != null)
{
this.logger.Tracelog(message);
}
return;
}
}
#endregion
}
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
namespace YourCompany.Logging
{
public sealed class Logger
{
internal static ILogger Current
{
get
{
return OperationContext.Current.LogContext;
}
}
// Every period of 15s, flush logs entry in queue to really write to file.
// Do not dispose the Timer here.
private static System.Threading.Timer LoggingQueueTimer = new Timer(timerCallback, null, 0 /* start immediately */,
Int32.Parse(ConfigurationManager.AppSettings[Constants.Configuration.WritLogQueueCheckPeriod]));
// The concurrent logs writing queue, logs will actually be written until DoGlobalWrite() method is called or timer checker found items in queue.
internal static ConcurrentQueue<LogEntry> LoggingQueue = new ConcurrentQueue<LogEntry>();
private static void timerCallback( object o)
{
DoGlobalWrite();
}
internal static void Write(LogMessageDirection direction, IMessage message)
{
Write(direction, new IMessage[] { message });
return;
}
internal static void Write(LogMessageDirection direction, IMessage[] messages)
{
if (OperationContext.Current != null)
{
try
{
switch (OperationContext.Current.LoggingOption)
{
case LogMessageOption.LogMessageAsText:
if (messages.Length > 1)
{
StringBuilder builder = new StringBuilder();
foreach (IMessage message in messages)
{
builder.Append(message.ToString());
}
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, builder.ToString());
}
else
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, messages[ 0].ToString());
}
break;
case LogMessageOption.LogMessageAsRaw:
if (messages.Length > 1)
{
using (MemoryStream buffer = new MemoryStream())
{
foreach (IMessage message in messages)
{
byte[] data = message.ToArray();
buffer.Write(data, 0, data.Length);
}
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, BitConverter.ToString(buffer.ToArray()).Replace( " - ", string.Empty));
}
}
else
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, BitConverter.ToString(messages[ 0].ToArray()).Replace( " - ", string.Empty));
}
break;
case LogMessageOption.LogMessageAsRawAndText:
if (messages.Length > 1)
{
using (MemoryStream buffer = new MemoryStream())
{
foreach (IMessage message in messages)
{
byte[] data = message.ToArray();
buffer.Write(data, 0, data.Length);
}
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, BitConverter.ToString(buffer.ToArray()).Replace( " - ", string.Empty));
}
StringBuilder builder = new StringBuilder();
foreach (IMessage message in messages)
{
builder.Append(message.ToString());
}
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, builder.ToString());
}
else
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, BitConverter.ToString(messages[ 0].ToArray()).Replace( " - ", string.Empty));
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, messages[ 0].ToString());
}
break;
default:
// nop
break;
}
}
catch
{
}
}
return;
}
public static void Write( string message)
{
if (OperationContext.Current != null)
{
try
{
if (OperationContext.Current.LoggingOption != LogMessageOption.None)
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, message);
}
}
catch
{
}
}
return;
}
public static void Write( string format, params object[] arg)
{
if (OperationContext.Current != null)
{
try
{
if (OperationContext.Current.LoggingOption != LogMessageOption.None)
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, string.Format(format, arg));
}
}
catch
{
}
}
return;
}
internal static void Write(LogMessageDirection direction, string format, params object[] arg)
{
if (OperationContext.Current != null)
{
try
{
if (OperationContext.Current.LoggingOption != LogMessageOption.None)
{
Current.Write(OperationContext.Current.ServiceName, OperationContext.Current.Identity, direction, string.Format(format, arg));
}
}
catch
{
}
}
return;
}
public static void DoGlobalWrite()
{
if (OperationContext.Current != null &&
OperationContext.Current.GlobalTraceIsOn)
{
try
{
LogEntry entry = null;
while (LoggingQueue.TryDequeue( out entry))
{
if(entry != null)
{
// Current.GlobalWrite(string.Format("From id:{0}. {1}", OperationContext.Current.Identity, message));
Current.GlobalWrite(entry.Message, entry.Category);
}
}
}
catch
{
#if DEBUG
throw;
#endif
}
}
return;
}
public static void GlobalWrite( string message, LogMessageCategory category)
{
try
{
// If app.config / web.config settings WriteWarningLogs=False, then don't write warning entry
// to get a better performance if there're a large amout of warnings in the system.
if (category == LogMessageCategory.Warning)
{
var cfg = ConfigurationManager.AppSettings[Constants.Configuration.WriteWarningLogs];
if (!String.IsNullOrEmpty(cfg) && !Boolean.Parse(cfg))
return;
}
// If app.config / web.config settings WriteInfoLogs=False, then don't write warning entry
// to get a better performance if there're a large amout of warnings in the system.
if (category == LogMessageCategory.Info)
{
var cfg = ConfigurationManager.AppSettings[Constants.Configuration.WriteInfoLogs];
if (!String.IsNullOrEmpty(cfg) && !Boolean.Parse(cfg))
return;
}
LoggingQueue.Enqueue( new LogEntry(String.Empty, String.Empty, LogMessageDirection.None, message,
category));
}
catch
{
#if DEBUG
throw;
#endif
}
return;
}
public static void GlobalWrite( string format, params object[] arg)
{
if (OperationContext.Current != null &&
OperationContext.Current.GlobalTraceIsOn)
{
try
{
// Current.GlobalWrite(string.Format("From id:{0}. {1}", OperationContext.Current.Identity, string.Format(format, arg)));
Current.GlobalWrite( string.Format(format, arg), LogMessageCategory.Info);
}
catch
{
}
}
return;
}
}
#region "Internal Methods"
public sealed class OperationContext : IDisposable
{
void IDisposable.Dispose()
{
using ( this.MessageBuffer) { }
return;
}
public OperationContext(IConfigurationSite site, bool isRemotingContext)
{
this.SerialHeaderAvailable = false;
this.Site = site;
this.IsRemotingContext = isRemotingContext;
this.Identity = site.Identity;
this.ServiceName = site.ServiceName;
this.ProtocolEncoding = site.ProtocolEncoding;
this.LoggingOption = site.LoggingOption;
this.MessageBuffer = new MemoryStream();
if (isRemotingContext)
{
this.ChannelContext = (CallContext)RemotingServices.Unmarshal(site.CallContextObj as ObjRef);
}
else
{
this.ChannelContext = site.CallContextObj as CallContext;
}
if (site.LoggingEnabled)
{
if (isRemotingContext)
{
this.LogContext = (ILogger)RemotingServices.Unmarshal(site.LogContextObj as ObjRef);
}
else
{
this.LogContext = site.LogContextObj as ILogger;
}
}
else
{
this.LogContext = null;
}
this.QueueResponses = new Queue<IMessage>();
this.Notifiers = this.Site.Notifiers;
this.MessageFilter = null;
this.GlobalTraceIsOn = site.GlobalTraceIsOn;
}
internal bool IsCancelSignaled
{
get { return this.Site.IsCancelSignaled; }
}
string sponsorGroup;
internal string SponsorGroup
{
get { return this.sponsorGroup; }
set
{
this.sponsorGroup = value;
this.Site.SponsorName = value;
}
}
internal string SponsorName
{
get;
set;
}
string callerIdentity;
internal string CallerIdentity
{
get { return this.callerIdentity; }
set
{
this.callerIdentity = value;
this.Site.CallerIdentity = value;
}
}
internal string TID
{
get;
set;
}
internal string SerialNo
{
get;
set;
}
internal CallChangedEventArg CreateCallChangedEventArg(CallChangedState state, string message)
{
CallChangedEventArg arg = new CallChangedEventArg(state, message);
FillDefaultValues(arg);
return arg;
}
internal EventArg CreateEventArg()
{
EventArg arg = new EventArg();
FillDefaultValues(arg);
return arg;
}
internal void FillDefaultValues(EventArg arg)
{
// arg.ProtocolKind = this.ChannelContext.ProtocolKind;
arg.ServiceName = this.ServiceName;
arg.SponsorName = string.IsNullOrEmpty( this.SponsorName) ? OperationContext.CompanyName : this.SponsorName;
arg.CallerIdentity = this.CallerIdentity;
arg.TID = this.TID;
arg.SerialNo = this.SerialNo;
return;
}
internal readonly IConfigurationSite Site;
internal readonly string Identity;
internal readonly bool IsRemotingContext;
internal readonly string ServiceName;
internal readonly CallContext ChannelContext;
internal readonly ILogger LogContext;
internal Encoding ProtocolEncoding;
internal readonly LogMessageOption LoggingOption;
internal MemoryStream MessageBuffer;
internal bool SerialHeaderAvailable;
internal Func<IMessage, bool> MessageFilter;
internal readonly Queue<IMessage> QueueResponses;
internal ReadOnlyCollection< object> Notifiers;
internal bool GlobalTraceIsOn;
// [ThreadStatic]
public static OperationContext Current;
internal static string CompanyName = string.Empty;
};
[Serializable]
public class CallChangedEventArg : EventArg
{
public readonly CallChangedState State;
public readonly string Message;
public CallChangedEventArg(CallChangedState state, string message)
{
this.State = state;
this.Message = message;
}
}
[Flags]
public enum CallChangedState
{
None = 0,
Connected = 1,
Disconnected = 2,
Success = 4,
Error = 8
};
[Serializable]
public class EventArg
{
string serviceName = string.Empty;
public string ServiceName
{
get { return this.serviceName; }
internal set { this.serviceName = value; }
}
string sponsorName = string.Empty;
public string SponsorName
{
get { return this.sponsorName; }
internal set { this.sponsorName = value; }
}
string callerIdentity = string.Empty;
public string CallerIdentity
{
get { return this.callerIdentity; }
internal set { this.callerIdentity = value; }
}
string tid = string.Empty;
public string TID
{
get { return this.tid; }
internal set { this.tid = value; }
}
string sn = string.Empty;
public string SerialNo
{
get { return this.sn; }
internal set { this.sn = value; }
}
}
public class ConfigurationSite : IConfigurationSite
{
public string OutputPath { get; set; }
public string SponsorName { get; set; }
public string CallerIdentity { get; set; }
public string ConnectionString { get; set; }
public bool GlobalTraceIsOn { get; set; }
public string ServiceName { get; set; }
public string Identity { get; set; }
public object CallContextObj { get; set; }
public object LogContextObj { get; set; }
public Encoding ProtocolEncoding { get; set; }
public bool IsCancelSignaled { get; set; }
public bool LoggingEnabled { get; set; }
public LogMessageOption LoggingOption { get; set; }
public ReadOnlyCollection< object> Notifiers { get; set; }
}
public interface IConfigurationSite
{
string OutputPath { get; }
string SponsorName { get; set; }
string CallerIdentity { get; set; }
string ConnectionString { get; }
bool GlobalTraceIsOn { get; }
string ServiceName { get; }
string Identity { get; }
object CallContextObj { get; }
object LogContextObj { get; }
Encoding ProtocolEncoding { get; }
bool IsCancelSignaled { get; }
bool LoggingEnabled { get; }
LogMessageOption LoggingOption { get; }
ReadOnlyCollection< object> Notifiers { get; }
}
internal class RollingDefaultTraceListener : DefaultTraceListener
{
long rollSizeKB;
int maxNumberOfRollingLogs;
RollingFlatFile logger;
public RollingDefaultTraceListener( long rollSizeKB, int maxNumberOfRollingLogs)
{
this.rollSizeKB = rollSizeKB;
this.maxNumberOfRollingLogs = maxNumberOfRollingLogs;
}
public new string LogFileName
{
get { return base.LogFileName; }
set
{
base.LogFileName = value;
if ( this.logger == null)
{
string fileName = Path.GetFileName(value);
string path = Path.Combine(Path.GetDirectoryName(value), Environment.MachineName + " _ " + fileName);
GlobalLoggers.CreateLogger(fileName, Encoding.UTF8, path, this.rollSizeKB, this.maxNumberOfRollingLogs);
// move the default log file to another location
Trace.Listeners.Remove( " Default ");
Trace.Listeners.Add( this);
this.logger = GlobalLoggers.Get(fileName);
}
}
}
public override void Write( string message)
{
WriteLine(message);
return;
}
public override void WriteLine( string message)
{
if ( this.logger != null)
{
this.logger.Tracelog(message);
}
return;
}
}
#endregion
}
RollingFlatFile.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace YourCompany.Logging
{
public class RollingFlatFile : IDisposable
{
public string FileName
{
get;
private set;
}
protected Encoding encoding;
protected string timeStampPattern;
protected object syncRoot = new object();
RollingHelper rollingHelper;
public RollingFlatFile(Encoding encoding, string fileName, long rollSizeKB = 1024, int maxNumberOfRollingLogs = 1, string timeStampPattern = " yyyy-MM-dd [hh-mm-ss] ")
{
this.encoding = encoding;
this.FileName = fileName;
this.RollSizeInBytes = rollSizeKB * 1024;
this.timeStampPattern = timeStampPattern;
this.MaxNumberOfRollingLogs = maxNumberOfRollingLogs;
if ( this.MaxNumberOfRollingLogs <= 0)
{
throw new ArgumentException( " Invalid number of rolling logs. ");
}
this.rollingHelper = new RollingHelper( this);
}
public long RollSizeInBytes
{
get;
set;
}
public int MaxNumberOfRollingLogs
{
get;
set;
}
string headerInfo = string.Empty;
public string HeaderInfo
{
get { return this.headerInfo; }
set
{
this.headerInfo = value;
InternalTracelog(value);
}
}
internal void InternalTracelog( string message)
{
LogEntry entry = new LogEntry( string.Empty, string.Empty, LogMessageDirection.None, message, LogMessageCategory.Info);
InternalTracelog(entry);
return;
}
internal void InternalTracelog( string format, params object[] args)
{
LogEntry entry = new LogEntry( string.Empty, string.Empty, LogMessageDirection.None, string.Format(format, args), LogMessageCategory.Info);
InternalTracelog(entry);
return;
}
internal void InternalTracelog(LogEntry entry)
{
lock ( this.syncRoot)
{
if ( this.rollingHelper != null)
{
List< string> lines = new List< string>();
StringBuilder argument = new StringBuilder();
argument.AppendFormat( " [{0:D2}/{1:D2}/{2:D4} {3:D2}:{4:D2}:{5:D2}.{6:D3}] ", entry.TimeStamp.Month, entry.TimeStamp.Day, entry.TimeStamp.Year, entry.TimeStamp.Hour, entry.TimeStamp.Minute, entry.TimeStamp.Second, entry.TimeStamp.Millisecond);
if ( this.rollingHelper.RollIfNecessary())
{
// write the header info
string value = string.Format( " {0} - {1} ", argument.ToString(), this.headerInfo);
lines.Add(value);
}
switch (entry.Direction)
{
case LogMessageDirection.InBound:
argument.Append( " - IN ");
break;
case LogMessageDirection.OutBound:
argument.Append( " - OUT ");
break;
}
switch (entry.Category)
{
case LogMessageCategory.Success:
argument.AppendFormat( " - [{0}] ", LogMessageCategory.Success.ToString());
break;
case LogMessageCategory.Warning:
argument.AppendFormat( " - [{0}] ", LogMessageCategory.Warning.ToString());
break;
case LogMessageCategory.Error:
argument.AppendFormat( " - [{0}] ", LogMessageCategory.Error.ToString());
break;
case LogMessageCategory.Info:
argument.AppendFormat( " - [{0}] ", LogMessageCategory.Info.ToString());
break;
default:
break;
}
argument.AppendFormat( " - {0} ", entry.Message);
lines.Add(argument.ToString());
this.rollingHelper.WriteLine(lines.ToArray());
}
}
return;
}
internal void InternalWriteLine()
{
lock ( this.syncRoot)
{
if ( this.rollingHelper != null)
{
this.rollingHelper.WriteLine( string.Empty);
}
}
return;
}
public void Dispose()
{
InternalClose();
return;
}
internal void InternalClose()
{
using ( this.rollingHelper) { }
this.rollingHelper = null;
return;
}
#region internal helper class
sealed class RollingHelper : IDisposable
{
RollingFlatFile owner;
StreamWriter writer;
public RollingHelper(RollingFlatFile owner)
{
this.owner = owner;
}
bool PerformsRolling
{
get { return this.owner.RollSizeInBytes > 0; }
}
public void WriteLine( params string[] values)
{
if ( this.writer == null)
{
CreateLogFile();
}
if ( this.writer != null)
{
foreach ( string value in values)
{
this.writer.WriteLine(value);
}
}
return;
}
private void Close()
{
using ( this.writer)
{
if ( this.writer != null)
{
this.writer.Close();
}
}
this.writer = null;
return;
}
public void Dispose()
{
Close();
return;
}
public bool RollIfNecessary()
{
bool wasRolled = false;
if ( this.PerformsRolling)
{
if (CheckIfRollNecessary())
{
PerformRoll();
wasRolled = true;
}
}
return wasRolled;
}
private void CreateLogFile()
{
System.Diagnostics.Debug.Assert( this.writer == null);
if (Directory.Exists(Path.GetDirectoryName( this.owner.FileName)) == false)
{
Directory.CreateDirectory(Path.GetDirectoryName( this.owner.FileName));
}
this.writer = new StreamWriter( this.owner.FileName, true, this.owner.encoding);
this.writer.AutoFlush = true;
return;
}
private bool CheckIfRollNecessary()
{
bool result = false;
if (File.Exists( this.owner.FileName))
{
FileInfo fileInfo = new FileInfo( this.owner.FileName);
// check for size roll, if enabled.
result = fileInfo.Length > this.owner.RollSizeInBytes;
}
return result;
}
private void PerformRoll()
{
DateTime rollDateTime = DateTime.Now;
string actualFileName = this.owner.FileName;
// calculate archive name
string archiveFileName = ComputeArchiveFileName(actualFileName, rollDateTime);
// close current file
Close();
// move file
SafeMove(actualFileName, archiveFileName, rollDateTime);
// delete rolling logs if needed
DeleteOldArchiveFiles(actualFileName);
// create a new file again
CreateLogFile();
return;
}
private void SafeMove( string actualFileName, string archiveFileName, DateTime currentDateTime)
{
try
{
if (File.Exists(archiveFileName))
{
File.Delete(archiveFileName);
}
// take care of tunneling issues http://support.microsoft.com/kb/172190
File.SetCreationTime(actualFileName, currentDateTime);
File.Move(actualFileName, archiveFileName);
}
catch (IOException)
{
// catch errors and attempt move to a new file with a GUID
archiveFileName = archiveFileName + Guid.NewGuid().ToString();
try
{
File.Move(actualFileName, archiveFileName);
}
catch (IOException) { }
}
return;
}
private string ComputeArchiveFileName( string actualFileName, DateTime currentDateTime)
{
string directory = Path.GetDirectoryName(actualFileName);
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(actualFileName);
string extension = Path.GetExtension(actualFileName);
// new archive name
string archiveFileNameWithTimestampWithoutExtension = fileNameWithoutExtension + " . " + currentDateTime.ToString( this.owner.timeStampPattern, CultureInfo.InvariantCulture);
return Path.Combine(directory, archiveFileNameWithTimestampWithoutExtension + extension);
}
private void DeleteOldArchiveFiles( string actualFileName)
{
string directory = Path.GetDirectoryName(actualFileName);
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(actualFileName);
string extension = Path.GetExtension(actualFileName);
DirectoryInfo info = new DirectoryInfo(directory);
FileInfo[] existingFiles = info.GetFiles( string.Format( " {0}*{1} ", fileNameWithoutExtension, extension));
List<FileInfo> deleteFiles = new List<FileInfo>();
Regex regex = new Regex( string.Format( @" {0}\.(.+).{1} ", fileNameWithoutExtension, extension));
for ( int index = 0; index < existingFiles.Length; index++)
{
Match sequenceMatch = regex.Match(existingFiles[index].FullName);
if (sequenceMatch.Success)
{
deleteFiles.Add(existingFiles[index]);
}
}
deleteFiles.Sort((x, y) => x.CreationTime < y.CreationTime ? 1 : x.CreationTime > y.CreationTime ? - 1 : 0);
for ( int index = this.owner.MaxNumberOfRollingLogs; index < deleteFiles.Count; index++)
{
try
{
deleteFiles[index].Delete();
}
catch
{
}
}
return;
}
}
#endregion
}
[Serializable]
public class LogEntry
{
public LogEntry(LogMessageDirection direction, string message, LogMessageCategory category, DateTime? timeStamp = null)
: this( null, null, direction, message, category, timeStamp)
{
}
public LogEntry( string serviceName, string identity, LogMessageDirection direction, string message, LogMessageCategory category, DateTime? timeStamp = null)
{
this.ServiceName = serviceName;
this.Identity = identity;
this.Direction = direction;
this.Message = message;
this.Category = category;
this.TimeStamp = timeStamp ?? DateTime.Now;
}
public readonly DateTime TimeStamp;
public readonly string ServiceName;
public readonly string Identity;
public readonly LogMessageDirection Direction;
public readonly LogMessageCategory Category;
public readonly string Message;
}
public interface IMessage
{
string Name { get; }
IMessage Predecessor { get; set; }
object PropertyBag { get; }
bool IsTransferMessage { get; set; }
// internal use only
bool IsSpecialMessage { get; }
byte[] ToArray();
string ToString();
}
public static class GlobalLoggers
{
#region RollingFlatFile extensions
public static void Tracelog( this RollingFlatFile entity, string message)
{
if (entity != null)
{
entity.InternalTracelog(message);
}
return;
}
public static void Tracelog( this RollingFlatFile entity, string format, params object[] args)
{
if (entity != null)
{
entity.InternalTracelog(format, args);
}
return;
}
public static void Tracelog( this RollingFlatFile entity, LogEntry entry)
{
if (entity != null)
{
entity.InternalTracelog(entry);
}
return;
}
public static void WriteLine( this RollingFlatFile entity)
{
if (entity != null)
{
entity.InternalWriteLine();
}
return;
}
public static void Close( this RollingFlatFile entity)
{
if (entity != null)
{
entity.InternalClose();
}
return;
}
#endregion
static Dictionary< string, RollingFlatFile> loggers = new Dictionary< string, RollingFlatFile>();
public static bool CreateLogger( string identity, Encoding encoding, string fileName, long rollSizeKB)
{
return CreateLogger(identity, encoding, fileName, rollSizeKB, 1);
}
public static bool CreateLogger( string identity, Encoding encoding, string fileName, long rollSizeKB, int maxNumberOfRollingLogs)
{
return CreateLogger(identity, encoding, fileName, rollSizeKB, maxNumberOfRollingLogs, " yyyy-MM-dd [hh-mm-ss] ");
}
public static bool CreateLogger( string identity, Encoding encoding, string fileName, long rollSizeKB, int maxNumberOfRollingLogs, string timeStampPattern)
{
bool result = loggers.ContainsKey(identity);
if (result == false)
{
lock (((ICollection)loggers).SyncRoot)
{
RollingFlatFile logger = new RollingFlatFile(encoding, fileName, rollSizeKB, maxNumberOfRollingLogs, timeStampPattern);
loggers.Add(identity, logger);
result = true;
}
}
return result;
}
public static void AttachLogger( string identity, RollingFlatFile logger)
{
lock (((ICollection)loggers).SyncRoot)
{
if (loggers.ContainsKey(identity))
{
throw new Exception( string.Format( " Logger '{0}' already exists! ", identity));
}
loggers.Add(identity, logger);
}
return;
}
public static void CloseAll()
{
try
{
lock (((ICollection)loggers).SyncRoot)
{
foreach (RollingFlatFile logger in loggers.Values)
{
using (logger) { }
}
loggers.Clear();
}
}
catch
{
}
return;
}
public static void Close( string identity)
{
lock (((ICollection)loggers).SyncRoot)
{
if (loggers.ContainsKey(identity))
{
using (loggers[identity]) { }
loggers.Remove(identity);
}
}
return;
}
public static RollingFlatFile Get( string identity)
{
RollingFlatFile result = null;
if (loggers.ContainsKey(identity))
{
result = loggers[identity];
}
return result;
}
}
}
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace YourCompany.Logging
{
public class RollingFlatFile : IDisposable
{
public string FileName
{
get;
private set;
}
protected Encoding encoding;
protected string timeStampPattern;
protected object syncRoot = new object();
RollingHelper rollingHelper;
public RollingFlatFile(Encoding encoding, string fileName, long rollSizeKB = 1024, int maxNumberOfRollingLogs = 1, string timeStampPattern = " yyyy-MM-dd [hh-mm-ss] ")
{
this.encoding = encoding;
this.FileName = fileName;
this.RollSizeInBytes = rollSizeKB * 1024;
this.timeStampPattern = timeStampPattern;
this.MaxNumberOfRollingLogs = maxNumberOfRollingLogs;
if ( this.MaxNumberOfRollingLogs <= 0)
{
throw new ArgumentException( " Invalid number of rolling logs. ");
}
this.rollingHelper = new RollingHelper( this);
}
public long RollSizeInBytes
{
get;
set;
}
public int MaxNumberOfRollingLogs
{
get;
set;
}
string headerInfo = string.Empty;
public string HeaderInfo
{
get { return this.headerInfo; }
set
{
this.headerInfo = value;
InternalTracelog(value);
}
}
internal void InternalTracelog( string message)
{
LogEntry entry = new LogEntry( string.Empty, string.Empty, LogMessageDirection.None, message, LogMessageCategory.Info);
InternalTracelog(entry);
return;
}
internal void InternalTracelog( string format, params object[] args)
{
LogEntry entry = new LogEntry( string.Empty, string.Empty, LogMessageDirection.None, string.Format(format, args), LogMessageCategory.Info);
InternalTracelog(entry);
return;
}
internal void InternalTracelog(LogEntry entry)
{
lock ( this.syncRoot)
{
if ( this.rollingHelper != null)
{
List< string> lines = new List< string>();
StringBuilder argument = new StringBuilder();
argument.AppendFormat( " [{0:D2}/{1:D2}/{2:D4} {3:D2}:{4:D2}:{5:D2}.{6:D3}] ", entry.TimeStamp.Month, entry.TimeStamp.Day, entry.TimeStamp.Year, entry.TimeStamp.Hour, entry.TimeStamp.Minute, entry.TimeStamp.Second, entry.TimeStamp.Millisecond);
if ( this.rollingHelper.RollIfNecessary())
{
// write the header info
string value = string.Format( " {0} - {1} ", argument.ToString(), this.headerInfo);
lines.Add(value);
}
switch (entry.Direction)
{
case LogMessageDirection.InBound:
argument.Append( " - IN ");
break;
case LogMessageDirection.OutBound:
argument.Append( " - OUT ");
break;
}
switch (entry.Category)
{
case LogMessageCategory.Success:
argument.AppendFormat( " - [{0}] ", LogMessageCategory.Success.ToString());
break;
case LogMessageCategory.Warning:
argument.AppendFormat( " - [{0}] ", LogMessageCategory.Warning.ToString());
break;
case LogMessageCategory.Error:
argument.AppendFormat( " - [{0}] ", LogMessageCategory.Error.ToString());
break;
case LogMessageCategory.Info:
argument.AppendFormat( " - [{0}] ", LogMessageCategory.Info.ToString());
break;
default:
break;
}
argument.AppendFormat( " - {0} ", entry.Message);
lines.Add(argument.ToString());
this.rollingHelper.WriteLine(lines.ToArray());
}
}
return;
}
internal void InternalWriteLine()
{
lock ( this.syncRoot)
{
if ( this.rollingHelper != null)
{
this.rollingHelper.WriteLine( string.Empty);
}
}
return;
}
public void Dispose()
{
InternalClose();
return;
}
internal void InternalClose()
{
using ( this.rollingHelper) { }
this.rollingHelper = null;
return;
}
#region internal helper class
sealed class RollingHelper : IDisposable
{
RollingFlatFile owner;
StreamWriter writer;
public RollingHelper(RollingFlatFile owner)
{
this.owner = owner;
}
bool PerformsRolling
{
get { return this.owner.RollSizeInBytes > 0; }
}
public void WriteLine( params string[] values)
{
if ( this.writer == null)
{
CreateLogFile();
}
if ( this.writer != null)
{
foreach ( string value in values)
{
this.writer.WriteLine(value);
}
}
return;
}
private void Close()
{
using ( this.writer)
{
if ( this.writer != null)
{
this.writer.Close();
}
}
this.writer = null;
return;
}
public void Dispose()
{
Close();
return;
}
public bool RollIfNecessary()
{
bool wasRolled = false;
if ( this.PerformsRolling)
{
if (CheckIfRollNecessary())
{
PerformRoll();
wasRolled = true;
}
}
return wasRolled;
}
private void CreateLogFile()
{
System.Diagnostics.Debug.Assert( this.writer == null);
if (Directory.Exists(Path.GetDirectoryName( this.owner.FileName)) == false)
{
Directory.CreateDirectory(Path.GetDirectoryName( this.owner.FileName));
}
this.writer = new StreamWriter( this.owner.FileName, true, this.owner.encoding);
this.writer.AutoFlush = true;
return;
}
private bool CheckIfRollNecessary()
{
bool result = false;
if (File.Exists( this.owner.FileName))
{
FileInfo fileInfo = new FileInfo( this.owner.FileName);
// check for size roll, if enabled.
result = fileInfo.Length > this.owner.RollSizeInBytes;
}
return result;
}
private void PerformRoll()
{
DateTime rollDateTime = DateTime.Now;
string actualFileName = this.owner.FileName;
// calculate archive name
string archiveFileName = ComputeArchiveFileName(actualFileName, rollDateTime);
// close current file
Close();
// move file
SafeMove(actualFileName, archiveFileName, rollDateTime);
// delete rolling logs if needed
DeleteOldArchiveFiles(actualFileName);
// create a new file again
CreateLogFile();
return;
}
private void SafeMove( string actualFileName, string archiveFileName, DateTime currentDateTime)
{
try
{
if (File.Exists(archiveFileName))
{
File.Delete(archiveFileName);
}
// take care of tunneling issues http://support.microsoft.com/kb/172190
File.SetCreationTime(actualFileName, currentDateTime);
File.Move(actualFileName, archiveFileName);
}
catch (IOException)
{
// catch errors and attempt move to a new file with a GUID
archiveFileName = archiveFileName + Guid.NewGuid().ToString();
try
{
File.Move(actualFileName, archiveFileName);
}
catch (IOException) { }
}
return;
}
private string ComputeArchiveFileName( string actualFileName, DateTime currentDateTime)
{
string directory = Path.GetDirectoryName(actualFileName);
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(actualFileName);
string extension = Path.GetExtension(actualFileName);
// new archive name
string archiveFileNameWithTimestampWithoutExtension = fileNameWithoutExtension + " . " + currentDateTime.ToString( this.owner.timeStampPattern, CultureInfo.InvariantCulture);
return Path.Combine(directory, archiveFileNameWithTimestampWithoutExtension + extension);
}
private void DeleteOldArchiveFiles( string actualFileName)
{
string directory = Path.GetDirectoryName(actualFileName);
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(actualFileName);
string extension = Path.GetExtension(actualFileName);
DirectoryInfo info = new DirectoryInfo(directory);
FileInfo[] existingFiles = info.GetFiles( string.Format( " {0}*{1} ", fileNameWithoutExtension, extension));
List<FileInfo> deleteFiles = new List<FileInfo>();
Regex regex = new Regex( string.Format( @" {0}\.(.+).{1} ", fileNameWithoutExtension, extension));
for ( int index = 0; index < existingFiles.Length; index++)
{
Match sequenceMatch = regex.Match(existingFiles[index].FullName);
if (sequenceMatch.Success)
{
deleteFiles.Add(existingFiles[index]);
}
}
deleteFiles.Sort((x, y) => x.CreationTime < y.CreationTime ? 1 : x.CreationTime > y.CreationTime ? - 1 : 0);
for ( int index = this.owner.MaxNumberOfRollingLogs; index < deleteFiles.Count; index++)
{
try
{
deleteFiles[index].Delete();
}
catch
{
}
}
return;
}
}
#endregion
}
[Serializable]
public class LogEntry
{
public LogEntry(LogMessageDirection direction, string message, LogMessageCategory category, DateTime? timeStamp = null)
: this( null, null, direction, message, category, timeStamp)
{
}
public LogEntry( string serviceName, string identity, LogMessageDirection direction, string message, LogMessageCategory category, DateTime? timeStamp = null)
{
this.ServiceName = serviceName;
this.Identity = identity;
this.Direction = direction;
this.Message = message;
this.Category = category;
this.TimeStamp = timeStamp ?? DateTime.Now;
}
public readonly DateTime TimeStamp;
public readonly string ServiceName;
public readonly string Identity;
public readonly LogMessageDirection Direction;
public readonly LogMessageCategory Category;
public readonly string Message;
}
public interface IMessage
{
string Name { get; }
IMessage Predecessor { get; set; }
object PropertyBag { get; }
bool IsTransferMessage { get; set; }
// internal use only
bool IsSpecialMessage { get; }
byte[] ToArray();
string ToString();
}
public static class GlobalLoggers
{
#region RollingFlatFile extensions
public static void Tracelog( this RollingFlatFile entity, string message)
{
if (entity != null)
{
entity.InternalTracelog(message);
}
return;
}
public static void Tracelog( this RollingFlatFile entity, string format, params object[] args)
{
if (entity != null)
{
entity.InternalTracelog(format, args);
}
return;
}
public static void Tracelog( this RollingFlatFile entity, LogEntry entry)
{
if (entity != null)
{
entity.InternalTracelog(entry);
}
return;
}
public static void WriteLine( this RollingFlatFile entity)
{
if (entity != null)
{
entity.InternalWriteLine();
}
return;
}
public static void Close( this RollingFlatFile entity)
{
if (entity != null)
{
entity.InternalClose();
}
return;
}
#endregion
static Dictionary< string, RollingFlatFile> loggers = new Dictionary< string, RollingFlatFile>();
public static bool CreateLogger( string identity, Encoding encoding, string fileName, long rollSizeKB)
{
return CreateLogger(identity, encoding, fileName, rollSizeKB, 1);
}
public static bool CreateLogger( string identity, Encoding encoding, string fileName, long rollSizeKB, int maxNumberOfRollingLogs)
{
return CreateLogger(identity, encoding, fileName, rollSizeKB, maxNumberOfRollingLogs, " yyyy-MM-dd [hh-mm-ss] ");
}
public static bool CreateLogger( string identity, Encoding encoding, string fileName, long rollSizeKB, int maxNumberOfRollingLogs, string timeStampPattern)
{
bool result = loggers.ContainsKey(identity);
if (result == false)
{
lock (((ICollection)loggers).SyncRoot)
{
RollingFlatFile logger = new RollingFlatFile(encoding, fileName, rollSizeKB, maxNumberOfRollingLogs, timeStampPattern);
loggers.Add(identity, logger);
result = true;
}
}
return result;
}
public static void AttachLogger( string identity, RollingFlatFile logger)
{
lock (((ICollection)loggers).SyncRoot)
{
if (loggers.ContainsKey(identity))
{
throw new Exception( string.Format( " Logger '{0}' already exists! ", identity));
}
loggers.Add(identity, logger);
}
return;
}
public static void CloseAll()
{
try
{
lock (((ICollection)loggers).SyncRoot)
{
foreach (RollingFlatFile logger in loggers.Values)
{
using (logger) { }
}
loggers.Clear();
}
}
catch
{
}
return;
}
public static void Close( string identity)
{
lock (((ICollection)loggers).SyncRoot)
{
if (loggers.ContainsKey(identity))
{
using (loggers[identity]) { }
loggers.Remove(identity);
}
}
return;
}
public static RollingFlatFile Get( string identity)
{
RollingFlatFile result = null;
if (loggers.ContainsKey(identity))
{
result = loggers[identity];
}
return result;
}
}
}
TraceWriter.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace YourCompany.Logging
{
public sealed class TraceWriter : IDisposable, ILogger
{
RollingFlatFile writer;
Encoding encoding;
string initialOutputDirectory = string.Empty;
bool traceToDatabase = false;
bool deleteFileIfExists = false;
bool createSeparateDirectory = true;
bool leaveStreamOpen = false;
public TraceWriter(Encoding encoding, long rollingSizeKB, int maxNumOfRollingLogs, string callerIdentity, string initialOutputDirectory, bool traceToDatabase, bool deleteFileIfExists, bool createSeparateDirectory)
: this(encoding, rollingSizeKB, maxNumOfRollingLogs, callerIdentity, initialOutputDirectory, traceToDatabase, deleteFileIfExists, createSeparateDirectory, true)
{
}
public TraceWriter(Encoding encoding, long rollingSizeKB, int maxNumOfRollingLogs, string callerIdentity, string initialOutputDirectory, bool traceToDatabase, bool deleteFileIfExists, bool createSeparateDirectory, bool keepStreamOpen)
{
this.encoding = encoding;
this.rollSizeKB = rollingSizeKB;
this.maxNumberOfRollingLogs = maxNumOfRollingLogs;
this.initialOutputDirectory = initialOutputDirectory;
this.traceToDatabase = traceToDatabase;
this.deleteFileIfExists = deleteFileIfExists;
this.createSeparateDirectory = createSeparateDirectory;
this.callerIdentity = callerIdentity;
this.leaveStreamOpen = keepStreamOpen;
}
long rollSizeKB = 10 * 1024;
public long LogKByteThreshold
{
get { return this.rollSizeKB; }
set { this.rollSizeKB = value; }
}
int maxNumberOfRollingLogs = 1;
public int MaxNumberOfRollingLogs
{
get { return this.maxNumberOfRollingLogs; }
set { this.maxNumberOfRollingLogs = value; }
}
public string OutputPath
{
get
{
string outputDirectory = this.initialOutputDirectory;
try
{
if ( this.createSeparateDirectory)
{
string[] parts = this.sponsorName.Split( @" \/ ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
foreach ( string part in parts)
{
outputDirectory = Path.Combine(outputDirectory, part);
}
outputDirectory = Path.Combine(outputDirectory, this.callerIdentity.Substring( 0, this.callerIdentity.Length / 2));
outputDirectory = Path.Combine(outputDirectory, this.callerIdentity);
if (Directory.Exists(outputDirectory) == false)
{
Directory.CreateDirectory(outputDirectory);
}
}
}
catch
{
}
return outputDirectory;
}
}
string sponsorName = string.Empty;
public string SponsorName
{
get { return this.sponsorName; }
set { this.sponsorName = value; }
}
string callerIdentity = string.Empty;
public string CallerIdentity
{
get { return this.callerIdentity; }
set { this.callerIdentity = value; }
}
public void Tracelog( string message)
{
LogEntry entry = new LogEntry( string.Empty, string.Empty, LogMessageDirection.None, message, LogMessageCategory.Info);
Tracelog(entry);
return;
}
public void Tracelog( string format, params object[] arg)
{
LogEntry entry = new LogEntry( string.Empty, string.Empty, LogMessageDirection.None, string.Format(format, arg), LogMessageCategory.Info);
Tracelog(entry);
return;
}
public void Tracelog(LogEntry entry)
{
try
{
// write to database?
if ( this.traceToDatabase)
{
#if DEBUG
throw new NotSupportedException();
#endif
}
else
{
if ( string.IsNullOrEmpty( this.callerIdentity))
{
throw new InvalidOperationException( " Caller Identity not set yet! ");
}
if ( this.writer == null)
{
string filePath = Path.Combine( this.OutputPath, this.callerIdentity + " .log ");
if ( this.deleteFileIfExists)
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
this.writer = new RollingFlatFile( this.encoding, filePath, this.rollSizeKB, this.maxNumberOfRollingLogs);
}
this.writer.Tracelog(entry);
}
}
catch
{
if ( this.traceToDatabase == false)
{
Close();
}
}
return;
}
public void WriteLine()
{
if ( this.traceToDatabase == false)
{
if ( this.writer != null)
{
this.writer.WriteLine();
}
}
return;
}
void IDisposable.Dispose()
{
Close();
return;
}
public void Close()
{
using ( this.writer) { }
this.writer = null;
return;
}
public void Write( string serviceName, string loggingIdentity, LogMessageDirection direction, string message)
{
LogEntry entry = new LogEntry(serviceName, loggingIdentity, direction, message, LogMessageCategory.Info);
Tracelog(entry);
return;
}
public void Write( string serviceName, string loggingIdentity, string message)
{
LogEntry entry = new LogEntry(serviceName, loggingIdentity, LogMessageDirection.None, message, LogMessageCategory.Info);
Tracelog(entry);
return;
}
public void GlobalWrite( string message, LogMessageCategory category)
{
LogEntry entry = new LogEntry(String.Empty, String.Empty, LogMessageDirection.None, message, category);
Tracelog(entry);
return;
}
}
}
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace YourCompany.Logging
{
public sealed class TraceWriter : IDisposable, ILogger
{
RollingFlatFile writer;
Encoding encoding;
string initialOutputDirectory = string.Empty;
bool traceToDatabase = false;
bool deleteFileIfExists = false;
bool createSeparateDirectory = true;
bool leaveStreamOpen = false;
public TraceWriter(Encoding encoding, long rollingSizeKB, int maxNumOfRollingLogs, string callerIdentity, string initialOutputDirectory, bool traceToDatabase, bool deleteFileIfExists, bool createSeparateDirectory)
: this(encoding, rollingSizeKB, maxNumOfRollingLogs, callerIdentity, initialOutputDirectory, traceToDatabase, deleteFileIfExists, createSeparateDirectory, true)
{
}
public TraceWriter(Encoding encoding, long rollingSizeKB, int maxNumOfRollingLogs, string callerIdentity, string initialOutputDirectory, bool traceToDatabase, bool deleteFileIfExists, bool createSeparateDirectory, bool keepStreamOpen)
{
this.encoding = encoding;
this.rollSizeKB = rollingSizeKB;
this.maxNumberOfRollingLogs = maxNumOfRollingLogs;
this.initialOutputDirectory = initialOutputDirectory;
this.traceToDatabase = traceToDatabase;
this.deleteFileIfExists = deleteFileIfExists;
this.createSeparateDirectory = createSeparateDirectory;
this.callerIdentity = callerIdentity;
this.leaveStreamOpen = keepStreamOpen;
}
long rollSizeKB = 10 * 1024;
public long LogKByteThreshold
{
get { return this.rollSizeKB; }
set { this.rollSizeKB = value; }
}
int maxNumberOfRollingLogs = 1;
public int MaxNumberOfRollingLogs
{
get { return this.maxNumberOfRollingLogs; }
set { this.maxNumberOfRollingLogs = value; }
}
public string OutputPath
{
get
{
string outputDirectory = this.initialOutputDirectory;
try
{
if ( this.createSeparateDirectory)
{
string[] parts = this.sponsorName.Split( @" \/ ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
foreach ( string part in parts)
{
outputDirectory = Path.Combine(outputDirectory, part);
}
outputDirectory = Path.Combine(outputDirectory, this.callerIdentity.Substring( 0, this.callerIdentity.Length / 2));
outputDirectory = Path.Combine(outputDirectory, this.callerIdentity);
if (Directory.Exists(outputDirectory) == false)
{
Directory.CreateDirectory(outputDirectory);
}
}
}
catch
{
}
return outputDirectory;
}
}
string sponsorName = string.Empty;
public string SponsorName
{
get { return this.sponsorName; }
set { this.sponsorName = value; }
}
string callerIdentity = string.Empty;
public string CallerIdentity
{
get { return this.callerIdentity; }
set { this.callerIdentity = value; }
}
public void Tracelog( string message)
{
LogEntry entry = new LogEntry( string.Empty, string.Empty, LogMessageDirection.None, message, LogMessageCategory.Info);
Tracelog(entry);
return;
}
public void Tracelog( string format, params object[] arg)
{
LogEntry entry = new LogEntry( string.Empty, string.Empty, LogMessageDirection.None, string.Format(format, arg), LogMessageCategory.Info);
Tracelog(entry);
return;
}
public void Tracelog(LogEntry entry)
{
try
{
// write to database?
if ( this.traceToDatabase)
{
#if DEBUG
throw new NotSupportedException();
#endif
}
else
{
if ( string.IsNullOrEmpty( this.callerIdentity))
{
throw new InvalidOperationException( " Caller Identity not set yet! ");
}
if ( this.writer == null)
{
string filePath = Path.Combine( this.OutputPath, this.callerIdentity + " .log ");
if ( this.deleteFileIfExists)
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
this.writer = new RollingFlatFile( this.encoding, filePath, this.rollSizeKB, this.maxNumberOfRollingLogs);
}
this.writer.Tracelog(entry);
}
}
catch
{
if ( this.traceToDatabase == false)
{
Close();
}
}
return;
}
public void WriteLine()
{
if ( this.traceToDatabase == false)
{
if ( this.writer != null)
{
this.writer.WriteLine();
}
}
return;
}
void IDisposable.Dispose()
{
Close();
return;
}
public void Close()
{
using ( this.writer) { }
this.writer = null;
return;
}
public void Write( string serviceName, string loggingIdentity, LogMessageDirection direction, string message)
{
LogEntry entry = new LogEntry(serviceName, loggingIdentity, direction, message, LogMessageCategory.Info);
Tracelog(entry);
return;
}
public void Write( string serviceName, string loggingIdentity, string message)
{
LogEntry entry = new LogEntry(serviceName, loggingIdentity, LogMessageDirection.None, message, LogMessageCategory.Info);
Tracelog(entry);
return;
}
public void GlobalWrite( string message, LogMessageCategory category)
{
LogEntry entry = new LogEntry(String.Empty, String.Empty, LogMessageDirection.None, message, category);
Tracelog(entry);
return;
}
}
}
配置文件:

<
appSettings
>
<!-- Specify the logging folder -->
<!-- Format:
1). Specific folder, e.g. D:\Logs\
2). Environment folder, e.g. {ApplicationData}\ABC\Logs\
{MyDocuments}\ABC\Logs\ -->
< add key ="LogFolder" value ="D:\Logs" />
<!-- Specify log file maximum rolling size (KB) -->
< add key ="MaxFileSize" value ="5000" />
<!-- Specify maximum numbers of split log files creation when rolling size exceeded -->
< add key ="MaxNumofRollingFlags" value ="50" />
<!-- Writing warning entry in log files may cause performance loss in case of a huge amount of rubbish in excel file! -->
<!-- Set this option to 'false' to get better performance for large file and high pressure system -->
< add key ="WriteWarningLogs" value ="True" />
<!-- Writing information log entries in log files may cause performance loss in case of a huge amount of rubbish in excel file! -->
<!-- Set this option to 'false' to get better performance for large file and high pressure system -->
< add key ="WriteInfoLogs" value ="True" />
<!-- The time interval between invokation of timer call back. - This timer is used to check if there're log entry in concurrent logging queue. -->
<!-- 15000 means 15 second. To allow concurrent logging, writting log entry is put into concurrent queue. Once a while the log items in the queue will be flushed into log file. -->
< add key ="WritLogQueueCheckPeriod" value ="5000" />
</ appSettings >
<!-- Specify the logging folder -->
<!-- Format:
1). Specific folder, e.g. D:\Logs\
2). Environment folder, e.g. {ApplicationData}\ABC\Logs\
{MyDocuments}\ABC\Logs\ -->
< add key ="LogFolder" value ="D:\Logs" />
<!-- Specify log file maximum rolling size (KB) -->
< add key ="MaxFileSize" value ="5000" />
<!-- Specify maximum numbers of split log files creation when rolling size exceeded -->
< add key ="MaxNumofRollingFlags" value ="50" />
<!-- Writing warning entry in log files may cause performance loss in case of a huge amount of rubbish in excel file! -->
<!-- Set this option to 'false' to get better performance for large file and high pressure system -->
< add key ="WriteWarningLogs" value ="True" />
<!-- Writing information log entries in log files may cause performance loss in case of a huge amount of rubbish in excel file! -->
<!-- Set this option to 'false' to get better performance for large file and high pressure system -->
< add key ="WriteInfoLogs" value ="True" />
<!-- The time interval between invokation of timer call back. - This timer is used to check if there're log entry in concurrent logging queue. -->
<!-- 15000 means 15 second. To allow concurrent logging, writting log entry is put into concurrent queue. Once a while the log items in the queue will be flushed into log file. -->
< add key ="WritLogQueueCheckPeriod" value ="5000" />
</ appSettings >
寫日志:

Logger.GlobalWrite(
"
abc
",LogMessageCategory.Success);
本文結束。本文實現了一個最簡單的寫日志的組件。 實現了下面的幾個功能:
- 可以用FileAppender的方式記日志
- 線程安全,支持多個線程同時Append同一日志文件
- 支持FileRolling,設置大小自動分卷