Review: std.logger

Dragos Carp via Digitalmars-d digitalmars-d at puremagic.com
Thu Jul 24 09:42:41 PDT 2014


>> It does the right thing... there is an overload: without 
>> additional arguments, the first argument (not necessary a 
>> string) is not a format string.
>>
>
> Now if write a bug:
> warning("there was an error because the file %s is missing!");
>
> and I forgot the argument, no problem, it will run without 
> errrors despite I made one. This is not an improvement over 
> writef.
>
> (this particular bug came up in DUB among others, and my last 
> example was a real case too in production software where the 
> users would call things with "%" in the names)

It is easy to make mistakes while producing text.

One nasty example:

   writef("On %s the exchange rate raised with more than 5%%", 
date);

later changed to:

   if (date == today())
     write("Today the exchange rate raised with more than 5%%");
   else
     writef("On %s the exchange rate raised with more than 5%%", 
date);

>
> Two different operations conceptually => two names.
>

The logging facility is a high-level utility library. I think, 
its main
design goal should be: the usage is a no-brainer, it stays out of
the flow of thoughts while using it. The decision whether a 
message is
a warning, an error, or just an info, it's enough of a burden for 
the
user already, who anyhow takes a lot of decisions while writing 
the
actual code.

It is a matter of opinion if warning and warningf are different
operations, both produce a message and warningf with one argument 
will
be inefficient in best case, or an error, as you described above.

We already have in standard library examples of overloaded 
functions
for "different operations".

For example "find" in std.algorithm (low-level library): it has an
overload which takes an element as argument and another one which 
takes
ranges. Some may say that this is conceptually not different, but 
then
if we look at the predicate of find, we see that they are quite
different: the first overload  matches the argument in the input 
range,
the second matches successive elements of the argument against
successive elements in the input range. You can say these are
conceptualy the same, but you can also say it is different 
(probably
this is the reason why findSplit* didn't bother to implement the
Element overload).

As I already said, I think that the analogy with write/writef is 
wrong
because of at least 2 reasons:
  1. write is a low level function which is part of the flow of 
thoughts
     for resolving the problem at hand
  2. Contrary to the log functions, write is used sometimes to 
produce
     non human-readable output, thus write(arg1, arg2, arg3) being 
a
     valid usage of it.

>>>
>>> Do you realize rolling loggers are not there because they are 
>>> supposed to be in another layer as subclasses of Logger?
>>>
>>
>> Contrary to the NullLogger, writing a rolling logger is a 
>> non-trivial task.
>
> NullLogger is there precisely because it's trivial and needed.
> Of course a rolling logger is not that trivial, but std.logger 
> is there to be orthogonal and a foundation not providing 
> everything non-trivial.

Probably you need a NullLogger, if you use the MultiLogger.
For a big application I think it is better to have a big log file,
and filter afterwards based on module, than the other way around:
you have 3 files and need to merge/correlate them.

Regarding having different log levels per module: ex. the 
application
has log level warning and a selected module log level trace, I 
think
there is a simpler solution. Instead of having different loggers 
for
each module, it would be easier to have an list, similar to an 
ACL,
generated at compile time, containg the log level of the selected
module(s). This list will be consulted by the log function in 
addition
to normal log level check.

This will avoid code like (again a decision to make):
   logFoo.warning("Log in my module");
   log.warning("Log in application log");



More information about the Digitalmars-d mailing list