1 // Written in the D programming language. 2 /* 3 NYSL Version 0.9982 4 5 A. This software is "Everyone'sWare". It means: 6 Anybody who has this software can use it as if he/she is 7 the author. 8 9 A-1. Freeware. No fee is required. 10 A-2. You can freely redistribute this software. 11 A-3. You can freely modify this software. And the source 12 may be used in any software with no limitation. 13 A-4. When you release a modified version to public, you 14 must publish it with your name. 15 16 B. The author is not responsible for any kind of damages or loss 17 while using or misusing this software, which is distributed 18 "AS IS". No warranty of any kind is expressed or implied. 19 You use AT YOUR OWN RISK. 20 21 C. Copyrighted to Kazuki KOMATSU 22 23 D. Above three clauses are applied both to source and binary 24 form of this software. 25 */ 26 27 /** 28 このモジュールは、ロガーおよびログ出力を行いやすくするための機能を提供します。 29 30 Examples: 31 ------------- 32 module foo; 33 34 import std.stdio; 35 import graphite.utils.logger; 36 import carbon.templates; 37 38 mixin defGlobalVariables!("logger", "logFile", 39 { 40 auto file = File("foo.txt", "w"); 41 return tuple(.logger!(LogFormat.readable)(file), file); 42 }); 43 44 45 void main() 46 { 47 int a = 12; 48 // 文字列として出力 49 logger.writeln!"notice"("a is ", a); 50 51 // 値をそのまま出力 52 logger.trace!"notice"(a, a * 2, a * 3); 53 54 // 例外の通知 55 try logger.captureException(enforce(0)); 56 catch(Exception ex){ 57 writeln(ex); 58 throw ex; 59 } 60 } 61 ------------- 62 */ 63 module graphite.utils.log; 64 65 import std.range; 66 import std.format; 67 import std.stdio; 68 import std.variant; 69 import std.json; 70 71 import graphite.utils.json; 72 73 74 /** 75 ログの重要度を表します 76 */ 77 enum Level 78 { 79 verbose, /// 80 notice, /// 81 warning, /// 82 error, /// 83 fatalError, /// 84 silent, /// 85 } 86 87 88 /** 89 ログのフロントエンドとバックエンドでやりとりされるデータです。 90 */ 91 struct LogElement(T...) 92 { 93 Level level; 94 string id; 95 string file; 96 size_t line; 97 string func; 98 T msg; 99 } 100 101 102 /** 103 ロガー 104 */ 105 struct Logger(Backend) 106 if(isOutputRange!(Backend, LogElement!(Variant))) 107 { 108 version(D_Ddoc) 109 { 110 /** 111 ロガーに値を出力します。 112 値は$(D LogElement!T)に格納され、バックエンドに渡されます。 113 */ 114 void trace(Level lv, T...)(T args); 115 void trace(string lv, T...)(T args); /// ditto 116 117 118 /** 119 ロガーに文字列を出力します。 120 文字列は$(D LogElement!string)に格納され、バックエンドに渡されます。 121 */ 122 void writeln(Level lv, T...)(T args); 123 void writeln(string lv, T...)(T args); /// ditto 124 void writefln(Level lv, T...)(string format, T args); /// ditto 125 void writefln(Level lv, T...)(string format, T args); /// ditto 126 127 128 /** 129 式を評価した結果例外やエラーが発生したら、ロガーにそれを出力します。 130 例外は$(D LogElement!Ex)に格納され、エラーは$(D LogElement!Er)に格納され、バックエンドに渡されます。 131 132 例外が発生しない場合、式の評価結果をそのまま返します。 133 */ 134 auto ref T captureException(Ex = Exception, Er = Error, T)(lazy T dg); 135 } 136 137 alias instance this; 138 139 LoggerImpl instance(string file = __FILE__, size_t line = __LINE__, string fn = __PRETTY_FUNCTION__) 140 { 141 return LoggerImpl(&this, file, line, fn); 142 } 143 144 145 static struct LoggerImpl 146 { 147 @disable this(this); 148 149 150 void trace(Level lv, T...)(T args) 151 if(lv != Level.silent) 152 { 153 if(lv >= logger.thrLv){ 154 LogElement!T log; 155 log.level = lv; 156 log.id = logger._id; 157 log.file = _file; 158 log.line = _line; 159 log.func = _func; 160 log.msg = args; 161 logger._b.put(log); 162 } 163 } 164 165 166 void trace(string lv, T...)(T args) 167 { 168 trace!(mixin(`Level.` ~ lv))(args); 169 } 170 171 172 void writeln(Level lv, T...)(T args) 173 { 174 if(lv >= logger.thrLv){ 175 auto app = appender!string(); 176 foreach(i, U; T) 177 app.formattedWrite("%s", args[i]); 178 trace!lv(app.data); 179 } 180 } 181 182 183 void writeln(string lv, T...)(T args) 184 { 185 writeln!(mixin(`Level.` ~ lv))(args); 186 } 187 188 189 void writefln(Level lv, T...)(string format, T args) 190 { 191 if(lv >= logger.thrLv){ 192 auto app = appender!string(); 193 app.formattedWrite(format, args); 194 trace!lv(app.data); 195 } 196 } 197 198 199 void writefln(string lv, T...)(string format, T args) 200 { 201 writefln!(mixin(`Level.` ~ lv))(format, args); 202 } 203 204 205 auto ref T captureException(Ex = Exception, Er = Error, T)(lazy T dg) 206 { 207 try return dg(); 208 catch(Ex ex){ 209 trace!(Level.error)(ex); 210 throw ex; 211 } 212 catch(Er er){ 213 trace!(Level.fatalError)(er); 214 throw er; 215 } 216 } 217 218 Logger* logger; 219 220 private: 221 string _file; 222 size_t _line; 223 string _func; 224 } 225 226 227 /** 228 ログに出力するしきい値レベルを設定・取得します。 229 230 Examples: 231 ---------------- 232 logger.thrLv = Level.warning; 233 logger.writeln!"notice"("このメッセージはログに残らない"); 234 logger.writeln!"warning"("ログに残るメッセージ"); 235 ---------------- 236 */ 237 Level thrLv() @property 238 { 239 return _thrLv; 240 } 241 242 243 /// ditto 244 void thrLv(Level level) @property 245 { 246 this._thrLv = level; 247 } 248 249 250 static if(is(typeof(_b.writer))) 251 { 252 /** 253 バックエンドが$(D writer)プロパティをもつ場合、その$(D writer)プロパティをそのまま返します。 254 */ 255 auto ref writer() @property 256 { 257 return _b.writer; 258 } 259 } 260 261 262 private: 263 Backend _b; 264 string _id; 265 Level _thrLv; 266 } 267 268 269 /** 270 バックエンドを指定してロガーを構築します。 271 どのような$(D T...)に対しても$(D isOuputRange!(R, LogElement!T))が$(D true)となるような出力レンジがバックエンドとなります。 272 */ 273 auto logger(alias id = __MODULE__, B)(B backend) 274 if(isOutputRange!(B, LogElement!Variant)) 275 { 276 static if(is(typeof(id) : string) && is(typeof({ enum _unused_ = id; }))) 277 enum idstr = id; 278 else 279 enum idstr = id.stringof; 280 281 return Logger!B(backend, idstr, Level.verbose); 282 } 283 284 unittest { 285 static struct Backend1(T) 286 { 287 LogElement!T[] logs; 288 289 void put(U...)(LogElement!U log) 290 { 291 static if(U.length == 1 && is(U[0] == T)) 292 logs ~= log; 293 } 294 } 295 296 import std.typecons; 297 298 LogElement!string[] arr; 299 auto b = Backend1!string(arr); 300 auto logger = .logger(&b); 301 logger.trace!"fatalError"("foo"); size_t instLine = __LINE__; 302 assert(b.logs[0].level == Level.fatalError); 303 assert(b.logs[0].id == __MODULE__); 304 assert(b.logs[0].file == __FILE__); 305 assert(b.logs[0].line == instLine); 306 assert(b.logs[0].func == __PRETTY_FUNCTION__); 307 assert(b.logs[0].msg[0] == "foo"); 308 309 logger.writefln!"warning"("%s is %s?", 124, "124"); instLine = __LINE__; 310 assert(b.logs[1].level == Level.warning); 311 assert(b.logs[1].id == __MODULE__); 312 assert(b.logs[1].file == __FILE__); 313 assert(b.logs[1].line == instLine); 314 assert(b.logs[1].func == __PRETTY_FUNCTION__); 315 assert(b.logs[1].msg[0] == "124 is 124?"); 316 317 logger.writeln!"notice"(124, " is " "124", "?"); instLine = __LINE__; 318 assert(b.logs[2].level == Level.notice); 319 assert(b.logs[2].id == __MODULE__); 320 assert(b.logs[2].file == __FILE__); 321 assert(b.logs[2].line == instLine); 322 assert(b.logs[2].func == __PRETTY_FUNCTION__); 323 assert(b.logs[2].msg[0] == "124 is 124?"); 324 } 325 326 unittest { 327 import std.exception; 328 import std.conv; 329 330 static struct Backend1 331 { 332 string[] logs; 333 334 void put(U...)(LogElement!U log) 335 { 336 auto app = appender!string(); 337 338 foreach(i, E; U) 339 app.formattedWrite("%s", log.msg[i]); 340 341 logs ~= app.data; 342 } 343 } 344 345 Backend1 b; 346 auto logger = .logger(&b); 347 348 string exStr; 349 try logger.captureException(enforce(0)); 350 catch(Exception ex) 351 exStr = ex.to!string(); 352 353 assert(b.logs[0] == exStr); 354 } 355 356 357 /** 358 出力レンジ、もしくは$(D std.stdio.File)を最終的な出力先としてロガーを構築します。 359 出力のフォーマットや形式は、$(D formattedWriter)によって決定されます。 360 ロガーに渡されたログは、$(D formattedWriter(w, log))によって$(D Writer w)に出力されます。 361 */ 362 auto logger(alias formattedWriter, alias id = __MODULE__, Writer)(Writer w) 363 if(is(typeof(backend!formattedWriter(w)))) 364 { 365 auto b = backend!formattedWriter(w); 366 return .logger!id(b); 367 } 368 369 370 /** 371 ファイルを最終出力先とするようなバックエンドを構築します。 372 ファイルへの出力フォーマットや形式は、$(D formattedWriter(file.lockingTextWriter, log))により決定されます。 373 */ 374 auto backend(alias formattedWriter)(File file) 375 if(is(typeof(backend!formattedWriter(file.lockingTextWriter)))) 376 { 377 static struct Backend 378 { 379 void put(T...)(LogElement!T log) 380 { 381 auto w = _file.lockingTextWriter; 382 formattedWriter(w, log); 383 } 384 385 private: 386 File _file; 387 } 388 389 return Backend(file); 390 } 391 392 393 unittest 394 { 395 import std.conv : to; 396 import std.string : chomp; 397 398 immutable filename = "unittest_file.txt"; 399 scope(exit) 400 std.file.remove(filename); 401 402 LogElement!() log; 403 { 404 auto b = backend!((ref w, log){ w.put(to!string(log)); })(File(filename, "w")); 405 b.put(log); 406 } 407 408 { 409 assert(chomp(std.file.readText(filename)) == to!string(log)); 410 } 411 } 412 413 414 /** 415 出力レンジを最終出力先とするようなバックエンドを構築します。 416 出力レンジの型および出力フォーマットや形式は、$(D formattedWriter(outputRange, log))により決定されます。 417 */ 418 auto backend(alias formattedWriter, Writer)(Writer w) 419 if(is(typeof((ref Writer w, LogElement!Variant log){ formattedWriter(w, log); }))) 420 { 421 static struct Backend 422 { 423 void put(T...)(LogElement!T log) 424 { 425 formattedWriter(_w, log); 426 } 427 428 429 ref Writer writer() @property 430 { 431 return _w; 432 } 433 434 435 private: 436 Writer _w; 437 } 438 439 return Backend(w); 440 } 441 442 443 /// ditto 444 auto backend(alias formattedWriter, Writer)(Writer* w) 445 if(is(typeof(backend!formattedWriter(*w))) && !is(Unqual!Writer == File)) 446 { 447 static struct Backend 448 { 449 void put(T...)(LogElement!T log) 450 { 451 formattedWriter(*_w, log); 452 } 453 454 455 ref Writer writer() @property 456 { 457 return *_w; 458 } 459 460 461 private: 462 Writer* _w; 463 } 464 465 466 return Backend(w); 467 } 468 469 470 struct LogFormat 471 { 472 @disable this(); 473 @disable this(this); 474 475 static: 476 void readable(Sink, T...)(ref Sink sink, LogElement!T log) 477 if(isOutputRange!(Sink, dchar)) 478 { 479 sink.formattedWrite("[%s] @%s[%s(%s) in %s]", log.level, log.id, log.file, log.line, log.func); 480 481 static if(T.length == 0) 482 sink.put('\n'); 483 else static if(T.length == 1) 484 sink.formattedWrite(": %s\n", log.msg); 485 else{ 486 sink.put(": {"); 487 foreach(i, U; T){ 488 sink.formattedWrite("%s", log.msg[i]); 489 490 if(i != T.length - 1) 491 sink.put(", "); 492 } 493 sink.put("}\n"); 494 } 495 } 496 497 unittest { 498 auto app = appender!string(); 499 readable(app, LogElement!()(Level.verbose, "ID", "FILE", 1111111, "FUNC")); 500 assert(app.data == "[verbose] @ID[FILE(1111111) in FUNC]\n"); 501 502 app = appender!string(); 503 readable(app, LogElement!(string)(Level.verbose, "ID", "FILE", 123123, "FUNC", "FOOBAR")); 504 assert(app.data == "[verbose] @ID[FILE(123123) in FUNC]: FOOBAR\n"); 505 506 app = appender!string(); 507 readable(app, LogElement!(string, int)(Level.verbose, "ID", "FILE", 123123, "FUNC", "FOOBAR", 323232)); 508 assert(app.data == `[verbose] @ID[FILE(123123) in FUNC]: {FOOBAR, 323232}`"\n"); 509 } 510 511 512 void json(Sink, T...)(ref Sink sink, LogElement!T log) 513 if(isOutputRange!(Sink, dchar)) 514 { 515 Variant[string] v; 516 517 v["level"] = log.level; 518 v["id"] = log.id; 519 v["file"] = log.file; 520 v["line"] = log.line; 521 v["func"] = log.func; 522 v["msg"] = variantArray(log.msg); 523 524 sink.formattedWrite("%s\n", JSONEnv!null.toJSONValue(v)); 525 } 526 527 unittest { 528 import std.string : chomp; 529 530 auto app = appender!string(); 531 json(app, LogElement!()(Level.verbose, "ID", "FILE", 1111111, "FUNC")); 532 JSONValue jv = parseJSON(chomp(app.data)); 533 assert(jv.type == JSON_TYPE.OBJECT); 534 assert(jv["level"].str == "verbose"); 535 assert(jv["id"].str == "ID"); 536 assert(jv["file"].str == "FILE"); 537 assert(1111111 == (jv["line"].type == JSON_TYPE.UINTEGER ? jv["line"].uinteger : jv["line"].integer)); 538 assert(jv["func"].str == "FUNC"); 539 assert(jv["msg"].array.length == 0); 540 541 app = appender!string(); 542 json(app, LogElement!(string)(Level.verbose, "ID", "FILE", 123123, "FUNC", "FOOBAR")); 543 jv = parseJSON(chomp(app.data)); 544 assert(jv.type == JSON_TYPE.OBJECT); 545 assert(jv["level"].str == "verbose"); 546 assert(jv["id"].str == "ID"); 547 assert(jv["file"].str == "FILE"); 548 assert(123123 == (jv["line"].type == JSON_TYPE.UINTEGER ? jv["line"].uinteger : jv["line"].integer)); 549 assert(jv["func"].str == "FUNC"); 550 assert(jv["msg"].array.length == 1); 551 assert(jv["msg"][0].str == "FOOBAR"); 552 553 app = appender!string(); 554 json(app, LogElement!(string, int)(Level.verbose, "ID", "FILE", 123123, "FUNC", "FOOBAR", 323232)); 555 jv = parseJSON(chomp(app.data)); 556 assert(jv.type == JSON_TYPE.OBJECT); 557 assert(jv["level"].str == "verbose"); 558 assert(jv["id"].str == "ID"); 559 assert(jv["file"].str == "FILE"); 560 assert(123123 == (jv["line"].type == JSON_TYPE.UINTEGER ? jv["line"].uinteger : jv["line"].integer)); 561 assert(jv["func"].str == "FUNC"); 562 assert(jv["msg"].array.length == 2); 563 assert(jv["msg"][0].str == "FOOBAR"); 564 assert(323232 == (jv["msg"][1].type == JSON_TYPE.UINTEGER ? jv["msg"][1].uinteger : jv["msg"][1].integer)); 565 } 566 } 567 568 569 /** 570 std.stdio.Fileに書き込むLogger 571 */ 572 template FileLogger(alias formattedWriter) 573 { 574 alias FileLogger = typeof(.logger!formattedWriter(std.stdio.stdin)); 575 }