1b4c3325e3c884660e2a9087951d92bd4a0bdc5f
[master-thesis.git] / Parasitemia / Logger / Logger.fs
1 namespace Logger
2
3 open System
4 open System.Text
5 open System.IO
6 open System.Diagnostics
7 open System.Threading
8 open System.Collections.Generic
9
10 type Severity = DEBUG = 1 | USER = 2 | WARNING = 3 | ERROR = 4 | FATAL = 5
11
12 type IListener = abstract NewEntry : Severity -> string -> unit
13
14 [<Sealed>]
15 type Log () =
16 let maxSizeFile = 10L * 1024L * 1024L // [byte] (10 MB).
17 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'.
18 let LogDefaultDirectory = "Parasitemia\\Log"
19 let filenameFormat = "{0:D4}.log"
20 let encoding = Encoding.GetEncoding("UTF-8")
21
22 let moduleName = System.Diagnostics.StackFrame(1).GetMethod().Module.Name
23
24 let mutable stream : StreamWriter = null
25
26 let mutable logDir : string = null
27 let mutable absoluteDir : string = null
28
29 let mutable nbEntries = 0L
30
31 let monitor = Object()
32
33 let listeners = List<IListener>()
34
35 let debug =
36 #if DEBUG
37 true
38 #else
39 false
40 #endif
41
42 static let instance = new Log()
43
44 let setLogDirectory (dir : string) =
45 lock monitor (fun () ->
46 logDir <- dir
47 absoluteDir <- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), logDir)
48
49 if stream <> null then
50 stream.Close()
51 stream <- null
52
53 try
54 if not <| Directory.Exists(absoluteDir) then
55 Directory.CreateDirectory(absoluteDir) |> ignore
56 with
57 | _ as ex -> Console.Error.WriteLine("Unable to create the log directory: {0}", absoluteDir))
58
59 let openLogFile () =
60 try
61 if stream = null || (nbEntries % (int64 nbEntriesCheckSize) = 0L) && stream.BaseStream.Length > maxSizeFile then
62 if stream <> null then
63 stream.Close()
64
65 let mutable n = 1
66 for existingFile in Directory.GetFiles(absoluteDir) do
67 let current_n = ref 0
68 if Int32.TryParse(existingFile.Remove(existingFile.LastIndexOf('.')), current_n) && !current_n > n then
69 n <- !current_n
70
71 let mutable filename = Path.Combine(absoluteDir, String.Format(filenameFormat, n))
72 try
73 if (FileInfo(filename).Length > maxSizeFile) then
74 filename <- Path.Combine(absoluteDir, String.Format(filenameFormat, n + 1))
75 with
76 | :? FileNotFoundException -> () // The file may not exist.
77
78 stream <- new StreamWriter(filename, true, encoding)
79 with
80 | _ as ex -> Console.Error.WriteLine("Can't open the file log: {0}", ex)
81
82 do
83 setLogDirectory LogDefaultDirectory
84
85 interface IDisposable with
86 member this.Dispose () =
87 if stream <> null then
88 stream.Dispose()
89
90 member private this.Write (message : string, severity : Severity) =
91 lock monitor (fun () ->
92 nbEntries <- nbEntries + 1L
93 openLogFile ()
94
95 if stream <> null then
96 let mutable moduleNameCaller = moduleName
97 match StackTrace().GetFrames() |> Array.tryPick (fun frame -> let name = frame.GetMethod().Module.Name
98 if name <> moduleName then Some name else None) with
99 | Some name -> moduleNameCaller <- name
100 | _ -> ()
101
102 let threadName = Thread.CurrentThread.Name
103
104 for listener in listeners do
105 listener.NewEntry severity message
106
107 try
108 stream.WriteLine(
109 "{0:yyyy-MM-dd HH:mm:ss.fff} [{1}] {{{2}}} ({3}) : {4}",
110 TimeZone.CurrentTimeZone.ToLocalTime(DateTime.UtcNow),
111 severity.ToString(),
112 moduleNameCaller,
113 (if String.IsNullOrEmpty(threadName) then Thread.CurrentThread.ManagedThreadId.ToString() else String.Format("{0}-{1}", threadName, Thread.CurrentThread.ManagedThreadId)),
114 message
115 )
116 stream.Flush()
117 with
118 | :? IOException as ex -> Console.Error.WriteLine("Unable to write to the log file: {0}", ex))
119
120
121 member private this.AddListener (listener : IListener) =
122 lock monitor (fun () ->
123 if not <| listeners.Contains(listener) then
124 listeners.Add(listener))
125
126 member private this.RmListener (listener : IListener) =
127 lock monitor (fun () ->
128 listeners.Remove(listener) |> ignore)
129
130 static member AddListener (listener : IListener) = instance.AddListener(listener)
131 static member RmListener (listener : IListener) = instance.RmListener(listener)
132
133 static member LogWithTime (message : string, severity : Severity, f : unit -> 'a option, [<ParamArray>] args: Object[]) : 'a option =
134 let sw = Stopwatch()
135 sw.Start()
136 let res = f ()
137 sw.Stop()
138 if res.IsSome then
139 instance.Write(String.Format(message, args) + sprintf " (time: %d ms)" sw.ElapsedMilliseconds, severity)
140 res
141
142 static member Debug (message : string, [<ParamArray>] args : Object[]) =
143 #if DEBUG
144 instance.Write(String.Format(message, args), Severity.DEBUG)
145 #else
146 ()
147 #endif
148
149 static member User (message : string, [<ParamArray>] args : Object[]) =
150 instance.Write(String.Format(message, args), Severity.USER)
151
152 static member Warning (message : string, [<ParamArray>] args : Object[]) =
153 instance.Write(String.Format(message, args), Severity.WARNING)
154
155 static member Error (message : string, [<ParamArray>] args : Object[]) =
156 instance.Write(String.Format(message, args), Severity.ERROR)
157
158 static member Fatal (message : string, [<ParamArray>] args : Object[]) =
159 instance.Write(String.Format(message, args), Severity.FATAL)
160