views:

130

answers:

7

I am trying to figure out the best strategy for logging on the async-IO web server I am working on. I thought the easiest way was to have a singleton class that keeps Filestreams open for the appropriate log files so I could just do something like:

Util.Logger.GetInstance().LogAccess(str);

Or something like that.

My class looks like this:

public sealed class Logger {
   private static StreamWriter sw;
   private static readonly Logger instance = new Logger();
   private Logger() {
      sw = new StreamWriter(logfile);
   }
   public static Logger GetInstance() {
      return instance;
   }
   public void LogAccess(string str) {
      sw.WriteLine(str);
   }
}

This is all just in my head really, and I'm looking for suggestions on how to make it better and also make sure I'm doing it properly. The biggest thing is I need it to be thread safe, which obviously it is not in its current state. Not sure of the best way to do that.

A: 

I don't think there's any simple way around locking if you want to write to the same file from multiple threads.

So the simple solution is to add a lock around any calls to the StreamWriter. Alternatively you could buffer the output in memory and only write it to the file once in a while which still requires locking, but the lock contention would be a lot lower. If you go to that length, though, you might as well go with a proper logging framework like log4net, which is thread-safe.

Evgeny
+2  A: 

a) Do not include "Log" in method names. It obvious that a logger logs. .Warning, .Error, etc are better method names since they describe which level the log entry has.

b) Create a background thread that writes to the log.

c) Enqueue entries from the logging methods and signal the worker thread.

d) Use (I don't know if I remember the method names correctly)

var methodInfo = new StackFrame(1).GetMethod();
var classAndMethod = methodInfo.DeclaringType.Name + "." + methodInfo.Name;

to get the calling method.

Doing that will give you only one thread that access the file.

jgauffin
I don't agree with (a), because `Warning` and `Error` are not verbs. Method names should be verbs by convention. `Warn` would be better, but how should we name methods that log errors and critical errors? `LogWarning` and `LogError` are IMO much better, or I'd rather use a generic method called `Log` with a `severity` argument that displays the type. While (b) could increase performance, most logging frameworks don't log in the background. It is tricky, and normally much safer to execute synchroniously.
Steven
Theres also a rule that says that the class name should not be repeated in the method names. In this case it's all about taste since it's quite obvious that Error and LogError do the same thing. I prefer a Error method instead of a Log method with a severity parameter since it's less to type and read. But as I said: It's all about taste. (b) I don't see how writing to logs in a separate thread is tricky or unsafe. It's quite straight forward. A thread, a queue and a lock, nothing more is needed
jgauffin
I didn't say it would be hard to implement; the thing is that logging is often an important part of a business process and in that case you don't want to continue processing before you know that your log message is persisted. Of course in other scenario's it could be fine to keep a queue of log messages. It depends. But for that reason logging frameworks usually process them synchronously.
Steven
An application can shutdown when a fatal exception is raised in two ways: A) You shut it down, then simply wait for the logging thread to exit after you have signaleed it (to let it write all entries). b) A unhandled exception terminates your application. Here you got a point if threads are terminated in middle of their work. I think they are. If catching the unhandled exception to be able to write it to something isn't enough, then it async logging isn't for you.
jgauffin
Can someone tell if all threads are stopped before the AppDomain.CurrentDomain.UnhandledException event have been raised?
jgauffin
+4  A: 

This is taken care of for you automatically if you use NLog - you define all of your loggers in a .config file and then you access all of them via the static LogManager class, which is a Singleton.

Here's an example which illustrates the thread-safe nature of NLog:

http://nlog-project.org/wiki/Tutorial#Adding_NLog_to_an_application

Aaronontheweb
+3  A: 

There's a method TextWriter.Synchronized which produces a thread-safe version of TextWriter. Try that.

Drew Hoskins
+1  A: 

Maybe you should try NLog or Log4net. Both of them are wonderful log framework.

But if you do want to write your own log component, Lock is a must when you output log messages. It's common that buffering log messages in memory and write them to file once in a time.

Jun1st
+1  A: 

Another framework that addreses these issues for you is the Object Guy's logging framework. It can optionally log in the background. Multiple threads can log to the same file. And multiple processes can log to the same file.

S. Mills
A: 

Or you could use a class with only shared methods..

Imports System.Threading

Public Class Logger
    Private Shared ReadOnly syncroot As New Object

    Public Shared Sub log(ByVal vInt As Integer)
        ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf logThread), CStr(vInt))
    End Sub

    Public Shared Sub log(ByVal vStr As String)
        ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf logThread), vStr)
    End Sub

    Private Shared Sub logThread(ByVal o As Object)
        Dim str As String = CStr(o)
        SyncLock syncroot
            Using objWriter As New System.IO.StreamWriter(GetLogPath, True)

                objWriter.WriteLine(str)
                objWriter.Close()

            End Using
        End SyncLock
    End Sub

    Private Shared Function GetLogPath() As String
        Return "logs.txt"
    End Function
End Class

I found it more usable this way than using a singleton :

Logger.log("Something to log")

Cheers

Alex Rouillard