diff --git a/lib/log/default.go b/lib/log/default.go
new file mode 100644
index 0000000000000000000000000000000000000000..beddd59aeb0bcda9058e47f9afe9c9dcfb93d431
--- /dev/null
+++ b/lib/log/default.go
@@ -0,0 +1,25 @@
+package log
+
+import "os"
+
+var Default *Logger = WriterLogger(os.Stderr)
+
+func Debug(message string)    { Default.Debug(message) }
+func Info(message string)     { Default.Info(message) }
+func Notice(message string)   { Default.Notice(message) }
+func Warning(message string)  { Default.Warning(message) }
+func Error(message string)    { Default.Error(message) }
+func Critical(message string) { Default.Critical(message) }
+
+func Debugf(template string, args ...interface{})    { Default.Debugf(template, args...) }
+func Infof(template string, args ...interface{})     { Default.Infof(template, args...) }
+func Noticef(template string, args ...interface{})   { Default.Noticef(template, args...) }
+func Warningf(template string, args ...interface{})  { Default.Warningf(template, args...) }
+func Errorf(template string, args ...interface{})    { Default.Errorf(template, args...) }
+func Criticalf(template string, args ...interface{}) { Default.Criticalf(template, args...) }
+
+var ErrorHandler func(err error) = defaultErrorHandler
+
+func defaultErrorHandler(err error) {
+	_, _ = os.Stderr.WriteString("Error while logging: " + err.Error() + "\n")
+}
diff --git a/lib/log/entry.go b/lib/log/entry.go
new file mode 100644
index 0000000000000000000000000000000000000000..37180acf4997052fffac16b1746dc04c744f1bd2
--- /dev/null
+++ b/lib/log/entry.go
@@ -0,0 +1,21 @@
+package log
+
+import (
+	"fmt"
+	"time"
+)
+
+type Entry struct {
+	Timestamp time.Time
+	Level     Level
+	Message   string
+	Fields    *Fields
+}
+
+func NewEntry(level Level, message string, fields *Fields) *Entry {
+	return &Entry{time.Now(), level, message, fields}
+}
+
+func NewEntryf(level Level, template string, args []interface{}, fields *Fields) *Entry {
+	return NewEntry(level, fmt.Sprintf(template, args...), fields)
+}
diff --git a/lib/log/fields.go b/lib/log/fields.go
new file mode 100644
index 0000000000000000000000000000000000000000..ccd860f14ee06af6ba24827626b8787b03612652
--- /dev/null
+++ b/lib/log/fields.go
@@ -0,0 +1,16 @@
+package log
+
+type Fields struct {
+	Name     string
+	Value    interface{}
+	previous *Fields
+}
+
+func (f *Fields) With(name string, value interface{}) *Fields {
+	return &Fields{name, value, f}
+}
+
+func (f *Fields) ForEach(do func(string, interface{}) bool) {
+	for i := f; i != nil && do(i.Name, i.Value); i = i.previous {
+	}
+}
diff --git a/lib/log/level.go b/lib/log/level.go
new file mode 100644
index 0000000000000000000000000000000000000000..b9c3b8c5513a93cda78e3f1757e3ab30a3d33378
--- /dev/null
+++ b/lib/log/level.go
@@ -0,0 +1,30 @@
+package log
+
+type Level byte
+
+const (
+	DEBUG Level = iota
+	INFO
+	NOTICE
+	WARNING
+	ERROR
+	CRITICAL
+)
+
+func (l Level) String() string {
+	switch l {
+	case DEBUG:
+		return "DEBUG"
+	case INFO:
+		return "INFO"
+	case NOTICE:
+		return "NOTICE"
+	case WARNING:
+		return "WARNING"
+	case ERROR:
+		return "ERROR"
+	case CRITICAL:
+		return "CRITICAL"
+	}
+	panic("unknown log level")
+}
diff --git a/lib/log/logger.go b/lib/log/logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..7565fb5eb076434633a2501722a4fae0e9e5ae9d
--- /dev/null
+++ b/lib/log/logger.go
@@ -0,0 +1,45 @@
+package log
+
+type logger interface {
+	Log(*Entry)
+}
+
+type Logger struct {
+	logger
+	Fields *Fields
+}
+
+func (l *Logger) WithField(name string, value interface{}) *Logger {
+	return &Logger{l, l.Fields.With(name, value)}
+}
+
+func (l *Logger) Debug(message string)    { l.Log(NewEntry(DEBUG, message, l.Fields)) }
+func (l *Logger) Info(message string)     { l.Log(NewEntry(INFO, message, l.Fields)) }
+func (l *Logger) Notice(message string)   { l.Log(NewEntry(NOTICE, message, l.Fields)) }
+func (l *Logger) Warning(message string)  { l.Log(NewEntry(WARNING, message, l.Fields)) }
+func (l *Logger) Error(message string)    { l.Log(NewEntry(ERROR, message, l.Fields)) }
+func (l *Logger) Critical(message string) { l.Log(NewEntry(CRITICAL, message, l.Fields)) }
+
+func (l *Logger) Debugf(template string, args ...interface{}) {
+	l.Log(NewEntryf(DEBUG, template, args, l.Fields))
+}
+
+func (l *Logger) Infof(template string, args ...interface{}) {
+	l.Log(NewEntryf(INFO, template, args, l.Fields))
+}
+
+func (l *Logger) Noticef(template string, args ...interface{}) {
+	l.Log(NewEntryf(NOTICE, template, args, l.Fields))
+}
+
+func (l *Logger) Warningf(template string, args ...interface{}) {
+	l.Log(NewEntryf(WARNING, template, args, l.Fields))
+}
+
+func (l *Logger) Errorf(template string, args ...interface{}) {
+	l.Log(NewEntryf(ERROR, template, args, l.Fields))
+}
+
+func (l *Logger) Criticalf(template string, args ...interface{}) {
+	l.Log(NewEntryf(CRITICAL, template, args, l.Fields))
+}
diff --git a/lib/log/writer.go b/lib/log/writer.go
new file mode 100644
index 0000000000000000000000000000000000000000..bd4eadc50bd20c32e2020c4f243e58c53cb7f363
--- /dev/null
+++ b/lib/log/writer.go
@@ -0,0 +1,16 @@
+package log
+
+import (
+	"io"
+)
+
+type writerLogger struct {
+	target io.Writer
+}
+
+func (l writerLogger) Log(entry *Entry) {
+}
+
+func WriterLogger(target io.WriteCloser) *Logger {
+	return &Logger{logger: writerLogger{target}}
+}