1 /** 2 * Logging support. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 * Simon Arlott 13 */ 14 15 module ae.sys.log; 16 17 import std.datetime; 18 import std.file; 19 import std.path; 20 import std.stdio; 21 import std.string; 22 23 import ae.sys.file; 24 25 import ae.utils.meta.rcclass; 26 import ae.utils.textout; 27 import ae.utils.time; 28 29 string logDir; 30 31 private void init() 32 { 33 import core.runtime; 34 35 if (!logDir) 36 logDir = getcwd().buildPath("logs"); 37 } 38 39 shared static this() { init(); } 40 static this() { init(); } 41 42 enum TIME_FORMAT = "Y-m-d H:i:s.u"; 43 44 private SysTime getLogTime() 45 { 46 return Clock.currTime(UTC()); 47 } 48 49 abstract class CLogger 50 { 51 public: 52 alias log opCall; 53 54 this(string name) 55 { 56 this.name = name; 57 open(); 58 } 59 60 abstract void log(in char[] str); 61 62 void rename(string name) 63 { 64 close(); 65 this.name = name; 66 open(); 67 } 68 69 void close() {} 70 71 protected: 72 string name; 73 74 void open() {} 75 void reopen() {} 76 } 77 alias RCClass!CLogger Logger; 78 79 class CRawFileLogger : CLogger 80 { 81 bool timestampedFilenames; 82 83 this(string name, bool timestampedFilenames = false) 84 { 85 this.timestampedFilenames = timestampedFilenames; 86 super(name); 87 } 88 89 private final void logStartLine() 90 { 91 /+ 92 if (!f.isOpen) // hack 93 { 94 if (fileName is null) 95 throw new Exception("Can't write to a closed log"); 96 reopen(); 97 RawFileLogger.log(str); 98 close(); 99 } 100 +/ 101 } 102 103 private final void logFragment(in char[] str) 104 { 105 f.write(str); 106 } 107 108 private final void logEndLine() 109 { 110 f.writeln(); 111 f.flush(); 112 } 113 114 override void log(in char[] str) 115 { 116 logStartLine(); 117 logFragment(str); 118 logEndLine(); 119 } 120 121 protected: 122 string fileName; 123 File f; 124 125 override void open() 126 { 127 // name may contain directory separators 128 string path = buildPath(logDir, name); 129 auto base = path.baseName(); 130 auto dir = path.dirName(); 131 132 auto t = getLogTime(); 133 string timestamp = timestampedFilenames ? format(" %02d-%02d-%02d", t.hour, t.minute, t.second) : null; 134 fileName = buildPath(dir, format("%04d-%02d-%02d%s - %s.log", t.year, t.month, t.day, timestamp, base)); 135 ensurePathExists(fileName); 136 f = File(fileName, "ab"); 137 } 138 139 override void reopen() 140 { 141 f = File(fileName, "ab"); 142 } 143 } 144 145 class CFileLogger : CRawFileLogger 146 { 147 this(string name, bool timestampedFilenames = false) 148 { 149 super(name, timestampedFilenames); 150 } 151 152 override void log(in char[] str) 153 { 154 auto ut = getLogTime(); 155 if (ut.day != currentDay) 156 { 157 f.writeln("\n---- (continued in next day's log) ----"); 158 f.close(); 159 open(); 160 f.writeln("---- (continued from previous day's log) ----\n"); 161 } 162 163 enum TIMEBUFSIZE = 1 + timeFormatSize(TIME_FORMAT) + 2; 164 static char[TIMEBUFSIZE] buf = "["; 165 auto writer = BlindWriter!char(buf.ptr+1); 166 putTime!TIME_FORMAT(writer, ut); 167 writer.put(']'); 168 writer.put(' '); 169 170 super.logStartLine(); 171 super.logFragment(buf[0..writer.ptr-buf.ptr]); 172 super.logFragment(str); 173 super.logEndLine(); 174 } 175 176 override void close() 177 { 178 //assert(f !is null); 179 if (f.isOpen) 180 f.close(); 181 } 182 183 private: 184 int currentDay; 185 186 protected: 187 final override void open() 188 { 189 super.open(); 190 currentDay = getLogTime().day; 191 f.writef("\n\n--------------- %s ---------------\n\n\n", getLogTime().formatTime!(TIME_FORMAT)()); 192 f.flush(); 193 } 194 195 final override void reopen() 196 { 197 super.reopen(); 198 f.writef("\n\n--------------- %s ---------------\n\n\n", getLogTime().formatTime!(TIME_FORMAT)()); 199 f.flush(); 200 } 201 } 202 alias RCClass!CFileLogger FileLogger; 203 alias rcClass!CFileLogger fileLogger; 204 205 class CConsoleLogger : CLogger 206 { 207 this(string name) 208 { 209 super(name); 210 } 211 212 override void log(in char[] str) 213 { 214 stderr.write(name, ": ", str, "\n"); 215 stderr.flush(); 216 } 217 } 218 alias RCClass!CConsoleLogger ConsoleLogger; 219 alias rcClass!CConsoleLogger consoleLogger; 220 221 class CNullLogger : CLogger 222 { 223 this() { super(null); } 224 override void log(in char[] str) {} 225 } 226 alias RCClass!CNullLogger NullLogger; 227 alias rcClass!CNullLogger nullLogger; 228 229 class CMultiLogger : CLogger 230 { 231 this(Logger[] loggers ...) 232 { 233 this.loggers = loggers.dup; 234 super(null); 235 } 236 237 override void log(in char[] str) 238 { 239 foreach (logger; loggers) 240 logger.log(str); 241 } 242 243 override void rename(string name) 244 { 245 foreach (logger; loggers) 246 logger.rename(name); 247 } 248 249 override void close() 250 { 251 foreach (logger; loggers) 252 logger.close(); 253 } 254 255 private: 256 Logger[] loggers; 257 } 258 alias RCClass!CMultiLogger MultiLogger; 259 alias rcClass!CMultiLogger multiLogger; 260 261 class CFileAndConsoleLogger : CMultiLogger 262 { 263 this(string name) 264 { 265 Logger f, c; 266 f = fileLogger(name); 267 c = consoleLogger(name); 268 super(f, c); 269 } 270 } 271 alias RCClass!CFileAndConsoleLogger FileAndConsoleLogger; 272 alias rcClass!CFileAndConsoleLogger fileAndConsoleLogger; 273 274 bool quiet; 275 276 shared static this() 277 { 278 import core.runtime; 279 foreach (arg; Runtime.args[1..$]) 280 if (arg == "-q" || arg == "--quiet") 281 quiet = true; 282 } 283 284 /// Create a logger depending on whether -q or --quiet was passed on the command line. 285 Logger createLogger(string name) 286 { 287 Logger result; 288 version (unittest) 289 result = consoleLogger(name); 290 else 291 if (quiet) 292 result = fileLogger(name); 293 else 294 result = fileAndConsoleLogger(name); 295 return result; 296 }