Logging mechanisms

From D Wiki
Revision as of 11:50, 16 July 2020 by WebFreak001 (talk | contribs) (Initial creation with 4 examples)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
Level: Novice
Cookbook Type: Recipe
Cookbook Status: Draft
Approximate reading time: 15 min
0.249 hr
D Version:
"2.079.0+" is not in the list (D1, D2) of allowed values for the "D Version" property.


There are countless ways to implement logging in D. On this page there are some simple examples to get you started with various levels of code complexity and compatibility.

Simple logging to console

The most simple and convenient way to log anything to the console in D is by simply using std.stdio and the write/writeln and writef/writefln functions.

writeln and writefln internally convert all given parameters to strings, basically like using std.conv : to, text. Out of the box this supports converting all primitive values to and from strings, converting enums to their names and back, dumping structs without toString function member by member and calling toString on structs or classes.

▶ Try it online

import std.datetime : Clock;
import std.stdio;

struct SomeStruct
{
    int a, b;
    bool c;
    string d;
}

void main()
{
    writeln("Hello world!");
    writeln("I can log all kinds of values: ",
        4, ' ',
        true, ' ',
        [-float.infinity, 4.5f], ' ',
        SomeStruct(1, 2, true, "cool")
    );
    writeln();

    writefln("[%s] %s can print many %s of while keeping the method call clean and free of concatenation",
        Clock.currTime, __traits(identifier, writefln), "values");
    writefln("Contrary to C, you can use %%s for every argument without worrying about the type: %s %s %s",
        [1, 2, 3, 4], true, SomeStruct.init);
    writefln!"This call is a template and has compile time validation of its arguments for type and count: %d %.2f"(
        42, 3.14159);
}

Sample output

Hello world!
I can log all kinds of values: 4 true [-inf, 4.5] SomeStruct(1, 2, true, "cool")

[2020-Jul-16 12:03:12.233102] writefln can print many values of while keeping the method call clean and free of concatenation
Contrary to C, you can use %s for every argument without worrying about the type: [1, 2, 3, 4] true SomeStruct(0, 0, false, "")
This call is a template and has compile time validation of its arguments for type and count: 42 3.14

Custom methods with writeln/writefln-like API

The writeln and writefln methods can easily be replicated by using std.conv : text and std.format : format respectively. You can use these APIs to log to more places than just files like sending logs over the network, storing them in memory or in a database or invoke C APIs.

▶ Try it online

import std.conv : text;
import std.format : format;

// some function taking in just a string, defined somewhere in some library
extern (D) void storeLogInDB(string severity, string logLine);

// our convenience functions we will be heavily using:
void logInfo(Args...)(Args args) { storeLogInDB("INFO", text(args)); }

// runtime format string
void logInfof(Args...)(string fmt, Args args) { storeLogInDB("INFO", format(fmt, args)); }
// compile-time format string (checked & optimized)
void logInfof(string fmt, Args...)(Args args) { storeLogInDB("INFO", format!fmt(args)); }

void main()
{
    logInfo("Logging important things: ", 1234);

    string failedLogin = "walter";
    int failedNum = 2;
    logInfof!"User %s failed login attempt %d"(failedLogin, failedNum);
}

Sample output

call to storeLogInDB("INFO", "Logging important things: 1234")
call to storeLogInDB("INFO", "User walter failed login attempt 2")

Logging to console with caller metadata

This code supersedes the legacy Using string mixins for logging page.

▶ Try it online

import std.datetime : Clock;
import std.stdio;

enum LogLevel { INFO, WARN, ERROR }

// Nested template to allow aliasing LogLevels.
// This makes it easily possible to create wrapper aliases without creating new
// wrapper functions which would alter the FILE, LINE and FUNCTION constants.
template log(LogLevel level)
{
    void log(Args...)(
        Args args,
        string fn = __FUNCTION__, // fully qualified function name of the caller
        string file = __FILE__,   // filename of the caller as specified in the compilation
        size_t line = __LINE__    // line number of the caller
    )
    {
        // Instead of using the default string conversion of Clock.currTime()
        // we could use Clock.currTime().toISOExtString() for a machine parsable
        // format or have a static constructor initializing a global MonoTime
        // variable at program startup and subtract from it here to have a time
        // offset from the start of the program being logged which is guaranteed
        // to only increase. (as opposed to the clock, which could change with
        // leap days, leap seconds or system clock manipulation)

        writeln(Clock.currTime(), // dump date & time with default format
            " [", level, "] ", // automatically converts enum member name to string
              file,
             '(', line, "): ",
              fn, ": ",
              args // actual log arguments, all dumped using writeln
        );
    }
}

// convenience aliases, uses nested templates to allow easily doing this
alias info = log!(LogLevel.INFO);
alias warn = log!(LogLevel.WARN);
alias error = log!(LogLevel.ERROR);

void main(string[] args)
{
    // nested templates of functions call like any function
    info("hello ", "world");
    warn("we are", " number ", 1);
    log!(LogLevel.INFO)("manual call");
    error(true);
}

Sample output

2020-Jul-16 10:30:32.4525449 [INFO] onlineapp.d(20): onlineapp.main: hello world
2020-Jul-16 10:30:32.4526464 [WARN] onlineapp.d(21): onlineapp.main: we are number 1
2020-Jul-16 10:30:32.4526682 [INFO] onlineapp.d(22): onlineapp.main: manual call
2020-Jul-16 10:30:32.4526883 [ERROR] onlineapp.d(23): onlineapp.main: true

vibe.d / vibe-core logging API

When making a Web Application with vibe.d you will want to use the vibe.d logging APIs and register with them. The vibe.d logging mechanisms support dynamically registering new output providers (console, DB, HTTP, etc) and logs will be distributed to all the registered providers. Documentation

▶ Try it online

import vibe.core.log;

void registerAppLoggers()
{
    registerLogger(cast(shared)new FileLogger("applog.txt"));
}

void main()
{
    logTrace("Registering loggers");
    
    registerAppLoggers();
    
    logWarn("now logging with %d loggers", getLoggers().length);
    logError("ERROR: something unexpected happened");
}

Sample output

console:
now logging with 2 loggers
ERROR: something unexpected happened

applog.txt:
[main(----) WRN] now logging with 2 loggers
[main(----) ERR] ERROR: something unexpected happened