namespace Logger open System open System.Collections.Generic open System.IO open System.Reflection open System.Threading open Logger.Constants open Logger.Types type internal FileWriter (logDir : string) = let mutable maxSizeFile = DEFAULT_MAX_SIZE_FILE let mutable stream : StreamWriter = null let mutable filename : string = null let mutable avoidRepeatingIdenticalMessages : bool = false let lastMessageHashes = Queue () do try if not <| Directory.Exists logDir then Directory.CreateDirectory logDir |> ignore with | _ -> Console.Error.WriteLine ("Unable to create the log directory: {0}", logDir) // Open a file to log into. Each file has a number ('entryNumber'). let openLogFile (entryNumber : int64) = if not (isNull logDir) then try if isNull stream || (entryNumber % (int64 NB_ENTRIES_CHECK_SIZE) = 0L) && stream.BaseStream.Length > maxSizeFile then if not (isNull stream) then stream.Close () if COMPRESS_ARCHIVED_FILES then Utils.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 Utils.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 > maxSizeFile then filename <- Path.Combine (logDir, String.Format (FILENAME_FORMAT, n + 1)) with | :? FileNotFoundException -> () // The file may not exist. let fileStream = File.Open (filename, FileMode.Append, FileAccess.Write, FileShare.Read) stream <- new StreamWriter (fileStream, encoding) with | ex -> Console.Error.WriteLine ("Can't open the file log: {0}", ex) let write (msg : Message) (entryNumber : int64) = openLogFile entryNumber let header = Utils.formatHeader msg if not <| isNull stream then try let writeAndFlush (header : string) (message : string) = stream.WriteLine (Utils.formatMessage header message) stream.Flush () if avoidRepeatingIdenticalMessages && msg.Message.Length > Constants.MIN_MESSAGE_LENGTH_TO_NOT_BE_REPEATED then let newMessageHash = hash msg.Message match lastMessageHashes |> Seq.tryPick (fun (hash, dateTime) -> if hash = newMessageHash then Some dateTime else None) with | Some dateTime -> writeAndFlush header (String.Format ("", dateTime)) | None -> lastMessageHashes.Enqueue (hash msg.Message, msg.DateTime) if lastMessageHashes.Count > Constants.MAX_NB_OF_MESSAGE_HASHES_WHEN_AVOIDING_REPEATING_IDENTICAL_MESSAGES then lastMessageHashes.Dequeue () |> ignore writeAndFlush header msg.Message else writeAndFlush header msg.Message with | :? IOException as ex -> Console.Error.WriteLine ("Unable to write to the log file: {0}", ex) let closeCurrentStream () = if not <| isNull stream then stream.Dispose () stream <- null let logFiles () = Directory.EnumerateFiles logDir |> Seq.filter (fun file -> FILENAME_PATTERN.IsMatch file || FILENAME_PATTERN_COMPRESSED.IsMatch file) |> Seq.sort let clearLogFiles (timeOld : TimeSpan) = closeCurrentStream () for file in logFiles () do let fileInfo = FileInfo file if fileInfo.LastWriteTime.Add timeOld <= DateTime.Now then // Special case for 0001.log to keep the file opened in text editor (developer convenience) if fileInfo.Name = String.Format (FILENAME_FORMAT, 1) then File.WriteAllText (file, "") else fileInfo.Delete () let logInternalErrorMessage (message : string) (entryNumber : int64) = write { Message = sprintf "Logger internal error: %s" message ThreadName = Thread.CurrentThread.Name ThreadId = Thread.CurrentThread.ManagedThreadId ModuleCaller = Assembly.GetExecutingAssembly().GetName().Name Severity = Severity.ERROR DateTime = TimeZone.CurrentTimeZone.ToLocalTime DateTime.UtcNow } entryNumber let mailbox = new MailboxProcessor ( fun inbox -> let rec loop (nbEntries : int64) = async { try let! command = inbox.Receive () match command with | Write message -> write message nbEntries return! loop (nbEntries + 1L) | Flush replyChannel -> replyChannel.Reply () return! loop nbEntries | Shutdown replyChannel -> closeCurrentStream () replyChannel.Reply () | ClearLogFilesOlderThan timeOld -> clearLogFiles timeOld return! loop 1L | SetAvoidRepeatingIdenticalMessage enabled -> avoidRepeatingIdenticalMessages <- enabled if not avoidRepeatingIdenticalMessages then lastMessageHashes.Clear () return! loop nbEntries with | ex -> logInternalErrorMessage (string ex) nbEntries return! loop (nbEntries + 1L) } loop 1L ) do mailbox.Start () interface IWriter with member val DebugLoggingEnabled = false with get, set member this.AvoidRepeatingIdenticalMessages with get () = avoidRepeatingIdenticalMessages and set value = mailbox.Post (SetAvoidRepeatingIdenticalMessage value) member this.LogDir = logDir member this.MaxSizeFile with get () = maxSizeFile and set value = maxSizeFile <- value member this.LogFiles = logFiles () member this.Write (message : Message) = mailbox.Post (Write message) member this.Flush () = mailbox.PostAndReply (fun replyChannel -> Flush replyChannel) member this.ClearLogFilesOlderThan (timeOld : TimeSpan) = mailbox.Post (ClearLogFilesOlderThan timeOld) interface IDisposable with member this.Dispose () = mailbox.PostAndReply ( fun replyChannel -> Shutdown replyChannel ) (mailbox :> IDisposable).Dispose()