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;
}
}
}