using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace ExtremelySimpleLogger { /// /// A that writes log output to a set of directories. /// This sink differs from in that it manages multiple log files in a directory, where a new file will be created every time the sink is created. /// Additionally, this sink will automatically delete the oldest log files if the amount of files exceeds a set limit. /// public class DirectorySink : Sink { /// /// The set of old files that are currently in the directory that this sink is referencing, and that have not been deleted on construction. /// The files in this list are ordered by creation date in ascending order, meaning that the first entry is the least recently created one. /// Note that this collection does not contain the . /// public readonly IList OldFiles; /// /// The that this sink is currently using as its destination to store the and . /// public readonly DirectoryInfo Directory; /// /// The that this sink is currently using as its destination. /// public FileInfo CurrentFile { get { lock (this.file) return this.file; } } private const string DefaultDateFormat = "yy-MM-dd_HH-mm-ss"; private readonly FileInfo file; private readonly StreamWriter writer; private readonly bool reopenOnWrite; /// /// Creates a new directory sink with the given settings. /// /// The directory that this sink should operate in /// The maximum amount of files that can exist in the directory before the oldest one gets deleted. 10 by default. /// Whether this sink should reopen the file every time it logs to it. If this is false, the file will be kept open by this sink. /// The way the name of the current log file gets formatted. yy-MM-dd_HH-mm-ss by default. public DirectorySink(string directory, int maxFiles = 10, bool reopenOnWrite = false, string dateFormat = DefaultDateFormat) : this(new DirectoryInfo(directory), maxFiles, reopenOnWrite, dateFormat) {} /// /// Creates a new directory sink with the given settings. /// /// The directory that this sink should operate in /// The maximum amount of files that can exist in the directory before the oldest one gets deleted. 10 by default. /// Whether this sink should reopen the file every time it logs to it. If this is false, the file will be kept open by this sink. /// The way the name of the current log file gets formatted. yy-MM-dd_HH-mm-ss by default. public DirectorySink(DirectoryInfo directory, int maxFiles = 10, bool reopenOnWrite = false, string dateFormat = DefaultDateFormat) { this.reopenOnWrite = reopenOnWrite; this.Directory = directory; try { if (!directory.Exists) directory.Create(); } catch (Exception e) { throw new IOException($"Failed to create directory sink directory {directory}", e); } try { // delete files in order of creation time so that older files are deleted first var ordered = directory.EnumerateFiles().OrderBy(f => f.CreationTime).ToList(); while (ordered.Count >= maxFiles) { ordered[0].Delete(); ordered.RemoveAt(0); } this.OldFiles = ordered.AsReadOnly(); } catch (Exception e) { throw new IOException($"Failed to delete old files in directory sink {directory}", e); } var date = DateTime.Now.ToString(dateFormat); this.file = new FileInfo(Path.Combine(directory.FullName, $"{date}.txt")); if (!reopenOnWrite) { this.writer = this.Append(); this.writer.AutoFlush = true; } } /// /// Logs the given message, which has already been formatted using . /// /// The logger that the message was passed to /// The importance level of this message /// The message to log protected override void Log(Logger logger, LogLevel level, string s) { lock (this.file) { if (this.reopenOnWrite) { using (var w = this.Append()) w.WriteLine(s); } else { this.writer.WriteLine(s); } } } /// /// Disposes this sink, freeing all of the resources it uses. /// public override void Dispose() { base.Dispose(); lock (this.file) { if (!this.reopenOnWrite) this.writer.Dispose(); } } private StreamWriter Append() { try { return this.file.AppendText(); } catch (Exception e) { throw new IOException($"Failed to append to directory sink file {this.file}", e); } } } }