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

Abstract

Diese Logger Klasse bietet Logging mit den Berichtsstufen INFO, WARN, FATAL und EVER an. Die Programminformationen werden sowohl in einem Tabellenblatt als auch in einer Datei festgehalten.

Die Anwendung dieser Logger Klasse ist nicht schwer: Einfach das allgemeine Modul Logger_Factory und das Klassenmodul Logger aus der unten bereitgestellten Beispieldatei in die eigene Anwendung kopieren, dann die Public Const AppVersion zum Beispiel mit dem Wert “Meine Anwendung Version 1.0” im Hauptmodul definieren, und dann kann man mit

GLogger.info "Info Meldung ..."
GLogger.warn "Warn Meldung ..."
GLogger.fatal "Fehler Meldung ..."
GLogger.ever "Nichtunterdrückbare Standard Meldung ..."

die eigenen Logmeldungen erzeugen und automatisch im Tabellenblatt Workflow und in der Logdatei “Meine Anwendung Version 1.0_Logfile_yyyymmdd.log” im Unterverzeichnis Logs speichern.

Ich erhielt den ursprünglichen Programmcode 2009 von Cliff G. und erweiterte ihn später. Cliff verwendete dieses Programm hauptsächlich zum Debuggen. Ich finde es auch sehr sinnvoll, um Programmläufe zu Revisionszwecken zu protokollieren. Vor Kurzem fügte ich Versionsinformationen und System- oder Excel-Parameter hinzu, um rasch wichtige Unterschiede zwischen verschiedenen Benutzerumgebungen zu ermitteln. Mit diesem Logger messe ich gewöhnlich auch einfache Laufzeiten von SQL Abfragen:

'Glogger is declared in module LoggerFactory and set in Sub auto_open()
Dim dtStamp As Date
'...
dtStamp = Now
'Retrieve data from database here
Glogger.info "SQL xxx ran " & Format(Now - dtStamp, "n:ss") & " [m:ss]"

Ein Anwendungsbeispiel: fair zufällig verteilen

Für und Wider

Dieses Logging Programm bietet meines Erachtens die sinnvollste sekundäre Funktionalität für jede VBA Anwendung. Man kann:

  • nachvollziehbar testen
  • einfach feststellen, ob mehrere Anwender eine Anwendung gleichzeitig nutzen
  • leicht erkennen, ein Anwenderproblem auf einer unterschiedlichen Umgebung beruht
  • auch sporadische Anwendungsfehler systematisch eingrenzen
  • über einen längeren Zeitraum hinweg auch Revisoren überzeugend die korrekte fehlerfreie Nutzung nachweisen (einzelne Logdateien können zwar manipuliert werden, aber eine größe Menge von Logdateien wirkt dennoch hinreichend überzeugend)
  • die Laufzeit von VBA (Unter-)Routinen grob bestimmen
  • die Durchlaufzeiten von gesamten Prozessen messen

Der letzte obige Punkt wird Betriebs- und Personalräte hellhörig machen:

  • wenn man Durchlaufzeiten von ganzen Prozessen misst, könnte man die Leistung einzelner Mitarbeiter ermitteln, vergleichen und ggf. gegen sie verwenden.

Dies wäre ein klarer Verstoß gegen die DSGVO (Datenschutz-Grundverordnung), siehe (externer Link) https://dsgvo-gesetz.de/

Ich habe dieses Logging nie gegen meine Mitarbeiter oder Anwender für eine Leistungsmessung verwendet, sondern lediglich für Nachschulungen genutzt, wenn ich fehlerhafte Nutzungen erkannte. Aber dies kann selbstverständlich nicht als Argument für eine unbedenkliche Nutzung dienen.

Eine Zustimmung von Betriebs- und Personalräten kann m. E. immer erreicht werden, wenn man auf die Freiwilligkeit dieser Selbstaufschreibung hinweist:

  • jede:r Anwender:in kann das Logging vor einem Programmlauf ein- oder ausschalten
  • jede:r Anwender:in kann die Log-Dateien zu jeder Zeit im Nachhinein löschen

Ich setzte und setze in Europa in mehreren Ländern (UK, Deutschland) bei mehreren Gesellschaften (Banken, Versicherungen, IT Providern) dieses Logging ohne jede Beanstandung erfolgreich ein.

Parameter

Public (öffentliche) Konstanten

AppVersion - Diese Zeichenkette sollte den Programmnamen und seine Version enthalten, z. B.:

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

Dann wird “… Version x” als Versionsinformation für dieses Programm protokolliert.

Compilerkonstanten

SEPARATE_LOGFILES_FOR_DIFFERENT_USERS - True erstellt für jeden Benutzer eigene Logdateien, False führt zu einer täglichen Logdatei für alle Benutzer

USE_LOGGER_AUTO_OPEN_CLOSE - True verwendet die Subroutinen auto_open und auto_close im Modul LoggerFactory, False nicht.

Logging_on_Screen - In LoggerFactory und Logger auf True setzen um Nachrichten auch im Tabellenblatt Workflow zu zeigen.

Logging_cashed - In LoggerFactory und Logger auf True setzen um das Logging zu beschleunigen. Log Nachrichten werden dann erst am Ende des Programlaufs in eine Datei geschrieben. Hierfür muss auch Logging_on_Screen auf True gesetzt werden.

Log_WMI_Info - In LoggerFactory auf True setzen um interessante Windows Management Instrumentation (WMI) Informationen auszugeben wie z. B. Prozessor-, Speicher-, Laufwerks- und Betriebssystem-Angaben.

Logging Variablen

LogFilePath - Vollständiger Pfadname der Logdatei

SubName - Muss am Anfang jeder Subroutine gesetzt werden um den Sub Namen korrekt im Log zu protokollieren

LogLevel - Die Logging Berichtsstufen:

	  1 - Alle Log Nachrichten protokollieren: INFO, WARN, FATAL, and EVER
	  2 - Alle Log Nachrichten mit Ausnahme von Stufe INFO protokollieren
	  3 - Nur FATAL und EVER Log Nachrichten protokollieren
	  4 - Lediglich EVER Log Nachrichten protokollieren
	  5 - Kein Logging

LogScreenRow - Startzeile für das Logging in Tabellenblatt Workflow (gewöhnlich 3)

Siehe auch

Write-Log, eine analoge Funktion für MS PowerShell.

Module

Bitte den Haftungsausschluss im Impressum beachten.

Normal

LoggerFactory enthält Konstanten, öffentliche Variablen, Standard Logger Einstellungen und optionale Auto-Open and Auto-Close Subroutinen.

Option Explicit
'This general module is named LoggerFactory. Together with class module Logger it offers logging functionality.
'Version When         Who             What
'      1 Once upon .. Cliff G.        Initial version
'     11 03-Nov-2023  Bernd Plumhoff  Log interesting Windows Management Instrumentation (WMI) infos #If Log_WMI_Info = True.
#Const SEPARATE_LOGFILES_FOR_DIFFERENT_USERS = False
#Const USE_LOGGER_AUTO_OPEN_CLOSE = True 'Enable auto_open and auto_close subs in here
#Const Logging_on_Screen = True          'IMPORTANT: Also change this constant in class module Logger! We like to see recent run's loggging messages on screen in tab Workflow
#Const Logging_cashed = False            'IMPORTANT: Also change this constant in class module Logger! Write logging messages into file at program end to speed this up
#Const Log_WMI_Info = True
Public GLogger As Logger                 'Global logfile object - variable scope is across all modules
Public GsThisLogFilePath As String
' Constant log levels
Public Const INFO_LEVEL As Integer = 1
Public Const WARN_LEVEL As Integer = 2
Public Const FATAL_LEVEL As Integer = 3
Public Const EVER_LEVEL As Integer = 4 'For logging messages which cannot be switched off
Public Const DISABLE_LOGGING As Integer = 5
'The application-specific defaults
Const DEFAULT_LOG_FILE_PATH As String = "" 'Force error if not set [Bernd 12-Aug-2009]
Const DEFAULT_LOG_LEVEL As Integer = INFO_LEVEL
Public Function getLogger(sSubName As String) As Logger
  Dim oLogger As New Logger
  oLogger.SubName = sSubName
  'Defaults to the specified values - but may be overridden before used
  oLogger.LogLevel = DEFAULT_LOG_LEVEL
  oLogger.LogFilePath = DEFAULT_LOG_FILE_PATH
  Set getLogger = oLogger
End Function
#If USE_LOGGER_AUTO_OPEN_CLOSE Then
  Sub auto_open()
  'Version Date        Programmer Change
  '9       12-Sep-2021 Bernd      Code outsorced to Start_Log so that user does not need to use auto_open.
  Start_Log
  End Sub
  Sub auto_close()
  'Version Date        Programmer Change
  '3       12-Sep-2021 Bernd      Code outsorced to End_Log so that user does not need to use auto_close.
  End_Log
  End Sub
#End If '#If USE_LOGGER_AUTO_OPEN_CLOSE
Sub Start_Log()
'Version Date        Programmer Change
'3       02-Nov-2023 Bernd      Log interesting Windows Management Instrumentation (WMI) infos.
Dim wb As Workbook
Dim i As Long
Dim s As String, sDel As String
#If Log_WMI_Info = True Then
  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
#End If
If Dir(ThisWorkbook.Path & "\Logs\", vbDirectory) = vbNullString Then
  MkDir ThisWorkbook.Path & "\Logs"
End If
If GLogger Is Nothing Then Set GLogger = New Logger
#If SEPARATE_LOGFILES_FOR_DIFFERENT_USERS Then
  'If AppVersion is not defined please define it in your main module like:
  'Public Const AppVersion As String = "Application Version ..."
  GLogger.LogFilePath = ThisWorkbook.Path & "\Logs\" & Environ("Userdomain") & _
    "_" & Environ("Username") & "_" & AppVersion & "_" & "Logfile_" & _
    Format(Now, "YYYYMMDD") & ".txt"
#Else
  GLogger.LogFilePath = ThisWorkbook.Path & "\Logs\" & AppVersion & "_" & _
    "Logfile_" & Format(Now, "YYYYMMDD") & ".txt"
#End If
GLogger.LogLevel = 1
#If Logging_on_Screen Then
  GLogger.LogScreenRow = 3
  wsW.Range("E2:E4").ClearContents
  wsW.Range("5:65535").Delete
#End If
'Initialize logger for this subroutine
With Application
GLogger.SubName = "Start_Log"
GLogger.ever "Logging started with " & AppVersion
#If Log_WMI_Info = True 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
                GLogger.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
'          Else
'            For i = LBound(oWMIProp.Value) To UBound(oWMIProp.Value)
'              If IsNumeric(oWMIProp.Value(i)) Then
'                s = s & oWMIProp.Name & "=" & Format(oWMIProp.Value(i), "#,##0") & ", "
'              Else
'                s = s & oWMIProp.Name & "='" & Trim(oWMIProp.Value(i)) & "', "
'              End If
'            Next i
          End If
        End If
      Next oWMIProp
      If Len(s) > Len(v & ": ") Then GLogger.ever Left(s, Len(s) - 2)
    Next oWMIObjEx
Next_v:
  Next v
#End If
#If Win64 Then
  s = "64"
#Else
  s = "32"
#End If
GLogger.ever .OperatingSystem & " / " & getOperatingSystem() & " and " & .Application & " [" & _
  ApplicationVersion() & "] (" & s & "-bit) " & .Version & .Build & " (" & .CalculationVersion & ")"
GLogger.info "Application ThousandsSeparator '" & .ThousandsSeparator & _
  "', DecimalSeparator '" & .DecimalSeparator & "', " & _
  IIf(Not (Application.UseSystemSeparators), "do not ", "") & "use system separators"
GLogger.info "App.Internl ThousandsSeparator '" & .International(xlThousandsSeparator) & _
  "', DecimalSeparator '" & .International(xlDecimalSeparator) & "', ListSeparator '" & _
  .International(xlListSeparator) & "'"
GLogger.info "App.Internl xlCountryCode '" & .International(xlCountryCode) & _
  "', xlCountrySetting '" & .International(xlCountrySetting) & "'"
End With
Set wb = ThisWorkbook
With wb.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
  s = s & sDel & .Item(i).Description
  sDel = ", "
Next i
GLogger.info s
'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 GLogger.info "CRC_VDI-TYPE: '" & s & "'"
s = ""
s = Environ("ORACLE_HOME_X64") 'If this does not exist we will not log anything
If s <> "" Then GLogger.info "Oracle Client: '" & s & "'"
On Error GoTo 0
End With
End Sub
Sub End_Log()
'Change History:
'Version Date        Programmer Change
'1       12-Sep-2021 Bernd      Initial version so that user does not need to use auto_close. He can manually call this sub.
If GLogger Is Nothing Then Call auto_open
GLogger.SubName = "End_Log"
'If AppVersion is not defined please define it in your main module like: Public Const AppVersion As String = "Application Version ..."
GLogger.ever "Logging finished with " & AppVersion
#If Logging_cashed Then
  Set GLogger = Nothing 'Necessary, or Class_Terminate() won't be called for GLogger because it's Public
#End If
End Sub
'---------------------------------------------------------------------------------------
' Procedure : getOperatingSystem
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Return the active OS details
' Copyright : The following may be altered and reused as you wish so long as the
'             copyright notice is left unchanged (including Author, Website and
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2012-Sep-27                 Initial Release
'---------------------------------------------------------------------------------------
Public Function getOperatingSystem()
  Dim localHost As String
  Dim objWMIService As Variant
  Dim colOperatingSystems As Variant
  Dim objOperatingSystem As Variant
  
  On Error GoTo Error_Handler
  
  localHost = "." 'Technically could be run against remote computers, if allowed
  Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & localHost & "\root\cimv2")
  Set colOperatingSystems = objWMIService.ExecQuery("Select * from Win32_OperatingSystem")
  
  For Each objOperatingSystem In colOperatingSystems
    getOperatingSystem = objOperatingSystem.Caption & " " & objOperatingSystem.Version
    Exit Function
  Next
  
Error_Handler_Exit:
  On Error Resume Next
  Exit Function
  
Error_Handler:
  MsgBox "The following error has occured." & vbCrLf & vbCrLf & _
         "Error Number: " & Err.Number & vbCrLf & _
         "Error Source: getOperatingSystem" & vbCrLf & _
         "Error Description: " & Err.Description, _
         vbCritical, "An Error has Occured!"
  Resume Error_Handler_Exit
  End Function
 
Function ApplicationVersion() As String
'Returns MS Excel's version - with a little kludge
'Source (EN): http://www.sulprobil.com/applicationversion_en/
'Source (DE): http://www.bplumhoff.de/applicationversion_de/
'(C) (P) by Bernd Plumhoff 19-Jan-2022 PB V0.3
Dim n As Integer
n = Val(Application.Version)
Select Case n
Case 16
    ApplicationVersion = "Excel 2016"
    'Excel 365 introduced Lambda - but this test does not seem to be
    'sufficient so far.
'    On Error Resume Next
'    ThisWorkbook.Names.Add Name:="HasLambda", RefersTo:="=LAMBDA(x,x)"
'    n = Evaluate("HasLambda(19)")
'    ThisWorkbook.Names("HasLambda").Delete
'    On Error GoTo 0
'    If n = 19 Then
'        ApplicationVersion = "Excel 365"
'    Else
        'Excel 2021 introduced RandArray
        On Error Resume Next
        n = Application.WorksheetFunction.RandArray(1, 1, 18, 18, True)(1)
        On Error GoTo 0
        If n = 18 Then
            ApplicationVersion = "Excel 2021/365" '"Excel 2021"
        Else
            'Excel 2019 introduced TextJoin
            On Error Resume Next
            n = Val(Application.WorksheetFunction.TextJoin(" ", True, "17"))
            On Error GoTo 0
            If n = 17 Then ApplicationVersion = "Excel 2019"
        End If
'    End If
Case 15
    ApplicationVersion = "Excel 2013"
Case 14
    ApplicationVersion = "Excel 2010"
Case 12
    ApplicationVersion = "Excel 2007"
Case 11
    ApplicationVersion = "Excel 2003"
Case 10
    ApplicationVersion = "Excel 2002"
Case 9
    ApplicationVersion = "Excel 2000"
Case 8
    ApplicationVersion = "Excel 97"
Case 7
    ApplicationVersion = "Excel 7/95"
Case 5
    ApplicationVersion = "Excel 5"
Case Else
    ApplicationVersion = "[Error]"
End Select
End Function
 
Sub List_Environ_Variables()
'Original code found here: http://www.office-loesung.de/ftopic46029_0_0_asc.php
'Note that you can call Environ with Environ(9) or Environ("USERNAME"), for example.
'Change History:
'Version Date        Programmer Change
'1       12-Dec-2020 Bernd      Initial version
Dim i As Long
Dim iPos As Long
Dim sKey As String
Dim sResult As String
Dim sValue As String
i = 1
Debug.Print "#", "Key", "Value"
Do
    sResult = Environ(i)
    If sResult <> "" Then
        iPos = InStr(sResult, "=")
        sKey = Left(sResult, iPos - 1)
        sValue = Mid(sResult, iPos + 1)
        Debug.Print iPos, sKey, sValue
    End If
    i = i + 1
Loop Until sResult = ""
End Sub

Logging_Example

Ein Beispielmodul General welches zeigt wie man den Logger nutzen kann:

Option Explicit

'Version When         Who             What
'     11 03-Nov-2023  Bernd Plumhoff  Log interesting Windows Management Instrumentation (WMI) infos.

Public Const AppVersion As String = "Logging_Version_11"

Sub Logging_Sample()
Dim i As Long

If GLogger Is Nothing Then Start_Log
'Initialize logger for this subroutine
GLogger.SubName = "Logging_Sample"
 
'Just do something to give log message examples
i = 2
Do While Not IsEmpty(wsData.Cells(i, 1))
    Select Case i
    Case Is < 6
        GLogger.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
 
#If Logging_cashed Then
Set GLogger = Nothing 'Necessary, or Class_Terminate() won't be called for GLogger since it's Public
#End If
 
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)
    'Initialize logger for this subroutine
    GLogger.SubName = "Logging_Warn"
    GLogger.warn i & " is 6, 7, or 8"
End Sub
 
Sub Logging_Fatal(i As Long)
    'Initialize logger for this subroutine
    GLogger.SubName = "Logging_Fatal"
    GLogger.fatal i & " is greater 8"
End Sub

Klassenmodule

Logger enthält die Logging Functionalität:

Option Explicit
'This class module is named Logger. Together with class module LoggerFactory it offers logging functionality.
'Version When         Who             What
'      1 Once upon .. Cliff G.        Initial version
'     11 03-Nov-2023  Bernd Plumhoff  Same version as LoggerFactory, log interesting Windows Management Instrumentation (WMI) infos.
#Const Logging_on_Screen = True 'IMPORTANT: Also change this constant in module LoggerFactory! We like to see recent run's loggging messages on screen in tab Workflow
#Const Logging_cashed = False   'IMPORTANT: Also change this constant in module LoggerFactory! Write logging messages into file at program end to speed this up
Const INFO_LEVEL_TEXT As String = "INFO:"
Const WARN_LEVEL_TEXT As String = "#WARN:"
Const FATAL_LEVEL_TEXT As String = "##FATAL:"
Const EVER_LEVEL_TEXT As String = "EVER:"
Private sThisSubName As String
Private iThisLogLevel As Integer
#If Logging_on_Screen Then
  Private iThisLogRow As Integer
  Public Property Let LogScreenRow(iLogRow As Integer)
    iThisLogRow = iLogRow
  End Property
  Public Property Get LogScreenRow() As Integer
    LogScreenRow = iThisLogRow
  End Property
#End If
Public Property Let LogFilePath(sLogFilePath As String)
  GsThisLogFilePath = sLogFilePath
End Property
Public Property Get LogFilePath() As String
  LogFilePath = GsThisLogFilePath
End Property
Public Property Let SubName(sSubName As String)
  sThisSubName = sSubName
End Property
Public Property Get SubName() As String
  SubName = sThisSubName
End Property
Public Property Let LogLevel(iLogLevel As Integer)
  iThisLogLevel = iLogLevel
End Property
Public Property Get LogLevel() As Integer
  LogLevel = iThisLogLevel
End Property
Public Sub info(sLogText As String)
  If Me.LogLevel = LoggerFactory.INFO_LEVEL Then
    Call WriteLog(LoggerFactory.INFO_LEVEL, sLogText)
  End If
End Sub
Public Sub warn(sLogText As String)
  If Me.LogLevel < LoggerFactory.FATAL_LEVEL Then
    Call WriteLog(LoggerFactory.WARN_LEVEL, sLogText)
  End If
End Sub
Public Sub fatal(sLogText As String)
  If Me.LogLevel <= LoggerFactory.FATAL_LEVEL Then
    Call WriteLog(LoggerFactory.FATAL_LEVEL, sLogText)
  End If
End Sub
Public Sub ever(sLogText As String)
  If Me.LogLevel <= LoggerFactory.EVER_LEVEL Then
    Call WriteLog(LoggerFactory.EVER_LEVEL, sLogText)
  End If
End Sub
Private Sub WriteLog(iLogLevel As Integer, sLogText As String)
  Dim FileNum As Integer, LogMessage As String, sDateTime As String, sLogLevel As String
  Select Case iLogLevel
  Case LoggerFactory.INFO_LEVEL
    sLogLevel = INFO_LEVEL_TEXT
  Case LoggerFactory.WARN_LEVEL
    sLogLevel = WARN_LEVEL_TEXT
  Case LoggerFactory.FATAL_LEVEL
    sLogLevel = FATAL_LEVEL_TEXT
  Case LoggerFactory.EVER_LEVEL
    sLogLevel = EVER_LEVEL_TEXT
  Case Else
    sLogLevel = "!INVALID LOG LEVEL!"
  End Select
  sDateTime = CStr(Now())
  LogMessage = sLogLevel & " " & Environ("Userdomain") & "\" & Environ("Username") & " " & sDateTime & " [" & Me.SubName & "] - " & sLogText
  #If Not Logging_cashed Then
    FileNum = FreeFile
    Open Me.LogFilePath For Append As #FileNum
    Print #FileNum, LogMessage
    Close #FileNum
  #End If
  #If Logging_on_Screen Then
    wsW.Cells(iThisLogRow, 5) = LogMessage
    iThisLogRow = iThisLogRow + 1
  #End If
End Sub
Private Sub Class_Initialize()
  #If Logging_cashed And Not Logging_on_Screen Then
    Err.Raise Number:=vbObjectError + 513, Description:="Logging_cashed requires Logging_on_Screen"
  #End If
End Sub
Private Sub Class_Terminate()
  #If Logging_cashed Then
    Dim i As Long, FileNum As Integer, LogMessage As String
    FileNum = FreeFile
    Open Me.LogFilePath For Append As #FileNum
    For i = 3 To iThisLogRow - 1
      LogMessage = wsW.Cells(i, 5).Text
      Print #FileNum, LogMessage
    Next i
    Close #FileNum
  #End If
End Sub

Bitte den Haftungsausschluss im Impressum beachten.

Logging_v11.xlsm [56 KB Excel Datei, ohne jegliche Gewährleistung]