Logging mechanisms
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.
Contents
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.
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.
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.
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
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