SimplePBLogger

Anwendungen, Tools, Userlibs und anderes nützliches.
Benutzeravatar
Qnode
Beiträge: 67
Registriert: 19.07.2018 20:41
Computerausstattung: i5, 16GB RAM, Win10, PureBasic 6.00 (Windows - x64)

SimplePBLogger

Beitrag von Qnode »

Hey,

ich hab mir einen kleinen Logger geschrieben, weil ich den für ein anderes Projekt verwenden will. Ist die erste kleine Sache, die ich PB fertiggestellt habe und sie spiegelt meinen aktuellen Stand in PB wider (bin ja noch nicht so lange dabei).

Ich bin nicht der Meinung, dass die Welt nur auf mein Progrämmchen mit den rudimentären Loggerfunktionen gewartet hat, aber falls jemand Zeit und Nerv hat, sich die Sache mal anzusehen und mir dann zu sagen, was alles falsch ist oder besser gelöst werden könnte, wäre das für mich ein sehr hilfreiches Feedback um mich weiterzuentwickeln. Stört euch bitte nicht an meinem ebenfalls rudimentären Englischkenntnissen, die Schule ist schon eine Weile her bei mir :?

Aktuelle Fassung vom 09.09.2018 (Copyright korrigiert)

Code: Alles auswählen

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; + SimplePBLogger                                                                                              
; + Utility to log informations in PureBasic-Programs                                               
; + Version 0.3                                                                                                    
; + MIT License                                                                                                   
; + Copyright (c) 2018 Joachim Weiß
; + Permission is hereby granted, free of charge, to any person obtaining a copy 
; + of this software and associated documentation files (the "Software"), to deal 
; + in the Software without restriction, including without limitation the rights 
; + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
; + copies of the Software, and to permit persons to whom the Software is 
; + furnished to do so, subject to the following conditions: +
; +                                                                                                                                       
; + The above copyright notice and this permission notice shall be included in all 
; + copies or substantial portions of the Software. 
; +                                                                                                                                       
; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
; + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
; + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
; + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
; + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
; + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  
; + SOFTWARE.                            
; + Hints:                                                                    
; + Before using the logger, initialize him! Use InitializeLogger an give him 
; + as parameters the path, where the logfiles shall be stored and optional         
; + the maximal number of logfiles you want to have for your application      
; + (standard is 10 - with the maxNumber you can avoid to have too much files)
; + To write an Entry in a Logfile use WriteLogEntry. Select a LogLevel from   
; + the constants (or create there a new loglevel, if you want) and create    
; + the message you want to store. You have also the option to store variables
; + and their values etc. Please use SPBL_Parameter. Create a variable of     
; + this structure an add key/value-pairs to the \entries-map. Put the        
; + variable as the third parameter in the WriteLogEntry-call                 
; + If you want to export or import logfiles you can do this with the         
; + ExportLogFiles / ImportLogFiles-procedure. These procedures need the path 
; + of the Destination where the files should be copied from/to. You have the 
; + option to tell the procedures how many files have to be copied. If there  
; + are more files than to copy, the procedures take the youngest files of the
; + given number.                                                             
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


DeclareModule SimplePBLogger  
  
  #SPBL_DEBUG = "   Debug   "
  #SPBL_INFORMATION = "-  Information  -"
  #SPBL_WARNING = "!  Warning  !"
  #SPBL_ERROR = "*** ERROR ***"
  
  Structure SPBL_Parameter  ; for optional key/value-entries to write in Logfiles
    Map Entries.s()
  EndStructure
  
  ; Procedure to initialize the logger. Returns #True if successful
  ; If there are more logfiles than allowed (is set with the
  ; MaxNumberLogfiles parameter) the procedure will delete the oldest file(s)
  Declare.i InitializeLogger(LocationLogfile.s, MaxNumberLogfiles.i = 10)
  
  ; Procedure to write an entry into the logfile. Returns #True if succesfull
  ; This procedure will create an new logfile, if there is no one for the actual
  ; date. 
  Declare.i WriteLogEntry(LogLevel.s, Message.s, *Parameters.SPBL_Parameter = #Null)
  
  ; Procedure to copy Logfiles from their standard location to another location
  ; e.g. for backup or for sendingt them to the programmer. Returns #True if successful
  Declare.i ExportLogFiles(TargetLocation.s, NumberOfFiles.i = 10)
  
  ; Procedure to copy Logfiles to their standard location e.g. to restore them
  ; Returns #True if successful
  Declare.i ImportLogFiles(FromLocation.s, NumberOfFiles.i = 10)
  
EndDeclareModule

Module SimplePBLogger
  
  EnableExplicit
  
  Structure Logfile ; 
    Name.s
    Date.i
  EndStructure
  
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    #Separator = "\"
  CompilerElse
    #Separator = "/"
  CompilerEndIf
  
  Global.s Location
  Global.i MaxFiles
  Global NewList CurrentLogFiles.Logfile()
  Global LoggerMutex = CreateMutex()
 
  Global.i LoggerIsInitialized = #False
  #LOGFILE = 1
  #LOGDIRECTORY = 1
  #TARGETDIRECTORY = 2
  
  
  Procedure.i InitializeLogger(LocationLogFiles.s, MaxNumberLogfiles.i = 10)
    LockMutex(LoggerMutex)
    If ExamineDirectory(#LOGDIRECTORY, LocationLogFiles, "Logfile_*.txt")
      Location = LocationLogFiles
      MaxFiles = MaxNumberLogfiles
      While NextDirectoryEntry(#LOGDIRECTORY)
        AddElement(CurrentLogFiles())
        With CurrentLogFiles()
          \Name = DirectoryEntryName(#LOGDIRECTORY)
          \Date = DirectoryEntryDate(#LOGDIRECTORY, #PB_Date_Created)
        EndWith
      Wend  
      SortStructuredList(CurrentLogFiles(), #PB_Sort_Ascending, 
                         OffsetOf(Logfile\Date), TypeOf(Logfile\Date))
      FinishDirectory(#LOGDIRECTORY)
      ; Make shure, that number of logfiles is lower than MaxFiles
      FirstElement(CurrentLogFiles())
      While ListSize(CurrentLogFiles()) > MaxFiles
        DeleteFile(CurrentLogFiles()\Name)
        DeleteElement(CurrentLogFiles(), 1) 
      Wend
      LoggerIsInitialized = #True
    EndIf
    UnlockMutex(LoggerMutex)
    ProcedureReturn LoggerIsInitialized
  EndProcedure
    
  Procedure.i  WriteLogEntry(LogLevel.s, Message.s, *Parameters.SPBL_Parameter = #Null)
    Define.i Result = #False
    LockMutex(LoggerMutex)
    If LoggerIsInitialized
      Define.s Filename
      Filename = Location + 
                 "Logfile_" + FormatDate("%yyyy_%mm_%dd", Date()) + ".txt"
      If OpenFile(#LOGFILE, Filename, #PB_File_Append)
        WriteStringN(#LOGFILE, "-------------------------------------------")
        WriteStringN(#LOGFILE, FormatDate("%hh:%ii:%ss: ", Date()))
        WriteStringN(#LOGFILE, LogLevel)
        WriteStringN(#LOGFILE, Message)
        If *Parameters <> #Null
          ForEach *Parameters\Entries()
            WriteStringN(#LOGFILE, MapKey(*Parameters\Entries()) + " => " +
                                   *Parameters\Entries())
          Next
        EndIf
        CloseFile(#LOGFILE)
        Result = #True
      EndIf
    EndIf
    UnlockMutex(LoggerMutex)
    ProcedureReturn Result
  EndProcedure
  
  Procedure ExportLogFiles(TargetLocation.s, NumberOfFiles.i = 10)
    Define.i Result = #False
    LockMutex(LoggerMutex)
    If LoggerIsInitialized
      If ExamineDirectory(#TARGETDIRECTORY, TargetLocation, "*.*")
        Define i.i = 1
        Define Destination.s
        SortStructuredList(CurrentLogFiles(), #PB_Sort_Descending,
                           OffsetOf(Logfile\Date), TypeOf(Logfile\Date))
        ForEach CurrentLogFiles()
          If i > NumberOfFiles
            Break
          EndIf
          Destination = TargetLocation
          If Right(TargetLocation, 1) <> #Separator
            Destination + #Separator
          EndIf
          Destination + GetFilePart(CurrentLogFiles()\Name)
          CopyFile(CurrentLogFiles()\Name, Destination)
          i + 1
        Next 
        FinishDirectory(#TARGETDIRECTORY)
        Result = #True
      EndIf  
    EndIf
    UnlockMutex(LoggerMutex)
    ProcedureReturn Result
  EndProcedure
  
  Procedure ImportLogFiles(FromLocation.s, NumberOfFiles.i = 10)
    Define.i  Result = #False
    LockMutex(LoggerMutex)
    If LoggerIsInitialized
      If ExamineDirectory(#TARGETDIRECTORY, FromLocation, "Logfile_*.txt")
        Define i.i = 1
        Define Destination.s
        NewList FileList.Logfile()
        While NextDirectoryEntry(#TARGETDIRECTORY)
          AddElement(FileList())
          With FileList()
            \Name = DirectoryEntryName(#TARGETDIRECTORY)
            \Date = DirectoryEntryDate(#TARGETDIRECTORY, #PB_Date_Created)
          EndWith
        Wend     
        SortStructuredList(FileList(), #PB_Sort_Ascending,
                           OffsetOf(Logfile\Date), TypeOf(Logfile\Date))
        ForEach FileList()
          If i > NumberOfFiles
            Break
          EndIf
          Destination = FromLocation
          If Right(FromLocation, 1) <> #Separator
            Destination + #Separator
          EndIf
          Destination + GetFilePart(FileList()\Name)
          CopyFile(Destination, Location + FileList()\Name)
          i + 1
        Next     
        FinishDirectory(#TARGETDIRECTORY)
        Result = #True
      EndIf  
    EndIf
    UnlockMutex(LoggerMutex)
    ProcedureReturn Result
  EndProcedure
 
     
  
EndModule


  


Beispielcode:

Code: Alles auswählen

; Examplecode for SimplePBLogger

Define.s LogDirectory = GetCurrentDirectory()
Define.i MaxNumberOfLogFiles = 25

Define loggerWorks = InitializeLogger(LogDirectory, MaxNumberOfLogFiles)
; you can check loggerWorks to find out if the logger can be used now.

WriteLogEntry(#SPBL_INFORMATION, "My first entry...")

; let's transport some information to the logfile
Define.i a = 10
Define.s b = "Hello"
Define information.SPBL_Parameter ; the wrapper for the variables
information\Entries("a") = Str(a) 
information\Entries("b") = b
WriteLogEntry(#SPBL_INFORMATION, "Here are some values:", information)

; Export the logfiles
ExportLogFiles(GetTemporaryDirectory(), MaxNumberOfLogFiles)

; to get the logfiles back, but maximal 10 files (standardoption):
ImportLogFiles(GetTemporaryDirectory())
  
Zuletzt geändert von Qnode am 09.09.2018 19:16, insgesamt 3-mal geändert.
Benutzeravatar
HeX0R
Beiträge: 2954
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win10 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2
Kontaktdaten:

Re: SimplePBLogger

Beitrag von HeX0R »

Ich habe das nur mal eben überflogen, aber ich denke hier solltest Du die beiden Zeilen (Delete...) besser tauschen, sonst löschst Du eine falsche Datei:

Code: Alles auswählen

While ListSize(CurrentLogFiles()) > MaxFiles
  DeleteElement(CurrentLogFiles(), 1)
  DeleteFile(CurrentLogFiles()\Name)
Wend
Benutzeravatar
#NULL
Beiträge: 2235
Registriert: 20.04.2006 09:50

Re: SimplePBLogger

Beitrag von #NULL »

EnableExplicit musst du direkt ins Module schreiben, wenn du es da verwenden willst, und außerhalb lieber dem Kontext überlassen.

Code: Alles auswählen

;EnableExplicit

DeclareModule SimplePBLogger
 
  EnableExplicit
 
  Define x = 44
  
EndDeclareModule

Module SimplePBLogger
  
  EnableExplicit

  Define y.i = 12
 
EndModule
Nett ist auch immer ein Beispiel Code. Den kannst du folgendermaßen ans Ende der Datei hängen:

Code: Alles auswählen

...

CompilerIf #PB_Compiler_IsMainFile
  
   SimplePBLogger::InitializeLogger("/tmp/")
   SimplePBLogger::WriteLogEntry("my log level", "hallo")
  
CompilerEndIf
my pb stuff..
Bild..jedenfalls war das mal so.
Benutzeravatar
mk-soft
Beiträge: 3695
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: SimplePBLogger

Beitrag von mk-soft »

Noch ein paar Mutex rein und es funktioniert auch mit Threads :wink:
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: SimplePBLogger

Beitrag von ts-soft »

@#NULL

Ein EnableExplicit im Bereich des Modules reicht :wink:
Entweder im Declare Bereich, oder im Modulbereich, wenn es nur dort gelten soll. Doppelt gemoppelt bringt aber nur unnötigen Schreibaufwand.
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
Qnode
Beiträge: 67
Registriert: 19.07.2018 20:41
Computerausstattung: i5, 16GB RAM, Win10, PureBasic 6.00 (Windows - x64)

Re: SimplePBLogger

Beitrag von Qnode »

#NULL hat geschrieben:Nett ist auch immer ein Beispiel Code. Den kannst du folgendermaßen ans Ende der Datei hängen...
Autsch! Hatte Beispielcode vorbereitet und vergessen, ihn zu posten. Hier ist er:

Code: Alles auswählen

; Examplecode for SimplePBLogger

Define.s LogDirectory = GetCurrentDirectory()
Define.i MaxNumberOfLogFiles = 25

Define loggerWorks = InitializeLogger(LogDirectory, MaxNumberOfLogFiles)
; you can check loggerWorks to find out if the logger can be used now.

WriteLogEntry(#SPBL_INFORMATION, "My first entry...")

; let's transport some information to the logfile
Define.i a = 10
Define.s b = "Hello"
Define information.SPBL_Parameter ; the wrapper for the variables
information\Entries("a") = Str(a) 
information\Entries("b") = b
WriteLogEntry(#SPBL_INFORMATION, "Here are some values:", information)

; Export the logfiles
ExportLogFiles(GetTemporaryDirectory(), MaxNumberOfLogFiles)

; to get the logfiles back, but maximal 10 files (standardoption):
ImportLogFiles(GetTemporaryDirectory())
  
Benutzeravatar
Qnode
Beiträge: 67
Registriert: 19.07.2018 20:41
Computerausstattung: i5, 16GB RAM, Win10, PureBasic 6.00 (Windows - x64)

Re: SimplePBLogger

Beitrag von Qnode »

HeX0R hat geschrieben:Ich habe das nur mal eben überflogen, aber ich denke hier solltest Du die beiden Zeilen (Delete...) besser tauschen, sonst löschst Du eine falsche Datei:

Code: Alles auswählen

While ListSize(CurrentLogFiles()) > MaxFiles
  DeleteElement(CurrentLogFiles(), 1)
  DeleteFile(CurrentLogFiles()\Name)
Wend

Hm, kannst du mir sagen warum? Ich hatte den Code ausgetestet und keine Fehler bemerkt (oder habe ich da was übersehen?). Aber ich verstehe, was du meinst und habe die Befehle umgestellt.
Zuletzt geändert von Qnode am 30.08.2018 18:19, insgesamt 1-mal geändert.
Benutzeravatar
Qnode
Beiträge: 67
Registriert: 19.07.2018 20:41
Computerausstattung: i5, 16GB RAM, Win10, PureBasic 6.00 (Windows - x64)

Re: SimplePBLogger

Beitrag von Qnode »

mk-soft hat geschrieben:Noch ein paar Mutex rein und es funktioniert auch mit Threads :wink:
Verlockender Gedanke :D . Habe mit Mutex schon mal rumgespielt, aber ich mal sehen, ob ich das hier reingebastelt bekomme..."
Benutzeravatar
HeX0R
Beiträge: 2954
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win10 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2
Kontaktdaten:

Re: SimplePBLogger

Beitrag von HeX0R »

Qnode hat geschrieben:Hm, kannst du mir sagen warum? Ich hatte den Code ausgetestet und keine Fehler bemerkt (oder habe ich da was übersehen?). Aber ich verstehe, was du meinst und habe die Befehle umgestellt.
Ich denke Du wolltest die ältesten Datein löschen, wenn Du aber erst das Element löschst, wird das nachfolgende Element aktiv und DeleteFile löscht das zweitälteste.
In der Theorie, dürfte so das älteste File nie gelöscht werden (wie gesagt nix ausprobiert).
Benutzeravatar
mk-soft
Beiträge: 3695
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: SimplePBLogger

Beitrag von mk-soft »

Part mit Mutex...

Code: Alles auswählen

...
Global MutexWriteLog = CreateMutex()
  
  Procedure.i  WriteLogEntry(LogLevel.s, Message.s, *Parameters.SPBL_Parameter = #Null)
    Define.i Result = #False
    If LoggerIsInitialized
      LockMutex(MutexWriteLog)
      Define.s Filename
      Filename = Location + 
                 "Logfile_" + FormatDate("%yyyy_%mm_%dd", Date()) + ".txt"
      If OpenFile(#LOGFILE, Filename, #PB_File_Append)
        WriteStringN(#LOGFILE, "-------------------------------------------")
        WriteStringN(#LOGFILE, FormatDate("%hh:%ii:%ss: ", Date()))
        WriteStringN(#LOGFILE, LogLevel)
        WriteStringN(#LOGFILE, Message)
        If *Parameters <> #Null
          ForEach *Parameters\Entries()
            WriteStringN(#LOGFILE, MapKey(*Parameters\Entries()) + " => " +
                                   *Parameters\Entries())
          Next
        EndIf
        CloseFile(#LOGFILE)
        Result = #True
      EndIf
      UnlockMutex(MutexWriteLog)
    EndIf
    ProcedureReturn Result
  EndProcedure
...  
Dann sollte aber auch immer in den Compiler-Option Threadsafe aktiviert sein, damit es auch nicht zu Problemen mit den Strings gibt.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Antworten