1 /******************************************************************************* 2 3 Utility functions to configure tango loggers from a config file. 4 5 Configures tango loggers, uses the AppendSyslog class to provide logfile 6 rotation. 7 8 In the config file, a logger can be configured using the following syntax: 9 10 ; Which logger to configure. In this case LoggerName is being configured. 11 ; A whole hierachy can be specified like LOG.MyApp.ThatOutput.X 12 ; And each level can be configured. 13 [LOG.LoggerName] 14 15 ; Whether to output to the terminal 16 ; warn, error, and fatal are written to stderr, info and trace to stdout 17 console = true 18 19 ; File to output to, no output to file if not given 20 file = log/logger_name.log 21 22 ; Whether to propagate the options down in the hierachy 23 propagate = false 24 25 ; The verbosity level, corresponse to the tango logger levels 26 level = info 27 28 ; Is this logger additive? That is, should we walk ancestors 29 ; looking for more appenders? 30 additive = true 31 32 Note that `LOG.Root` will be treated specially: it will configure the 33 'root' logger, which is the parent of all loggers. 34 `Root` is case insensitive, so `LOG.root` or `LOG.ROOT` will work as well. 35 36 See the class Config for further options and documentation. 37 38 There are global logger configuration options as well: 39 40 ; Global options are in the section [LOG] 41 [LOG] 42 43 ; Buffer size for output 44 buffer_size = 2048 45 46 See the class MetaConfig for further options and documentation. 47 48 Upon calling the configureLoggers function, logger related configuration 49 will be read and the according loggers configured accordingly. 50 51 Usage Example (you probably will only need to do this): 52 53 ---- 54 import Log = ocean.util.log.Config; 55 // ... 56 Log.configureLoggers(Config().iterateCategory!(Log.Config)("LOG"), 57 Config().get!(Log.MetaConfig)("LOG")); 58 ---- 59 60 Copyright: 61 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 62 All rights reserved. 63 64 License: 65 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 66 Alternatively, this file may be distributed under the terms of the Tango 67 3-Clause BSD License (see LICENSE_BSD.txt for details). 68 69 *******************************************************************************/ 70 71 module ocean.util.log.Config; 72 73 74 import ocean.meta.types.Qualifiers; 75 76 import ocean.io.Stdout; 77 import ocean.core.Array : insertShift, removePrefix, removeSuffix, sort; 78 import ocean.util.config.ConfigFiller; 79 import ocean.util.config.ConfigParser; 80 import ocean.util.log.AppendFile; 81 import ocean.util.log.AppendSysLog; 82 import ocean.text.util.StringSearch; 83 84 import ocean.util.log.Logger; 85 import ocean.util.log.InsertConsole; 86 import ocean.util.log.Appender; 87 import ocean.util.log.AppendStderrStdout; 88 import ocean.util.log.Event; 89 import ocean.util.log.ILogger; 90 91 // Log layouts 92 import ocean.util.log.layout.LayoutMessageOnly; 93 import ocean.util.log.layout.LayoutStatsLog; 94 import ocean.util.log.layout.LayoutSimple; 95 import ocean.util.log.LayoutDate; 96 97 import core.sys.posix.strings; 98 99 100 /******************************************************************************* 101 102 Configuration class for loggers 103 104 *******************************************************************************/ 105 106 class Config 107 { 108 /*************************************************************************** 109 110 Level of the logger 111 112 ***************************************************************************/ 113 114 public cstring level; 115 116 /*************************************************************************** 117 118 Whether to use console output or not 119 120 ***************************************************************************/ 121 122 public SetInfo!(bool) console; 123 124 /*************************************************************************** 125 126 Whether to use syslog output or not 127 128 ***************************************************************************/ 129 130 public SetInfo!(bool) syslog; 131 132 /*************************************************************************** 133 134 Layout to use for console output 135 136 ***************************************************************************/ 137 138 public istring console_layout = "simple"; 139 140 /*************************************************************************** 141 142 Whether to use file output and if, which file path 143 144 ***************************************************************************/ 145 146 public SetInfo!(istring) file; 147 148 /*************************************************************************** 149 150 Layout to use for file output 151 152 ***************************************************************************/ 153 154 public istring file_layout = "date"; 155 156 /*************************************************************************** 157 158 Whether to propagate that level to the children 159 160 ***************************************************************************/ 161 162 public bool propagate; 163 164 /*************************************************************************** 165 166 Whether this logger should be additive or not 167 168 ***************************************************************************/ 169 170 bool additive; 171 172 /*************************************************************************** 173 174 Whether this logger should be part of the global logging stats 175 mechanism 176 177 ***************************************************************************/ 178 179 bool collect_stats = true; 180 181 /*************************************************************************** 182 183 Buffer size of the buffer output, overwrites the global setting 184 given in MetaConfig 185 186 ***************************************************************************/ 187 188 public size_t buffer_size = 0; 189 } 190 191 /******************************************************************************* 192 193 Configuration class for logging 194 195 *******************************************************************************/ 196 197 class MetaConfig 198 { 199 /*************************************************************************** 200 201 Tango buffer size, if 0, internal stack based buffer of 2048 will be 202 used. 203 204 ***************************************************************************/ 205 206 size_t buffer_size = 0; 207 } 208 209 /******************************************************************************* 210 211 Convenience alias for iterating over Config classes 212 213 *******************************************************************************/ 214 215 alias ocean.util.config.ConfigFiller.ConfigIterator!(Config, ConfigParser) ConfigIterator; 216 217 /******************************************************************************* 218 219 Convenience alias for layouts 220 221 *******************************************************************************/ 222 223 alias Appender.Layout Layout; 224 225 /******************************************************************************* 226 227 Gets a new layout instance, based on the given name. 228 229 Params: 230 layout_str = name of the desired layout 231 232 Returns: 233 an instance of a suitable layout based on the input string 234 235 Throws: 236 if `layout_str` cannot be matched to any layout 237 238 *******************************************************************************/ 239 240 public Layout newLayout ( cstring layout_str ) 241 { 242 mstring tweaked_str = layout_str.dup; 243 244 StringSearch!() s; 245 246 s.strToLower(tweaked_str); 247 248 tweaked_str = removePrefix(tweaked_str, "layout"); 249 250 tweaked_str = removeSuffix(tweaked_str, "layout"); 251 252 switch ( tweaked_str ) 253 { 254 case "messageonly": 255 return new LayoutMessageOnly; 256 257 case "stats": 258 case "statslog": 259 return new LayoutStatsLog; 260 261 case "simple": 262 return new LayoutSimple; 263 264 case "date": 265 return new LayoutDate; 266 267 default: 268 // Has to be 2 statements because `istring ~ cstring` 269 // yields `cstring` instead of `istring`. 270 istring msg = "Invalid layout requested : "; 271 msg ~= layout_str; 272 throw new Exception(msg, __FILE__, __LINE__); 273 } 274 } 275 276 /// 277 unittest 278 { 279 // In a real app those would be full-fledged implementation 280 alias LayoutSimple AquaticLayout; 281 alias LayoutSimple SubmarineLayout; 282 283 void myConfigureLoggers ( 284 ConfigIterator config, 285 MetaConfig m_config, 286 scope Appender delegate ( istring file, Layout layout ) file_appender, 287 bool use_insert_appender = false) 288 { 289 Layout makeLayout (cstring name) 290 { 291 if (name == "aquatic") 292 return new AquaticLayout; 293 if (name == "submarine") 294 return new SubmarineLayout; 295 return ocean.util.log.Config.newLayout(name); 296 } 297 ocean.util.log.Config.configureNewLoggers(config, m_config, 298 file_appender, &makeLayout, use_insert_appender); 299 } 300 } 301 302 /******************************************************************************* 303 304 Sets up logging configuration for `ocean.util.log.Logger` 305 306 Calls the provided `file_appender` delegate once per log being configured and 307 passes the returned appender to the log's add() method. 308 309 Params: 310 config = an instance of an class iterator for Config 311 m_config = an instance of the MetaConfig class 312 file_appender = delegate which returns appender instances to write to 313 a file 314 use_insert_appender = true if the InsertConsole appender should be used 315 (needed when using the AppStatus module) 316 317 *******************************************************************************/ 318 319 public void configureNewLoggers ( 320 ConfigIterator config, MetaConfig m_config, 321 scope Appender delegate ( istring file, Layout layout ) file_appender, 322 bool use_insert_appender = false) 323 { 324 configureNewLoggers(config, m_config, file_appender, 325 (cstring v) { return newLayout(v); }, use_insert_appender); 326 } 327 328 329 /******************************************************************************* 330 331 Sets up logging configuration for `ocean.util.log.Logger` 332 333 Calls the provided `file_appender` delegate once per log being configured 334 and passes the returned `Appender` to the `Logger.add` method. 335 336 This is an extra overload because using a delegate literal as a parameter's 337 default argument causes linker error in D1. 338 339 Params: 340 config = an instance of an class iterator for Config 341 m_config = an instance of the MetaConfig class 342 file_appender = delegate which returns appender instances to write to 343 a file 344 makeLayout = A delegate that returns a `Layout` instance from 345 a name, or throws on error. 346 By default, wraps `ocean.util.log.Config.newLayout` 347 use_insert_appender = true if the InsertConsole appender should be used 348 (needed when using the AppStatus module) 349 350 *******************************************************************************/ 351 352 public void configureNewLoggers ( 353 ConfigIterator config, MetaConfig m_config, 354 scope Appender delegate ( istring file, Layout layout ) file_appender, 355 scope Layout delegate (cstring) makeLayout, bool use_insert_appender = false) 356 { 357 // DMD1 cannot infer the common type between both return, we have to work 358 // around it... 359 static Appender console_appender_fn (bool insert_appender, Layout layout) 360 { 361 if (insert_appender) 362 return new InsertConsole(layout); 363 else 364 return new AppendStderrStdout(ILogger.Level.Warn, layout); 365 } 366 367 // The type needs to be spelt out loud because DMD2 is clever enough 368 // to see it's a function and not a delegate, but not clever enough 369 // to understand we want a delegate in the end... 370 scope Appender delegate(Layout) appender_dg = (Layout l) 371 { return console_appender_fn(use_insert_appender, l); }; 372 373 configureLoggers(config, m_config, file_appender, appender_dg, makeLayout); 374 } 375 376 377 /******************************************************************************* 378 379 Sets up logging configuration. Calls the provided file_appender delegate once 380 per log being configured and passes the returned appender to the log's add() 381 method. 382 383 Params: 384 config = an instance of an class iterator for Config 385 m_config = an instance of the MetaConfig class 386 file_appender = delegate which returns appender instances to write to 387 a file 388 console_appender = Delegate which returns an Appender suitable to use 389 as console appender. Might not be called if console 390 writing is disabled. 391 makeLayout = A delegate that returns a `Layout` instance from 392 a name, or throws on error. 393 394 *******************************************************************************/ 395 396 private void configureLoggers 397 (ConfigIterator config, MetaConfig m_config, 398 scope Appender delegate (istring file, Layout layout) file_appender, 399 scope Appender delegate (Layout) console_appender, 400 scope Layout delegate (cstring) makeLayout) 401 { 402 // It is important to ensure that parent loggers are configured before child 403 // loggers. This is because parent loggers will override the settings of 404 // child loggers when the 'propagate' property is enabled, thus preventing 405 // child loggers from customizing their properties via the config file(s). 406 // However, since the parsed configuration is stored in an AA, ordered 407 // iteration of the logging config is not directly possible. For this 408 // reason, the names of all loggers present in the configuration are first 409 // sorted, and the loggers are then configured based on the sorted list. The 410 // sorting is performed in increasing order of the lengths of the logger 411 // names so that parent loggers appear before child loggers. 412 413 istring[] logger_names; 414 istring root_name; 415 416 foreach (name; config) 417 { 418 if (name.length == "root".length 419 && strncasecmp(name.ptr, "root".ptr, "root".length) == 0) 420 root_name = name; 421 else 422 logger_names ~= name; 423 } 424 425 sort(logger_names); 426 // As 'Root' is the parent logger of all loggers, we need to special-case it 427 // and put it at the beginning of the list 428 if (root_name.length) 429 { 430 logger_names.insertShift(0); 431 logger_names[0] = root_name; 432 } 433 434 Config settings; 435 436 foreach (name; logger_names) 437 { 438 bool console_enabled = false; 439 bool syslog_enabled = false; 440 441 config.fill(name, settings); 442 443 if (root_name == name) 444 { 445 name = null; 446 console_enabled = settings.console(true); 447 syslog_enabled = settings.syslog(false); 448 } 449 else 450 { 451 console_enabled = settings.console(); 452 syslog_enabled = settings.syslog(); 453 } 454 configureLogger(name.length ? Log.lookup(name) : Log.root, 455 settings, name, 456 file_appender, console_appender, 457 console_enabled, syslog_enabled, m_config.buffer_size, 458 makeLayout); 459 } 460 } 461 462 463 /******************************************************************************* 464 465 Sets up logging configuration. Calls the provided file_appender delegate once 466 per log being configured and passes the returned appender to the log's add() 467 method. 468 469 Params: 470 log = Logger to configure 471 settings = an instance of an class iterator for Config 472 name = name of this logger 473 file_appender = delegate which returns appender instances to write to 474 a file 475 console_appender = Delegate which returns an Appender suitable to use 476 as console appender. Might not be called if console 477 writing is disabled. 478 console_enabled = `true` if a console appender should be added (by 479 calling `console_enabled`). 480 syslog_enabled = `true` if a syslog appender should be added. 481 makeLayout = A delegate that returns a `Layout` instance from 482 a name, or throws on error. 483 By default, wraps `ocean.util.log.Config.newLayout`. 484 485 *******************************************************************************/ 486 487 public void configureLogger 488 (Logger log, Config settings, istring name, 489 scope Appender delegate ( istring file, Layout layout ) file_appender, 490 scope Appender delegate (Layout) console_appender, 491 bool console_enabled, bool syslog_enabled, size_t buffer_size, 492 scope Layout delegate (cstring) makeLayout = (cstring v) { return newLayout(v); }) 493 { 494 if (settings.buffer_size) 495 buffer_size = settings.buffer_size; 496 497 if (buffer_size > 0) 498 log.buffer(new mstring(buffer_size)); 499 500 log.clear(); 501 502 // if console/file/syslog is specifically set, don't inherit other 503 // appenders (unless we have been specifically asked to be additive) 504 log.additive = settings.additive || 505 !(settings.console.set || settings.file.set || settings.syslog.set); 506 507 if (settings.file.set) 508 { 509 log.add(file_appender(settings.file(), makeLayout(settings.file_layout))); 510 } 511 512 if (syslog_enabled) 513 log.add(new AppendSysLog); 514 515 if (console_enabled) 516 { 517 log.add(console_appender(makeLayout(settings.console_layout))); 518 } 519 520 log.collectStats(settings.collect_stats, settings.propagate); 521 setupLoggerLevel(log, name, settings); 522 } 523 524 version (unittest) 525 { 526 import ocean.core.Array : copy; 527 import ocean.core.Test : test; 528 } 529 530 // When the 'propagate' property of a logger is set, its settings get propagated 531 // to all child loggers. However, every child logger should be able to define 532 // its own settings overriding any automatically propagated setting from the 533 // parent logger. Since loggers are stored in an AA, the order in which they are 534 // configured is undeterministic. This could potentially result in parent 535 // loggers being configured after child loggers and thus overriding any 536 // specifically defined setting in the child logger. To avoid this from 537 // happening, parent loggers are deliberately configured before child loggers. 538 // This unit test block confirms that this strict configuration order is 539 // enforced, and parent loggers never override the settings of child loggers. 540 unittest 541 { 542 class TempAppender : Appender 543 { 544 private mstring latest_log_msg; 545 private Mask mask_; 546 547 final override public void append (LogEvent event) 548 { 549 copy(this.latest_log_msg, event.toString()); 550 } 551 552 final override public Mask mask () { return this.mask_; } 553 final override public istring name () { return null; } 554 } 555 556 auto config_parser = new ConfigParser(); 557 558 auto config_str = 559 ` 560 [LOG.A] 561 level = trace 562 propagate = true 563 file = dummy 564 565 [LOG.A.B] 566 level = info 567 propagate = true 568 file = dummy 569 570 [LOG.A.B.C] 571 level = warn 572 propagate = true 573 file = dummy 574 575 [LOG.A.B.C.D] 576 level = error 577 propagate = true 578 file = dummy 579 580 [LOG.Root] 581 level = trace 582 propagate = true 583 file = dummy 584 `; 585 586 auto temp_appender = new TempAppender; 587 588 Appender appender(istring, Layout) 589 { 590 return temp_appender; 591 } 592 593 config_parser.parseString(config_str); 594 595 auto log_config = iterate!(Config)("LOG", config_parser); 596 auto dummy_meta_config = new MetaConfig(); 597 598 configureNewLoggers(log_config, dummy_meta_config, &appender); 599 600 auto log_D = Log.lookup("A.B.C.D"); 601 602 log_D.trace("trace log (shouldn't be sent to appender)"); 603 test!("==")(temp_appender.latest_log_msg, ""); 604 605 log_D.info("info log (shouldn't be sent to appender)"); 606 test!("==")(temp_appender.latest_log_msg, ""); 607 608 log_D.warn("warn log (shouldn't be sent to appender)"); 609 test!("==")(temp_appender.latest_log_msg, ""); 610 611 log_D.error("error log"); 612 test!("==")(temp_appender.latest_log_msg, "error log"); 613 614 log_D.fatal("fatal log"); 615 test!("==")(temp_appender.latest_log_msg, "fatal log"); 616 } 617 618 /******************************************************************************* 619 620 Sets up the level configuration of a logger. 621 622 Params: 623 log = logger to configure 624 name = name of logger 625 config = config settings for the logger 626 627 Throws: 628 Exception if the config for a logger specifies an invalid level 629 630 *******************************************************************************/ 631 632 public void setupLoggerLevel ( Logger log, istring name, Config config ) 633 { 634 with (config) if (level.length > 0) 635 { 636 StringSearch!() s; 637 638 level = s.strEnsureLower(level); 639 640 switch (level) 641 { 642 case "trace": 643 case "debug": 644 log.level(ILogger.Level.Trace, propagate); 645 break; 646 647 case "info": 648 log.level(ILogger.Level.Info, propagate); 649 break; 650 651 case "warn": 652 log.level(ILogger.Level.Warn, propagate); 653 break; 654 655 case "error": 656 log.level(ILogger.Level.Error, propagate); 657 break; 658 659 case "fatal": 660 log.level(ILogger.Level.Fatal, propagate); 661 break; 662 663 case "none": 664 case "off": 665 case "disabled": 666 log.level(ILogger.Level.None, propagate); 667 break; 668 669 default: 670 throw new Exception(cast(istring) ("Invalid log level '" 671 ~ level ~ "' " ~ 672 "requested for logger '" 673 ~ name ~ "'")); 674 } 675 } 676 }