1 /**
2  * Defines several common default log writers.
3  */
4 module slf4d.default_provider.writers;
5 
6 import slf4d;
7 import slf4d.writer;
8 
9 /**
10  * Writes logs to the standard output stream. If the log message's severity is
11  * ERROR or higher, it's written to stderr, otherwise to stdout.
12  */
13 class StdoutLogWriter : LogWriter {
14     void write(immutable LogMessage msg, string serializedMessage) shared {
15         import std.stdio : writeln, stdout, stderr;
16         if (msg.level.value >= Levels.ERROR.value) {
17             stderr.writeln(serializedMessage);
18             stderr.flush();
19         } else {
20             stdout.writeln(serializedMessage);
21             stdout.flush();
22         }
23     }
24 }
25 
26 /**
27  * A log writer that always writes to the same file.
28  */
29 shared class SingleFileLogWriter : LogWriter {
30     import std.stdio;
31     private File file;
32 
33     public this(File file) {
34         this.file = cast(shared(File)) file;
35     }
36 
37     void write(immutable LogMessage _, string serializedMessage) {
38         synchronized(this) {
39             auto unsharedFile = cast(File*) &this.file;
40             unsharedFile.writeln(serializedMessage);
41             unsharedFile.flush();
42         }
43     }
44 }
45 
46 /**
47  * A log writer that writes log messages to files in a directory, switching to
48  * a new file when the current one reaches a set size (defaults to 2GB).
49  */
50 shared class RotatingFileLogWriter : LogWriter {
51     import std.stdio;
52     import std.file;
53     import std.path;
54     import std.string;
55     import std.algorithm;
56     import slf4d.default_provider.serializers;
57 
58     private File currentFile;
59     private string logDir;
60     private string logFilePrefix;
61     private immutable ulong maxLogFileSize;
62 
63     /**
64      * Constructs a new rotating log file strategy.
65      * Params:
66      *   logDir = The directory to store log files in. It will be created,
67      *            recursively, if it doesn't exist.
68      *   logFilePrefix = The prefix for all log files generated.
69      *   maxLogFileSize = The maximum size, in bytes, of log files. Defaults to
70      *                    2GB.
71      */
72     public this(string logDir, string logFilePrefix = "log", ulong maxLogFileSize = 2_000_000_000) {
73         this.logDir = logDir;
74         this.logFilePrefix = logFilePrefix;
75         this.maxLogFileSize = maxLogFileSize;
76         this.initFile();
77     }
78 
79     void write(immutable LogMessage _, string serializedMessage) {
80         synchronized(this) {
81             auto unsharedFile = cast(File*) &this.currentFile;
82             // Check if we need to close this file and open a new one.
83             if (getSize(unsharedFile.name) > this.maxLogFileSize) {
84                 unsharedFile.close();
85                 *unsharedFile = File(getNextFileName(), "a");
86             }
87             unsharedFile.writeln(serializedMessage);
88             unsharedFile.flush();
89         }
90     }
91 
92     /**
93      * Generates a log file name.
94      * Returns: A log file name.
95      */
96     private string getNextFileName() {
97         import std.datetime;
98         SysTime now = Clock.currTime();
99         string filename = this.logFilePrefix ~ "_" ~ formatTimestampISO8601(now) ~ ".log";
100         return buildPath(this.logDir, filename);
101     }
102 
103     /** 
104      * Helper method for initially selecting the file that should be written to.
105      */
106     private void initFile() {
107         auto unsharedFile = cast(File*) &this.currentFile;
108         // If the log dir doesn't exist yet, make it and start a new file.
109         if (!exists(this.logDir)) {
110             mkdirRecurse(this.logDir);
111             *unsharedFile = File(getNextFileName(), "a");
112         } else {// Otherwise, search for the best file to continue from.
113             DirEntry[] potentialFilesToContinue;
114             foreach (DirEntry entry; dirEntries(this.logDir, SpanMode.shallow, false)) {
115                 if (
116                     entry.size() < this.maxLogFileSize &&
117                     startsWith(baseName(entry.name), this.logFilePrefix)
118                 ) {
119                     potentialFilesToContinue ~= entry;
120                 }
121             }
122             sort!((e1, e2) => e1.timeLastModified > e2.timeLastModified)(potentialFilesToContinue);
123             if (potentialFilesToContinue.length > 0) {
124                 *unsharedFile = File(potentialFilesToContinue[0].name, "a");
125             } else {
126                 *unsharedFile = File(getNextFileName(), "a");
127             }
128         }
129     }
130 }