1 module gfx.core.log; 2 3 import core.sync.mutex : Mutex; 4 import std.datetime : SysTime; 5 6 struct LogTag 7 { 8 string tag; 9 uint mask = 0xffff_ffff; 10 11 /// add a log entry with this tag 12 void log (in Severity sev, scope lazy string msg) const 13 { 14 .log (sev, mask, tag, msg); 15 } 16 17 /// add a formatted log entry with this tag 18 void logf (Args...)(in Severity sev, in string fmt, Args args) const 19 { 20 .logf (sev, mask, tag, fmt, args); 21 } 22 23 /// add a trace log entry with this tag 24 void trace (scope lazy string msg) const 25 { 26 .log (Severity.trace, mask, tag, msg); 27 } 28 29 /// add a formatted trace log entry with this tag 30 void tracef (Args...)(in string fmt, Args args) const 31 { 32 .logf (Severity.trace, mask, tag, fmt, args); 33 } 34 35 /// add a debug log entry with this tag 36 void debug_ (scope lazy string msg) const 37 { 38 .log (Severity.debug_, mask, tag, msg); 39 } 40 41 /// add a formatted debug log entry with this tag 42 void debugf (Args...)(in string fmt, Args args) const 43 { 44 .logf (Severity.debug_, mask, tag, fmt, args); 45 } 46 47 /// add a info log entry with this tag 48 void info (scope lazy string msg) const 49 { 50 .log (Severity.info, mask, tag, msg); 51 } 52 53 /// add a formatted info log entry with this tag 54 void infof (Args...)(in string fmt, Args args) const 55 { 56 .logf (Severity.info, mask, tag, fmt, args); 57 } 58 59 /// add a warning log entry with this tag 60 void warning (scope lazy string msg) const 61 { 62 .log (Severity.warning, mask, tag, msg); 63 } 64 65 /// add a formatted warning log entry with this tag 66 void warningf (Args...)(in string fmt, Args args) const 67 { 68 .logf (Severity.warning, mask, tag, fmt, args); 69 } 70 71 /// add a error log entry with this tag 72 void error (scope lazy string msg) const 73 { 74 .log (Severity.error, mask, tag, msg); 75 } 76 77 /// add a formatted error log entry with this tag 78 void errorf (Args...)(in string fmt, Args args) const 79 { 80 .logf (Severity.error, mask, tag, fmt, args); 81 } 82 } 83 84 /// Log messages severity 85 enum Severity 86 { 87 /// 88 trace, 89 /// debug messages 90 debug_, 91 /// 92 info, 93 /// 94 warning, 95 /// 96 error, 97 } 98 99 /// The default severity 100 enum defaultSeverity = Severity.info; 101 102 /// The default filter mask 103 enum uint defaultMask = 0xffff_ffff; 104 105 /// add a log entry 106 void log (in Severity sev, in string tag, scope lazy string msg) 107 { 108 s_mutex.lock(); 109 scope(exit) s_mutex.unlock(); 110 111 if (cast(int)sev >= cast(int)s_sev) { 112 s_logger.print(s_msgFmt.formatMsg(sev, tag, msg)); 113 } 114 } 115 /// ditto 116 void log (in Severity sev, in uint mask, in string tag, scope lazy string msg) 117 { 118 s_mutex.lock(); 119 scope(exit) s_mutex.unlock(); 120 121 if (cast(int)sev >= cast(int)s_sev && (mask & s_mask) != 0) { 122 s_logger.print(s_msgFmt.formatMsg(sev, tag, msg)); 123 } 124 } 125 /// ditto 126 void logf (Args...) (in Severity sev, in string tag, in string fmt, Args args) 127 { 128 import std.format : format; 129 130 s_mutex.lock(); 131 scope(exit) s_mutex.unlock(); 132 133 if (cast(int)sev >= cast(int)s_sev) { 134 s_logger.print(s_msgFmt.formatMsg(sev, tag, format(fmt, args))); 135 } 136 } 137 /// ditto 138 void logf (Args...) (in Severity sev, in uint mask, in string tag, in string fmt, Args args) 139 { 140 import std.format : format; 141 142 s_mutex.lock(); 143 scope(exit) s_mutex.unlock(); 144 145 if (cast(int)sev >= cast(int)s_sev && (mask & s_mask) != 0) { 146 s_logger.print(s_msgFmt.formatMsg(sev, tag, format(fmt, args))); 147 } 148 } 149 150 /// add a log entry with trace severity 151 void trace (in string tag, scope lazy string msg) 152 { 153 log (Severity.trace, tag, msg); 154 } 155 /// ditto 156 void trace (in uint mask, in string tag, scope lazy string msg) 157 { 158 log (Severity.trace, mask, tag, msg); 159 } 160 /// ditto 161 void tracef (Args...)(in string tag, in string fmt, Args args) 162 { 163 logf (Severity.trace, tag, fmt, args); 164 } 165 /// ditto 166 void tracef (Args...)(in uint mask, in string tag, in string fmt, Args args) 167 { 168 logf (Severity.trace, mask, tag, fmt, args); 169 } 170 171 /// add a log entry with debug severity 172 void debug_ (in string tag, scope lazy string msg) 173 { 174 log (Severity.debug_, tag, msg); 175 } 176 /// ditto 177 void debug_ (in uint mask, in string tag, scope lazy string msg) 178 { 179 log (Severity.debug_, mask, tag, msg); 180 } 181 /// ditto 182 void debugf (Args...)(in string tag, in string fmt, Args args) 183 { 184 logf (Severity.debug_, tag, fmt, args); 185 } 186 /// ditto 187 void debugf (Args...)(in uint mask, in string tag, in string fmt, Args args) 188 { 189 logf (Severity.debug_, mask, tag, fmt, args); 190 } 191 192 /// add a log entry with info severity 193 void info (in string tag, scope lazy string msg) 194 { 195 log (Severity.info, tag, msg); 196 } 197 /// ditto 198 void info (in uint mask, in string tag, scope lazy string msg) 199 { 200 log (Severity.info, mask, tag, msg); 201 } 202 /// ditto 203 void infof (Args...)(in string tag, in string fmt, Args args) 204 { 205 logf (Severity.info, tag, fmt, args); 206 } 207 /// ditto 208 void infof (Args...)(in uint mask, in string tag, in string fmt, Args args) 209 { 210 logf (Severity.info, mask, tag, fmt, args); 211 } 212 213 /// add a log entry with warning severity 214 void warning (in string tag, scope lazy string msg) 215 { 216 log (Severity.warning, tag, msg); 217 } 218 /// ditto 219 void warning (in uint mask, in string tag, scope lazy string msg) 220 { 221 log (Severity.warning, mask, tag, msg); 222 } 223 /// ditto 224 void warningf (Args...)(in string tag, in string fmt, Args args) 225 { 226 logf (Severity.warning, tag, fmt, args); 227 } 228 /// ditto 229 void warningf (Args...)(in uint mask, in string tag, in string fmt, Args args) 230 { 231 logf (Severity.warning, mask, tag, fmt, args); 232 } 233 234 /// add a log entry with error severity 235 void error (in string tag, scope lazy string msg) 236 { 237 log (Severity.error, tag, msg); 238 } 239 /// ditto 240 void error (in uint mask, in string tag, scope lazy string msg) 241 { 242 log (Severity.error, mask, tag, msg); 243 } 244 /// ditto 245 void errorf (Args...)(in string tag, in string fmt, Args args) 246 { 247 logf (Severity.error, tag, fmt, args); 248 } 249 /// ditto 250 void errorf (Args...)(in uint mask, in string tag, in string fmt, Args args) 251 { 252 logf (Severity.error, mask, tag, fmt, args); 253 } 254 255 /// An abstract logger. All log operations are synchronized by a global mutex. 256 /// Implementations do not need to bother about thread safety. 257 interface Logger 258 { 259 /// print msg into the log 260 void print(in string msg); 261 /// release resource associated with the logger 262 void close(); 263 } 264 265 /// A logger that prints into a file 266 class FileLogger : Logger 267 { 268 import std.stdio : File; 269 270 private File file; 271 272 /// open file pointed to by name with mode 273 this (in string name, in string mode="a") 274 { 275 file = File(name, mode); 276 } 277 278 override void print(in string msg) 279 { 280 file.writeln(msg); 281 } 282 283 override void close() 284 { 285 file.close(); 286 } 287 } 288 289 /// A logger that prints to stdout 290 class StdOutLogger : Logger 291 { 292 override void print (in string msg) 293 { 294 import std.stdio : stdout; 295 296 stdout.writeln(msg); 297 } 298 299 override void close() {} 300 } 301 302 /// A logger that prints to stderr 303 class StdErrLogger : Logger 304 { 305 override void print (in string msg) 306 { 307 import std.stdio : stderr; 308 309 stderr.writeln(msg); 310 } 311 312 override void close() {} 313 } 314 315 /// The installed logger. 316 /// By default StdOutLogger. 317 /// Assigning null re-assign the default 318 @property Logger logger() 319 { 320 s_mutex.lock(); 321 scope(exit) s_mutex.unlock(); 322 323 return s_logger; 324 } 325 /// ditto 326 @property void logger (Logger logger) 327 { 328 s_mutex.lock(); 329 scope(exit) s_mutex.unlock(); 330 331 if (logger !is s_logger) { 332 s_logger.close(); 333 } 334 s_logger = logger; 335 if (!s_logger) { 336 s_logger = new StdOutLogger; 337 } 338 } 339 340 /// Minimum Severity for message filtering. 341 /// All messages with lower severity are filtered out. 342 /// by default Severity.info. 343 @property Severity severity() 344 { 345 s_mutex.lock(); 346 scope(exit) s_mutex.unlock(); 347 348 return s_sev; 349 } 350 /// ditto 351 @property void severity (in Severity sev) 352 { 353 s_mutex.lock(); 354 scope(exit) s_mutex.unlock(); 355 356 s_sev = sev; 357 } 358 359 /// A mask for bypassing some messages. 360 /// Should only be used in debug builds. 361 /// by default 0xffffffff 362 @property uint mask() 363 { 364 s_mutex.lock(); 365 scope(exit) s_mutex.unlock(); 366 367 return s_mask; 368 } 369 /// ditto 370 @property void mask(in uint mask) 371 { 372 s_mutex.lock(); 373 scope(exit) s_mutex.unlock(); 374 375 s_mask = mask; 376 } 377 378 /// the default format string for log messages 379 enum string defaultMsgFormat = "%d %s: %t %m"; 380 381 /// The format string for log messages 382 /// The entries are as follow: 383 /// - %d: datetime (formatted according timeFormat) 384 /// - %s: severity 385 /// - %t: tag 386 /// - %m: msg 387 /// The default format string is defaultMsgFormat 388 @property string msgFormat() 389 { 390 s_mutex.lock(); 391 scope(exit) s_mutex.unlock(); 392 393 return s_msgFmt.fmt; 394 } 395 /// ditto 396 @property void msgFormat(in string fmt) 397 { 398 import std.utf : validate; 399 400 validate(fmt); 401 402 s_mutex.lock(); 403 scope(exit) s_mutex.unlock(); 404 405 s_msgFmt = MessageFmt(fmt); 406 } 407 408 /// the default format string for date-time 409 enum string defaultTimeFormat = "HH:ii:ss.FFF"; 410 411 /// The format string for date-time in log message 412 /// The format of this string is the same as smjg.libs.util.datetime: 413 /// http://pr.stewartsplace.org.uk/d/sutil/doc/datetimeformat.html 414 @property string timeFormat() 415 { 416 s_mutex.lock(); 417 scope(exit) s_mutex.unlock(); 418 419 return s_timeFmt; 420 } 421 /// ditto 422 @property void timeFormat(in string fmt) 423 { 424 import std.utf : validate; 425 426 validate(fmt); 427 428 s_mutex.lock(); 429 scope(exit) s_mutex.unlock(); 430 431 s_timeFmt = fmt; 432 } 433 434 private: 435 436 __gshared { 437 Mutex s_mutex; 438 Logger s_logger; 439 Severity s_sev; 440 uint s_mask; 441 string s_timeFmt; 442 MessageFmt s_msgFmt; 443 } 444 445 shared static this() 446 { 447 s_mutex = new Mutex(); 448 s_logger = new StdOutLogger; 449 s_sev = defaultSeverity; 450 s_mask = defaultMask; 451 s_timeFmt = defaultTimeFormat; 452 s_msgFmt = MessageFmt(defaultMsgFormat); 453 } 454 455 shared static ~this() 456 { 457 s_logger.close(); 458 } 459 460 struct MessageFmt 461 { 462 enum EntryType { 463 str, datetime, sev, tag, msg, 464 } 465 struct Entry { 466 EntryType typ; 467 string str; 468 } 469 470 this (string fmt) 471 { 472 this.fmt = fmt; 473 474 string curStr; 475 bool percent; 476 foreach (char c; fmt) { 477 if (percent) { 478 switch (c) { 479 case 'd': entries ~= Entry(EntryType.datetime); break; 480 case 's': entries ~= Entry(EntryType.sev); break; 481 case 't': entries ~= Entry(EntryType.tag); break; 482 case 'm': entries ~= Entry(EntryType.msg); break; 483 default: throw new Exception("unknown log format entry: %"~c); 484 } 485 percent = false; 486 } 487 else if (c == '%') { 488 if (curStr.length) { 489 entries ~= Entry(EntryType.str, curStr); 490 curStr = null; 491 } 492 percent = true; 493 } 494 else { 495 curStr ~= c; 496 } 497 } 498 if (curStr.length) { 499 entries ~= Entry(EntryType.str, curStr); 500 } 501 } 502 503 string fmt; 504 Entry[] entries; 505 506 string formatMsg(in Severity sev, in string tag, in string msg) 507 { 508 import std.datetime : Clock; 509 510 string res; 511 foreach (entry; entries) { 512 final switch (entry.typ) { 513 case EntryType.str: res ~= entry.str; break; 514 case EntryType.datetime: res ~= formatTime(Clock.currTime); break; 515 case EntryType.sev: res ~= sevString(sev); break; 516 case EntryType.tag : res ~= tag; break; 517 case EntryType.msg : res ~= msg; break; 518 } 519 } 520 return res; 521 } 522 } 523 524 string sevString(in Severity sev) pure 525 { 526 final switch (sev) 527 { 528 case Severity.trace: return "TRACE"; 529 case Severity.debug_: return "DEBUG"; 530 case Severity.info: return "INFO"; 531 case Severity.warning: return "WARN"; 532 case Severity.error: return "ERROR"; 533 } 534 } 535 536 string formatTime(SysTime time) 537 { 538 import gfx.priv.datetimeformat : format; 539 540 return format(time, s_timeFmt); 541 }