不用Log4net用并发队列ConcurrentQueue自己实现写日志的组件(C#)

项目中要实现写日志Logging,但客户不想用Log4net,说不想用任何第三方组件,好吧,我们自己写一个简单的记日志的组件吧。但要实现下面的几个功能:

  • 可以用FileAppender的方式记日志
  • 线程安全,支持多个线程同时Append同一日志文件
  • 支持FileRolling,设置大小自动分卷

我们知道是Log4net是线程安全的,虽然也有人说不是进程安全的。我们自己实现,要支持多个线程同时Append同一日志文件,需要加锁。这里我们用并发队列ConcurrentQueue来实现(.NET Framework 4.0 及以上),思路就是当线程请求写日志,首先把请求放入队列,然后批量一次写入。实现最高性能。由于写入的瓶颈在IO上,而文件无需频繁打开,异步写入的方式自然性能高很多。缺点是机器掉电的话,队列里面的日志请求丢失。具体实现是当请求写日志的时候队列入队Queue.Enqueue,然后每隔几秒钟批量写入日志,出队(while (Queue.TryDequeue(out entry)))。

 

 

ILogger.cs

 

View Code
 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 }

 

 

Logger.cs

View Code
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, null0 /* 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 { getset; }

        public string SponsorName { getset; }

        public string CallerIdentity { getset; }

        public string ConnectionString { getset; }

        public bool GlobalTraceIsOn { getset; }

        public string ServiceName { getset; }

        public string Identity { getset; }

        public object CallContextObj { getset; }

        public object LogContextObj { getset; }

        public Encoding ProtocolEncoding { getset; }

        public bool IsCancelSignaled { getset; }

        public bool LoggingEnabled { getset; }

        public LogMessageOption LoggingOption { getset; }

        public ReadOnlyCollection<object> Notifiers { getset; }
    }
    public interface IConfigurationSite
    {
        string OutputPath { get; }
        string SponsorName { getset; }
        string CallerIdentity { getset; }
        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

 

View Code
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 = 1024int maxNumberOfRollingLogs = 1string 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, truethis.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(nullnull, 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 { getset; }

        object PropertyBag { get; }
        bool IsTransferMessage { getset; }
        // 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

View Code
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(0this.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;
        }
    }
}

 

配置文件:

View Code
<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>

 

写日志:

View Code
Logger.GlobalWrite("abc",LogMessageCategory.Success);


本文结束。本文实现了一个最简单的写日志的组件。 实现了下面的几个功能:

  • 可以用FileAppender的方式记日志
  • 线程安全,支持多个线程同时Append同一日志文件
  • 支持FileRolling,设置大小自动分卷

 

 

posted on 2012-08-04 15:15  Mainz  阅读(3146)  评论(5编辑  收藏  举报

导航