diff --git a/dlog/doc.go b/dlog/doc.go
new file mode 100644
index 0000000..28e95fb
--- /dev/null
+++ b/dlog/doc.go
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019. Genome Research Ltd. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License,
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * @file doc.go
+ * @author Keith James
+ */
+
+/*
+Package dlog is a log (https://golang.org/pkg/log/) backend for
+logshim (http://github.com/kjsanger/logshim).
+*/
+package dlog
diff --git a/dlog/log.go b/dlog/log.go
new file mode 100644
index 0000000..f45ff2e
--- /dev/null
+++ b/dlog/log.go
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2019. Genome Research Ltd. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License,
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * @file log.go
+ * @author Keith James
+ */
+
+package dlog
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "strings"
+ "time"
+
+ "github.com/kjsanger/logshim"
+)
+
+type levelName string
+
+const (
+ errorLevel levelName = "ERROR"
+ warnLevel levelName = "WARN"
+ // noticeLevel levelName = "NOTICE"
+ infoLevel levelName = "INFO"
+ debugLevel levelName = "DEBUG"
+)
+
+func translateLevel(level logshim.Level) (levelName, error) {
+ var (
+ lvn levelName
+ err error
+ )
+
+ switch level {
+ case logshim.ErrorLevel:
+ lvn = errorLevel
+ case logshim.WarnLevel:
+ lvn = warnLevel
+ case logshim.NoticeLevel:
+ fallthrough
+ case logshim.InfoLevel:
+ lvn = infoLevel
+ case logshim.DebugLevel:
+ lvn = debugLevel
+ default:
+ lvn = warnLevel
+ err = fmt.Errorf("invalid log level %d, defaulting to "+
+ "WARN level", level)
+ }
+
+ return lvn, err
+}
+
+type StdLogger struct {
+ name string
+ Level logshim.Level
+ *log.Logger
+}
+
+func New(writer io.Writer, level logshim.Level) *StdLogger {
+ lg := log.New(writer, "", log.LstdFlags|log.Lshortfile)
+
+ _, err := translateLevel(level)
+ if err != nil {
+ log.Print(errorLevel, "log configuration error", err)
+ level = logshim.WarnLevel
+ }
+
+ return &StdLogger{"StdLog", level, lg}
+}
+
+func (log *StdLogger) Name() string {
+ return log.name
+}
+
+func (log *StdLogger) Err(err error) logshim.Message {
+ effectiveLevel := logshim.InfoLevel
+ if err != nil {
+ effectiveLevel = logshim.ErrorLevel
+ }
+
+ active := log.Level >= effectiveLevel
+ msg := &stdMessage{active, effectiveLevel, &strings.Builder{}}
+ msg.Err(err)
+ return msg
+}
+
+func (log *StdLogger) Error() logshim.Message {
+ active := log.Level >= logshim.ErrorLevel
+ msg := &stdMessage{active, logshim.ErrorLevel, &strings.Builder{}}
+ return msg
+}
+
+func (log *StdLogger) Warn() logshim.Message {
+ active := log.Level >= logshim.WarnLevel
+ msg := &stdMessage{active, logshim.WarnLevel, &strings.Builder{}}
+ return msg
+}
+
+func (log *StdLogger) Notice() logshim.Message {
+ active := log.Level >= logshim.NoticeLevel
+ msg := &stdMessage{active, logshim.InfoLevel, &strings.Builder{}}
+ return msg
+}
+
+func (log *StdLogger) Info() logshim.Message {
+ active := log.Level >= logshim.InfoLevel
+ msg := &stdMessage{active, logshim.InfoLevel, &strings.Builder{}}
+ return msg
+}
+
+func (log *StdLogger) Debug() logshim.Message {
+ active := log.Level >= logshim.DebugLevel
+ msg := &stdMessage{active, logshim.DebugLevel, &strings.Builder{}}
+ return msg
+}
+
+type stdMessage struct {
+ active bool
+ level logshim.Level
+ builder *strings.Builder
+}
+
+func (msg *stdMessage) Err(err error) logshim.Message {
+ if msg.active {
+ msg.builder.WriteString(fmt.Sprintf(" error: %v", err))
+ }
+ return msg
+}
+
+func (msg *stdMessage) Bool(key string, val bool) logshim.Message {
+ if msg.active {
+ msg.builder.WriteString(fmt.Sprintf(" %s: %v", key, val))
+ }
+ return msg
+}
+
+func (msg *stdMessage) Int(key string, val int) logshim.Message {
+ if msg.active {
+ msg.builder.WriteString(fmt.Sprintf(" %s: %d", key, val))
+ }
+ return msg
+}
+
+func (msg *stdMessage) Int64(key string, val int64) logshim.Message {
+ if msg.active {
+ msg.builder.WriteString(fmt.Sprintf(" %s: %d", key, val))
+ }
+ return msg
+}
+
+func (msg *stdMessage) Uint64(key string, val uint64) logshim.Message {
+ if msg.active {
+ msg.builder.WriteString(fmt.Sprintf(" %s: %d", key, val))
+ }
+ return msg
+}
+
+func (msg *stdMessage) Str(key string, val string) logshim.Message {
+ if msg.active {
+ msg.builder.WriteString(fmt.Sprintf(" %s: %s", key, val))
+ }
+ return msg
+}
+
+func (msg *stdMessage) Time(key string, val time.Time) logshim.Message {
+ if msg.active {
+ msg.builder.WriteString(fmt.Sprintf(" %s: %v", key, val))
+ }
+ return msg
+}
+
+func (msg *stdMessage) Msg(val string) {
+ if msg.active {
+ lvn, err := translateLevel(msg.level)
+ if err != nil {
+ // This should never happen because the Logger constructor corrects
+ // invalid level values.
+ log.Print(errorLevel, "log configuration error", err)
+ }
+
+ msg.builder.WriteString(" ")
+ msg.builder.WriteString(val)
+ log.Print(lvn, msg.builder.String())
+ // Once this method is called, deactivate for all future calls
+ msg.active = false
+ }
+}
+
+func (msg *stdMessage) Msgf(format string, a ...interface{}) {
+ msg.Msg(fmt.Sprintf(format, a...))
+}
diff --git a/dlog/log_test.go b/dlog/log_test.go
new file mode 100644
index 0000000..1ee3442
--- /dev/null
+++ b/dlog/log_test.go
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019. Genome Research Ltd. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License,
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * @file log_test.go
+ * @author Keith James
+ */
+
+package dlog
+
+import (
+ "os"
+ "testing"
+
+ "github.com/pkg/errors"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/kjsanger/logshim"
+)
+
+func TestNew(t *testing.T) {
+ for _, level := range logshim.Levels() {
+ logger := New(os.Stderr, level)
+ assert.NotNil(t, logger, "StdLogger level %d was nil", level)
+ }
+}
+
+func TestStdLogger_Err(t *testing.T) {
+ log := New(os.Stderr, logshim.ErrorLevel)
+ assert.NotNil(t, log.Err(errors.New("test error")),
+ "Err() should return a Message")
+}
+
+func TestStdLogger_Error(t *testing.T) {
+ log := New(os.Stderr, logshim.ErrorLevel)
+ assert.NotNil(t, log.Error(), "Error() should return a Message")
+}
+
+func TestStdLogger_Warn(t *testing.T) {
+ log := New(os.Stderr, logshim.ErrorLevel)
+ assert.NotNil(t, log.Warn(), "Warn() should return a Message")
+}
+
+func TestStdLogger_Notice(t *testing.T) {
+ log := New(os.Stderr, logshim.ErrorLevel)
+ assert.NotNil(t, log.Notice(), "Notice() should return a Message")
+}
+
+func TestStdLogger_Info(t *testing.T) {
+ log := New(os.Stderr, logshim.ErrorLevel)
+ assert.NotNil(t, log.Info(), "Info() should return a Message")
+}
+
+func TestStdLogger_Debug(t *testing.T) {
+ log := New(os.Stderr, logshim.ErrorLevel)
+ assert.NotNil(t, log.Debug(), "Debug() should return a Message")
+}
diff --git a/go.mod b/go.mod
index 64b860c..607e9eb 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,8 @@
module github.com/kjsanger/logshim
go 1.12
+
+require (
+ github.com/pkg/errors v0.8.1
+ github.com/stretchr/testify v1.3.0
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..9229611
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,9 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=