X-Git-Url: http://git.euphorik.ch/?a=blobdiff_plain;f=Parasitemia%2FLogger%2FLogger.fs;h=b964517b9368007d7a9621e8538c01c854d382b8;hb=8cf2153bd18919de41745534d3dbf134f085e13c;hp=cc023141fc895f21318962af8476ae34a867b65e;hpb=9343c4deb0bf88c58d9c92d465d8e99f64656875;p=master-thesis.git diff --git a/Parasitemia/Logger/Logger.fs b/Parasitemia/Logger/Logger.fs index cc02314..b964517 100644 --- a/Parasitemia/Logger/Logger.fs +++ b/Parasitemia/Logger/Logger.fs @@ -1,170 +1,225 @@ namespace Logger open System -open System.Text -open System.IO open System.Diagnostics +open System.IO open System.Threading -open System.Collections.Generic - -type Severity = DEBUG = 1 | USER = 2 | WARNING = 3 | ERROR = 4 | FATAL = 5 -type IListener = abstract NewEntry : Severity -> string -> unit +open Logger +open Logger.Types [] type Log () = - let maxSizeFile = 10L * 1024L * 1024L // [byte] (10 MB). - let nbEntriesCheckSize = 100; // Each 100 entries added we check the size of the log file to test if it is greater than 'MAX_SIZE_FILE'. - let LogDefaultDirectory = "Parasitemia\\Log" - let filenameFormat = "{0:D4}.log" - let encoding = Encoding.GetEncoding("UTF-8") - - let moduleName = System.Diagnostics.StackFrame(1).GetMethod().Module.Name - - let mutable stream: StreamWriter = null - - let mutable logDir: string = null - let mutable absoluteDir: string = null - - let mutable nbEntries = 0L - - let monitor = Object() - - let listeners = List() - - let debug = -#if DEBUG - true -#else - false -#endif - - static let instance = new Log() - - let setLogDirectory (dir: string) = - lock monitor (fun () -> - logDir <- dir - absoluteDir <- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), logDir) - - if stream <> null - then - stream.Close() - stream <- null - - try - if not <| Directory.Exists(absoluteDir) - then - Directory.CreateDirectory(absoluteDir) |> ignore - with - | _ as ex -> Console.Error.WriteLine("Unable to create the log directory: {0}", absoluteDir)) - - let openLogFile () = - try - if stream = null || (nbEntries % (int64 nbEntriesCheckSize) = 0L) && stream.BaseStream.Length > maxSizeFile - then - if stream <> null - then - stream.Close() - - let mutable n = 1 - for existingFile in Directory.GetFiles(absoluteDir) do - let current_n = ref 0 - if Int32.TryParse(existingFile.Remove(existingFile.LastIndexOf('.')), current_n) && !current_n > n - then - n <- !current_n - - let mutable filename = Path.Combine(absoluteDir, String.Format(filenameFormat, n)) - try - if (FileInfo(filename).Length > maxSizeFile) - then - filename <- Path.Combine(absoluteDir, String.Format(filenameFormat, n + 1)) - with - | :? FileNotFoundException -> () // The file may not exist. - - stream <- new StreamWriter(filename, true, encoding) - with - | _ as ex -> Console.Error.WriteLine("Can't open the file log: {0}", ex) - - do - setLogDirectory LogDefaultDirectory - - interface IDisposable with - member this.Dispose () = - if stream <> null - then - stream.Dispose() - - member private this.Write (message: string, severity: Severity) = - lock monitor (fun () -> - nbEntries <- nbEntries + 1L - openLogFile () - - if stream <> null - then - let mutable moduleNameCaller = moduleName - match StackTrace().GetFrames() |> Array.tryPick (fun frame -> let name = frame.GetMethod().Module.Name - if name <> moduleName then Some name else None) with - | Some name -> moduleNameCaller <- name - | _ -> () - - let threadName = Thread.CurrentThread.Name - - for listener in listeners do - listener.NewEntry severity message - - try - stream.WriteLine( - "{0:yyyy-MM-dd HH:mm:ss.fff} [{1}] {{{2}}} ({3}) : {4}", - TimeZone.CurrentTimeZone.ToLocalTime(DateTime.UtcNow), - severity.ToString(), - moduleNameCaller, - (if String.IsNullOrEmpty(threadName) then Thread.CurrentThread.ManagedThreadId.ToString() else String.Format("{0}-{1}", threadName, Thread.CurrentThread.ManagedThreadId)), - message - ) - stream.Flush() - with - | :? IOException as ex -> Console.Error.WriteLine("Unable to write to the log file: {0}", ex)) - - - member private this.AddListener (listener: IListener) = - lock monitor (fun () -> - if not <| listeners.Contains(listener) - then - listeners.Add(listener)) - - member private this.RmListener (listener: IListener) = - lock monitor (fun () -> - listeners.Remove(listener) |> ignore) - - static member AddListener (listener: IListener) = instance.AddListener(listener) - static member RmListener (listener: IListener) = instance.RmListener(listener) - - static member LogWithTime (message: string, severity: Severity, f: unit -> 'a option, [] args: Object[]) : 'a option = - let sw = Stopwatch() - sw.Start() + static let mutable writer : IWriter = new ConsoleWriter () :> IWriter + static let monitor = obj () + static let listeners = Listeners () + + /// + /// 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) + ) + + /// + /// 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 + ) + + /// + /// Return all log files (the current one and the archived) as full paths. + /// + static member LogFiles = writer.LogFiles + + /// + /// Wait that all the previous messages are written. + /// + static member Flush () = writer.Flush () + + static member DebugLoggingEnabled + with get () = writer.DebugLoggingEnabled + and set value = writer.DebugLoggingEnabled <- value + + /// + /// 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 + + /// + /// 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 + + static member ClearLogFilesOlderThan (timeOld : TimeSpan) = + writer.ClearLogFilesOlderThan timeOld + + /// + /// 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() - if res.IsSome - then - instance.Write(String.Format(message, args) + sprintf " (time: %d ms)" sw.ElapsedMilliseconds, severity) - res - - static member Debug (message: string, [] args: Object[]) = -#if DEBUG - instance.Write(String.Format(message, args), Severity.DEBUG) -#else - () -#endif - - static member User (message: string, [] args: Object[]) = - instance.Write(String.Format(message, args), Severity.USER) - - static member Warning (message: string, [] args: Object[]) = - instance.Write(String.Format(message, args), Severity.WARNING) - - static member Error (message: string, [] args: Object[]) = - instance.Write(String.Format(message, args), Severity.ERROR) - - static member Fatal (message: string, [] args: Object[]) = - instance.Write(String.Format(message, args), Severity.FATAL) - + sw.Stop () + 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 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 -> Log.Write s Severity.INFO) format + + /// + /// [F#] Write Warning message to log + /// + static member 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 -> Log.Write s Severity.ERROR) format + + /// + /// [F#] Write Fatal message to log + /// + static member 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, [| |]) + + /// + /// 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