Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Log #432

Closed
wants to merge 8 commits into from

2 participants

José Armando García Sancio Jonathan M Davis
José Armando García Sancio

Just here to compute the patch.

Jose Armando... added some commits January 30, 2012
Implementation for std.log. e5fc8d1
Some minor API changes
1. Dropped log!info("message"), etc. Use info("message), etc.
2. opCall now alias to format. Ie. info("Format %s message", Severity.info).
   this means that to concatenate strings you need to info.write("Hello ", "world");
270bf86
minor document fixes e24eb9c
Minor comment improvements b4d9a77
More comment improvements ecb3f4a
Fix the documentation. Add LREF and XREF. e8615d7
Rename format to writef 088939e
Fix some build issues 3b35710
Jonathan M Davis jmdavis closed this April 20, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 8 unique commits by 1 author.

Feb 21, 2012
Implementation for std.log. e5fc8d1
Some minor API changes
1. Dropped log!info("message"), etc. Use info("message), etc.
2. opCall now alias to format. Ie. info("Format %s message", Severity.info).
   this means that to concatenate strings you need to info.write("Hello ", "world");
270bf86
minor document fixes e24eb9c
Minor comment improvements b4d9a77
More comment improvements ecb3f4a
Fix the documentation. Add LREF and XREF. e8615d7
Rename format to writef 088939e
Fix some build issues 3b35710
This page is out of date. Refresh to see the latest.
1  posix.mak
@@ -159,6 +159,7 @@ STD_MODULES = $(addprefix std/, algorithm array ascii base64 bigint		\
159 159
         cpuid cstream ctype csv date datetime datebase dateparse demangle	\
160 160
         encoding exception file format functional getopt gregorian		\
161 161
         json loader math mathspecial md5 metastrings mmfile numeric		\
  162
+        log \
162 163
         outbuffer parallelism path perf process random range regex		\
163 164
         regexp signals socket socketstream stdint stdio stdiobase		\
164 165
         stream string syserror system traits typecons typetuple uni		\
2,848  std/log.d
... ...
@@ -0,0 +1,2848 @@
  1
+// Written in the D programming language.
  2
+// XXX TODO make sure that the examples are correct.
  3
+
  4
+/++
  5
+Implements an application level logging mechanism.
  6
+
  7
+The std.log module defines a set of functions useful for many common logging
  8
+tasks. Five logging severity levels are defined. In the order of severity they
  9
+are $(LREF info), $(LREF warning), $(LREF error), $(LREF critical) and
  10
+$(LREF fatal). Verbose messages are logged using the $(LREF vlog) template.
  11
+
  12
+By default std.log will configure itself using the command line arguments
  13
+passed to the process and using the process's environment variables. For a list
  14
+of the default command line options, environment variables and their meaning,
  15
+see $(LREF Configuration) and $(LREF FileLogger.Configuration).
  16
+
  17
+Example:
  18
+---
  19
+import std.log;
  20
+
  21
+void main(string[] args)
  22
+{
  23
+    info("You passed %s argument(s)", args.length - 1);
  24
+    info.when(args.length > 1).write("Arguments: ", args[1 .. $]);
  25
+
  26
+    warning("This is a warning message.");
  27
+    error("This is an error message!");
  28
+    dfatal("This is a debug fatal message");
  29
+
  30
+    vlog(1)("Verbosity 1 message");
  31
+    vlog(2)("Verbosity 2 message");
  32
+
  33
+    foreach (i; 0 .. 10)
  34
+    {
  35
+        info.when(every(9))("Every nine");
  36
+
  37
+        if(info.willLog)
  38
+        {
  39
+            auto message = "Cool message";
  40
+            // perform some complex operation
  41
+            // ...
  42
+            info(message);
  43
+        }
  44
+    }
  45
+
  46
+    try critical("Critical message");
  47
+    catch(CriticalException e)
  48
+    {
  49
+        // shutdown application...
  50
+    }
  51
+
  52
+    fatal("This is a fatal message!!!");
  53
+    assert(false, "Never reached");
  54
+}
  55
+---
  56
+
  57
+Copyright: Jose Armando Garcia Sancio 2011-.
  58
+
  59
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
  60
+
  61
+Authors: Jose Armando Garcia Sancio
  62
+
  63
+Source: $(PHOBOSSRC std/_log.d)
  64
++/
  65
+module std.log;
  66
+
  67
+import core.atomic: atomicOp;
  68
+import core.sync.mutex: Mutex;
  69
+import core.sync.rwmutex: ReadWriteMutex;
  70
+import core.runtime: Runtime;
  71
+import core.time: Duration, dur;
  72
+import std.stdio: File, stderr;
  73
+import std.string: split, toUpper, toStringz;
  74
+import std.ascii: newline;
  75
+import std.conv: to;
  76
+import std.datetime: SysTime, Clock, UTC, FracSec, DateTime;
  77
+import std.exception: enforce;
  78
+import std.getopt: getopt;
  79
+import std.process: getenv;
  80
+import std.array: Appender, array, appender;
  81
+import std.format: formattedWrite;
  82
+import std.path: globMatch, buildPath, baseName;
  83
+import std.algorithm: endsWith;
  84
+import std.file: remove;
  85
+import std.functional: binaryFun;
  86
+
  87
+version(Windows) import core.sys.windows.windows;
  88
+else version(Posix)
  89
+{
  90
+    import core.sys.posix.unistd;
  91
+    import core.sys.posix.sys.utsname;
  92
+}
  93
+
  94
+version(unittest)
  95
+{
  96
+    import core.exception;
  97
+    import core.thread: Thread;
  98
+    import std.exception: assertThrown;
  99
+    import std.path: sep;
  100
+    import std.algorithm: canFind, startsWith;
  101
+}
  102
+
  103
+/++
  104
+Maps to the $(LREF LogFilter) for the specified severity.
  105
+
  106
+Example:
  107
+---
  108
+info("Info severity message");
  109
+warning("Warning severity message");
  110
+error("Error severity message");
  111
+critical("Critical severity message");
  112
+dfatal("Fatal message in debug mode and critical message in release mode");
  113
+fatal("Fatal severity message");
  114
+---
  115
+
  116
+$(BOOKTABLE Description of supported severities.,
  117
+    $(TR $(TH Severity) $(TH Description))
  118
+    $(TR $(TD $(D fatal))
  119
+         $(TD Logs a fatal severity message. Fatal _log messages terminate the
  120
+              application after the message is persisted. Fatal _log message
  121
+              cannot be disable at compile time or at run time.))
  122
+    $(TR $(TD $(D dfatal))
  123
+         $(TD Logs a debug fatal message. Debug fatal _log messages _log at
  124
+              fatal severity in debug mode and _log at critical severity in
  125
+              release mode. See fatal and critical severity levels for a
  126
+              description of their behavior.))
  127
+    $(TR $(TD $(D critical))
  128
+         $(TD Logs a critical severity message. Critical _log messages throw a
  129
+              $(LREF CriticalException) exception after the message is
  130
+              persisted. Critical _log messages cannot be disable at compile
  131
+              time or at run time.))
  132
+    $(TR $(TD $(D error))
  133
+         $(TD Logs an error severity message. Error _log messages are disable
  134
+              at compiled time by setting the version to $(I strip_log_error).
  135
+              Error _log messages are disable at run time by setting the
  136
+              minimun severity to $(LREF Severity.fatal) or
  137
+              $(LREF Severity.critical) in $(LREF Configuration). Disabling
  138
+              _error _log messages at compile time or at run time also disables
  139
+              lower severity messages, e.g. warning and info.))
  140
+    $(TR $(TD $(D warning))
  141
+         $(TD Logs a warning severity message. Warning _log messages are
  142
+              disable at compiled time by setting the version to
  143
+              $(I strip_log_warning). Warning _log messages are disable at run
  144
+              time by setting the minimum severity to $(LREF Severity.error) in
  145
+              $(LREF Configuration). Disabling _warning _log messages at compile
  146
+              time or at run time also disables lower severity messages, e.g.
  147
+              info.))
  148
+    $(TR $(TD $(D info))
  149
+         $(TD Logs a info severity message. Info _log messages are disable at
  150
+              compiled time by setting the version to $(I strip_log_info). Info
  151
+              _log messages are disable at run time by setting the minimum
  152
+              severity to $(LREF Severity.warning) in $(LREF Configuration).
  153
+              Disabling _info _log messages at compile time or at run time also
  154
+              disables verbose _log messages.)))
  155
++/
  156
+template log(Severity severity)
  157
+{
  158
+    version(strip_log_error) private alias Severity.critical minSeverity;
  159
+    else version(strip_log_warning) private alias Severity.error minSeverity;
  160
+    else version(strip_log_info) private alias Severity.warning minSeverity;
  161
+    else private alias Severity.info minSeverity;
  162
+
  163
+    static if(severity > minSeverity) alias NoopLogFilter._singleton log;
  164
+    else
  165
+    {
  166
+        static if(severity == Severity.info) alias _info log;
  167
+        else static if(severity == Severity.warning) alias _warning log;
  168
+        else static if(severity == Severity.error) alias _error log;
  169
+        else static if(severity == Severity.critical) alias _critical log;
  170
+        else static if(severity == Severity.fatal) alias _fatal log;
  171
+    }
  172
+}
  173
+alias log!(Severity.fatal) fatal; /// ditto
  174
+debug alias log!(Severity.fatal) dfatal; /// ditto
  175
+else alias log!(Severity.critical) dfatal; /// ditto
  176
+alias log!(Severity.critical) critical; /// ditto
  177
+alias log!(Severity.error) error; /// ditto
  178
+alias log!(Severity.warning) warning; /// ditto
  179
+alias log!(Severity.info) info; /// ditto
  180
+
  181
+/++
  182
+Verbose log messages are log at the info severity _level. To disable them at
  183
+compile time set the version to $(I strip_log_info) which also disables all
  184
+messages of info severity at compile time. To enable verbose log messages at
  185
+run time use the $(LREF Configuration.maxVerboseLevel) property and the
  186
+$(LREF Configuration.verboseFilter) property.
  187
+
  188
+Example:
  189
+---
  190
+vlog(1)("A verbose 1 message");
  191
+---
  192
++/
  193
+auto vlog(string file = __FILE__)(int level)
  194
+{
  195
+    static if(Severity.info > logImpl!(Severity.info).minSeverity)
  196
+    {
  197
+        return NoopLogFilter._singleton;
  198
+    }
  199
+    else return _info.vlog(level, file);
  200
+}
  201
+
  202
+unittest
  203
+{
  204
+    auto logger = new shared(TestLogger);
  205
+    auto testConfig = new Configuration(logger);
  206
+    testConfig.minSeverity = Severity.warning;
  207
+
  208
+    auto logInfo = new LogFilter(Severity.info, testConfig, 0);
  209
+    auto logWarning = new LogFilter(Severity.warning, testConfig, 0);
  210
+    auto logError = new LogFilter(Severity.error, testConfig, 0);
  211
+    auto logCritical = new LogFilter(Severity.critical, testConfig, 0);
  212
+    auto logFatal = new LogFilter(Severity.fatal, testConfig, 0);
  213
+
  214
+    auto loggedMessage = "logged message";
  215
+
  216
+    // Test willLog
  217
+    assert(!logInfo.willLog);
  218
+    assert(logWarning.willLog);
  219
+    assert(logError.willLog);
  220
+    assert(logCritical.willLog);
  221
+    assert(logFatal.willLog);
  222
+
  223
+    // Test logging and severity filtering
  224
+    logInfo.write(loggedMessage);
  225
+    assert(!logger.called);
  226
+
  227
+    logger.clear();
  228
+    logWarning.write(loggedMessage);
  229
+    assert(logger.called);
  230
+    assert(logger.severity == Severity.warning);
  231
+    assert(logger.message == loggedMessage);
  232
+
  233
+    logger.clear();
  234
+    logError.write(loggedMessage);
  235
+    assert(logger.called);
  236
+    assert(logger.severity == Severity.error);
  237
+    assert(logger.message == loggedMessage);
  238
+
  239
+    logger.clear();
  240
+    logError.writef("%s", loggedMessage);
  241
+    assert(logger.called);
  242
+    assert(logger.severity == Severity.error);
  243
+    assert(logger.message == loggedMessage);
  244
+
  245
+    logger.clear();
  246
+    assertThrown!CriticalException(logCritical.write(loggedMessage));
  247
+    assert(logger.called);
  248
+    assert(logger.severity == Severity.critical);
  249
+    assert(logger.message == loggedMessage);
  250
+    assert(logger.flushCalled);
  251
+
  252
+    logger.clear();
  253
+    assertThrown!AssertError(logFatal.write(loggedMessage));
  254
+    assert(logger.called);
  255
+    assert(logger.severity == Severity.fatal);
  256
+    assert(logger.message == loggedMessage);
  257
+    assert(logger.flushCalled);
  258
+
  259
+    logger.clear();
  260
+    logWarning.writef("%s", loggedMessage);
  261
+    assert(logger.called);
  262
+    assert(logger.severity == Severity.warning);
  263
+    assert(logger.message == loggedMessage);
  264
+
  265
+    // logInfo didn't log so when(true) shouldn't log either
  266
+    assert(!logInfo.when(true).willLog);
  267
+
  268
+    // LogWarning would log so when(true) should log also
  269
+    assert(logWarning.when(true).willLog);
  270
+
  271
+    // when(false) shouldn't log
  272
+    assert(!logError.when(false).willLog);
  273
+}
  274
+
  275
+/++
  276
+Conditionally records a log message by checking the severity level and any
  277
+user defined condition. Instances of LogFilter are alised by the $(LREF log) and
  278
+$(LREF vlog) template and the $(LREF fatal), $(LREF dfatal), $(LREF critical),
  279
+$(LREF error), $(LREF warning) and $(LREF info) aliases.
  280
+
  281
+Examples:
  282
+---
  283
+error("Log an %s message!", Severity.error);
  284
+error.write("Log an ", Severity.error, " message!");
  285
+error.writef("Also logs an %s message!", Severity.error);
  286
+---
  287
+Logs a message if the specified severity level is enable.
  288
+
  289
+---
  290
+void coolFunction(Object object)
  291
+{
  292
+    fatal.when(object is null)("I don't like null objects!");
  293
+    // ...
  294
+}
  295
+
  296
+foreach(i; 0 .. 10)
  297
+{
  298
+    info.when(first())("Only log this one time per thread run");
  299
+}
  300
+---
  301
+Logs a message if the specified severity level is enable and all the user
  302
+defined condition are true.
  303
+
  304
+---
  305
+void removeDirectory(string dir = "/tmp/log")
  306
+{
  307
+    info.when(rich!"!="(dir, "/tmp/log"))("Trying to remove dir");
  308
+    // ...
  309
+}
  310
+---
  311
+Logs a rich message if the specified severity level is enable and all the user
  312
+defined condition are true.
  313
++/
  314
+final class LogFilter
  315
+{
  316
+    this(Severity severity,
  317
+         Configuration configuration,
  318
+         ulong threadId,
  319
+         bool privateBuffer = false)
  320
+    {
  321
+        enforce(configuration);
  322
+
  323
+        _config = configuration;
  324
+        _privateBuffer = privateBuffer;
  325
+
  326
+        _message.severity = severity;
  327
+        _message.threadId = threadId;
  328
+    }
  329
+
  330
+    this() {}
  331
+
  332
+    /++
  333
+       Returns true if a message to this logger will be recorded.
  334
+
  335
+       Example:
  336
+---
  337
+if(error.willLog)
  338
+{
  339
+    string message;
  340
+    // Perform some computation
  341
+    // ...
  342
+    error(message);
  343
+}
  344
+---
  345
+     +/
  346
+    @property bool willLog()
  347
+    {
  348
+        return _config !is null && _message.severity <= _config.minSeverity;
  349
+    }
  350
+
  351
+    /++
  352
+       Returns this object if the parameter now evaluates to true and
  353
+       $(LREF willLog) returns true. Otherwise, it returns an object that will
  354
+       not log messages. Note: The now parameter is only evaluated if
  355
+       $(LREF willLog) returns true.
  356
+
  357
+       Example:
  358
+---
  359
+foreach(i; 0 .. 10)
  360
+{
  361
+   warning.when(i == 9)("Executed loop when i = 9");
  362
+   // ...
  363
+}
  364
+---
  365
+     +/
  366
+    LogFilter when(lazy bool now)
  367
+    {
  368
+        if(willLog && now) return this;
  369
+
  370
+        return _noopLogFilter;
  371
+    }
  372
+
  373
+    unittest
  374
+    {
  375
+        auto logger = new shared(TestLogger);
  376
+        auto testConfig = new Configuration(logger);
  377
+
  378
+        auto logError = new LogFilter(Severity.error, testConfig, 0);
  379
+
  380
+        auto loggedMessage = "logged message";
  381
+
  382
+        assert(logError.when(rich!"<="(1, 2)).when(rich!"=="(0, 0)).willLog);
  383
+    }
  384
+
  385
+    /++
  386
+       Returns this object and appends the log message with a reason if the
  387
+       parameter now evaluates to true and $(LREF willLog) returns true.
  388
+       Otherwise, it returns an object that will not log messages. Note: The now
  389
+       parameter is only evaluated if $(LREF willLog) return true.
  390
+
  391
+       Example:
  392
+---
  393
+foreach(i; 0 .. 10)
  394
+{
  395
+   warning.when(rich!"=="(i, 9))("Executed loop when i = 9");
  396
+   // ...
  397
+}
  398
+---
  399
+     +/
  400
+    LogFilter when(lazy Rich!bool now)
  401
+    {
  402
+        if(willLog && now.value)
  403
+        {
  404
+            auto filter = this;
  405
+            if(!_privateBuffer)
  406
+            {
  407
+                filter = new LogFilter(_message.severity,
  408
+                                       _config,
  409
+                                       _message.threadId,
  410
+                                       true);
  411
+            }
  412
+            else filter.writer.put("&& ");
  413
+
  414
+            filter.writer.put("when(");
  415
+            filter.writer.put(now.reason);
  416
+            filter.writer.put(") ");
  417
+
  418
+            return filter;
  419
+        }
  420
+
  421
+        return _noopLogFilter;
  422
+    }
  423
+
  424
+    /++
  425
+       Concatenates all the arguments and logs them. Note: The parameters are
  426
+       only evaluated if $(LREF willLog) returns true.
  427
+
  428
+       Example:
  429
+---
  430
+auto pi = 3.14159265;
  431
+
  432
+info.write("The value of pi is ", pi);
  433
+---
  434
+     +/
  435
+    void write(string file = __FILE__, int line = __LINE__, T...)(lazy T args)
  436
+    {
  437
+        writef!(file, line)("%1:$s", args);
  438
+    }
  439
+
  440
+    /++
  441
+       Formats the parameters args given the _format string fmt and
  442
+       logs them. Note: The parameters are only evaluated if $(LREF willLog)
  443
+       evaluates to true. For a description of the _format string see
  444
+       $(XREF _format, formattedWrite).
  445
+
  446
+       Example:
  447
+---
  448
+auto goldenRatio = 1.61803399;
  449
+
  450
+vlog(1).writef("The number %s is the golden ratio", goldenRatio);
  451
+
  452
+// The same as above...
  453
+vlog(1)("The number %s is the golden ration", goldenRatio);
  454
+---
  455
+     +/
  456
+    void writef(string file = __FILE__, int line = __LINE__, T...)
  457
+               (lazy string fmt, lazy T args)
  458
+    {
  459
+        if(willLog)
  460
+        {
  461
+            scope(exit) handleSeverity();
  462
+
  463
+            _message.file = file;
  464
+            _message.line = line;
  465
+
  466
+            // record message
  467
+            scope(exit) writer.clear();
  468
+            writer.reserve(fmt.length);
  469
+            formattedWrite(writer, fmt, args);
  470
+            _message.message = writer.data;
  471
+
  472
+            // record the time stamp
  473
+            _message.time = Clock.currTime(UTC());
  474
+
  475
+            _config.logger.log(_message);
  476
+        }
  477
+    }
  478
+    alias writef opCall; /// ditto
  479
+
  480
+    private void handleSeverity()
  481
+    {
  482
+        if(_message.severity == Severity.fatal)
  483
+        {
  484
+            /*
  485
+               The other of the scope(exit) is important. We want _fatalHandler
  486
+               to run before the assert.
  487
+             */
  488
+            scope(exit) assert(false);
  489
+            scope(exit) _config.fatalHandler();
  490
+            _config.logger.flush();
  491
+        }
  492
+        else if(_message.severity == Severity.critical)
  493
+        {
  494
+            _config.logger.flush();
  495
+            throw new CriticalException(_message.message.idup);
  496
+        }
  497
+    }
  498
+
  499
+    unittest
  500
+    {
  501
+        auto loggedMessage = "Verbose log message";
  502
+
  503
+        auto logger = new shared(TestLogger);
  504
+        auto testConfig = new Configuration(logger);
  505
+        testConfig.minSeverity = Severity.warning;
  506
+        testConfig.maxVerboseLevel = 3;
  507
+        testConfig.verboseFilter = "*log.d=2";
  508
+
  509
+        auto logInfo = new LogFilter(Severity.info, testConfig, 0);
  510
+        auto logWarning = new LogFilter(Severity.warning, testConfig, 0);
  511
+
  512
+        // Test vlogging and module filtering
  513
+        logger.clear();
  514
+        auto verboseLog = logWarning.vlog(2);
  515
+        assert(verboseLog.willLog);
  516
+        verboseLog.write(loggedMessage);
  517
+        assert(logger.called);
  518
+        assert(logger.severity == Severity.warning);
  519
+        assert(logger.message == loggedMessage);
  520
+
  521
+        // test format
  522
+        logger.clear();
  523
+        verboseLog.writef("%s", loggedMessage);
  524
+        assert(logger.called);
  525
+        assert(logger.severity == Severity.warning);
  526
+        assert(logger.message == loggedMessage);
  527
+
  528
+        // test large verbose level
  529
+        logger.clear();
  530
+        verboseLog = logWarning.vlog(3);
  531
+        verboseLog.write(loggedMessage);
  532
+        assert(!logger.called);
  533
+
  534
+        // test wrong module
  535
+        logger.clear();
  536
+        verboseLog = logWarning.vlog(4, "not_this");
  537
+        verboseLog.writef("%s", loggedMessage);
  538
+        assert(!logger.called);
  539
+
  540
+        // test verbose level
  541
+        logger.clear();
  542
+        verboseLog = logWarning.vlog(3, "not_this");
  543
+        verboseLog.writef("%s", loggedMessage);
  544
+        assert(logger.called);
  545
+        assert(logger.severity == Severity.warning);
  546
+        assert(logger.message == loggedMessage);
  547
+
  548
+        // test severity config too high
  549
+        logger.clear();
  550
+        auto infoVerboseLog = logInfo.vlog(2);
  551
+        assert(!infoVerboseLog.willLog);
  552
+        infoVerboseLog.writef("%s", loggedMessage);
  553
+        assert(!logger.called);
  554
+    }
  555
+
  556
+    LogFilter vlog(int level, string file = __FILE__)
  557
+    {
  558
+        if(willLog && _config.matchesVerboseFilter(file, level))
  559
+        {
  560
+            return this;
  561
+        }
  562
+
  563
+        return _noopLogFilter;
  564
+    }
  565
+
  566
+    private @property ref Appender!(char[]) writer()
  567
+    {
  568
+        if(_privateBuffer) return _privateWriter;
  569
+        else return _threadWriter;
  570
+    }
  571
+
  572
+    private Logger.LogMessage _message;
  573
+    private Configuration _config;
  574
+    private bool _privateBuffer;
  575
+    private Appender!(char[]) _privateWriter;
  576
+
  577
+    private static Appender!(char[]) _threadWriter;
  578
+
  579
+    __gshared private static LogFilter _noopLogFilter;
  580
+
  581
+    shared static this() { _noopLogFilter = new LogFilter; }
  582
+}
  583
+
  584
+/++
  585
+Exception thrown when logging a critical message. See $(LREF critical).
  586
++/
  587
+final class CriticalException : Exception
  588
+{
  589
+    private this(string message, string file = __FILE__, int line = __LINE__)
  590
+    {
  591
+        super(message, null, file, line);
  592
+    }
  593
+}
  594
+
  595
+unittest
  596
+{
  597
+    // test that both LogFilter and NoopLogFilter same public methods
  598
+    void publicInterface(T)()
  599
+    {
  600
+        T filter;
  601
+        if(filter.willLog) {}
  602
+
  603
+        filter.write("hello ", 1, " world");
  604
+        filter("format string", true, 4, 5.0, "hello world");
  605
+        filter.writef("format string", true, 4, 5.0);
  606
+        filter.when(true).write("message");
  607
+        filter.when(rich!"=="(0, 0)).write("better message");
  608
+        filter.vlog(0, "file");
  609
+        filter.vlog(0);
  610
+    }
  611
+
  612
+    static assert(__traits(compiles, publicInterface!LogFilter));
  613
+    static assert(__traits(compiles, publicInterface!NoopLogFilter));
  614
+}
  615
+
  616
+// Used by the module to disable logging at compile time.
  617
+final class NoopLogFilter
  618
+{
  619
+    pure nothrow const @property bool willLog() { return false; }
  620
+
  621
+    nothrow const ref const(NoopLogFilter) when(lazy bool now)
  622
+    { return this; }
  623
+    nothrow const ref const(NoopLogFilter) when(lazy Rich!bool now)
  624
+    { return this; }
  625
+
  626
+    nothrow const void write(T...)(lazy T args) {}
  627
+    nothrow const void writef(T...)(lazy string fmt, lazy T args) {}
  628
+    alias writef opCall;
  629
+
  630
+    pure nothrow const ref const(NoopLogFilter) vlog(int level,
  631
+                                                     string file = null)
  632
+    { return this; }
  633
+
  634
+    private this() {}
  635
+
  636
+    private static immutable NoopLogFilter _singleton;
  637
+
  638
+    shared static this() { _singleton = new immutable(NoopLogFilter); }
  639
+}
  640
+
  641
+/++
  642
+Defines the severity levels supported by the logging library. Should be used
  643
+in conjuntion with the log template. See $(LREF log) for an explanation of
  644
+their semantic.
  645
++/
  646
+
  647
+enum Severity
  648
+{
  649
+    fatal = 0, ///
  650
+    critical, /// ditto
  651
+    error, /// ditto
  652
+    warning, /// ditto
  653
+    info /// ditto
  654
+}
  655
+
  656
+unittest
  657
+{
  658
+    // assert default values
  659
+    auto testConfig = new Configuration(new shared(TestLogger));
  660
+    assert(testConfig.minSeverity == Severity.error);
  661
+
  662
+    auto name = "program_name";
  663
+    auto args = [name,
  664
+                 "--" ~ testConfig.minSeverityFlag,
  665
+                 "info",
  666
+                 "--" ~ testConfig.verboseFilterFlag,
  667
+                 "*logging=2,module=0",
  668
+                 "--" ~ testConfig.maxVerboseLevelFlag,
  669
+                 "3",
  670
+                 "--ignoredOption"];
  671
+
  672
+    testConfig.parseCommandLine(args);
  673
+
  674
+    // assert that all expected options where removed
  675
+    assert(args.length == 2);
  676
+    assert(args[0] == name);
  677
+
  678
+    assert(testConfig.minSeverity == Severity.info);
  679
+
  680
+    // assert max verbose level
  681
+    assert(testConfig.matchesVerboseFilter("file", 3));
  682
+
  683
+    // assert vmodule entries
  684
+    assert(testConfig.matchesVerboseFilter("std" ~ sep ~ "logging.d", 2));
  685
+    assert(testConfig.matchesVerboseFilter("module.d", 0));
  686
+
  687
+    // === test changing the command line flags ===
  688
+    // remember the defaults
  689
+    auto defaultSeverityFlag = testConfig.minSeverityFlag;
  690
+    auto defaultFilterFlag = testConfig.verboseFilterFlag;
  691
+    auto defaultLevelFlag = testConfig.maxVerboseLevelFlag;
  692
+
  693
+    // change the default
  694
+    testConfig.minSeverityFlag = "severity";
  695
+    testConfig.verboseFilterFlag = "filter";
  696
+    testConfig.maxVerboseLevelFlag = "level";
  697
+
  698
+    args = [name,
  699
+            "--" ~ testConfig.minSeverityFlag,
  700
+            "warning",
  701
+            "--" ~ testConfig.verboseFilterFlag,
  702
+            "*log=2,unittest.d=0",
  703
+            "--" ~ testConfig.maxVerboseLevelFlag,
  704
+            "4",
  705
+            "--" ~ defaultSeverityFlag,
  706
+            "--" ~ defaultFilterFlag,
  707
+            "--" ~ defaultLevelFlag];
  708
+
  709
+    testConfig.parseCommandLine(args);
  710
+
  711
+    // assert that all expected options where removed
  712
+    assert(args.length == 4);
  713
+    assert(args[0] == name);
  714
+
  715
+    assert(testConfig.minSeverity == Severity.warning);
  716
+
  717
+    // assert max verbose level
  718
+    assert(testConfig.matchesVerboseFilter("file", 4));
  719
+
  720
+    // assert vmodule entries
  721
+    assert(testConfig.matchesVerboseFilter("std" ~ sep ~ "log.d", 2));
  722
+    assert(testConfig.matchesVerboseFilter("unittest.d", 0));
  723
+
  724
+    // reset the defaults
  725
+    testConfig.minSeverityFlag = defaultSeverityFlag;
  726
+    testConfig.verboseFilterFlag = defaultFilterFlag;
  727
+    testConfig.maxVerboseLevelFlag = defaultLevelFlag;
  728
+
  729
+    // === test that an error in parseCommandLine doesn't invalidate object
  730
+    args = [name,
  731
+            "--" ~ testConfig.minSeverityFlag,
  732
+            "info",
  733
+            "--" ~ testConfig.verboseFilterFlag,
  734
+            "*logging=2,module=abc",
  735
+            "--" ~ testConfig.maxVerboseLevelFlag,
  736
+            "3",
  737
+            "--ignoredOption"];
  738
+
  739
+    // set known values
  740
+    testConfig.minSeverity = Severity.error;
  741
+    testConfig.verboseFilter = "log=2";
  742
+    testConfig.maxVerboseLevel = 1;
  743
+
  744
+    assertThrown(testConfig.parseCommandLine(args));
  745
+
  746
+    // test that nothing changed
  747
+    assert(testConfig.minSeverity == Severity.error);
  748
+    assert(testConfig.verboseFilter == "log=2");
  749
+    assert(testConfig.maxVerboseLevel = 1);
  750
+}
  751
+
  752
+/++
  753
+Module configuration.
  754
+
  755
+This object is used to configure the logging module if the default behavior is
  756
+not wanted.
  757
++/
  758
+final class Configuration
  759
+{
  760
+    /++
  761
+       Modifies the configuration object based on the passed parameter.
  762
+
  763
+       The function processes every entry in commandLine looking for valid
  764
+       command line options. All of the valid options are enumerated in the
  765
+       fields of this structure that end in 'Flag', e.g. minSeverityFlag.
  766
+       When a valid command line option is found its value is stored in the
  767
+       mapping object's property and it is removed from commandLine. For any
  768
+       property not set explicitly the value used before this call is used. Here
  769
+       is a list of all the flags and how they map to the object's property:
  770
+
  771
+       $(UL
  772
+          $(LI $(D minSeverityFlag) maps to $(D minSeverity))
  773
+          $(LI $(D verboseFilterFlag) maps to $(D verboseFilter))
  774
+          $(LI $(D maxVerboseLevelFlag) maps to $(D maxVerboseLevel)))
  775
+
  776
+       Example:
  777
+---
  778
+import std.log;
  779
+
  780
+void main(string[] args)
  781
+{
  782
+    // Overwrite the defaults...
  783
+    config.minSeverity = Severity.info;
  784
+    // ...
  785
+
  786
+    // Parse the command line
  787
+    config.parseCommandLine(args);
  788
+}
  789
+---
  790
+       This example overrites the default for the minimum severity property and
  791
+       later configures logging to any configuration option passed through
  792
+       the command line.
  793
+
  794
+       Note:
  795
+       A call to this function is not required if the module will be initialized
  796
+       using the default options.
  797
+     +/
  798
+    void parseCommandLine(ref string[] commandLine)
  799
+    {
  800
+        auto severity = minSeverity;
  801
+        auto level = maxVerboseLevel;
  802
+        auto filter = verboseFilter;
  803
+
  804
+        getopt(commandLine,
  805
+               std.getopt.config.passThrough,
  806
+               _minSeverityFlag, &severity,
  807
+               _verboseFilterFlag, &filter,
  808
+               _maxVerboseLevelFlag, &level);
  809
+
  810
+        // try verbose filter first
  811
+        verboseFilter = filter;
  812
+        minSeverity = severity;
  813
+        maxVerboseLevel = level;
  814
+    }
  815
+
  816
+    /++
  817
+       Command line flag for setting the minimum severity level. The default
  818
+       value is $(D "minloglevel") which at the command line is
  819
+       $(I --minloglevel).
  820
+     +/
  821
+    @property string minSeverityFlag(string minSeverityFlag)
  822
+    {
  823
+        enforce(_rwmutex.writer().tryLock());
  824
+        scope(exit) _rwmutex.writer().unlock();
  825
+        return _minSeverityFlag = minSeverityFlag;
  826
+    }
  827
+    /// ditto
  828
+    @property string minSeverityFlag()
  829
+    {
  830
+        synchronized(_rwmutex.reader()) return _minSeverityFlag;
  831
+    }
  832
+
  833
+    /++
  834
+       Command line flag for setting the per module verbose filter
  835
+       configuration. The default value is $(D "vmodule") which at the command
  836
+       line is $(I --vmodule).
  837
+     +/
  838
+    @property string verboseFilterFlag(string verboseFilterFlag)
  839
+    {
  840
+        enforce(_rwmutex.writer().tryLock());
  841
+        scope(exit) _rwmutex.writer().unlock();
  842
+        return _verboseFilterFlag = verboseFilterFlag;
  843
+    }
  844
+    /// ditto
  845
+    @property string verboseFilterFlag()
  846
+    {
  847
+        synchronized(_rwmutex.reader()) return _verboseFilterFlag;
  848
+    }
  849
+
  850
+    /++
  851
+       Command line flag for setting the maximum verbose level. The default
  852
+       value is $(D "v") which at the command line is $(I --v).
  853
+     +/
  854
+    @property string maxVerboseLevelFlag(string maxVerboseLevelFlag)
  855
+    {
  856
+        enforce(_rwmutex.writer().tryLock());
  857
+        scope(exit) _rwmutex.writer().unlock();
  858
+        return _maxVerboseLevelFlag = maxVerboseLevelFlag;
  859
+    }
  860
+    /// ditto
  861
+    @property string maxVerboseLevelFlag()
  862
+    {
  863
+        synchronized(_rwmutex.reader()) return _maxVerboseLevelFlag;
  864
+    }
  865
+
  866
+    unittest
  867
+    {
  868
+        auto testConfig = new Configuration(new shared(TestLogger));
  869
+
  870
+        assert((testConfig.minSeverity = Severity.fatal) == Severity.critical);
  871
+        assert((testConfig.minSeverity = Severity.critical) ==
  872
+               Severity.critical);
  873
+        assert((testConfig.minSeverity = Severity.error) == Severity.error);
  874
+    }
  875
+
  876
+    /++
  877
+       Specifies the minimum _severity for logging messages.
  878
+
  879
+       Only messages with a _severity greater than or equal to the value of this
  880
+       property are logged.
  881
+
  882
+       Example:
  883
+---
  884
+config.minSeverity = Severity.warning
  885
+---
  886
+       This example will enable logging for messages with severity
  887
+       $(D Severity.fatal), $(D Severity.critical), $(D Severity.error) and
  888
+       $(D Severity.warning).
  889
+
  890
+       The default value is $(D Severity.error).
  891
+     +/
  892
+    @property Severity minSeverity(Severity severity)
  893
+    {
  894
+        enforce(_rwmutex.writer().tryLock());
  895
+        scope(exit) _rwmutex.writer().unlock();
  896
+
  897
+        // cannot disable critical severity
  898
+        _minSeverity = severity < Severity.critical ?
  899
+            Severity.critical :
  900
+            severity;
  901
+        return _minSeverity;
  902
+    }
  903
+    /// ditto
  904
+    @property Severity minSeverity()
  905
+    {
  906
+        synchronized(_rwmutex.reader()) return _minSeverity;
  907
+    }
  908
+
  909
+    unittest
  910
+    {
  911
+        auto testConfig = new Configuration(new shared(TestLogger));
  912
+
  913
+        // Test max verbose level
  914
+        testConfig.maxVerboseLevel = 1;
  915
+        assert(testConfig.matchesVerboseFilter("file", 1));
  916
+        assert(testConfig.matchesVerboseFilter("file", 0));
  917
+        assert(!testConfig.matchesVerboseFilter("file", 2));
  918
+
  919
+        assert(testConfig.maxVerboseLevel == 1);
  920
+    }
  921
+
  922
+    /++
  923
+       Specifies the maximum verbose _level for logging verbose messages.
  924
+
  925
+       Verbose messages are logged if their verbose _level is less than or equal
  926
+       to the value of this property. This property is ignore if the module
  927
+       logging the verbose message matches an entry specified in the property
  928
+       for per module verbose filtering.
  929
+
  930
+       Example:
  931
+---
  932
+config.minSeverity = Severity.info;
  933
+config.maxVerboseLevel(5);
  934
+
  935
+vlog(4)("Log this message");
  936
+vlog(5)("Also log this message");
  937
+vlog(6)("Don't log this message");
  938
+---
  939
+       This example will enable verbose logging for verbose message with a
  940
+       _level of 5 or less.
  941
+
  942
+       The default value is $(D int.min).
  943
+     +/
  944
+    @property int maxVerboseLevel(int level)
  945
+    {
  946
+        enforce(_rwmutex.writer().tryLock());
  947
+        scope(exit) _rwmutex.writer().unlock();
  948
+        _level = level;
  949
+
  950
+        return _level;
  951
+    }
  952
+    /// ditto
  953
+    @property int maxVerboseLevel()
  954
+    {
  955
+        synchronized(_rwmutex.reader()) return _level;
  956
+    }
  957
+
  958
+    unittest
  959
+    {
  960
+        auto vmodule = "module=1,*another=3,even*=2,cat?=4,*dog?=1,evenmore=10";
  961
+        auto testConfig = new Configuration(new shared(TestLogger));
  962
+        testConfig.verboseFilter = vmodule;
  963
+
  964
+        // Test exact patterns
  965
+        assert(testConfig.matchesVerboseFilter("module", 1));
  966
+        assert(testConfig.matchesVerboseFilter("module.d", 1));
  967
+        assert(!testConfig.matchesVerboseFilter("amodule", 1));
  968
+
  969
+        // Test *
  970
+        assert(testConfig.matchesVerboseFilter("package"~sep~"another", 3));
  971
+        assert(testConfig.matchesVerboseFilter("package"~sep~"another.d", 3));
  972
+        assert(!testConfig.matchesVerboseFilter("package"~sep~"dontknow", 3));
  973
+
  974
+        assert(testConfig.matchesVerboseFilter("evenmore", 2));
  975
+        assert(testConfig.matchesVerboseFilter("evenmore.d", 2));
  976
+        assert(!testConfig.matchesVerboseFilter("package"~sep~"evenmore.d", 2));
  977
+
  978
+        // Test ?
  979
+        assert(testConfig.matchesVerboseFilter("cats.d", 4));
  980
+        assert(!testConfig.matchesVerboseFilter("cat", 4));
  981
+
  982
+        // Test * and ?
  983
+        assert(testConfig.matchesVerboseFilter("package"~sep~"dogs.d", 1));
  984
+        assert(!testConfig.matchesVerboseFilter("package"~sep~"doggies.d", 1));
  985
+        assert(!testConfig.matchesVerboseFilter("package"~sep~"horse", 1));
  986
+
  987
+        // Test that it can match any of the entries
  988
+        assert(testConfig.matchesVerboseFilter("evenmore.d", 10));
  989
+
  990
+        // Test invalid strings
  991
+        assertThrown(testConfig.verboseFilter = "module=2,");
  992
+        assertThrown(testConfig.verboseFilter = "module=a");
  993
+        assertThrown(testConfig.verboseFilter = "module=2,another=");
  994
+
  995
+        // assert output
  996
+        assert(vmodule == testConfig.verboseFilter);
  997
+    }
  998
+
  999
+    /++
  1000
+       Specifies the per module verbose filter configuration.
  1001
+
  1002
+       A verbose message with level $(I x) gets logged at severity level info
  1003
+       if there is an entry that matches the source file, and if the verbose
  1004
+       level of that entry is greater than or equal to $(I x).
  1005
+
  1006
+       The format of the configuration string is as follow
  1007
+       $(I [pattern]=[level],...), where $(I [pattern]) may contain any
  1008
+       character allowed in a file name and $(I [level]) is convertible to an
  1009
+       integer. For an exmplanation of how $(I [pattern]) matches the source
  1010
+       file please see $(XREF path, globMatch).
  1011
+
  1012
+       For every $(I [pattern]=[level]) in the configuration string an entry is
  1013
+       created.
  1014
+
  1015
+       Example:
  1016
+---
  1017
+config.verboseFilter = "module=2,great*=3,*test=1";
  1018
+---
  1019
+
  1020
+       The code above sets a verbose logging configuration that:
  1021
+       $(UL
  1022
+          $(LI Logs verbose 2 and lower messages from 'module{,.d}')
  1023
+          $(LI Logs verbose 3 and lower messages from anything starting with
  1024
+               'great')
  1025
+          $(LI Logs verbose 1 and lower messages from any file that ends with
  1026
+               'test{,.d}'))
  1027
+
  1028
+       Note: If the verbose message matches the pattern part of the entry, then
  1029
+       the maximum verbose level property is ignored.
  1030
+
  1031
+       For example in the default configuration if the command line contains
  1032
+       $(I --minloglevel=info --v=2 --vmodule=web=1).
  1033
+---
  1034
+module web;
  1035
+
  1036
+// ...
  1037
+
  1038
+vlog(2)("Verbose message is not logged");
  1039
+---
  1040
+       The verbose message above is not logged even though it is less than or
  1041
+       equal to 2, as specified in the command line.
  1042
+
  1043
+       The default value is $(D null).
  1044
+     +/
  1045
+    @property string verboseFilter(string vmodule)
  1046
+    {
  1047
+        enforce(_rwmutex.writer().tryLock());
  1048
+        scope(exit) _rwmutex.writer().unlock();
  1049
+
  1050
+        typeof(_modulePatterns) patterns;
  1051
+        typeof(_moduleLevels) levels;
  1052
+
  1053
+        foreach(entry; split(vmodule, ","))
  1054
+        {
  1055
+            enforce(entry != "");
  1056
+
  1057
+            auto entryParts = array(split(entry, "="));
  1058
+            enforce(entryParts.length == 2);
  1059
+            enforce(entryParts[0] != "");
  1060
+
  1061
+            string altName;
  1062
+            if(!endsWith(entryParts[0], ".d")) altName = entryParts[0] ~ ".d";
  1063
+
  1064
+            patterns ~= [ entryParts[0], altName ];
  1065
+            levels ~= to!int(entryParts[1]);
  1066
+        }
  1067
+        assert(patterns.length == levels.length);
  1068
+
  1069
+        _modulePatterns = patterns;
  1070
+        _moduleLevels = levels;
  1071
+        _vmodule = vmodule;
  1072
+
  1073
+        return _vmodule;
  1074
+    }
  1075
+    /// ditto
  1076
+    @property string verboseFilter()
  1077
+    {
  1078
+        synchronized(_rwmutex.reader()) return _vmodule;
  1079
+    }
  1080
+
  1081
+    /++
  1082
+       Function pointer for handling log message with a severity of fatal.
  1083
+
  1084
+       This function is called by the thread trying to log a fatal message. The
  1085
+       function handler should not return; otherwise $(D std.log) will
  1086
+       $(D assert(false)).
  1087
+
  1088
+       The default value is $(D function void() {}).
  1089
+     +/
  1090
+    @property void function() fatalHandler(void function() handler)
  1091
+    {
  1092
+        enforce(_rwmutex.writer().tryLock());
  1093
+        scope(exit) _rwmutex.writer().unlock();
  1094
+
  1095
+        _fatalHandler = handler ?
  1096
+            handler :
  1097
+            cast(void function()) function void() {};
  1098
+
  1099
+        return _fatalHandler;
  1100
+    }
  1101
+
  1102
+    /++
  1103
+       Implementation of the $(D Logger) interface used to persiste log messages
  1104
+
  1105
+       This property allows the caller to change and configure the backend
  1106
+       _logger to a different $(D Logger). It will throw an exception if it is
  1107
+       changed after a logging call has been made.
  1108
+