[Module] Logging with multiple/custom log targets.

Share your advanced PureBasic knowledge/code with the community.
Env
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 27, 2010 3:20 pm
Location: Wales, United Kingdom

[Module] Logging with multiple/custom log targets.

Post by Env »

Hey folks,

Just sharing a module I put together for the purpose of having a neat and handy way to log messages.

Features:
  • - Add/Remove Custom Log Handlers (e.g. If you wanted to create one for a text file output)
    - Methods protected with mutex, so should be fine using with threaded applications
    - Built-in console log output handler.
    - Enable/disable timestamp decoration
    - Set custom timestamp format
    - Verbose messages which will only be posted to the log output(s) when EnableVerbose is used.
Example Included - This runs on the console, so if you see nothing then compile as a Console target.

Code: Select all

; ====================================================================================================
; Title:        Logging Module
; Description:  Module for logging messages to custom targets
; Author:       Michael R. King (Env)
; License:      MIT
; Revision:     1

; If you like it, feel free to use it, if you really like it then you can buy me coffee :)
; https://ko-fi.com/mikerking
; ====================================================================================================

DeclareModule Log
  
  Enumeration Level
    #Info
    #Warning
    #Error
    #Verbose
  EndEnumeration
  
  Prototype pLogOutput(Level, Message.s)        ;- Prototype for Log Outputs
  
  Declare AddLogOutput(*Output.pLogOutput)      ;- Add a logging output procedure
  Declare RemoveLogOutput(*Output.pLogOutput)   ;- Remove a logging output procedure
  
  Declare EnableVerbose(State = #True)          ;- Enable Verbose messages
  Declare EnableTimestamp(State = #True)        ;- Enable timestamp inclusion
  Declare SetTimestampFormat(Format.s = "%hh:%ii:%ss") ;- Set the Timestamp format
  Declare EnableConsoleOutput(State = #True)    ;- Enable built-in Console log output
  
  Declare Info(Message.s)                       ;- Post an informative message to the log
  Declare Warning(Message.s)                    ;- Post a warning to the log
  Declare Error(Message.s)                      ;- Post an error message to the log
  Declare Verbose(Message.s)                    ;- Post a verbose message to the log
  
EndDeclareModule

Module Log
  
  Structure sLogger
    *mutex
    verbose.a
    timestamp.a
    tsFormat.s
    List *output()
  EndStructure
  
  Global gLogger.sLogger
  With gLogger
    \mutex = CreateMutex()
    \timestamp = #True
    \tsFormat = "%hh:%ii:%ss"
  EndWith
  
  Procedure ConsoleLogOutput(Level, Message.s)
    Select Level
      Case Log::#Info
        ConsoleColor(7, 0)
      Case Log::#Warning
        ConsoleColor(14, 0)
      Case Log::#Error
        ConsoleColor(12, 0)
      Case Log::#Verbose
        ConsoleColor(3, 0)
    EndSelect
    PrintN(Message)
  EndProcedure
  
  Procedure AddLogOutput(*Output.pLogOutput)
    With gLogger
      RemoveLogOutput(*Output)
      LockMutex(\mutex)
      AddElement(\output())
      \output() = *Output
      UnlockMutex(\mutex)
    EndWith
  EndProcedure
  
  Procedure RemoveLogOutput(*Output.pLogOutput)
    With gLogger
      LockMutex(\mutex)
      ForEach \output()
        If \output() = *Output
          DeleteElement(\output())
          ProcedureReturn
        EndIf
      Next
      UnlockMutex(\mutex)
    EndWith
  EndProcedure
  
  Procedure EnableVerbose(State = #True)
    With gLogger
      If State
        \verbose = #True
      Else
        \verbose = #False
      EndIf
    EndWith
  EndProcedure
  
  Procedure EnableTimestamp(State = #True)
    With gLogger
      If State
        \timestamp = #True
      Else
        \timestamp = #False
      EndIf
    EndWith
  EndProcedure
  
  Procedure SetTimestampFormat(Format.s = "%hh:%ii:%ss")
    gLogger\tsFormat = Format
  EndProcedure
  
  Procedure EnableConsoleOutput(State = #True)
    If State
      AddLogOutput(@ConsoleLogOutput())
    Else
      RemoveLogOutput(@ConsoleLogOutput())
    EndIf
  EndProcedure
  
  Procedure.s Timestamp()
    If gLogger\timestamp
      ProcedureReturn "[" + FormatDate(gLogger\tsFormat, Date()) + "] "
    EndIf
    ProcedureReturn ""
  EndProcedure
  
  Procedure Info(Message.s)
    Protected *handler.pLogOutput
    With gLogger
      LockMutex(\mutex)
      ForEach \output()
        *handler = \output()
        *handler(#Info, Timestamp() + Message)
      Next
      UnlockMutex(\mutex)
    EndWith
  EndProcedure
  
  Procedure Warning(Message.s)
    Protected *handler.pLogOutput
    With gLogger
      LockMutex(\mutex)
      ForEach \output()
        *handler = \output()
        *handler(#Warning, Timestamp() + "Warning: " + Message)
      Next
      UnlockMutex(\mutex)
    EndWith
  EndProcedure
  
  Procedure Error(Message.s)
    Protected *handler.pLogOutput
    With gLogger
      LockMutex(\mutex)
      ForEach \output()
        *handler = \output()
        *handler(#Error, Timestamp() + "ERROR: " + Message)
      Next
      UnlockMutex(\mutex)
    EndWith
  EndProcedure
  
  Procedure Verbose(Message.s)
    Protected *handler.pLogOutput
    With gLogger
      LockMutex(\mutex)
      If \verbose
        ForEach \output()
          *handler = \output()
          *handler(#Verbose, Timestamp() + Message)
        Next
      EndIf
      UnlockMutex(\mutex)
    EndWith
  EndProcedure
  
EndModule

CompilerIf #PB_Compiler_IsMainFile
  
 ; ========= EXAMPLE =========
  
  ; Our Custom Log Output
  Procedure MyLogTarget(MessageLevel, Message.s)
    Debug Message
  EndProcedure
  
  ; Add our Custom Log Output to the Logger
  Log::AddLogOutput(@MyLogTarget())
  
  ; Use a Console to demonstrate the logging
  OpenConsole("Log Test")
  EnableGraphicalConsole(#True)
  Log::EnableConsoleOutput(#True)
  
  ; Enable Verbose messages
  Log::EnableVerbose(#True)
  
  ; Log some messages
  Log::Verbose("Example has Started (This is a verbose message which can be filtered out by disabling them)")
  
  Log::Info("This is just an informative message!")
  Log::Warning("This is a warning!")
  Log::Error("An error has happened.  Luckily this is just an example error.")
  
  Log::Info("The Example has ended. Hit return to quit")
  Input()
  End 0

CompilerEndIf
Last edited by Env on Fri Feb 08, 2019 5:33 pm, edited 1 time in total.
Thanks!
Env
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 27, 2010 3:20 pm
Location: Wales, United Kingdom

Re: [Module] Logging with multiple/custom log targets.

Post by Env »

Example of a custom log file writer:

Code: Select all

#Log_FileOutput = "log.txt"

Procedure FileLogOutput(MessageLevel, Message.s)
  Protected.i file
  If FileSize(#Log_FileOutput) < 1
    file = CreateFile(#PB_Any, #Log_FileOutput)
  Else
    file = OpenFile(#PB_Any, #Log_FileOutput)
  EndIf
  If IsFile(file)
    FileSeek(file, Lof(file))
    WriteStringN(file, Message)
    CloseFile(file)
  EndIf    
EndProcedure

Log::AddLogOutput(@FileLogOutput())
Thanks!
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: [Module] Logging with multiple/custom log targets.

Post by #NULL »

Very nice, thanks :)
Post Reply