“Testing can show the presence of bugs, but not their absence.” [Edsger W. Dijkstra]

Abstract

This clsLog class enables message logging with severities INFO, WARN, FATAL, and EVER to a log file as well as to an Excel sheet.

Using this clsLog class is not complicated: Copy general module modLog and class module clsLog from sample file at the end of this page into your own application, then define Public Const AppVersion with value “My Application Version 1.0” for example in your main module, and now you can with

Logger.info "Info message ..."
Logger.warn "Warn message ..."
Logger.fatal "Error message ..."
Logger.ever "Not suppressable standard message ..."

create your own log messages and store them automatically in sheet Workflow and in log file “My Application Version 1.0_Logfile_yyyymmdd.log” in subfolder Logs.

I find this program very useful to log program executions for revision purposes or to have a program explain its individual execution steps to the user in detail. Additionally, I added version information and system or Excel parameters to quickly identify important differences between different user environments. With this logger, I also typically measure simple runtime durations of SQL queries:

Dim dtStamp As Date
'...
dtStamp = Now
'Retrieve data from database here
Logger.info "SQL xxx ran " & Format(Now - dtStamp, "n:ss") & " [m:ss]"

See Best Practice Excel VBA for a usage sample.

Pros and Cons

In my opinion this Logger program offers the most reasonable secondary functionality of any VBA application. You can:

  • test in a traceable manner
  • let a program explain in detail all its execution steps
  • easily see whether more than one user are running a program simultaneously
  • easily detect whether an issue is caused by a different user environment
  • systematically isolate even sporadic application errors
  • convincingly show auditors the correct bugfree application over a longer time period (single log files could be manipulated but a larger set of log files is convincing)
  • roughly measure the runtime of VBA subroutines
  • measure the throughput time of whole processes

The last point above might worry workers’ council:

  • if you measure throughput times of whole processes, you can detect the performance of single employees, compare them and potentially use this information against them.

This would be a clear legal breach of the GDPR (General Data Protection Regulation), see (external link) https://gdpr-info.eu/

I never used this logging for performance measurement of my staff or of any user, but I sometimes detected user errors and then performed a re-training. But of course this cannot serve as an argument for its uncritical usage.

I think you can always reach agreement with workers’ council by pointing out that this is a voluntary protocol:

  • each user can switch this logging on or off before starting an application
  • each user can delete the log files at any point in time after they have been created

I used and still use this logging in several European countries (UK, Germany) in different companies (banks, insurance companies, IT providers) without any complaint so far.

Parameters

Logging variables

log_level - The level for which logging should be performed:

	  0 - Report all log messages: INFO, WARN, FATAL, and EVER
	  1 - Report all log messages but not INFOs
	  2 - Report from FATAL level onwards, i. e. just FATAL and EVER messages
	  3 - Report only EVER messages
	  4 - Switch off logging

log_sub_name - Set at the beginning of each subroutine to enable the logger to report on the right subroutine name

log_screen_row - Row from where to start logging in sheet Workflow (value: 3)

log_file_path - Full pathname of log file

log_separate_Logfiles_for_each_User - True = Separate daily log files for different users, False = One daily log file for all users

log_show_WMI_Info - Call Start_Log with value True if you like to log interesting Windows Management Instrumentation (WMI) information such as processor, memory, disk, and operating system data.

log_show_Reference_Details - Call Start_Log with value True to show all reference details. False will just show description.

Public constant

AppVersion - Define something like

Public Const AppVersion As String = "... Version x"

Then “… Version x” will be logged as version information for this application.

Sample Output

Standard output for the logs are sheet Workflow and the logfile in subfolder Logs:

EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Start_Log] - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Start_Log] - Logging started with Logging_Version_3
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Start_Log] - Microsoft Windows 11 Home 10.0.22000 (64-Bit) and Excel 2024 (64-Bit)
INFO: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Start_Log] - Application ThousandsSeparator '.', DecimalSeparator ',', use system separators
INFO: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Start_Log] - App.Internl ThousandsSeparator '.', DecimalSeparator ',', ListSeparator ';'
INFO: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Start_Log] - App.Internl xlCountryCode '49', xlCountrySetting '49'
INFO: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Start_Log] - VBAProject References: Visual Basic For Applications, Microsoft Excel 16.0 Object Library, OLE Automation, Microsoft Office 16.0 Object Library
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - Log Level: 0  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
INFO: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - 2 is a number less than 6
INFO: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - 3 is a number less than 6
INFO: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - 4 is a number less than 6
INFO: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - 5 is a number less than 6
#WARN: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Warn] - 6 is 6, 7, or 8
#WARN: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Warn] - 7 is 6, 7, or 8
#WARN: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Warn] - 8 is 6, 7, or 8
##FATAL: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Fatal] - 9 is greater 8
##FATAL: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Fatal] - 10 is greater 8
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - Log Level: 0  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - Log Level: 1  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#WARN: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Warn] - 6 is 6, 7, or 8
#WARN: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Warn] - 7 is 6, 7, or 8
#WARN: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Warn] - 8 is 6, 7, or 8
##FATAL: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Fatal] - 9 is greater 8
##FATAL: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Fatal] - 10 is greater 8
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - Log Level: 1  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - Log Level: 2  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
##FATAL: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Fatal] - 9 is greater 8
##FATAL: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Fatal] - 10 is greater 8
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [Logging_Sample] - Log Level: 2  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [End_Log] - Logging finished with Logging_Version_3
EVER: BERND-CAPTIVA\earso 24.08.2026 17:33:09 [End_Log] - <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

See Also

Write-Log, a similar logging function for MS PowerShell.

Modules

Please read my Disclaimer.

Normal

modLog contains constants, public variables, and default logger settings.

Note: The subroutine Start_Log requires (calls) the subroutines ApplicationVersion and (external link!) getOperatingSystem, and the module (external link!) LibFileTools. This module and these procedures are contained in the download file below.

Option Explicit

'This general module is named modLog. Together with class module
'clsLog it offers logging functionality.
'Requires LibFileTools:  https://github.com/cristianbuse/VBA-FileTools
'and GetOperatingSystem: https://www.devhut.net/vba-determine-the-installed-os/
'Usage example:
'
'Sub Main()
'Dim Logger        As clsLog
'Call Start_Log 'Only necessary ONCE on topmost calling level
'Set Logger = New clsLog
'Logger.Name = "Main"
'Logger.LogLevel = g_log_params.log_level
'...
'Logger.info "Some info" 'Or Logger.warn "Some warning" or ...
'...
'Call End_Log 'Only necessary ONCE on topmost calling level
'End Sub
'
'Sub Sub1()
'Dim Logger        As clsLog
'Set Logger = New clsLog
'Logger.Name = "Sub1"
'Logger.LogLevel = g_log_params.log_level
'...
'Logger.info "Some info" 'Or Logger.warn "Some warning" or ...
'...
'End Sub
'
'Version When         Who             What
'      2 19-Jun-2026  Bernd Plumhoff  No support of auto_open in modLog anymore
'      3 24-Aug-2026  Bernd Plumhoff  Replacing precompiler variables by runtime variables
'                                     and getting rid of cached option (which I never used)

Public Enum LogLevel
  logInfo = 0
  logWarn
  logFatal
  logEver     'For messages which cannot be switched off unless logging is disabled
  logDisabled
End Enum

Public Type g_Logger
  log_level                           As LogLevel
  log_sub_name                        As String
  log_screen_row                      As Integer 'Starting at 3, for tab Workflow
  log_file_path                       As String
  log_separate_Logfiles_for_each_User As Boolean
  log_show_WMI_Info                   As Boolean
  log_show_Reference_Details          As Boolean
End Type

Public g_log_params As g_Logger

Sub Start_Log(Optional b_separate_Logfiles_for_each_User As Boolean = False, _
  Optional b_show_WMI_Info As Boolean = False, _
  Optional b_show_Reference_Details As Boolean = False)
Dim i             As Long
Dim s             As String
Dim sDel          As String
Dim sPath         As String
Dim oWMISrvEx     As Object 'SWbemServicesEx
Dim oWMIObjSet    As Object 'SWbemServicesObjectSet
Dim oWMIObjEx     As Object 'SWbemObjectEx
Dim oWMIProp      As Object 'SWbemProperty
Dim sWQL          As String 'WQL Statement
Dim v             As Variant
Dim Logger        As clsLog

Set Logger = New clsLog
Logger.Name = "Start_Log"
Logger.LogLevel = g_log_params.log_level
g_log_params.log_separate_Logfiles_for_each_User = b_separate_Logfiles_for_each_User
g_log_params.log_show_WMI_Info = b_show_WMI_Info
g_log_params.log_show_Reference_Details = b_show_Reference_Details
'LibFileTools are necessary just for the next 5 lines:
sPath = GetLocalPath(ThisWorkbook.Path)
If sPath = "" Then sPath = ThisWorkbook.Path
If Right(sPath, 1) <> "\" Then sPath = sPath & "\"
sPath = sPath & "Logs\"
If Not IsFolder(sPath) Then CreateFolder (sPath)
If g_log_params.log_separate_Logfiles_for_each_User Then
  'If AppVersion is not defined please define it in your main module like:
  'Public Const AppVersion As String = "Application Version ..."
  g_log_params.log_file_path = sPath & Environ("Userdomain") & _
    "_" & Environ("Username") & "_" & AppVersion & "_" & "Logfile_" & _
    Format(Now, "YYYYMMDD") & ".txt"
Else
  g_log_params.log_file_path = sPath & AppVersion & "_" & _
    "Logfile_" & Format(Now, "YYYYMMDD") & ".txt"
End If
If g_log_params.log_screen_row < 3 Then
  g_log_params.log_screen_row = 3
  wsW.Range("E2:E4").ClearContents
  wsW.Range("5:65535").Delete
End If
With Application
Logger.ever String(180, ">")
Logger.ever "Logging started with " & AppVersion
If g_log_params.log_show_WMI_Info Then
  Set oWMISrvEx = GetObject("winmgmts:root/CIMV2")
  For Each v In Array("BaseService", "Processor", "PhysicalMemoryArray", "LogicalDisk", "OperatingSystem")
    'Not: "NetworkAdapterConfiguration", "VideoController", "OnBoardDevice", "Printer", "Product"
    Set oWMIObjSet = oWMISrvEx.ExecQuery("Select * From Win32_" & v)
    For Each oWMIObjEx In oWMIObjSet
      s = v & ": "
      For Each oWMIProp In oWMIObjEx.Properties_
        If Not IsNull(oWMIProp.Value) Then
          If Not IsArray(oWMIProp.Value) Then
            Select Case v
            Case "BaseService"
              If InStr("'SystemName'", "'" & oWMIProp.Name & "'") > 0 Then
                Logger.ever oWMIProp.Name & "='" & Trim(oWMIProp.Value) & "'"
                GoTo Next_v
              End If
            Case "Processor"
              If InStr( _
                "'Name'Description'NumberOfEnabledCore'AddressWidth'DataWidth'CurrentClockSpeed'LoadPercentage'", _
                "'" & oWMIProp.Name & "'") > 0 Then
                If IsNumeric(oWMIProp.Value) Then
                  s = s & oWMIProp.Name & "=" & Format(oWMIProp.Value, "#,##0") & ", "
                Else
                  s = s & oWMIProp.Name & "='" & Trim(oWMIProp.Value) & "', "
                End If
              End If
            Case "PhysicalMemoryArray"
              If InStr("'MaxCapacityEx'", _
                "'" & oWMIProp.Name & "'") > 0 Then s = s & oWMIProp.Name & "=" & _
                Format(oWMIProp.Value, "#,##0") & ", "
            Case "LogicalDisk"
              If InStr("'DeviceID'ProviderName'Size'FreeSpace'", _
                "'" & oWMIProp.Name & "'") > 0 Then
                If IsNumeric(oWMIProp.Value) Then
                  s = s & oWMIProp.Name & "=" & Format(oWMIProp.Value, "#,##0") & ", "
                Else
                  s = s & oWMIProp.Name & "='" & Trim(oWMIProp.Value) & "', "
                End If
              End If
            Case "OperatingSystem"
              If InStr( _
                "'FreePhysicalMemory'FreeVirtualMemory'FreeSpaceInPagingFiles'MaxProcessMemorySize'InstallDate'", _
                "'" & oWMIProp.Name & "'") > 0 Then s = s & oWMIProp.Name & "=" & _
                Format(oWMIProp.Value, "#,##0") & ", "
            End Select
          End If
        End If
      Next oWMIProp
      If Len(s) > Len(v & ": ") Then Logger.ever Left(s, Len(s) - 2)
    Next oWMIObjEx
Next_v:
  Next v
End If
#If Win64 Then
  s = "64"
#Else
  s = "32"
#End If
Logger.ever getOperatingSystem() & " and " & ApplicationVersion() & _
  " (" & s & "-Bit)" '& .Version & .Build & " (" & .CalculationVersion & ")"
Logger.info "Application ThousandsSeparator '" & .ThousandsSeparator & _
  "', DecimalSeparator '" & .DecimalSeparator & "', " & _
  IIf(Not (Application.UseSystemSeparators), "do not ", "") & "use system separators"
Logger.info "App.Internl ThousandsSeparator '" & .International(xlThousandsSeparator) & _
  "', DecimalSeparator '" & .International(xlDecimalSeparator) & "', ListSeparator '" & _
  .International(xlListSeparator) & "'"
Logger.info "App.Internl xlCountryCode '" & .International(xlCountryCode) & _
  "', xlCountrySetting '" & .International(xlCountrySetting) & "'"
End With
With ThisWorkbook.VBProject.References 'In case of error tick box Trust access to the VBA project object
  'model under File / Options / Trust Center / Trust Center Settings / Macro Settings
  s = "VBAProject References: "
  On Error Resume Next
  For i = 1 To .Count
    If g_log_params.log_show_Reference_Details Then
      Logger.info s
      s = ""
      s = s & .Item(i).Description
      s = s & ", FullPath: '" & .Item(i).fullPath & "'"
      s = s & ", Guid: " & .Item(i).GUID
      s = s & ", BuiltIn: " & .Item(i).BuiltIn
      s = s & ", IsBroken: " & .Item(i).IsBroken
      s = s & ", Major: " & .Item(i).Major
      s = s & ", Minor: " & .Item(i).Minor
    Else
      s = s & sDel & .Item(i).Description
      sDel = ", "
    End If
  Next i
  Logger.info s
End With
'Now two examples of environment variables which might not exist for all Windows / Excel installations.
'Use Sub List_Environ_Variables below to see which variables exist on your system.
s = ""
s = Environ("CRC_VDI-TYPE") 'If this does not exist we will not log anything
If s <> "" Then Logger.info "CRC_VDI-TYPE: '" & s & "'"
s = ""
s = Environ("ORACLE_HOME") 'If this does not exist we will not log anything
If s <> "" Then Logger.info "Oracle Client: '" & s & "'"
s = ""
s = Environ("ORACLE_HOME_X64") 'If this does not exist we will not log anything
If s <> "" Then Logger.info "Oracle Client: '" & s & "'"
On Error GoTo 0
End Sub

Sub End_Log()
Dim Logger        As clsLog

Set Logger = New clsLog
Logger.Name = "End_Log"
Logger.LogLevel = g_log_params.log_level
'If AppVersion is not defined please define it in your main module like:
'Public Const AppVersion As String = "Application Version ..."
Logger.ever "Logging finished with " & AppVersion
Logger.ever String(182, "<")
End Sub

A sample module General which just shows how you could use the logger:

Option Explicit

'Version When         Who             What
'      3 24-Aug-2026  Bernd Plumhoff  Replacing precompiler variables by runtime variables
'                                     and getting rid of cached option (which I never used)

Public Const AppVersion As String = "Logging_Version_3"

Sub Logging_Sample()
Dim i        As Long
Dim Logger   As clsLog

Call Start_Log
Set Logger = New clsLog
Logger.Name = "Logging_Sample"
Logger.LogLevel = g_log_params.log_level
 
'Just do something to give log message examples
Logger.LogLevel = logInfo
Logger.ever "Log Level: " & Logger.LogLevel & "  " & String(180, ">")
i = 2
Do While Not IsEmpty(wsData.Cells(i, 1))
  Select Case i
  Case Is < 6
    Logger.info i & " is a number less than 6"
  Case Is < 9
    Call Logging_Warn(i)
  Case Else
    Call Logging_Fatal(i)
  End Select
  i = i + 1
Loop
Logger.ever "Log Level: " & Logger.LogLevel & "  " & String(180, "<")
Logger.LogLevel = logWarn
Logger.ever "Log Level: " & Logger.LogLevel & "  " & String(180, ">")
i = 2
Do While Not IsEmpty(wsData.Cells(i, 1))
  Select Case i
  Case Is < 6
    Logger.info i & " is a number less than 6"
  Case Is < 9
    Call Logging_Warn(i)
  Case Else
    Call Logging_Fatal(i)
  End Select
  i = i + 1
Loop
Logger.ever "Log Level: " & Logger.LogLevel & "  " & String(180, "<")
Logger.LogLevel = logFatal
Logger.ever "Log Level: " & Logger.LogLevel & "  " & String(180, ">")
i = 2
Do While Not IsEmpty(wsData.Cells(i, 1))
  Select Case i
  Case Is < 6
    Logger.info i & " is a number less than 6"
  Case Is < 9
    Call Logging_Warn(i)
  Case Else
    Call Logging_Fatal(i)
  End Select
  i = i + 1
Loop
Logger.ever "Log Level: " & Logger.LogLevel & "  " & String(180, "<")
Logger.LogLevel = logDisabled 'Nothing will be logged, not even EVER
Logger.ever "Log Level: " & Logger.LogLevel & "  " & String(180, ">")
i = 2
Do While Not IsEmpty(wsData.Cells(i, 1))
  Select Case i
  Case Is < 6
    Logger.info i & " is a number less than 6"
  Case Is < 9
    Call Logging_Warn(i)
  Case Else
    Call Logging_Fatal(i)
  End Select
  i = i + 1
Loop
Logger.ever "Log Level: " & Logger.LogLevel & "  " & String(180, "<")
Logger.LogLevel = logInfo
Call End_Log 'Only necessary ONCE on topmost calling level
End Sub
 
'You do not need extra subroutines to log warn messages or fatal messages.
'They are just examples of additional subroutines which do some logging.
Sub Logging_Warn(i As Long)
  Dim Logger   As clsLog
  
  Set Logger = New clsLog
  Logger.Name = "Logging_Warn"
  Logger.LogLevel = g_log_params.log_level
  Logger.warn i & " is 6, 7, or 8"
End Sub
 
Sub Logging_Fatal(i As Long)
  Dim Logger   As clsLog
  
  Set Logger = New clsLog
  Logger.Name = "Logging_Fatal"
  Logger.LogLevel = g_log_params.log_level
  Logger.fatal i & " is greater 8"
End Sub

Class Modules

clsLog contains the logging functionality.

Option Explicit

'This class module is named clsLog. Together with module modLog it offers logging functionality.
'Version When         Who             What
'      2 19-Jun-2026  Bernd Plumhoff  No support of auto_open in modLog anymore
'      3 24-Aug-2026  Bernd Plumhoff  Replacing precompiler variables by runtime variables
'                                     and getting rid of cached option (which I never used)

Private Type m_Logger
  log_level          As LogLevel
  log_last_level     As LogLevel
  log_sub_name       As String
  log_last_sub_name  As String
  m_saved            As Boolean
End Type
Private m_log_params As m_Logger

Public Property Let Name(sName As String)
  g_log_params.log_sub_name = sName
End Property

Public Property Get Name() As String
  Name = g_log_params.log_sub_name
End Property

Public Property Let LogLevel(ByVal enLevel As LogLevel)
  g_log_params.log_level = enLevel
End Property

Public Property Get LogLevel() As LogLevel
  LogLevel = g_log_params.log_level
End Property

Public Sub info(sLogText As String)
  If g_log_params.log_level = logInfo Then
    Call WriteLog(logInfo, sLogText)
  End If
End Sub

Public Sub warn(sLogText As String)
  If g_log_params.log_level <= logWarn Then
    Call WriteLog(logWarn, sLogText)
  End If
End Sub

Public Sub fatal(sLogText As String)
  If g_log_params.log_level <= logFatal Then
    Call WriteLog(logFatal, sLogText)
  End If
End Sub

Public Sub ever(sLogText As String)
  If g_log_params.log_level <= logEver Then
    Call WriteLog(logEver, sLogText)
  End If
End Sub

Private Sub WriteLog(enLogLevel As LogLevel, sLogText As String)
  Dim FileNum As Integer
  Dim LogMessage As String
  LogMessage = Array("INFO:", "#WARN:", "##FATAL:", "EVER:")(enLogLevel) & _
    " " & Environ("Userdomain") & "\" & Environ("Username") & " " & _
    CStr(Now()) & " [" & g_log_params.log_sub_name & "] - " & sLogText
  FileNum = FreeFile
  Open g_log_params.log_file_path For Append As #FileNum
  Print #FileNum, LogMessage
  Close #FileNum
  wsW.Cells(g_log_params.log_screen_row, 5) = LogMessage
  g_log_params.log_screen_row = g_log_params.log_screen_row + 1
End Sub

Private Sub Class_Initialize()
  m_log_params.log_level = g_log_params.log_level
  m_log_params.log_sub_name = g_log_params.log_sub_name
End Sub

Private Sub Class_Terminate()
  g_log_params.log_level = m_log_params.log_level
  g_log_params.log_sub_name = m_log_params.log_sub_name
End Sub

Download

Please read my Disclaimer.

Logging_with_clsLog.xlsm [238 KB Excel file, open and use at your own risk]

Note: A comprehensive documentation of my Excel implementations can be found in Excel VBA A Collection.