Limited Length Logging
Preventing a log file from growing forever
Introduction
This example is a simple class that provides a method to write to a log file and control its size. It lets you set the maximum number of lines that will be maintained in the log. Once the max number of lines have been reached then the oldest entry is taken off the top each time a new line is added.
Background
Several years ago I worked with a team that was dealing with a serious customer issue where errors being logged over several years had actually filled the systems hard drive completely. Lesson learned!
For me it is important that programs elegantly log any errors - especially services that have no UI where there is no place to display errors for the users. It's also important to make sure the logging doesn't just go on forever or you, at the very least, will be wasting a lot of disc space; a worst you could wind up using up all of the available disc space and cause serious problems for the end user. This class, written in VB.NET, lets you define the output log file name and the maximum number of lines to maintain. The default is 99 lines.
Using the code
To use this class create a new class in your project and then cut and past the class code below into the new class (replacing everything).
You can then create the ErrorLogger object as follows:
Dim MyLogger as New ErrorLogger(MyLogfilename, 150)
There are two methods in this class: Logit
and MakeEventLogEntry
. MakeEventLogEntry
is for adding an entry to the system Event Viewer Application log. You can either make a separate call to MakeEventLogEntry
, or whenever you call the Logit
method you can set the AddToSystemApplicationLog
flag to true and any message you place into your own log file will be added to the System Application log. The flag is optional and defaults to False
. This uses the optional parameter feature offered in VB.NET (I know many of you think that this is just syntactacal candy - but it requires less code to do this than to overload the method).
There are two properties: LogFilename
and MaxLogLines
. Both have defaults set (explained in the Intellisense). You can set these when instantiating an object from the class using the New()
keyword, or you can set them separately.
Here is the class - including Intellisense.
Imports System.IO
Public Class ErrorLogger
''' <summary>
''' Creates a new instance of the ErrorLogger
''' </summary>
''' <param name="MaxLogLines">The maximum number of lines to maintain in the log file</param>
''' <param name="LogFileName">The fully qualified path and name of the log file</param>
''' <remarks></remarks>
Sub New(ByVal LogFileName As String, ByVal MaxLogLines As Integer)
Me.LogFileName = New FileInfo(LogFileName)
Me.MaxLogLines = MaxLogLines
End Sub
''' <summary>
''' Creates a new instance of the ErrorLogger
''' </summary>
''' <param name="LogFileName">The fully qualified path and name of the log file</param>
''' <remarks></remarks>
Sub New(ByVal LogFileName As String)
Me.LogFileName = New FileInfo(LogFileName)
End Sub
''' <summary>
''' Creates a new instance of the ErrorLogger with the default settings
''' of MaxLogLines (99) and LogFileName ([directorypath\assmblyname].log).
''' </summary>
''' <remarks></remarks>
Sub New()
'accept a New without paramters and use the defaults for LogFileName and MaxLogLines
End Sub
''' <summary>
''' Gets/Sets the maximum number of lines the log will contain. This is circular so once the
''' maximum number of lines is reached (specified with MaxLogLines property) a line will be removed from the top of the file.
''' </summary>
Public Property MaxLogLines As Integer = 99
Private _LogFileName As FileInfo = New FileInfo(Path.Combine(My.Application.Info.DirectoryPath, _
System.Reflection.Assembly.GetEntryAssembly().GetName().Name & ".log"))
''' <summary>
''' Gets/Sets the fully qualified path for the log file. If the path to the log
''' file specified does not exist it will throw an exception.
''' </summary>
Public Property LogFileName As FileInfo
Get
Return _LogFileName
End Get
Set(value As FileInfo)
If Not value.Directory.Exists Then
Throw New ArgumentException("Folder/Path for specified log file name does not exist!")
End If
_LogFileName = value
End Set
End Property
''' <summary>
'''Places a message into the log file (defined with the property LogFileName).
''' </summary>
''' <param name="Message">The message you wish to log</param>
''' <param name="AddToSystemApplicationLog">If True then the message
''' will also be placed into the Windows system Application Eventlog</param>
''' <remarks></remarks>
Public Sub Logit(ByVal Message As String, Optional ByVal AddToSystemApplicationLog As Boolean = False)
Static LogLines As New List(Of String)
If LogLines.Count = 0 Then
If Me.LogFileName.Exists Then
Try
LogLines = IO.File.ReadAllLines(Me.LogFileName.FullName).ToList
Catch ex As Exception
Try
MakeEventlogEntry("Cannot read log file!")
Catch exx As Exception
Exit Sub 'There's no place to log anything
End Try
Exit Sub 'there's nothing we can do really...
End Try
End If
End If
'clean up the message
If Message.EndsWith(Environment.NewLine) Then
Message = Message.Substring(0, Message.Length - 2)
End If
Message = System.DateTime.Now & " " & Message
LogLines.Add(Message)
'make sure we keep only MaxEntries entries
Do While LogLines.Count > Me.MaxLogLines
LogLines.RemoveAt(0)
Loop
Try
IO.File.WriteAllLines(Me.LogFileName.FullName, LogLines)
If AddToSystemApplicationLog Then
MakeEventlogEntry(Message)
End If
Catch ex As Exception
Try
MakeEventlogEntry("Cannot write to log file!")
Catch exx As Exception
Exit Sub 'There's no place to log anything
End Try
Exit Sub 'There's no where to log it so just ignore error
End Try
End Sub
''' <summary>
''' Places the message into the Windows Eventlog Application section
''' </summary>
''' <param name="Message"></param>
''' <remarks></remarks>
Public Sub MakeEventlogEntry(Message As String)
Dim sSource As String = My.Application.Info.ProductName
Dim sLog As String = "Application"
Dim sMachine As String = "."
Dim ESCD As New EventSourceCreationData(sSource, sLog)
If Not EventLog.SourceExists(sSource, sMachine) Then
System.Diagnostics.EventLog.CreateEventSource(ESCD)
EventLog.WriteEntry(sSource, Message, EventLogEntryType.Error)
End If
End Sub
End Class
You will notice there are three New()
overloads. Since the LogFilename
and MaxLogLines
properties both have defaults these overloaded New()
methods let you accept either, none or both of them.
Points of Interest
Adding the MakeEventlogEntry
method may be overkill, but I use this class when I create services - and I think most sophisticated users expect service errors to be logged in the Event Log.
The number of entries in the log file should be set only once at run-time and never changed. If it is the program will take care of it, but its more likely you may confuse your user if they visit the log file often.
Scaling
As another Code Project contributor has aptly pointed out; this method of logging does not scale up very much. If your logging needs exceed about 5 to 10k (gut numbers here...) of logging then you should consider alternate methods. This class essentially rewrites the entire log file each time, so there would be a performance and memory cost associated with it for large logs. In the example above I have limited the log to 150 lines, and the default in the class is 99. These numbers will have little impact on most any application that will benefit from this smaller logging requirement. If, however, your application needs 5-thousand lines - this is not the method for you.