출처: https://www.codeproject.com/Articles/15364/LogString-A-Simple-C-Application-Event-Logging-C
Introduction
When interfacing to real-world devices, it is typical to have one or more background processing tasks responsible for collecting data and/or controlling the devices. Monitoring these tasks can be done in a variety of ways, some of which can involve complex GUI components for status reporting and user interaction for control. As the name implies, the LogString
class provides only a subset of this type of functionality.
There are many logging frameworks and APIs available, along with tools to deal with the Windows Event Log. I counted no less than 30 related CodeProject articles. While you could possibly use some of these tools, their functionality is not generally applicable to the requirements described below. That said, I should note that the project LogString
was developed for uses log4net [^] for its system logging. The difference, of course, is that the two logging facilities (log4net
and LogString
) are used for completely different purposes.
This article also details how I used some basic .NET 2.0 Framework capabilities in the solution. There are no tricks or undocumented system features here. You can think of this as a C# 2 beginner's tutorial that shows some of the language features that would be useful in your day-to-day development work.
Requirements
The development of the LogString
class came about because the application needed the ability for the user to monitor the activity of multiple background processing tasks in real-time. In other words, they needed to be able to view processing events as they happened. This is shown here:
The specific requirements are:
- Multiple application-based log strings ("Task-1", "Task-2", etc.).
- Each application log is relatively low volume. For my purposes, this meant that there are typically no more than 1 or 2 log events every couple of minutes.
- The total size of a logging
string
is limited to the last 750-1000 event entries. Older events are not needed and are permanently discarded.
[Note: This is the currentLogString
behavior, but a more practical implementation will have the ability to maintain a much longer log history.] - Multiple background processing tasks and viewers shall be able to simultaneously access each log, i.e. thread-safe operation.
- Log viewers shall have the ability receive asynchronous notification that the log has been updated.
- Simple persistence model.
- The primary viewer is a read-only multi-line TextBox control with the latest log entries shown at the top.
LogString Class
LogString
is implemented as a single class and has a very simple interface:
Static Methods
static LogString GetLogString(string name); // Get a LogString instance
static void PersistAll(); // Save all LogString instances
static void ClearAll(); // Clear the contents of all LogString instances
static void RemoveLogString(string name); // Remove the named LogString instance
Instance Methods
void Add(string message); // Add a message
void Persist(); // Save this LogString instance
void Clear(); // Clear this LogString instance
Instance Properties
string Log; // This is the log string
delegate void LogUpdateDelegate(); // The notification delegate
event LogUpdateDelegate OnLogUpdate; // The update event
Option properties: See table.
Type</strong /> | Property Name | Description | Default Value |
bool | ReverseOrder | If true , add new entries to the start of the log. This makes viewing real-time updates easier because new items appear at the top while older entries scroll off the bottom. If set to false , new entries are appended to the end of the log text. | true |
bool | LineTerminate | Terminate each new entry with CRLF. | true |
bool | TimeStamp | Add timestamp to each log entry. If false , no timestamp is added. | true |
int | MaxChars | Maximum number of characters allowed in the log. Once the log string reaches this size, the oldest text is removed: From the end if ReverseOrder is true , from the start if ReverseOrder is false . | 32000 |
The GetLogString()
method is used to access a named LogString
instance:
LogString myLogger = LogString.GetLogString("Task1");
The returned object is a singleton
. A background processing task would add a log entry by calling the Add()
method:
myLogger.Add("Something important happened!");
A monitoring task accesses the entire contents of the log though the Log
property. If textBox1
is a TextBox
component, the log contents would be viewed with:
textBox1.Text = myLogger.Log
Automatic updates to a viewer are achieved by adding a LogUpdateDelegate
delegate to the OnLogUpdate
event of a LogString
instance:
myLogger.OnLogUpdate += new LogString.LogUpdateDelegate(this.LogUpdate);
The LogUpdate
function updates the TextBox
component through the Invoke()
method. These details are discussed below.
Each log string
can be individually persisted with the Persist()
instance method, but it would be more typical to use the PersistAll() static
method when the application exits (or e.g. at timed intervals) to persist all logs with a single call:
LogString.PersistAll();
An individual log can be cleared with the Clear()
method or all logs with ClearAll()
.
The LogStringTestApp
project, which includes the LogString
class, demonstrates most of this functionality, including logging from background threads.
static LogString GetLogString(string name); // Get a LogString instance
static void PersistAll(); // Save all LogString instances
static void ClearAll(); // Clear the contents of all LogString instances
static void RemoveLogString(string name); // Remove the named LogString instance
Instance Methods
void Add(string message); // Add a message
void Persist(); // Save this LogString instance
void Clear(); // Clear this LogString instance
Instance Properties
string Log; // This is the log string
delegate void LogUpdateDelegate(); // The notification delegate
event LogUpdateDelegate OnLogUpdate; // The update event
Option properties: See table.
Type</strong /> | Property Name | Description | Default Value |
bool | ReverseOrder | If true , add new entries to the start of the log. This makes viewing real-time updates easier because new items appear at the top while older entries scroll off the bottom. If set to false , new entries are appended to the end of the log text. | true |
bool | LineTerminate | Terminate each new entry with CRLF. | true |
bool | TimeStamp | Add timestamp to each log entry. If false , no timestamp is added. | true |
int | MaxChars | Maximum number of characters allowed in the log. Once the log string reaches this size, the oldest text is removed: From the end if ReverseOrder is true , from the start if ReverseOrder is false . | 32000 |
The GetLogString()
method is used to access a named LogString
instance:
LogString myLogger = LogString.GetLogString("Task1");
The returned object is a singleton
. A background processing task would add a log entry by calling the Add()
method:
myLogger.Add("Something important happened!");
A monitoring task accesses the entire contents of the log though the Log
property. If textBox1
is a TextBox
component, the log contents would be viewed with:
textBox1.Text = myLogger.Log
Automatic updates to a viewer are achieved by adding a LogUpdateDelegate
delegate to the OnLogUpdate
event of a LogString
instance:
myLogger.OnLogUpdate += new LogString.LogUpdateDelegate(this.LogUpdate);
The LogUpdate
function updates the TextBox
component through the Invoke()
method. These details are discussed below.
Each log string
can be individually persisted with the Persist()
instance method, but it would be more typical to use the PersistAll() static
method when the application exits (or e.g. at timed intervals) to persist all logs with a single call:
LogString.PersistAll();
An individual log can be cleared with the Clear()
method or all logs with ClearAll()
.
The LogStringTestApp
project, which includes the LogString
class, demonstrates most of this functionality, including logging from background threads.
static LogString GetLogString(string name); // Get a LogString instance
static void PersistAll(); // Save all LogString instances
static void ClearAll(); // Clear the contents of all LogString instances
static void RemoveLogString(string name); // Remove the named LogString instance
Instance Methods
void Add(string message); // Add a message
void Persist(); // Save this LogString instance
void Clear(); // Clear this LogString instance
Instance Properties
string Log; // This is the log string
delegate void LogUpdateDelegate(); // The notification delegate
event LogUpdateDelegate OnLogUpdate; // The update event
Option properties: See table.
Type</strong /> | Property Name | Description | Default Value |
bool | ReverseOrder | If true , add new entries to the start of the log. This makes viewing real-time updates easier because new items appear at the top while older entries scroll off the bottom. If set to false , new entries are appended to the end of the log text. | true |
bool | LineTerminate | Terminate each new entry with CRLF. | true |
bool | TimeStamp | Add timestamp to each log entry. If false , no timestamp is added. | true |
int | MaxChars | Maximum number of characters allowed in the log. Once the log string reaches this size, the oldest text is removed: From the end if ReverseOrder is true , from the start if ReverseOrder is false . | 32000 |
The GetLogString()
method is used to access a named LogString
instance:
LogString myLogger = LogString.GetLogString("Task1");
The returned object is a singleton
. A background processing task would add a log entry by calling the Add()
method:
myLogger.Add("Something important happened!");
A monitoring task accesses the entire contents of the log though the Log
property. If textBox1
is a TextBox
component, the log contents would be viewed with:
textBox1.Text = myLogger.Log
Automatic updates to a viewer are achieved by adding a LogUpdateDelegate
delegate to the OnLogUpdate
event of a LogString
instance:
myLogger.OnLogUpdate += new LogString.LogUpdateDelegate(this.LogUpdate);
The LogUpdate
function updates the TextBox
component through the Invoke()
method. These details are discussed below.
Each log string
can be individually persisted with the Persist()
instance method, but it would be more typical to use the PersistAll() static
method when the application exits (or e.g. at timed intervals) to persist all logs with a single call:
LogString.PersistAll();
An individual log can be cleared with the Clear()
method or all logs with ClearAll()
.
The LogStringTestApp
project, which includes the LogString
class, demonstrates most of this functionality, including logging from background threads.
Multiple Singletons
A static Hashtable
is used to maintain all named log string
s:
private static Hashtable m_LogsTable = new Hashtable();
public static LogString GetLogString(string name)
{
// If it exists, return the existing log.
if (m_LogsTable.ContainsKey(name)) return (LogString)m_LogsTable[name];
// Create and return a new log.
LogString rv = new LogString(name);
m_LogsTable.Add(name, rv); // add to table
return rv;
}
// Constructor
private LogString(string name)
{
m_strName = name;
ReadLog(); // Read existing
}
Each LogString
instance returned is a singleton
. LogString
instances can be removed from the table with the RemoveLogString
method. This would only need to be done if you were creating many uniquely named logs so that the table would not fill up with unused instances.
Notice that the LogString
constructor reads its log file (if it exists). This will automatically restore the contents of the named log string
when it is instantiated.
Access Locking
In order to provide thread safe operation, whenever the internal log string
is read or modified, it must be locked.
public void Clear()
{
lock (m_strLog) // lock resource
{
m_strLog = string.Empty;
}
WriteLog(); // This will remove the log file
// Notify listeners of the update
if (OnLogUpdate != null) OnLogUpdate();
}
When one thread is inside the lock code block, another thread that executes a lock on the same object will be blocked until the other thread exits their block. I have used the C# lock
syntax here instead of the more general purpose Mutex
(or Monitor
). There are several reasons for this:
- I do not need cross-application resource locking, which is what a
Mutex
/Monitor
class can provide. - The
lock
code is cleaner. You do not have to surround the resource use code withWaitOne()
/ReleaseMutex()
calls. - You don't have to worry about exceptions being thrown in the locked code block. The
lock
statement usestry..catch..finally
to ensure that thelock
is properly released. As such, you can also call return from inside alock
code block. - For more details about access locking, see Thread Synchronization (C# Programming Guide) [^].
Event Generation
A public delegate
/event
pair are made available to clients that want to be notified that a LogString
instance has been updated (i.e. the Log string
has been modified).
public delegate void LogUpdateDelegate();
public event LogUpdateDelegate OnLogUpdate;
When a change to the log string
has been made, all delegate
s that have been added to the event will be notified when LogString
calls OnLogUpdate
when there are delegate
s present:
if (OnLogUpdate != null) OnLogUpdate();
A client would use the following to update a TextBox
component:
private System.Windows.Forms.TextBox txtLog;
...
myLogger.OnLogUpdate += new LogString.LogUpdateDelegate(this.LogUpdate);
...
private delegate void UpdateDelegate();
private void LogUpdate()
{
Invoke(new UpdateDelegate(
delegate
{
txtLog.Text = myLogger.Log;
})
);
}
The Invoke()
call is necessary so that the UI component updates can occur from other threads. The Anonymous delegate
shown here is a new .NET 2.0 feature (see C#2 Anonymous Methods [^]) . Not having to define an extra delegate
method makes the code cleaner and easier to follow.
Conclusion
If you have requirements that are close to what I've outlined, then you can use the LogString
class as is or modify it for your own purposes. Many enhancements are possible. The C# language tools described are fairly basic and should be in your everyday tool box. Enjoy!
History
- 28th August, 2006: Initial post
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
'언어 > C#' 카테고리의 다른 글
Academy of programmer - How to use NLog in WinForms app (0) | 2018.03.20 |
---|---|
The log4net Tutorial: Logging in C# (hands-on from beginner to advanced) (0) | 2018.03.20 |
log관련 (0) | 2018.03.19 |
STREAMWRITER로 텍스트 로그 남기기 (0) | 2018.03.18 |
Log4Net 사용법 (0) | 2018.03.18 |