X-Git-Url: http://git.euphorik.ch/?p=master-thesis.git;a=blobdiff_plain;f=Parasitemia%2FLogger%2FLogger.fs;fp=Parasitemia%2FLogger%2FLogger.fs;h=b964517b9368007d7a9621e8538c01c854d382b8;hp=7d54843d53909a27a775637ffc13b9f41f7dceb0;hb=8cf2153bd18919de41745534d3dbf134f085e13c;hpb=6250f10c807301a760b8659f9c00ca6dbbd4c7b7 diff --git a/Parasitemia/Logger/Logger.fs b/Parasitemia/Logger/Logger.fs index 7d54843..b964517 100644 --- a/Parasitemia/Logger/Logger.fs +++ b/Parasitemia/Logger/Logger.fs @@ -1,248 +1,225 @@ namespace Logger open System -open System.Text -open System.IO -open System.IO.Compression open System.Diagnostics +open System.IO open System.Threading -open System.Collections.Generic - -type Severity = DEBUG = 1 | INFO = 2 | WARNING = 3 | ERROR = 4 | FATAL = 5 - -type IListener = - abstract NewEntry : severity : Severity -> header : string -> message : string -> unit - -type private Message = - { - Message : string - ThreadName : string - ThreadId : int - ModuleCaller : string - Severity : Severity - } -type private Command = - | Write of Message - | Stop of AsyncReplyChannel +open Logger +open Logger.Types [] type Log () = + static let mutable writer : IWriter = new ConsoleWriter () :> IWriter + static let monitor = obj () + static let listeners = Listeners () - let extractNumberFromLogfilepath (path : string) : int option = - if isNull path then - None - else - let filename = path.Substring (path.LastIndexOf Path.DirectorySeparatorChar + 1) - let filenameWithoutExtension = filename.Remove (filename.IndexOf '.') - match Int32.TryParse filenameWithoutExtension with - | (true, n) -> Some n - | _ -> None - - let [] MAX_SIZE_FILE = 52428800L // [byte] (50 MB). - let [] NB_ENTRIES_CHECK_SIZE = 100; // Each 100 entries added we check the size of the log file to test if it is greater than 'MAX_SIZE_FILE'. - let [] COMPRESS_ARCHIVED_FILES = true - let [] FILENAME_FORMAT = "{0:D4}.log" - let [] COMPRESSED_FILE_POSTFIX = ".gzip" - let encoding = Encoding.GetEncoding "UTF-8" - - let compress (filename : string) = - use inputStream = new FileStream (filename, FileMode.Open, FileAccess.Read) - let filenameCompressed = filename + COMPRESSED_FILE_POSTFIX - use compressedStream = new GZipStream (new FileStream (filenameCompressed, FileMode.Create, FileAccess.Write), CompressionLevel.Optimal) - inputStream.CopyTo compressedStream - - let moduleName = System.Diagnostics.StackFrame(1).GetMethod().Module.Name - - let mutable stream : StreamWriter = null - let mutable filename : string = null - - let mutable logDir : string = null - - let monitor = Object () - - let listeners = List () - - let debug = -#if DEBUG - true -#else - false -#endif - - static let instance = new Log () - - let openLogFile (entryNumber : int64) = - if not (isNull logDir) then - try - if isNull stream || (entryNumber % (int64 NB_ENTRIES_CHECK_SIZE) = 0L) && stream.BaseStream.Length > MAX_SIZE_FILE - then - if not (isNull stream) - then - stream.Close () - if COMPRESS_ARCHIVED_FILES then - compress filename - File.Delete filename - - // Search the last id among the log files. - let mutable n = 1 - for existingFile in Directory.GetFiles logDir do - match extractNumberFromLogfilepath existingFile with - | Some n' when n' > n -> n <- n' - | _ -> () - - filename <- Path.Combine (logDir, String.Format (FILENAME_FORMAT, n)) - try - if File.Exists (filename + COMPRESSED_FILE_POSTFIX) || FileInfo(filename).Length > MAX_SIZE_FILE - then - filename <- Path.Combine (logDir, String.Format (FILENAME_FORMAT, n + 1)) - with - | :? FileNotFoundException -> () // The file may not exist. - - stream <- new StreamWriter (filename, true, encoding) - with - | ex -> Console.Error.WriteLine ("Can't open the file log: {0}", ex) - - let write (msg : Message) (entryNumber : int64) = - openLogFile entryNumber - - let header = - String.Format ( - "{0:yyyy-MM-dd HH:mm:ss.fff} [{1}] {{{2}}} ({3})", - DateTime.Now, - string msg.Severity, - msg.ModuleCaller, - (if String.IsNullOrEmpty msg.ThreadName then string msg.ThreadId else sprintf "%s-%i" msg.ThreadName msg.ThreadId) + /// + /// Must be called first before any other action. + /// + static member LogDirectory + with get () = lock monitor (fun () -> writer.LogDir) + and set value = + lock monitor ( + fun () -> + Log.Close () + if String.IsNullOrWhiteSpace value then + writer <- new ConsoleWriter () + else + writer <- new FileWriter (value) ) - for listener in listeners do - listener.NewEntry msg.Severity header msg.Message - - if not (isNull stream) - then - try - stream.WriteLine ("{0} : {1}", header, msg.Message) - stream.Flush () - with - | :? IOException as ex -> Console.Error.WriteLine ("Unable to write to the log file: {0}", ex) - - let writeAgent = - new MailboxProcessor ( - fun inbox -> - let rec loop (nbEntries : int64) = - async { - let! command = inbox.Receive () - match command with - | Write message -> - write message nbEntries - return! loop (nbEntries + 1L) - | Stop replyChannel -> - replyChannel.Reply () - } - loop 1L + /// + /// Close the log. 'LogDirectory' must be set again to reopen it. + /// + static member Close () = + lock monitor ( + fun () -> + writer.Flush () + (writer :> IDisposable).Dispose () + writer <- new ConsoleWriter () :> IWriter ) - do - writeAgent.Start () + /// + /// Return all log files (the current one and the archived) as full paths. + /// + static member LogFiles = writer.LogFiles - let setLogDirectory (dir : string) = - lock monitor ( - fun () -> - logDir <- dir - - if not <| isNull stream then - stream.Close () - stream <- null - - try - if not <| Directory.Exists logDir - then - Directory.CreateDirectory logDir |> ignore - with - | _ -> Console.Error.WriteLine ("Unable to create the log directory: {0}", logDir) - ) - - interface IDisposable with - member this.Dispose () = - if not (isNull stream) - then - stream.Dispose () - (writeAgent :> IDisposable).Dispose () - - member private this.Write (message : string) (severity : Severity) = - let moduleNameCaller = - match StackTrace().GetFrames() |> Array.tryPick (fun frame -> let name = frame.GetMethod().Module.Name - if name <> moduleName then Some name else None) with - | Some name -> name - | _ -> moduleName - - let command = - Write - { - Message = message - ThreadName = Thread.CurrentThread.Name - ThreadId = Thread.CurrentThread.ManagedThreadId - ModuleCaller = moduleNameCaller - Severity = severity - } - - writeAgent.Post command - - /// - /// Will stop and wait a reply. Used to flush the remaining messages. - /// - member private this.Stop () = - writeAgent.PostAndReply ( - fun replyChannel -> - Stop replyChannel - ) + /// + /// Wait that all the previous messages are written. + /// + static member Flush () = writer.Flush () - member this.LogDirectory - with get () = logDir - and set value = setLogDirectory value + static member DebugLoggingEnabled + with get () = writer.DebugLoggingEnabled + and set value = writer.DebugLoggingEnabled <- value - static member SetLogDirectory (dir : string) = - instance.LogDirectory <- dir + /// + /// Avoid to repeat a message by writting a reference to a previous message instead of the message. + /// 'false' by default. + /// + static member AvoidRepeatingIdenticalMessages + with get () = writer.AvoidRepeatingIdenticalMessages + and set value = writer.AvoidRepeatingIdenticalMessages <- value - member this.AddListener (listener : IListener) = - lock monitor ( - fun () -> - if not <| listeners.Contains listener - then - listeners.Add listener - ) + /// + /// The maximum size of the current file log. If the file exceed this value it will be zipped and a new file will be created. + /// The file size is only tested each time a certain number of messages have been written so the file may exceed this value a bit. + /// + /// + static member SetLogFilesMaxSize (size : int64) = + writer.MaxSizeFile <- size - member this.RmListener (listener : IListener) = - lock monitor (fun () -> listeners.Remove listener |> ignore) + static member ClearLogFilesOlderThan (timeOld : TimeSpan) = + writer.ClearLogFilesOlderThan timeOld - static member AddListener (listener : IListener) = instance.AddListener listener - static member RmListener (listener : IListener) = instance.RmListener listener + /// + /// Remove all archived log files and empty the current one. + /// + static member ClearLogFiles () = + Log.ClearLogFilesOlderThan (TimeSpan 0L) + + /// + /// Total size in bytes. + /// + static member CurrentLogSize () : int64 = + Log.LogFiles + |> Seq.map (fun file -> try (FileInfo file).Length with | _ex -> 0L) + |> Seq.sum + + static member AddListener (listener : IListener) = + listeners.Add listener + + static member RemoveListener (listener : IListener) = + listeners.Remove listener + + static member private Write (message : string) (severity : Severity) = + let msg = + { + Message = message + ThreadName = Thread.CurrentThread.Name + ThreadId = Thread.CurrentThread.ManagedThreadId + ModuleCaller = Utils.callerModuleName () + Severity = severity + DateTime = TimeZone.CurrentTimeZone.ToLocalTime DateTime.UtcNow + } + listeners.NewEntry msg + writer.Write msg + /// + /// [F#] Execute the given function and measure its time. + /// + /// Severity for writing to log + /// Function to test + /// Format string for output static member LogWithTime (severity : Severity) (f : unit -> 'a) (format : Printf.StringFormat<'b, 'a>) : 'b = let sw = Stopwatch () sw.Start () let res = f () sw.Stop () - Printf.kprintf (fun s -> instance.Write (s + sprintf " (time: %d ms)" sw.ElapsedMilliseconds) severity; res) format + Printf.kprintf (fun s -> Log.Write (s + sprintf " (time: %d ms)" sw.ElapsedMilliseconds) severity; res) format + /// + /// [F#] Write Debug message to log (if DebugLoggingEnabled = true) + /// static member Debug format = -#if DEBUG - Printf.kprintf (fun s -> instance.Write s Severity.DEBUG) format -#else - Printf.kprintf (fun _ -> ()) format // TODO: can it be simplify? -#endif + if writer.DebugLoggingEnabled then + Printf.kprintf (fun s -> Log.Write s Severity.DEBUG) format + else + // [BGR] FIXME: is it possible to simplify a bit here? It's more CPU consuming than the C# couterpart. + Printf.kprintf (fun _ -> ()) format + /// + /// [F#] Write Info message to log + /// static member Info format = - Printf.kprintf (fun s -> instance.Write s Severity.INFO) format + Printf.kprintf (fun s -> Log.Write s Severity.INFO) format + /// + /// [F#] Write Warning message to log + /// static member Warning format = - Printf.kprintf (fun s -> instance.Write s Severity.WARNING) format + Printf.kprintf (fun s -> Log.Write s Severity.WARNING) format + /// + /// [F#] Write Error message to log + /// static member Error format = - Printf.kprintf (fun s -> instance.Write s Severity.ERROR) format + Printf.kprintf (fun s -> Log.Write s Severity.ERROR) format + /// + /// [F#] Write Fatal message to log + /// static member Fatal format = - Printf.kprintf (fun s -> instance.Write s Severity.FATAL) format + Printf.kprintf (fun s -> Log.Write s Severity.FATAL) format + + /// + /// Write DEBUG message to log (if DebugLoggingEnabled = true) + /// + static member DEBUG (message : string, [] args : obj array) = + if writer.DebugLoggingEnabled then + if isNull args || args.Length = 0 then + Log.Write message Severity.DEBUG + else + Log.Write (String.Format (message, args)) Severity.DEBUG + + /// + /// Write DEBUG message to log (if DebugLoggingEnabled = true) + /// + static member DEBUG (message : string) = Log.DEBUG (message, [| |]) - static member Shutdown () = - instance.Stop () + /// + /// Write INFO message to log + /// + static member INFO (message : string, [] args : obj array) = + if isNull args || args.Length = 0 then + Log.Write message Severity.INFO + else + Log.Write (String.Format (message, args)) Severity.INFO + + /// + /// Write INFO message to log + /// + static member INFO (message : string) = Log.INFO (message, [| |]) + + /// + /// Write WARNING message to log + /// + static member WARNING (message : string, [] args : obj array) = + if isNull args || args.Length = 0 then + Log.Write message Severity.WARNING + else + Log.Write (String.Format (message, args)) Severity.WARNING + + /// + /// Write WARNING message to log + /// + static member WARNING (message : string) = Log.WARNING (message, [| |]) + + /// + /// Write ERROR message to log + /// + static member ERROR (message : string, [] args : obj array) = + if isNull args || args.Length = 0 then + Log.Write message Severity.ERROR + else + Log.Write (String.Format (message, args)) Severity.ERROR + + /// + /// Write ERROR message to log + /// + static member ERROR (message : string) = Log.ERROR (message, [| |]) + + /// + /// Write FATAL message to log + /// + static member FATAL (message : string, [] args : obj array) = + if isNull args || args.Length = 0 then + Log.Write message Severity.FATAL + else + Log.Write (String.Format (message, args)) Severity.FATAL + + /// + /// Write FATAL message to log + /// + static member FATAL (message : string) = Log.FATAL (message, [| |]) \ No newline at end of file