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 }