|
I finally got around to doing some research on this, and you're 100% correct about the threads.
They take a lot of cycles setting up, and almost as much winding down. In addition, they allocate quite a bit of memory as well.
In my research, it turns out that Thread.Sleep is best used for testing, not production, and if used in production, it should be Thread.Sleep(0), which is almost of no use, LOL!
Thanks for the heads up, now I have some of my code to fix up!
|
|
|
|
|
Very nice Mehdi looks like a very useful tool.
Thanks for your effort.
|
|
|
|
|
Thanks Mike!
Its the man, not the machine - Chuck Yeager
If at first you don't succeed... get a better publicist
If the final destination is death, then we should enjoy every second of the journey.
|
|
|
|
|
I have a test project in my solution which uses nUnit.
For the first test it is OK, but after that any test will fail with:
nUnitTester.Tester.EntitySaveAndLoad:
SetUp : System.IO.IOException : The process cannot access the file \Test\nUnitTester\bin\Debug\logs\Test.log; because it is being used by another process.
|
|
|
|
|
NUnit keeps the session/objects active in memory, so the files are still open.
Try closing the nunit gui and running again.
Its the man, not the machine - Chuck Yeager
If at first you don't succeed... get a better publicist
If the final destination is death, then we should enjoy every second of the journey.
|
|
|
|
|
Closing the GUI solves the problem, but it is frustrating.
Any way to close the file or dispose the logger I can put in TearDown?
|
|
|
|
|
Yes, null the objects you are using.
You can also change the private shutdown method to public and call that when you want.
Its the man, not the machine - Chuck Yeager
If at first you don't succeed... get a better publicist
If the final destination is death, then we should enjoy every second of the journey.
|
|
|
|
|
|
Hi Mehdi
thanks for this nice piece of code!!
I'm using your logger in a webservice and I noticed it doesn't act properly with date logs: it continues to log on the same file.... the problem is it store the _lastFileDate in memory but if the process is recycled it resets the value and so it doesn't create a new file...
Thank you!
|
|
|
|
|
Interesting, I will look into it.
Its the man, not the machine - Chuck Yeager
If at first you don't succeed... get a better publicist
|
|
|
|
|
Here is my modified version that fit my needs: it appends datetime to filename and then compare current datetime with the one extracted from filename in order to change log file; I also added a LogVerbosity to set threshold logging message...
using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Threading;
using System.Globalization;
namespace MyLogger
{
public interface ILog
{
void Debug(object msg, params object[] objs);
void Error(object msg, params object[] objs);
void Info(object msg, params object[] objs);
void Warn(object msg, params object[] objs);
void Fatal(object msg, params object[] objs);
}
public enum LogVerbosity
{
FATAL = 0,
ERROR,
WARNING,
INFO,
DEBUG
}
internal class FileLogger
{
public static readonly FileLogger Instance = new FileLogger();
private FileLogger()
{
AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
AppDomain.CurrentDomain.DomainUnload += new EventHandler(CurrentDomain_ProcessExit);
_worker = new Thread(new ThreadStart(Writer));
_worker.IsBackground = true;
_worker.Start();
}
private Thread _worker;
private bool _working = true;
private Queue _que = new Queue();
private StreamWriter _output;
private string _baseFilename;
private string _filename;
private int _sizeLimit = 0;
private long _lastSize = 0;
private DateTime _lastFileDate;
private bool _showMethodName = false;
private string _FilePath = "";
private const string dateTimeFormat = "yyyy-MM-dd";
private LogVerbosity _verbosity = LogVerbosity.DEBUG;
public bool ShowMethodNames
{
get { return _showMethodName; }
}
public LogVerbosity Verbosity
{
get { return _verbosity; }
set { _verbosity = value; }
}
public void Init(string filename, int sizelimitKB, bool showmethodnames, LogVerbosity verbosity)
{
_showMethodName = showmethodnames;
_sizeLimit = sizelimitKB;
_baseFilename = filename;
_filename = buildFilename();
_verbosity = verbosity;
_FilePath = Path.GetDirectoryName(filename);
if (_FilePath != "")
{
_FilePath = Directory.CreateDirectory(_FilePath).FullName;
if (_FilePath.EndsWith("\\") == false)
_FilePath += "\\";
}
_output = new StreamWriter(_filename, true);
FileInfo fi = new FileInfo(_filename);
_lastSize = fi.Length;
_lastFileDate = fi.LastWriteTime;
}
private string buildFilename()
{
return Path.Combine(Path.GetDirectoryName(_baseFilename), string.Format("{0}_{1}{2}", Path.GetFileNameWithoutExtension(_baseFilename), DateTime.Today.ToString(dateTimeFormat), Path.GetExtension(_baseFilename)));
}
private void shutdown()
{
_working = false;
_worker.Abort();
}
void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
shutdown();
}
private void Writer()
{
while (_working)
{
while (_que.Count > 0)
{
object o = _que.Dequeue();
if (_output != null && o != null)
{
if (_sizeLimit > 0)
{
#region [ rolling size limit ]
_lastSize += ("" + o).Length;
if (_lastSize > _sizeLimit * 1000)
{
_output.Flush();
_output.Close();
int count = 1;
while (File.Exists(_FilePath + Path.GetFileNameWithoutExtension(_filename) + "." + count.ToString("0000")))
count++;
File.Move(_filename,
_FilePath +
Path.GetFileNameWithoutExtension(_filename) +
"." + count.ToString("0000"));
_output = new StreamWriter(_filename, true);
_lastSize = 0;
}
#endregion
}
string fileDate = _filename.Substring(_filename.LastIndexOf('_') + 1, dateTimeFormat.Length);
DateTime fileDateFromName = DateTime.ParseExact(fileDate, dateTimeFormat, CultureInfo.InvariantCulture);
if (DateTime.Now.Subtract(_lastFileDate).Days > 0 || DateTime.Now.Subtract(fileDateFromName).Days > 0)
{
#region [ rolling dates ]
_output.Flush();
_output.Close();
_filename = buildFilename();
_output = new StreamWriter(_filename, true);
_lastFileDate = DateTime.Now;
_lastSize = 0;
#endregion
}
_output.Write(o);
}
}
if (_output != null)
_output.Flush();
Thread.Sleep(500);
}
_output.Flush();
_output.Close();
}
private string FormatLog(string log, string type, string meth, string msg, object[] objs)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(
"" + DateTime.Now.ToString("dd'/'MM'/'yyyy HH:mm:ss.fff") +
"|" + log +
"|" + Thread.CurrentThread.ManagedThreadId +
"|" + type +
"|" + meth +
"| " + string.Format(msg, objs));
return sb.ToString();
}
public void Log(string logtype, string type, string meth, string msg, params object[] objs)
{
_que.Enqueue(FormatLog(logtype, type, meth, msg, objs));
}
}
internal class logger : ILog
{
public logger(Type type)
{
typename = type.Namespace + "." + type.Name;
}
private string typename = "";
private void log(string logtype, string msg, params object[] objs)
{
string meth = "";
if (FileLogger.Instance.ShowMethodNames)
{
System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace(2);
System.Diagnostics.StackFrame sf = st.GetFrame(0);
meth = sf.GetMethod().Name;
}
FileLogger.Instance.Log(logtype, typename, meth, msg, objs);
}
#region ILog Members
public void Debug(object msg, params object[] objs)
{
if(FileLogger.Instance.Verbosity >= LogVerbosity.DEBUG)
log("DEBUG", "" + msg, objs);
}
public void Error(object msg, params object[] objs)
{
if (FileLogger.Instance.Verbosity >= LogVerbosity.ERROR)
log("ERROR", "" + msg, objs);
}
public void Info(object msg, params object[] objs)
{
if (FileLogger.Instance.Verbosity >= LogVerbosity.INFO)
log("INFO", "" + msg, objs);
}
public void Warn(object msg, params object[] objs)
{
if (FileLogger.Instance.Verbosity >= LogVerbosity.WARNING)
log("WARN", "" + msg, objs);
}
public void Fatal(object msg, params object[] objs)
{
if (FileLogger.Instance.Verbosity >= LogVerbosity.FATAL)
log("FATAL", "" + msg, objs);
}
#endregion
}
public static class LogManager
{
public static ILog GetLogger(Type obj)
{
return new logger(obj);
}
public static void Configure(string filename, int sizelimitKB, bool showmethodnames, LogVerbosity verbosity)
{
FileLogger.Instance.Init(filename, sizelimitKB, showmethodnames, verbosity);
}
}
}
|
|
|
|
|
Great job
Its the man, not the machine - Chuck Yeager
If at first you don't succeed... get a better publicist
|
|
|
|
|
Can I use this to log to a database table instead of a text file?
|
|
|
|
|
Not in this version I'm afraid, you can however write the database code yourself, just implement your own writer method.
Its the man, not the machine - Chuck Yeager
If at first you don't succeed... get a better publicist
|
|
|
|
|
nice article.
Would be best if you mentioned the features your version is missing compared to the original log4net.
|
|
|
|
|
Short answer :
if you don't already know about log4net, then you don't need to, use this code instead.
if you know log4net already, then there is not much I can tell you.
Long answer:
- log4net has a lot of output sinks e.g. database, console, remote computer etc.
- log4net has an extensive configuration format which controls every aspect of where and how messsages are sent e.g. console output, formatting of text file
- you will spend a lot of time trying to figure out how to setup the config file and probably just copy that in new projects and never look back. ( a pain to reconfigure)
Its the man, not the machine - Chuck Yeager
|
|
|
|
|
"you will spend a lot of time trying to figure out how to setup the config file and probably just copy that in new projects and never look back. ( a pain to reconfigure )"
This is certainly the truth. You could accomplish the same level of flexibility by just exposing events such as 'before log' and 'after log' etc and implement a single method versus a config file... I understand that there is the need to be flexible, but not variable ways in one application, at least not usually. Certainly you could be 'polyglot' logging in one app, but once you set it up, you're not likely to change it. Kind of obviates the need to expose the entire configuration in config files since it's not a "pipeline only" solution in that you have to actually call the logger in code.
|
|
|
|
|