1 /** 2 * Core routines and types 3 * 4 * Authors: Tristan Brice Velloza Kildaire (deavmi) 5 */ 6 module gogga.core; 7 8 import dlog.core; 9 import dlog.basic; 10 import std.conv : to; 11 import dlog.utilities : flatten; 12 import std.array : join; 13 14 /** 15 * The gogga styles supported 16 */ 17 public enum GoggaMode 18 { 19 /** 20 * TwoKTwenty3 is: `[<file>] (<module>:<lineNumber>) <message>` 21 */ 22 TwoKTwenty3, 23 24 /** 25 * Simple mode is just: `[<LEVEL>] <message>` 26 */ 27 SIMPLE, 28 29 /** 30 * Rustacean mode is: `[<LEVEL>] (<filePath>/<functionName>:<lineNumber>) <message>` 31 */ 32 RUSTACEAN, 33 34 /** 35 * Simple rustacean mode is: `[<LEVEL>] (<functionName>:<lineNumber>) <message>` 36 */ 37 RUSTACEAN_SIMPLE 38 } 39 40 /** 41 * Information obtained during compilation time (if any) 42 */ 43 private struct GoggaCompInfo 44 { 45 /** 46 * compile time usage file 47 */ 48 public string fullFilePath; 49 50 /** 51 * compile time usage file (relative) 52 */ 53 public string file; 54 55 /** 56 * compile time usage line number 57 */ 58 public ulong line; 59 60 /** 61 * compile time usage module 62 */ 63 public string moduleName; 64 65 /** 66 * compile time usage function 67 */ 68 public string functionName; 69 70 /** 71 * compile time usage function (pretty) 72 */ 73 public string prettyFunctionName; 74 75 /** 76 * Constructs the compilation information with the provided 77 * parameters 78 * 79 * Params: 80 * __FILE_FULL_PATH__ = compile time usage file 81 * __FILE__ = compile time usage file (relative) 82 * __LINE__ = compile time usage line number 83 * __MODULE__ = compile time usage module 84 * __FUNCTION__ = compile time usage function 85 * __PRETTY_FUNCTION__ = compile time usage function (pretty) 86 */ 87 this(string fullFilePath, string file, ulong line, string moduleName, string functionName, string prettyFunctionName) 88 { 89 this.fullFilePath = fullFilePath; 90 this.file = file; 91 this.line = line; 92 this.moduleName = moduleName; 93 this.functionName = functionName; 94 this.prettyFunctionName = prettyFunctionName; 95 } 96 97 /** 98 * Flattens the known compile-time information into a string array 99 * 100 * Returns: a string[] 101 */ 102 public string[] toArray() 103 { 104 return [fullFilePath, file, to!(string)(line), moduleName, functionName, prettyFunctionName]; 105 } 106 } 107 108 /** 109 * A `GoggaMessage` which comprises 110 * of a `GoggaCompInfo` for context 111 */ 112 private class GoggaMessage : BasicMessage 113 { 114 /** 115 * The line information 116 */ 117 private GoggaCompInfo ctx; 118 119 /** 120 * Sets the context 121 * 122 * Params: 123 * ctx = the context 124 */ 125 public void setContext(GoggaCompInfo ctx) 126 { 127 this.ctx = ctx; 128 } 129 130 /** 131 * Returns the context 132 * 133 * Returns: the context 134 */ 135 public GoggaCompInfo getContext() 136 { 137 return this.ctx; 138 } 139 } 140 141 /** 142 * The Gogga transformer which 143 * applies stylization to 144 * incoming `GoggaMessage`(s) 145 */ 146 private class GoggaTransform : Transform 147 { 148 /** 149 * Mode to use for stylization 150 */ 151 private GoggaMode mode; 152 153 /** 154 * Sets the stylization to 155 * use when transforming 156 * the message's text 157 * 158 * Params: 159 * mode = the `GoggaMode` 160 */ 161 public void setMode(GoggaMode mode) 162 { 163 this.mode = mode; 164 } 165 166 /** 167 * Transforms the incoming message 168 * to use the Gogga stylization. This 169 * will be a no-op if the incoming 170 * message is not a `GoggaMessage`. 171 * 172 * Params: 173 * message = the message to transform 174 * Returns: the transformed message, 175 * else the same exact one 176 */ 177 public Message transform(Message message) 178 { 179 // Only handle GoggaMessage(s) 180 GoggaMessage goggaMesg = cast(GoggaMessage)message; 181 if(goggaMesg is null) 182 { 183 return null; 184 } 185 186 /* The generated output string */ 187 string finalOutput; 188 189 190 191 /* Extract the Level */ 192 Level level = goggaMesg.getLevel(); 193 194 /* Extract the text */ 195 string text = goggaMesg.getText(); 196 197 /* get the context data */ 198 string[] context = goggaMesg.getContext().toArray(); 199 200 201 /** 202 * Simple mode is just: `[<LEVEL>] <message>` 203 */ 204 if(this.mode == GoggaMode.SIMPLE) 205 { 206 finalOutput = cast(string)debugColor("["~to!(string)(level)~"] ", level); 207 208 finalOutput ~= text~"\n"; 209 } 210 /** 211 * TwoKTwenty3 is: `[<file>] (<module>:<lineNumber>) <message>` 212 */ 213 else if(this.mode == GoggaMode.TwoKTwenty3) 214 { 215 /* Module information (and status debugColoring) */ 216 string moduleInfo = cast(string)debugColor("["~context[1]~"]", level); 217 218 /* Function and line number info */ 219 string funcInfo = cast(string)(colorSrc("("~context[4]~":"~context[2]~")")); 220 221 finalOutput = moduleInfo~" "~funcInfo~" "~text~"\n"; 222 } 223 /** 224 * Rustacean mode is: `[<LEVEL>] (<filePath>/<functionName>:<lineNumber>) <message>` 225 */ 226 else if(this.mode == GoggaMode.RUSTACEAN) 227 { 228 finalOutput = cast(string)debugColor(to!(string)(level)~"\t", level); 229 finalOutput ~= cast(string)(colorSrc(context[1]~"/"~context[4]~":"~context[2]~" ")); 230 finalOutput ~= text~"\n"; 231 } 232 /** 233 * Simple rustacean mode is: `[<LEVEL>] (<functionName>:<lineNumber>) <message>` 234 */ 235 else if(this.mode == GoggaMode.RUSTACEAN_SIMPLE) 236 { 237 finalOutput = cast(string)debugColor(to!(string)(level)~"\t", level); 238 finalOutput ~= cast(string)(colorSrc(context[4]~":"~context[2]~" ")); 239 finalOutput ~= text~"\n"; 240 } 241 242 goggaMesg.setText(finalOutput); 243 return message; 244 } 245 } 246 247 /** 248 * A `GoggaLogger` 249 */ 250 public final class GoggaLogger : BasicLogger 251 { 252 /** 253 * The Gogga text transformer 254 */ 255 private GoggaTransform gogTrans; 256 257 /** 258 * Whether debug prints are enabled or not 259 */ 260 private bool debugEnabled = false; 261 262 /** 263 * Constructs a new `GoggaLogger` 264 */ 265 this() 266 { 267 super(); 268 this.gogTrans = new GoggaTransform(); 269 addTransform(this.gogTrans); 270 } 271 272 /** 273 * Sets the style of logging 274 * to use 275 * 276 * Params: 277 * mode = the `GoggaMode` 278 */ 279 public void mode(GoggaMode mode) 280 { 281 this.gogTrans.setMode(mode); 282 } 283 284 /** 285 * Performs the actual logging 286 * by packing up everything before 287 * sending it to the `log(Message)` 288 * method 289 * 290 * Params: 291 * segments = the compile-time segments 292 * info = the context 293 * level = the log level to use 294 */ 295 private void doLog(TextType...)(TextType segments, GoggaCompInfo info, Level level) 296 { 297 /* Create a new GoggaMessage */ 298 GoggaMessage message = new GoggaMessage(); 299 300 /* Set context to the line information */ 301 message.setContext(info); 302 303 /* Set the level */ 304 message.setLevel(level); 305 306 /** 307 * Grab all compile-time arguments and make them 308 * into an array, then join them together and 309 * set that text as the message's text 310 */ 311 message.setText(join(flatten(segments), " ")); 312 313 /* Log this message */ 314 log(message); 315 } 316 317 /** 318 * Logs using the default context an arbitrary amount of arguments 319 * specifically setting the context's level to ERROR 320 * 321 * Params: 322 * segments = the arbitrary argumnets (alias sequence) 323 * __FILE_FULL_PATH__ = compile time usage file 324 * __FILE__ = compile time usage file (relative) 325 * __LINE__ = compile time usage line number 326 * __MODULE__ = compile time usage module 327 * __FUNCTION__ = compile time usage function 328 * __PRETTY_FUNCTION__ = compile time usage function (pretty) 329 */ 330 public void error(TextType...)(TextType segments, 331 string c1 = __FILE_FULL_PATH__, 332 string c2 = __FILE__, ulong c3 = __LINE__, 333 string c4 = __MODULE__, string c5 = __FUNCTION__, 334 string c6 = __PRETTY_FUNCTION__) 335 { 336 doLog(segments, GoggaCompInfo(c1, c2, c3, c4, c5, c6), Level.ERROR); 337 } 338 339 /** 340 * Logs using the default context an arbitrary amount of arguments 341 * specifically setting the context's level to INFO 342 * 343 * Params: 344 * segments = the arbitrary argumnets (alias sequence) 345 * __FILE_FULL_PATH__ = compile time usage file 346 * __FILE__ = compile time usage file (relative) 347 * __LINE__ = compile time usage line number 348 * __MODULE__ = compile time usage module 349 * __FUNCTION__ = compile time usage function 350 * __PRETTY_FUNCTION__ = compile time usage function (pretty) 351 */ 352 public void info(TextType...)(TextType segments, 353 string c1 = __FILE_FULL_PATH__, 354 string c2 = __FILE__, ulong c3 = __LINE__, 355 string c4 = __MODULE__, string c5 = __FUNCTION__, 356 string c6 = __PRETTY_FUNCTION__) 357 { 358 doLog(segments, GoggaCompInfo(c1, c2, c3, c4, c5, c6), Level.INFO); 359 } 360 361 /** 362 * Logs using the default context an arbitrary amount of arguments 363 * specifically setting the context's level to WARN 364 * 365 * Params: 366 * segments = the arbitrary argumnets (alias sequence) 367 * __FILE_FULL_PATH__ = compile time usage file 368 * __FILE__ = compile time usage file (relative) 369 * __LINE__ = compile time usage line number 370 * __MODULE__ = compile time usage module 371 * __FUNCTION__ = compile time usage function 372 * __PRETTY_FUNCTION__ = compile time usage function (pretty) 373 */ 374 public void warn(TextType...)(TextType segments, 375 string c1 = __FILE_FULL_PATH__, 376 string c2 = __FILE__, ulong c3 = __LINE__, 377 string c4 = __MODULE__, string c5 = __FUNCTION__, 378 string c6 = __PRETTY_FUNCTION__) 379 { 380 doLog(segments, GoggaCompInfo(c1, c2, c3, c4, c5, c6), Level.WARN); 381 } 382 383 /** 384 * Logs using the default context an arbitrary amount of arguments 385 * specifically setting the context's level to DEBUG and will 386 * only print if debugging is enabled 387 * 388 * Params: 389 * segments = the arbitrary argumnets (alias sequence) 390 * __FILE_FULL_PATH__ = compile time usage file 391 * __FILE__ = compile time usage file (relative) 392 * __LINE__ = compile time usage line number 393 * __MODULE__ = compile time usage module 394 * __FUNCTION__ = compile time usage function 395 * __PRETTY_FUNCTION__ = compile time usage function (pretty) 396 */ 397 public void debug_(TextType...)(TextType segments, 398 string c1 = __FILE_FULL_PATH__, 399 string c2 = __FILE__, ulong c3 = __LINE__, 400 string c4 = __MODULE__, string c5 = __FUNCTION__, 401 string c6 = __PRETTY_FUNCTION__) 402 { 403 doLog(segments, GoggaCompInfo(c1, c2, c3, c4, c5, c6), Level.DEBUG); 404 } 405 406 /** 407 * Alias for debug_ 408 */ 409 public alias dbg = debug_; 410 } 411 412 /** 413 * Colorise the text provided accoridng to the level and then 414 * reset the colors at the end 415 * 416 * Params: 417 * text = the text to colorise 418 * level = the color to use 419 * Returns: the byte sequence of characters and controls 420 */ 421 private byte[] debugColor(string text, Level level) 422 { 423 /* The generated message */ 424 byte[] messageBytes; 425 426 /* If INFO, set green */ 427 if(level == Level.INFO) 428 { 429 messageBytes = cast(byte[])[27, '[','3','2','m']; 430 } 431 /* If WARN, set yellow */ 432 else if(level == Level.WARN) 433 { 434 messageBytes = cast(byte[])[27, '[','3','1', ';', '9', '3', 'm']; 435 } 436 /* If ERROR, set red */ 437 else if(level == Level.ERROR) 438 { 439 messageBytes = cast(byte[])[27, '[','3','1','m']; 440 } 441 /* If DEBUG, set pink */ 442 else 443 { 444 messageBytes = cast(byte[])[27, '[','3','5','m']; 445 } 446 447 /* Add the message */ 448 messageBytes ~= cast(byte[])text; 449 450 /* Switch back debugColor */ 451 messageBytes ~= cast(byte[])[27, '[', '3', '9', 'm']; 452 453 /* Reset coloring */ 454 messageBytes ~= [27, '[', 'm']; 455 456 return messageBytes; 457 } 458 459 /** 460 * Colors the provided text in a gray fashion and then 461 * resets back to normal 462 * 463 * Params: 464 * text = the text to gray color 465 * Returns: the byte sequence of characters and controls 466 */ 467 private byte[] colorSrc(string text) 468 { 469 /* The generated message */ 470 byte[] messageBytes; 471 472 /* Reset coloring */ 473 messageBytes ~= [27, '[', 'm']; 474 475 /* Color gray */ 476 messageBytes ~= [27, '[', '3', '9', ';', '2', 'm']; 477 478 /* Append the message */ 479 messageBytes ~= text; 480 481 /* Reset coloring */ 482 messageBytes ~= [27, '[', 'm']; 483 484 return messageBytes; 485 } 486 487 version(unittest) 488 { 489 import std.stdio : writeln, stdout; 490 } 491 492 unittest 493 { 494 GoggaLogger gLogger = new GoggaLogger(); 495 gLogger.addHandler(new FileHandler(stdout)); 496 gLogger.setLevel(Level.INFO); 497 498 // Test the normal modes 499 gLogger.info("This is an info message"); 500 gLogger.warn("This is a warning message"); 501 gLogger.error("This is an error message"); 502 503 // We shouldn't see anything as debug is off 504 gLogger.dbg("This is a debug which is hidden", 1); 505 506 // Now enable debugging and you should see it 507 gLogger.setLevel(Level.DEBUG); 508 gLogger.dbg("This is a VISIBLE debug", true); 509 510 // Make space between unit tests 511 writeln(); 512 } 513 514 unittest 515 { 516 GoggaLogger gLogger = new GoggaLogger(); 517 gLogger.addHandler(new FileHandler(stdout)); 518 gLogger.setLevel(Level.INFO); 519 520 gLogger.mode(GoggaMode.TwoKTwenty3); 521 522 // Test the normal modes 523 gLogger.info("This is an info message"); 524 gLogger.warn("This is a warning message"); 525 gLogger.error("This is an error message"); 526 527 // We shouldn't see anything as debug is off 528 gLogger.dbg("This is a debug which is hidden", 1); 529 530 // Now enable debugging and you should see it 531 gLogger.setLevel(Level.DEBUG); 532 gLogger.dbg("This is a VISIBLE debug", true); 533 534 // Make space between unit tests 535 writeln(); 536 } 537 538 unittest 539 { 540 GoggaLogger gLogger = new GoggaLogger(); 541 gLogger.addHandler(new FileHandler(stdout)); 542 gLogger.setLevel(Level.INFO); 543 544 gLogger.mode(GoggaMode.SIMPLE); 545 546 // Test the normal modes 547 gLogger.info("This is an info message"); 548 gLogger.warn("This is a warning message"); 549 gLogger.error("This is an error message"); 550 551 // We shouldn't see anything as debug is off 552 gLogger.dbg("This is a debug which is hidden", 1); 553 554 // Now enable debugging and you should see it 555 gLogger.setLevel(Level.DEBUG); 556 gLogger.dbg("This is a VISIBLE debug", true); 557 558 // Make space between unit tests 559 writeln(); 560 } 561 562 unittest 563 { 564 GoggaLogger gLogger = new GoggaLogger(); 565 gLogger.addHandler(new FileHandler(stdout)); 566 gLogger.setLevel(Level.INFO); 567 568 gLogger.mode(GoggaMode.RUSTACEAN); 569 570 // Test the normal modes 571 gLogger.info("This is an info message"); 572 gLogger.warn("This is a warning message"); 573 gLogger.error("This is an error message"); 574 575 // We shouldn't see anything as debug is off 576 gLogger.dbg("This is a debug which is hidden", 1); 577 578 // Now enable debugging and you should see it 579 gLogger.setLevel(Level.DEBUG); 580 gLogger.dbg("This is a VISIBLE debug", true); 581 582 // Make space between unit tests 583 writeln(); 584 } 585 586 unittest 587 { 588 GoggaLogger gLogger = new GoggaLogger(); 589 gLogger.addHandler(new FileHandler(stdout)); 590 gLogger.setLevel(Level.INFO); 591 592 gLogger.mode(GoggaMode.RUSTACEAN_SIMPLE); 593 594 // Test the normal modes 595 gLogger.info("This is an info message"); 596 gLogger.warn("This is a warning message"); 597 gLogger.error("This is an error message"); 598 599 // We shouldn't see anything as debug is off 600 gLogger.dbg("This is a debug which is hidden", 1); 601 602 // Now enable debugging and you should see it 603 gLogger.setLevel(Level.DEBUG); 604 gLogger.dbg("This is a VISIBLE debug", true); 605 606 // Make space between unit tests 607 writeln(); 608 }