patternMinor
Extensible logging
Viewed 0 times
extensibleloggingstackoverflow
Problem
Whenever I need logging functionality in .net, I use a logging framework, such as NLog. Obviously there's no logging framework for vba, at least none that I know of.
As much as I love using NLog, the way it works, with targets, rules and loggers, was going to be way too heavy and complex for my needs.
So I kept the idea of a logger, and centralized log message formatting in a
LogManager class module
```
Option Explicit
Public Enum LogLevel
TraceLevel = 0
DebugLevel
InfoLevel
WarnLevel
ErrorLevel
FatalLevel
End Enum
Private Type TLogManager
Formatter As ILogMessageFormatter
Loggers As New Dictionary
End Type
Private this As TLogManager
Public Property Get Formatter() As ILogMessageFormatter
Set Formatter = this.Formatter
End Property
Public Property Set Formatter(ByVal value As ILogMessageFormatter)
Set this.Formatter = value
End Property
Public Sub Register(ByVal logger As ILogger)
If Not this.Loggers.Exists(logger.Name) Then
this.Loggers.Add logger.Name, logger
Else
Err.Raise vbObjectError + 1098, "LogManager.Register", "There is already a logger registered with name '" & logger.Name & "'."
End If
End Sub
Public Function IsEnabled(ByVal level As LogLevel) As Boolean
Dim logger As ILogger
Dim item As Variant
For Each item In this.Loggers.Items
Set logger = item
If level >= logger.MinLevel Then
IsEnabled = True
Exit Function
End If
Next
End Function
Public Sub Log(ByVal level As LogLevel, ByVal message As String, Optional ByVal loggerName As String)
Dim logger As ILogger
If loggerName = vbNullString Then
Dim item As Variant
For Each item In this.Loggers.Items
As much as I love using NLog, the way it works, with targets, rules and loggers, was going to be way too heavy and complex for my needs.
So I kept the idea of a logger, and centralized log message formatting in a
LogManager static class - this class provides the entire client API; client code doesn't directly deal with ILogger implementations, rather calls the LogManager.Log method, which "dispatches" the log message to all relevant loggers.LogManager class module
```
Option Explicit
Public Enum LogLevel
TraceLevel = 0
DebugLevel
InfoLevel
WarnLevel
ErrorLevel
FatalLevel
End Enum
Private Type TLogManager
Formatter As ILogMessageFormatter
Loggers As New Dictionary
End Type
Private this As TLogManager
Public Property Get Formatter() As ILogMessageFormatter
Set Formatter = this.Formatter
End Property
Public Property Set Formatter(ByVal value As ILogMessageFormatter)
Set this.Formatter = value
End Property
Public Sub Register(ByVal logger As ILogger)
If Not this.Loggers.Exists(logger.Name) Then
this.Loggers.Add logger.Name, logger
Else
Err.Raise vbObjectError + 1098, "LogManager.Register", "There is already a logger registered with name '" & logger.Name & "'."
End If
End Sub
Public Function IsEnabled(ByVal level As LogLevel) As Boolean
Dim logger As ILogger
Dim item As Variant
For Each item In this.Loggers.Items
Set logger = item
If level >= logger.MinLevel Then
IsEnabled = True
Exit Function
End If
Next
End Function
Public Sub Log(ByVal level As LogLevel, ByVal message As String, Optional ByVal loggerName As String)
Dim logger As ILogger
If loggerName = vbNullString Then
Dim item As Variant
For Each item In this.Loggers.Items
Solution
-
It's really nit-picky, but there's no reason to declare the starting point for an Enum unless you don't want to start at zero.
You're also repeating yourself a lot here. It's almost hungarian. They're all
-
I really recommend an Enum for your custom error numbers. It's much easier to maintain if they're all in one place.
These become much simpler with an Enum that looks something like this.
-
This is an absolute pleasure to read. Great use of white space.
Maybe I'm tired, or maybe it's just good. I can't tell for sure at the moment, but I think you have some pretty solid code here. Good naming and formatting, as well as a very nice abstraction and use of interfaces.
It's really nit-picky, but there's no reason to declare the starting point for an Enum unless you don't want to start at zero.
Public Enum LogLevel
TraceLevel = 0
DebugLevel
'.....You're also repeating yourself a lot here. It's almost hungarian. They're all
LogLevels. I think you'd be fine to shorten these up to Trace,Debug, Info etc. Of course, vba can be a bit strange about when you can and can't call enum members with a full reference like this LogLevel.Trace, so you may or may not want to actually do that. I'm on the fence about it personally. But you can't, because Debug is a reserved keyword.... -
I really recommend an Enum for your custom error numbers. It's much easier to maintain if they're all in one place.
Err.Raise vbObjectError + 1098, "LogManager.Register", "There is already a logger registered with name '" & logger.Name & "'."
'.......
Err.Raise vbObjectError + 1099, "LogManager.Log", "There is no registered logger named '" & loggerName & "'."These become much simpler with an Enum that looks something like this.
Public Enum LogMangerError
SomeError = vbObjectError + 1098
SomeOtherError
End Enum
'......
Err.Raise SomeError, "LogManager.Register", "There is already a logger registered with name '" & logger.Name & "'."-
This is an absolute pleasure to read. Great use of white space.
Select Case level
Case LogLevel.DebugLevel
FormatLogLevel = "DEBUG"
Case LogLevel.ErrorLevel
FormatLogLevel = "ERROR"
Case LogLevel.FatalLevel
FormatLogLevel = "FATAL"
Case LogLevel.InfoLevel
FormatLogLevel = "INFO"
Case LogLevel.TraceLevel
FormatLogLevel = "TRACE"
Case LogLevel.WarnLevel
FormatLogLevel = "WARNING"
End SelectMaybe I'm tired, or maybe it's just good. I can't tell for sure at the moment, but I think you have some pretty solid code here. Good naming and formatting, as well as a very nice abstraction and use of interfaces.
Code Snippets
Public Enum LogLevel
TraceLevel = 0
DebugLevel
'.....Err.Raise vbObjectError + 1098, "LogManager.Register", "There is already a logger registered with name '" & logger.Name & "'."
'.......
Err.Raise vbObjectError + 1099, "LogManager.Log", "There is no registered logger named '" & loggerName & "'."Public Enum LogMangerError
SomeError = vbObjectError + 1098
SomeOtherError
End Enum
'......
Err.Raise SomeError, "LogManager.Register", "There is already a logger registered with name '" & logger.Name & "'."Select Case level
Case LogLevel.DebugLevel
FormatLogLevel = "DEBUG"
Case LogLevel.ErrorLevel
FormatLogLevel = "ERROR"
Case LogLevel.FatalLevel
FormatLogLevel = "FATAL"
Case LogLevel.InfoLevel
FormatLogLevel = "INFO"
Case LogLevel.TraceLevel
FormatLogLevel = "TRACE"
Case LogLevel.WarnLevel
FormatLogLevel = "WARNING"
End SelectContext
StackExchange Code Review Q#64109, answer score: 8
Revisions (0)
No revisions yet.