User Tools

Site Tools


vba:classes:log4vba

[VBA] Log4vba

Eine Klasse für Access und Excel um Errors abzufangen, zu loggen etc.

Version 1.5.6 21.10.2019
Das Modul hat versteckte Attribute. Damit diese aktiv übernommen werden reicht es nicht aus, den Code in ein neues Modul zu kopieren. Man muss das Modul aus der Datei nach VBA importieren.
Bild zum Import

Download log4vba.cls (V-1.5.6)

Mit dem Errohandling und Debug-Informationen in VBA ist es immer wieder mühsam, jedesmal alles auszuprogrammieren. Darum habe ich für mich diese Klasse geschrieben. Sie ist einfach zu implementieren und ermöglicht ein einheitliches Error-Handling über ein gesammtes Projekt.

Definitionen

Für die Ausgabe der Resultate verwendete ich die Funktion print_r() bzw. d().

Compile-Settings

Am Anfang der Klasse hat es Compile-Settings. Falls die entsprechenden Module von mir im Projekt vorhanden sind, können die entsprechenden Settings auf True gestellt werden. Die Ausgaben in die Konsole oder in das LogFile können damit verbessert werden. Sie sind abernicht zwingend!

ms_product

Gewisse Funktionen sind abhängig, ob die Klasse in einem Word, Access, Excel verwendet wird.

#Const C_ACCESS = "ACCESS"
#Const C_EXCEL = "EXCEL"
#Const ms_product = C_ACCESS

lib_json_exists

Angabe, ob das [VBA] JSON in diesem Projekt vorhanden ist oder nicht. Falls nicht reduziert sich nur die Ausgabe beim Debugen. Ist aber kein Problem.

'Ab lib_json Version 2.1.0
#Const lib_json_exists = False

lib_printr_exists

Angabe, ob das Modul [VBA] print_r() in diesem Projekt vorhanden ist oder nicht

#Const lib_printr_exists = False

Creatoren

Es gibt verschiedene Möglichkeiten ein Log4vba zu initialisieren

Methode Rückgabetyp Beschreibung
instance Log4vba Erstellt eine neue Instance
startMethode Log4vba Erstellt ein Clone einer Instance und setzt die Methode fest
startClass Log4vba Erstellt ein Clone einer Instance und setzt die Klasse fest
construct Log4vba Initialisiert ein bestehendes Objekt neu

Auslöser

Die folgenden Funktion werden verwendet um ein Fehler oder eine Meldung auszuwerten

Methode Rückgabetyp Beschreibung
debugMsg Boolean Logt eine Nachricht, sofern DebugMode = True ist
debugValue Boolean Logt eine Variable, sofern DebugMode = True ist
info Boolean Logt als Information
warning Boolean Logt als Warnung
error Boolean Logt als Error
logUserType Boolean Logt einen ausgewählten Typ. AUch UserType

LogTypen

Es gibt verschiedene Logtypen. Diese sind über den Enum eLogType in manchen Funktionen auswählbar. Die Werte sind nicht kombinierbar.

Typ Enum Wert Beschreibung Auslöser
Debug ltDebug 1 Debug ist dazugeeignet, während dem Entwickeln und Testen verschiedene Informationen auszugeben. Darum werdedie Informationen im auf Debug-Lever nur ausgegeben, wenn die Eigenschaft debugMode auf True gesetzt ist debugMsg, debugValue
Info ltInfo 2 Einfache informative Ausgabe. info
Warning ltWarning 3 Eine Warnung ist zwar wichtig, aber nicht wichtig genug um den Code zu unterbechen warning
Error ltError 4 Bei einem Fehler. Meistens will man da den Code unterbrechen error

Mit der Funktion addUserType() können Applikationspezifische weitere Levels defniert werden

TypeSettings

Jedem LogTyp kann man definieren, wie er sich verhalten soll. Die Settings des Enum eTypeSetting sind kombinierbar

Enum Wert Beschreibung
eprNothing 0 Keine Rück-/Ausgabe
Angaben über den Return-Wert. Sind nicht kombinierbar. Bei mehrfachwahl ist wird der Erste verwendet
eprReturnAssert 1 Rückgabewert für debug.assert
eprReturnMsg 2 Rückgabewert ist die Fehlermeldung
Angaben, was mit der Meldung gemacht werden soll. Sie können kombiniert werden
eprOutConsole 4 Ausgabe ins Direktfenster
eprOutMsgBox 8 Als MassegeBox ausgeben
eprOutLogFile 16 In ein LogFile schreiben(DB-Pfad\DB-Name.log)
eprOutFunction 512 Es kann eine Funktion angegeben werden, die ausgeführt wird (eval). Nur für Access
eprOutTable 1024 Die Logeinträge werden in eine Tabelle geschrieben
Angaben zur Formatierung. Sie können kombiniert werden
eprFrmtSourceExtend 32 Beim zusammensetzen der Source die Originalsource nicht ersetzen sondern erweitern
eprFrmtNoSource 256 Die Source nicht anzeigen
eprFrmtJson 64 Json wird höher gewertet als lib_printr_exists
eprFrmtPrintR 128 PrintR wird höher gewertet als json, wird nur verwendet wenn lib_json_exists eingbunden ist
eprFrmtNoFormat 2048 Weder json noch print_r unterstützen, auch wenn die Constante dies zulassen würde

Properties

Einige Properties können gesetzt oder gelesen werden. Alle last* Properties sind information zur letzten Meldung, die verarbeitet wurde

Name Type Lesen(r)/Schreiben(w) Beschreibung
debugMode Boolean rw DebugMode ein/ausschalten
typeSetting(eLogType) eTypeSetting rw Die gesammelten Paramter für ein Logtype
lastLogType eLogType r Letzter Log-Type
lastLogMessage String r Letzte Message
lastLogSource String r letzte Source
lastLogTimeStamp String r Letzter Timestamp

Creatoren

singleton()

singleton ist ab Version 1.5.0 keine eigene Funktion mehr. Ich habe festgestellt, dass VBA singleton unterstützt, wenn die Einstellung Attribute VB_PredeclaredId = True gesetzt ist. Und das ist bei dieser Klasse der Fall.

Um auf die singelton zuzugreiffen, müssen die Klammern weggelassen werden.

Log4vba

Beispiel

'Info mit ausgeben
log4vba.info "Test", "Immediate"
02.05.2017 11:31:14   INFO      Immediate - Test
 
'Ausgabe anpassen
log4vba.typeSettings(ltInfo) = eprOutConsole+eprFrmtNoSource
 
'Erneuter Test. Die Source wird nicht mehr ausgegeben
log4vba.info "Test", "Immediate"
02.05.2017 11:32:04   INFO      Test
 
'Debug ist normalerweise nicht eingeschaltet Darum keine Ausgabe
log4vba.debugMsg "Debug Test"
 
'Debug einschalten
Log4vba.debugMode = True
 
'Test wiederholen
log4vba.debugMsg "Debug Test"
02.05.2017 11:33:00   DEBUG     Debug Test

instance()

Gibt eine neue Instanz von Log4vba zurück

object = Log4vba.instance([debugMode])
'/**
' * Erstellt eine neue Instanz von Log4vba
' * @param  eLogType    Standard LogType
' * @return Log4vba
' */
Public Function instance(Optional ByVal iDebugMode = False) As Log4vba

Beispiel

Dim logger As Log4vba
Set logger = Log4vba.instance(true)
'...
debug.assert logger.error (Err)

startMethode()

Erstellt einen Clone der aktuellen Instanz und setzt die Methode fest. Der Start und auch später das Ende werden als Debug gelogt. Der Name der Methode muss mitgegeben werden. Zudem empfiehlt es sich, alle Parameter, welche die Methode empfängt, ebenfall zu übergeben. Siehe im Beispiel am Ende der Seite Loggen in einem Projekt.

Set object = object.startMethode(name [,values])
'/**
' * Startet eine Logging für eine Methode
' * @param  String          Name der Methode
' * @param  Array<>         Ein Arry mit den Aufrufparamtern der Method
' * @return Log4vba         Ein Clone des Objektes
' */
Public Function startMethode( _
        ByVal iSource As String, _
        ParamArray iValueArray() As Variant _
) As Log4vba

startClass()

Erstellt ein Cloneder aktuellen Instanz und setzt die Klasse fest. Siehe dazu das Beispiel Loggen mit Klassen

Set object = object.startMethode(class)
'/**
' * Startet eine Logging für eine Methode
' * @param  Variant         Name der Klasse oder die Instanz
' * @return Log4vba         Ein Clone des Objektes
' */
Public Function startClass(ByRef iClass As Variant) As Log4vba

construct()

Iniitialisier ein bestehendes Objekt neu.

object.construct([debugMode])
'/**
' * Setzt Standardwerte für eine bestehende Instanz
' * @param  eLogType    Standard LogType
' * @return Log4vba
' */
Public Function construct(Optional ByVal iDebugMode = False) As Log4vba

Auslöser

Rückgabewerte der Auslöser

Alle Auslöser haben Boolean als Rückgabewert. Wenn eprAssert gesetzt ist, wird ein entsprechender Wert zurück gegeben. Ansonsten immer True.

  • True: Der Code soll weiterlaufen
  • False: Der Code soll unterbrochen werden

Somit kann der Rückgabewert mit Debug.assert abgefangen werden um in den Debug-Mode zu wechseln.

Beispiel

'Error auf eprAsset + MsgBox mit den Frage Debug Y/N
Log4vba().typeSettings(ltError) = eprAssert + eprMsgBox
 
'Fehler generieren. In der MsgBox wird Yes gedrückt
? Log4vba().error("Ich bin ein Fehler", vbObjectError)
False
 
'und jetzt mit No drücken. Ergo nicht in den Debug-Mode wechseln
? Log4vba().error("Ich bin ein Fehler", vbObjectError)
True

debugMsg()

Sofern debugMode auf True ist, schreibt der Logger einfach die Message ins Log

flag = Log4vba().debugMsg(Message [,Settings])
'/**
' * Nur eine Message im Debugmodus ausgeben
' * @param  String          Freier Text der im Log erscheint
' * @param  eTypeSetting    Die Setings für diesen Type einmalig übersteuern
' * @return Boolen/String   Rückgabewert für debug.assert oder LogMessage
' */
Public Function debugMsg( _
        ByVal iComment As String, _
        Optional ByVal iOverwriteSetting As eTypeSetting = eTypeSetting.[_NA] _
) As Variant

debugValue()

Parst ein Value in ein String und schreibt diesen zusammen mit einem Kommentar ins Log, sofern debugMode True ist.

flag = Log4vba.debugValue([Message] ,Variable [,Settings])
'/**
' * Debug für eine Variable
' * @param  String          Freier Text der im Log erscheint
' * @param  Variant         Variable, die ausgewertet werden soll
' * @param  eTypeSetting    Die Setings für diesen Type einmalig übersteuern
' * @return Boolen/String   Rückgabewert für debug.assert oder LogMessage
' */
Public Function debugValue( _
        Optional ByVal iComment As String, _
        Optional ByVal iValue As Variant, _
        Optional ByVal iOverwriteSetting As eTypeSetting = eTypeSetting.[_NA] _
) As Variant

Beispiel

Ein Beipiel in Direktfenster

Log4vba.debugMode = true
Log4vba.debugValue "Mein Array", array("Stefan", "Erb", "Winterthur")
25.11.2016 10:41:07   DEBUG     Mein Array: ['Stefan','Erb','Winterthur']

Die Ausgabe mit lib_printr_exists aktiviert und eprPrintR für ltDebug.

Log4vba.debugMode = true
Log4vba.typeSettings(ltDebug) = eprConsole+eprPrintR  'Ist als Standard bereits gesetzt. Ist hier nur zur Veranschaulichung
Log4vba.debugValue "Mein Array", array("Stefan", "Erb", "Winterthur")
25.11.2016 12:44:09   DEBUG     Mein Array
                                <Variant()>  (
                                    [0] => <String> 'Stefan'
                                    [1] => <String> 'Erb'
                                    [2] => <String> 'Winterthur'
                                )

info()

Schreibt eine Information. Der Erste Paramter kann ein Text oder ein ErrObject sein

flag = Log4vba.info(Err) 
flag = Log4vba.info(Message [,ErrorBumber [,Source [,Array(Parameters) [,Settings]]]])
'/**
' * @param  ErrObject/String
' * @param  Long            Error-Nummer
' * @param  String          Name der Methode
' * @param  Array<>         Ein Arry mit den Aufrufparamtern der Method
' * @param  eTypeSetting    Die Setings für diesen Type einmalig übersteuern
' * @return Boolen/String   Rückgabewert für debug.assert oder LogMessage
' */
Public Function info( _
        ByRef iError As Variant, _
        Optional ByVal iSource As String, _
        Optional ByRef iValueArray As Variant, _
        Optional ByVal iNumber As Long, _
        Optional ByVal iOverwriteSetting As eTypeSetting = eTypeSetting.[_NA] _
) As Variant

Paramters

  • iError Ein ErrObject oder ein Fehler/Lig/Infotext
  • iSource Name des Funktion, in der der Fehler abgefangen wird
  • iValueArray Array mit den Werten, mit denen die Methode aufgerufen wurde
  • iNumber Fehlernummer
  • iOverwriteSetting Damit kann das Settings für den Type Info einmalig übersteuert werden

Standartsetting

eprConsole
Ausgabe in Konsole

Beispiel

Public Sub testLog4vba2(ByVal iId As Long, ByRef iArray As Variant)
On Error GoTo Err_Handler
    'Fehler generieren
    Err.raise 11    'Division by zero
 
    Exit Sub
Err_Handler:
    Log4vba().info "Manuelle Meldung", "testLog4vba2()"
    Debug.Assert Log4vba.info(Err, , "testLog4vba2()", Array(iId, iArray))
End Sub
testLog4vba2 123, array("Stefan", "Erb", "Winterthur")
25.11.2016 11:19:05   INFO      testLog4vba2()()
                                (-2147221504) Manelle Meldung
25.11.2016 11:19:05   INFO      testLog4vba2()(123, ['Stefan','Erb','Winterthur'])
                                (11) Division by zero

warning()

Analog zu info

Standartsetting

eprConsole + eprMsgBox
Ausgabe in Konsole und als MessageBox (OKonly)

error()

Analog zu info

Standartsetting

eprConsole + eprMsgBox + eprAssert
Ausgabe in Konsole und als Message. Bei der Message kann in den Debug-Mode gewechselt werden, sofern die Methode auf debug.assert gesetzt wird.

Ausgabe

Die Log-Info können auf verschiedene Arten ausgegeben werden. Das kann sich nach LogType unterscheiden. Die verschiedenen Ausgabetypen sind kombinierbar

logger.typeSettings(ltWarning) = eprOutConsole + eprOutLogFile

Konsole

Mit dem TypeSetting eprOutConsole aktiviert man die Ausgabe in die Konsole (DirektFenster).

Message Box

Die Meldungen können auch als MsgBox angezeigt werden. Dazu verwendet man das TypeSetting eprOutMsgBox.

Logdatei

Das Log kann auch in eine Externe Datei geschrieben werden. Mit dem TypeSetting eprOutLogFile wird das aktiviert. Es wirt automatisch eine Datei erstellt und verwendet. Sie ist so zu finden: DB-Pfad\DB-Name.log

Eigene Funktion

Nur in MS Access
Man kann die Auswertung des Logs auch einer eigenen Funktion deligieren. Mit dem TypeSetting eprOutFunction wird das aktiviert. Dem Property functionName muss der lokale Funktionsname mitgegeben werden. Es muss eine Funktion und keine Sub sein, da hier mit eval() gearbeitet wird. Die Parameter der Funktion müssen so sein

Public Function myLogFunction(ByVal iLogType As eLogType, ByVal iText As String)

Weiteres dazu im Beispiel Loggen mit eigener Auswertung eprOutFunction.

Log Tabelle

Das Log kannauch in eine eigene Tabelle (Access) oder Sheet (Excel) geschrieben werden. Dazu beim TypeSetting den Wert eprOutTable setzen und falls der Name nicht T_LOG sein soll, noch den Tabellennamen dem Property logTable übergeben.

Weitere Methoden

resetSettings()

Setzt das Objektauf den Standard zurück. Wenn kein logType mitgegeben wird, dann wird alles zurückgesetzt. Ink. dem debugMode. Ansonsten werde nur die Settings des ausgewählten LogType zurückgesezt

object.resetSettings([eLogType)
'/**
' * Setzt alle settings auf den Standard zurück
' * @param  LogType     Falls gesetzt, wird nur der eine Logtype zurückgesetzt
' */
Public Sub resetSettings( _
        Optional ByVal iLogType As eLogType = eLogType.[_NA] _
)

Beispiel

Loggen in einem Projekt

In dem Property myLogger wird der Logger definiert. Die Methode myStart führt nur die Methode myMethode aus. Im Direktfenster sieht man nachher schön, wie die verschieden Schritte durch die Funktionen aufegezeichnet werden

myLogger ist eine Statische Instanz von log4vba. Da werden alle Standardsettings gehandhabt

myLogger
'/**
' * Den eigenen Logger definieren
' * @return myLogger
' */
Public Property Get myLogger() As Log4vba
    Static logger As Log4vba
    If logger Is Nothing Then
        Set logger = Log4vba.instance
        'Spezielle Settings, wie man die gerne im Projekt hätte
        logger.typeSettings(ltWarning) = eprOutConsole
        logger.typeSettings(ltError) = eprOutConsole
        'Ich will den DebuMode haben, da mein Projek noch lange nicht fertig ist.
        logger.debugMode = True
    End If
    Set myLogger = logger
End Property

myStart ist eine einfache Sub, die mindestens eine Nummer als Parameter erwartet. Mit startMethode wird eine Instanz von log4vba erstellt, die nur innerhalb der Methode Gültigkeit hat.

myStart
Public Sub myStart(ByVal iId As String, Optional ByRef iObject As Object)
    Dim log As Log4vba: Set log = myLogger.startMethode("myStart", iId, iObject)
On Error GoTo Err_Handler
 
    myMethode iId, Array(1, 2), iObject
    Exit Sub
Err_Handler:
    Debug.Assert log.error(Err)
End Sub

Eine Weitere Methode, wieder mit einer eigenen Instanz von log4vba

myMethode
Public Sub myMethode(ByVal iId As String, ByRef iArray As Variant, Optional ByRef iObject As Object)
    Dim log As Log4vba: Set log = myLogger.startMethode("myMethode", iId, iArray, iObject)
    Dim nextId As Long
    Dim dividend As Long
On Error GoTo Err_Handler
 
    'Eine Variabel auswerten
    nextId = iId + 1
    log.debugValue "Nächste ID", nextId
    log.debugValue "übergebener Array", iArray
 
    'Und jetzt ein passiert ein Fehler. Der 11er wird abgefangen
    dividend = 0
    If dividend = 0 Then log.warning "Achtung, Dividend muss definiert werden", "test()", , 11, eprFrmtSourceExtend
    nextId = iId / dividend
 
    'Und noch ein Fehler. Der 94 nicht
    nextId = iId / Null
 
    Exit Sub
 
Err_Handler:
    'Division by zero -> Ersetzen mit Division durch 1
    If Err.number = 11 Then
        log.info Err
        dividend = 1
        Resume
    End If
    'Restliche Fehler als Fehler behandeln
    Debug.Assert log.error(Err)
End Sub

Und der Test im Direktfenster zeigt, wie die Methodengebunden Instanzen sauber ausgeben, wo was passiert ist

myStart 125, currentdb
30.11.2016 11:35:11   DEBUG     myStart - started (125, <Database>)
30.11.2016 11:35:11   DEBUG     myMethode - started (125, [1,2], <Database>)
30.11.2016 11:35:11   DEBUG     myMethode - Nächste ID: 126
30.11.2016 11:35:11   DEBUG     myMethode - übergebener Array: [1,2]
30.11.2016 11:35:11   INFO      myMethode - [#11] Division by zero
30.11.2016 11:35:11   ERROR     myMethode - [#94] Invalid use of Null
30.11.2016 11:35:11   DEBUG     myMethode - ended
30.11.2016 11:35:11   DEBUG     myStart - ended

Loggen mit Klassen

Hier will ich zeigen, wie man mit Klassen und Methoden loggen kann.

Als erstes eine Klasse. Hier eine Testklasse mit dem passenden Namen LoggerTest. Das Property myLogger ist im Beispiel weiter oben bereits erläutert.

LoggerTest.cls
Option Explicit
 
'Der Klasenlogger
Private classLogger As Log4vba
 
'/**
' * Initialisieren des Objektes. Hier wird auch der Klassenlogger serstellt
' */
Private Sub Class_Initialize()
    Set classLogger = myLogger.startClass(Me)
End Sub
 
' * Eine Trstfunktion
' * @param  Long    Eine Zahl
' */
Public Sub runTest(ByVal iValue As Long)
    'Den Methodenlogger aus dem Klassenlogger erstellen
    Dim log As Log4vba: Set log = classLogger.startMethode("runTest", iValue)
On Error GoTo Err_Handler
 
    'Eine beliebige Info loggen
    log.info "Jetzt folgt dann gleich ein Fehler"
 
    'En Fehler generieren
    Debug.Print iValue / 0
 
    Exit Sub
Err_Handler:
    'Fehler loggen
    log.error Err
End Sub

Dann eine lokale Methode, welche die Klasse LoggerTest verwendet.

udf_runLoggerTest.bas
Sub runLoggerTest()
    Dim log As Log4vba: Set log = myLogger.startMethode("runLoggerTest")
 
    Dim test As New LoggerTest
    test.runTest 1
 
End Sub

Das Resultat:

19.12.2016 09:30:48   DEBUG     runLoggerTest - started
19.12.2016 09:30:48   DEBUG     LoggerTest - created
19.12.2016 09:30:48   DEBUG     LoggerTest.runTest - started (1)
19.12.2016 09:30:48   INFO      LoggerTest.runTest - Jetzt folgt dann gleich ein Fehler
19.12.2016 09:30:48   ERROR     LoggerTest.runTest - [#11] Division by zero
19.12.2016 09:30:48   DEBUG     LoggerTest.runTest - ended
19.12.2016 09:30:48   DEBUG     LoggerTest - terminated
19.12.2016 09:30:48   DEBUG     runLoggerTest - ended

Und wenn man in myLogger den Debug-Mode ausschaltet bliebt noch das folgende übrig

19.12.2016 09:32:58   INFO      LoggerTest.runTest - Jetzt folgt dann gleich ein Fehler
19.12.2016 09:32:58   ERROR     LoggerTest.runTest - [#11] Division by zero

Loggen mit eigener Auswertung (eprOutFunction)

Wenn man das Log selber auswerten will, kann man entweder die Klassen erweitern oder man schreibt sich eine Funktion, welche mit eval() ausführbar ist. \\Ich habe zum Beispiel in einem Projekt ein Formular, welches ein grosses Textfeld beinhaltet.

Das Formular hat ein Textfeld mit dem Namen txtConsole

FRM_SYS_CONSOLE.log
Public Sub log(ByVal iText As String)
    Me.txtConsole = IIf(Not IsNull(Me.txtConsole), Me.txtConsole & vbCrLf, Empty) & iText
    Me.txtConsole.SelStart = Len(Me.txtConsole)
    Me.txtConsole.SelLength = 0
End Sub

in einem Modul habe ich die Funktion writeToConsole() hinterlegt. Diese öffnet das Formular FRM_SYS_CONSOLE und übergibt den Logtext. Die Funktion muss die 2 Parameter eLogType und String haben. Diese werden vom eval() abgefüllt

writeToConsole
'/**
' * Funktion um die Logger-Beiträge in das Konsolenformular zu schreiben
' * Die Typen und Reihenfolge der Paramter sind durch log4vba definiert
' * @param  eLogType
' * @param  String
' */
Public Function writeToConsole(ByVal iLogType As eLogType, ByVal iText As String)
    If Not CurrentProject.AllForms("FRM_SYS_CONSOLE").IsLoaded Then DoCmd.OpenForm "FRM_SYS_CONSOLE"
    Form_FRM_SYS_CONSOLE.log iText
End Function

Meine Loggerdefinition Beinhaltet die folgende Einstellung

        Set cLog = Log4vba.instance()
        cLog.functionName = "writeToConsole"
        cLog.typeSettings(ltInfo) = eprOutConsole + eprFrmtNoSource + eprOutFunction

Wenn ich nun eine Info logge, dann wird writeToConsole() ausgeführt

Code

vba/classes/log4vba.txt · Last modified: 21.10.2019 12:10:41 by yaslaw