namespace Logger open System open System.Text open System.IO open System.Diagnostics 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 [] 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(System.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() 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)