overhauled log package

This commit is contained in:
Erik Winter 2021-03-29 08:35:42 +02:00
parent 4326fb11d6
commit a1f5551d76
15 changed files with 871 additions and 615 deletions

10
go.mod
View File

@ -3,8 +3,12 @@ module git.sr.ht/~ewintr/go-kit
go 1.13
require (
github.com/davecgh/go-spew v1.1.1
github.com/go-kit/kit v0.10.0
golang.org/x/text v0.3.2
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/text v0.3.3
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
)

21
go.sum
View File

@ -36,6 +36,7 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -62,6 +63,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -125,10 +127,14 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
@ -157,6 +163,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
@ -181,6 +189,7 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -208,6 +217,7 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@ -223,6 +233,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@ -291,10 +303,13 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -312,8 +327,6 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -335,6 +348,8 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@ -345,6 +360,8 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1,34 +0,0 @@
package log_test
import (
"bytes"
"fmt"
"git.sr.ht/~ewintr/go-kit/log"
)
// LoggerTestable represents a data structure for a log context
type LoggerTestable struct {
TestKey string `json:"key"`
}
func (l LoggerTestable) ContextName() string { return "test" }
func Example() {
var buff bytes.Buffer
var logger log.Logger
logger = log.NewLogger(&buff)
// Please ignore the following line, it was added to allow better
// assertion of the results when logging.
logger = logger.AddContext("time", "-")
logger = log.Add(logger, LoggerTestable{
TestKey: "value",
})
logger.Info("this is an example.")
fmt.Println(buff.String())
// Output: {"message":"this is an example.","test":{"key":"value"},"time":"-"}
}

View File

@ -1,76 +0,0 @@
package log
import (
"io"
"runtime"
"strconv"
"strings"
"time"
kitlog "github.com/go-kit/kit/log"
)
type GoKit struct {
debugEnabled bool
info kitlog.Logger
debug kitlog.Logger
}
func caller(depth int) kitlog.Valuer {
return func() interface{} {
_, file, line, _ := runtime.Caller(depth)
return file + ":" + strconv.Itoa(line)
}
}
func newGoKitLogger(logWriter io.Writer) Logger {
w := kitlog.NewSyncWriter(logWriter)
t := kitlog.TimestampFormat(time.Now, time.RFC3339)
info := kitlog.With(kitlog.NewJSONLogger(w), "time", t)
debug := kitlog.With(info, "debug", true, "caller", caller(4))
return &GoKit{
info: info,
debug: debug,
}
}
// AddContext attaches a key-value information to the log message
func (gk *GoKit) AddContext(contextKey string, contextValue interface{}) Logger {
return &GoKit{
debugEnabled: gk.debugEnabled,
info: kitlog.With(gk.info, contextKey, contextValue),
debug: kitlog.With(gk.debug, contextKey, contextValue),
}
}
// Info writes out the log message
func (gk *GoKit) Info(message string) error {
return gk.info.Log("message", normalizeString(message))
}
// Debug writes out the log message when debug is enabled
func (gk *GoKit) Debug(message string) error {
if gk.debugEnabled {
return gk.debug.Log("message", normalizeString(message))
}
return nil
}
// DebugEnabled sets debug flag to enable or disabled
func (gk *GoKit) DebugEnabled(enable bool) {
gk.debugEnabled = enable
}
// DebugStatus returns whether or not debug is enabled
func (gk *GoKit) DebugStatus() bool {
return gk.debugEnabled
}
func normalizeString(s string) string {
ss := strings.Fields(s)
if len(ss) == 0 {
return "(MISSING)"
}
return strings.Join(ss, " ")
}

View File

@ -1,238 +0,0 @@
package log_test
import (
"bytes"
"encoding/json"
"fmt"
"testing"
"git.sr.ht/~ewintr/go-kit/log"
"git.sr.ht/~ewintr/go-kit/test"
)
type testLogWriter struct {
Logs []string
}
func newLogWriter() *testLogWriter {
return &testLogWriter{}
}
func (t *testLogWriter) Write(p []byte) (n int, err error) {
t.Logs = append(t.Logs, string(p))
return
}
func (t *testLogWriter) count() int {
return len(t.Logs)
}
func (t *testLogWriter) last() string {
if len(t.Logs) == 0 {
return ""
}
return t.Logs[len(t.Logs)-1]
}
func TestGoKit(t *testing.T) {
t.Run("new-logger", func(t *testing.T) {
t.Parallel()
var buf bytes.Buffer
logger := log.NewLogger(&buf)
test.NotZero(t, logger)
})
t.Run("info", func(t *testing.T) {
t.Parallel()
logWriter := newLogWriter()
logger := log.NewLogger(logWriter)
test.NotZero(t, logger)
test.Equals(t, 0, logWriter.count())
msg := "log this"
test.OK(t, logger.Info(msg))
test.Equals(t, 1, logWriter.count())
testLogLine(t, false, msg, logWriter.last())
msg = "log again"
test.OK(t, logger.Info(msg))
test.Equals(t, 2, logWriter.count())
testLogLine(t, false, msg, logWriter.last())
})
t.Run("debug", func(t *testing.T) {
t.Parallel()
logWriter := newLogWriter()
logger := log.NewLogger(logWriter)
test.NotZero(t, logger)
// starts with debug disabled
test.Equals(t, false, logger.DebugStatus())
msg := "log this"
logger.DebugEnabled(true)
test.Equals(t, true, logger.DebugStatus())
logger.Debug(msg)
test.Equals(t, 1, logWriter.count())
testLogLine(t, true, msg, logWriter.last())
msg = "log again"
logger.DebugEnabled(false)
test.Equals(t, false, logger.DebugStatus())
logger.Debug(msg)
test.Equals(t, 1, logWriter.count())
})
t.Run("normalize-string", func(t *testing.T) {
t.Parallel()
missingMsg := "(MISSING)"
for _, tc := range []struct {
context string
logMessage string
expResult string
}{
{
context: "empty string",
logMessage: "",
expResult: missingMsg,
},
{
context: "single whitespace",
logMessage: " ",
expResult: missingMsg,
},
{
context: "multiple whitespace",
logMessage: "\t ",
expResult: missingMsg,
},
{
context: "simple line",
logMessage: "just some text",
expResult: "just some text",
},
{
context: "multiline message",
logMessage: "one\ntwo\nthree",
expResult: "one two three",
},
} {
t.Log(tc.context)
logWriter := newLogWriter()
logger := log.NewLogger(logWriter)
test.NotZero(t, logger)
logger.DebugEnabled(true)
test.OK(t, logger.Info(tc.logMessage))
testLogLine(t, false, tc.expResult, logWriter.last())
test.OK(t, logger.Debug(tc.logMessage))
testLogLine(t, true, tc.expResult, logWriter.last())
}
})
t.Run("add-context", func(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
message string
contextKey string
contextValue interface{}
}{
{
message: "empty key",
contextValue: "value",
},
{
message: "empty value",
contextKey: "a key",
},
{
message: "not empty",
contextKey: "a key",
contextValue: "value",
},
{
message: "slice",
contextKey: "a key",
contextValue: []string{"one", "two"},
},
{
message: "map",
contextValue: map[string]interface{}{
"key1": "value1",
"key2": 2,
"key3": nil,
},
},
{
message: "struct",
contextValue: struct {
Key1 string
Key2 int
Key3 interface{}
key4 string
}{
Key1: "value1",
Key2: 2,
Key3: nil,
key4: "unexported",
},
},
} {
t.Log(tc.message)
logWriter := newLogWriter()
logger := log.NewLogger(logWriter)
test.NotZero(t, logger)
test.OK(t, logger.AddContext(tc.contextKey, tc.contextValue).Info("log message"))
test.Equals(t, 1, logWriter.count())
testContext(t, tc.contextKey, tc.contextValue, logWriter.last())
}
})
}
func testLogLine(t *testing.T, debug bool, message, line string) {
test.Equals(t, true, isValidJSON(line))
test.Includes(t, `"time":`, line)
test.Includes(t, fmt.Sprintf(`"message":%q`, message), line)
if debug {
test.Includes(t, `"debug":true`, line)
test.Includes(t, `"caller":"`, line)
}
}
func testContext(t *testing.T, key string, value interface{}, line string) {
value, err := toJSON(value)
test.OK(t, err)
test.Includes(t, fmt.Sprintf(`%q:%s`, key, value), line)
}
func toJSON(i interface{}) (string, error) {
j, err := json.Marshal(i)
if err != nil {
return "", err
}
return string(j), nil
}
func isValidJSON(s string) bool {
var js map[string]interface{}
err := json.Unmarshal([]byte(s), &js)
if err != nil {
return false
}
return true
}

81
log/gokitio.go Normal file
View File

@ -0,0 +1,81 @@
package log
import (
"io"
kitlog "github.com/go-kit/kit/log"
)
type GoKitIOLogger struct {
fields Fields
level LogLevel
logger kitlog.Logger
}
func NewGoKitIOLogger(out io.Writer) Logger {
kl := kitlog.NewJSONLogger(out)
return &GoKitIOLogger{
fields: make(Fields),
level: LevelInfo,
logger: kl,
}
}
func (kl *GoKitIOLogger) SetLogLevel(loglevel LogLevel) {
kl.level = loglevel
}
func (kl *GoKitIOLogger) WithField(key string, value interface{}) Logger {
return kl.With(Fields{
key: value,
})
}
func (kl *GoKitIOLogger) WithErr(err error) Logger {
return kl.With(Fields{
"error": err,
})
}
func (kl *GoKitIOLogger) With(fields Fields) Logger {
newFields := make(Fields)
for k, v := range kl.fields {
newFields[k] = v
}
for k, v := range fields {
newFields[k] = v
}
return &GoKitIOLogger{
fields: newFields,
level: kl.level,
logger: kl.logger,
}
}
func (kl *GoKitIOLogger) Debug(message string) {
if kl.level == LevelDebug {
kl.log(message)
}
}
func (kl *GoKitIOLogger) Info(message string) {
if kl.level != LevelError {
kl.log(message)
}
}
func (kl *GoKitIOLogger) Error(message string) {
kl.log(message)
}
func (kl *GoKitIOLogger) log(message string) {
kv := make([]interface{}, len(kl.fields)*2)
for k, v := range kl.fields {
kv = append(kv, k, v)
}
kv = append(kv, "level", kl.level, "message", message)
kl.logger.Log(kv...)
}

229
log/gokitio_test.go Normal file
View File

@ -0,0 +1,229 @@
package log_test
import (
"errors"
"fmt"
"testing"
"git.sr.ht/~ewintr/go-kit/log"
"git.sr.ht/~ewintr/go-kit/test"
)
func TestGoKitIOLogger(t *testing.T) {
logline := "test line"
tw := &testWriter{}
logger := log.NewGoKitIOLogger(tw)
logger.SetLogLevel(log.LevelDebug)
for _, tc := range []struct {
name string
logfunc func(string)
}{
{
name: "debug",
logfunc: logger.Debug,
},
{
name: "info",
logfunc: logger.Info,
},
{
name: "error",
logfunc: logger.Error,
},
} {
t.Run(tc.name, func(t *testing.T) {
tw.Flush()
tc.logfunc(logline)
test.Includes(t, logline, tw.LogLines...)
})
}
}
func TestGoKitIOLoggerSetLogLevel(t *testing.T) {
loglines := map[log.LogLevel]string{
log.LevelDebug: "debug",
log.LevelInfo: "info",
log.LevelError: "error",
}
tw := &testWriter{}
logger := log.NewGoKitIOLogger(tw)
for _, tc := range []struct {
name string
level log.LogLevel
exp []string
}{
{
name: "debug",
level: log.LevelDebug,
exp: []string{
loglines[log.LevelDebug],
loglines[log.LevelInfo],
loglines[log.LevelError],
},
},
{
name: "info",
level: log.LevelInfo,
exp: []string{
loglines[log.LevelInfo],
loglines[log.LevelError],
},
},
{
name: "error",
level: log.LevelError,
exp: []string{
loglines[log.LevelError],
},
},
} {
t.Run(tc.name, func(t *testing.T) {
tw.Flush()
logger.SetLogLevel(tc.level)
logger.Debug(loglines[log.LevelDebug])
logger.Info(loglines[log.LevelInfo])
logger.Error(loglines[log.LevelError])
test.Equals(t, len(tc.exp), len(tw.LogLines))
for _, ll := range tc.exp {
test.Includes(t, ll, tw.LogLines...)
}
})
}
}
func TestGoKitIOLoggerWithField(t *testing.T) {
tw := &testWriter{}
logger := log.NewGoKitIOLogger(tw)
logger.SetLogLevel(log.LevelDebug)
// the following only tests whether the shortcut to With() works
// extensive testing of the fields in combination with levels is
// in the TestGoKitIOLoggerWith test
for _, tc := range []struct {
name string
value interface{}
}{
{
name: "string",
value: "value",
},
} {
t.Run(tc.name, func(t *testing.T) {
tw.Flush()
key, message := "key", "message"
fieldLogger := logger.WithField(key, tc.value)
fieldLogger.Info(message)
test.Equals(t, 1, len(tw.LogLines))
test.Includes(t, message, tw.LogLines[0])
test.Includes(t, key, tw.LogLines[0])
test.Includes(t, fmt.Sprintf("%v", tc.value), tw.LogLines[0])
})
}
}
func TestGoKitIOLoggerWithErr(t *testing.T) {
tw := &testWriter{}
logger := log.NewGoKitIOLogger(tw)
logger.SetLogLevel(log.LevelDebug)
// the following only tests whether the shortcut to With() works
// extensive testing of the fields in combination with levels is
// in the TestGoKitIOLoggerWith test
for _, tc := range []struct {
name string
value error
}{
{
name: "string",
value: errors.New("value"),
},
} {
t.Run(tc.name, func(t *testing.T) {
tw.Flush()
key, message := "key", "message"
fieldLogger := logger.WithField(key, tc.value)
fieldLogger.Info(message)
test.Equals(t, 1, len(tw.LogLines))
test.Includes(t, message, tw.LogLines[0])
test.Includes(t, key, tw.LogLines[0])
test.Includes(t, tc.value.Error(), tw.LogLines[0])
})
}
}
func TestGoKitIOLoggerWith(t *testing.T) {
tw := &testWriter{}
logger := log.NewGoKitIOLogger(tw)
logger.SetLogLevel(log.LevelDebug)
for _, tc := range []struct {
name string
value interface{}
}{
{
name: "string",
value: "value",
},
{
name: "int",
value: 3,
},
} {
t.Run(tc.name, func(t *testing.T) {
key := "key"
fieldLogger := logger.With(log.Fields{
key: tc.value,
})
for _, lf := range []struct {
name string
log func(string)
flog func(string)
}{
{
name: "debug",
log: logger.Debug,
flog: fieldLogger.Debug,
},
{
name: "info",
log: logger.Info,
flog: fieldLogger.Info,
},
{
name: "error",
log: logger.Error,
flog: fieldLogger.Error,
},
} {
t.Run(lf.name, func(t *testing.T) {
tw.Flush()
message := "normal"
fieldMessage := "field"
lf.log(message)
lf.flog(fieldMessage)
test.Equals(t, 2, len(tw.LogLines))
// first line is normal logger
test.Includes(t, message, tw.LogLines[0])
test.NotIncludes(t, key, tw.LogLines[0])
// second line is logger with fields
test.Includes(t, fieldMessage, tw.LogLines[1])
test.Includes(t, key, tw.LogLines[1])
test.Includes(t, fmt.Sprintf("%v", tc.value), tw.LogLines[1])
})
}
})
}
}

View File

@ -1,55 +1,27 @@
// Package log implements a generic interface to log
package log
import (
"encoding/json"
"io"
import "io"
const (
LevelDebug = LogLevel("debug")
LevelInfo = LogLevel("info")
LevelError = LogLevel("error")
)
// Logger represents a log implementation
type LogLevel string
type Fields map[string]interface{}
type Logger interface {
AddContext(string, interface{}) Logger
Info(string) error
Debug(string) error
DebugEnabled(bool)
DebugStatus() bool
SetLogLevel(loglevel LogLevel)
WithField(key string, value interface{}) Logger
WithErr(err error) Logger
With(fields Fields) Logger
Debug(message string)
Info(message string)
Error(message string)
}
// NewLogger returns a Logger implementation
func NewLogger(logWriter io.Writer) Logger {
return newGoKitLogger(logWriter)
}
type loggerWriter struct {
Logger
}
func (l *loggerWriter) Write(p []byte) (n int, err error) {
var fields map[string]interface{}
if err = json.Unmarshal(p, &fields); err != nil {
l.Logger.Info(string(p))
return
}
delete(fields, "time")
var message string
if m, ok := fields["message"]; ok {
message = m.(string)
delete(fields, "message")
}
if len(fields) == 0 {
l.Logger.Info(message)
return
}
l.Logger.AddContext("fields", fields).Info(message)
return
}
// NewWriter returns io.Writer implementation based on a logger
func NewWriter(l Logger) io.Writer {
return &loggerWriter{l}
func New(out io.Writer) Logger {
return NewLogrusLogger(out)
}

View File

@ -1,72 +1,15 @@
package log_test
import (
"bytes"
"fmt"
"testing"
"git.sr.ht/~ewintr/go-kit/log"
"git.sr.ht/~ewintr/go-kit/test"
)
func TestNewWriter(t *testing.T) {
defaultMessage := "this is a test"
for _, tc := range []struct {
m string
message string
expected []string
notExpected []string
}{
{
m: "string input",
message: defaultMessage,
expected: []string{
fmt.Sprintf(`"message":%q`, defaultMessage),
},
},
{
m: "json map",
message: fmt.Sprintf(`{"message":%q, "custom": "value"}`, defaultMessage),
expected: []string{
fmt.Sprintf(`"message":%q`, defaultMessage),
`"fields":{`,
`"custom":"value"`,
},
},
{
m: "json map correct time",
message: fmt.Sprintf(`{"message":%q, "time": "value"}`, defaultMessage),
expected: []string{
fmt.Sprintf(`"message":%q`, defaultMessage),
`"time":"`,
},
notExpected: []string{
`"fields":{`,
`"time": "value"`,
},
},
} {
var buf bytes.Buffer
logger := log.NewLogger(&buf)
t.Run(tc.m, func(t *testing.T) {
w := log.NewWriter(logger)
w.Write([]byte(tc.message))
for _, e := range tc.expected {
test.Includes(t, e, buf.String())
}
for _, e := range tc.notExpected {
test.NotIncludes(t, e, buf.String())
}
})
}
type testWriter struct {
LogLines []string
}
func TestLog(t *testing.T) {
t.Run("new-logger", func(t *testing.T) {
var buf bytes.Buffer
logger := log.NewLogger(&buf)
test.NotZero(t, logger)
})
func (tw *testWriter) Write(p []byte) (int, error) {
tw.LogLines = append(tw.LogLines, string(p))
return len(p), nil
}
func (tw *testWriter) Flush() {
tw.LogLines = []string{}
}

View File

@ -1,43 +0,0 @@
package log
import (
"runtime"
"strconv"
)
// Contexter ensures type is intentionally a log context
type Contexter interface {
ContextName() string
}
// Caller represents a runtime file:line caller for log context
type Caller func() string
// ContextName returns the key for the log context
func (c Caller) ContextName() string { return "caller" }
// NewCaller returns a log context for runtime file caller with full path
func NewCaller(depth int) Caller {
return func() string {
_, file, line, _ := runtime.Caller(depth)
return file + ":" + strconv.Itoa(line)
}
}
// Add adds a contexter interface to a Logger
func Add(l Logger, cc ...Contexter) Logger {
for _, c := range cc {
if caller, ok := c.(Caller); ok {
l = l.AddContext(c.ContextName(), caller())
continue
}
l = l.AddContext(c.ContextName(), c)
}
return l
}
// AttachError adds a context called `attached_error` for error message that
// is relevant to the log entry.
func AttachError(l Logger, e error) Logger {
return l.AddContext("attached_error", e.Error())
}

View File

@ -1,105 +0,0 @@
package log_test
import (
"bytes"
"fmt"
"strings"
"testing"
"git.sr.ht/~ewintr/go-kit/log"
"git.sr.ht/~ewintr/go-kit/test"
)
type (
ContextA string
ContextB string
)
func (l ContextA) ContextName() string { return "context_a" }
func (l ContextB) ContextName() string { return "context_b" }
func TestLogContext(t *testing.T) {
t.Run("new caller", func(t *testing.T) {
caller := log.NewCaller(1)
s := caller()
test.Includes(t, "logctx_test.go:", s)
})
t.Run("add context", func(t *testing.T) {
var buff bytes.Buffer
logger := log.NewLogger(&buff)
for _, tc := range []struct {
m string
cc []log.Contexter
}{
{
m: "single context",
cc: []log.Contexter{ContextA("AA")},
},
{
m: "multiple context",
cc: []log.Contexter{ContextA("AA"), ContextB("BB")},
},
{
m: "with caller context",
cc: []log.Contexter{ContextA("AA"), log.NewCaller(0)},
},
} {
t.Run(tc.m, func(t *testing.T) {
log.Add(logger, tc.cc...).Info("something")
for _, context := range tc.cc {
switch s := context.(type) {
case ContextA, ContextB:
test.Includes(t, fmt.Sprintf("%q:%q", s.ContextName(), s), buff.String())
case log.Caller:
file := s()
i := strings.LastIndexByte(file, ':')
test.Includes(t, fmt.Sprintf(`%q:"%s`, s.ContextName(), file[:i+1]), buff.String())
}
}
})
}
})
t.Run("attach error", func(t *testing.T) {
var (
errOne = fmt.Errorf("error one")
errTwo = fmt.Errorf("error two")
)
for _, tc := range []struct {
m string
errs []error
err error
}{
{
m: "single call",
errs: []error{errOne},
err: errOne,
},
{
m: "multiple calls overwrite",
errs: []error{errOne, errTwo},
err: errTwo,
},
} {
t.Run(tc.m, func(t *testing.T) {
var buff bytes.Buffer
logger := log.NewLogger(&buff)
currentLogger := logger
for _, err := range tc.errs {
currentLogger = log.AttachError(currentLogger, err)
}
currentLogger.Info("something")
test.Includes(t,
fmt.Sprintf("\"attached_error\":%q", tc.err.Error()), buff.String())
})
}
})
}

78
log/logrus.go Normal file
View File

@ -0,0 +1,78 @@
package log
import (
"fmt"
"io"
"github.com/sirupsen/logrus"
)
type LogrusLogger struct {
fields Fields
logger *logrus.Logger
}
func NewLogrusLogger(out io.Writer) Logger {
lr := &logrus.Logger{
Out: out,
Formatter: new(logrus.JSONFormatter),
Level: logrus.InfoLevel,
}
return &LogrusLogger{
fields: make(Fields),
logger: lr,
}
}
func (ll *LogrusLogger) SetLogLevel(loglevel LogLevel) {
switch LogLevel(loglevel) {
case LevelDebug:
ll.logger.SetLevel(logrus.DebugLevel)
case LevelInfo:
ll.logger.SetLevel(logrus.InfoLevel)
case LevelError:
ll.logger.SetLevel(logrus.ErrorLevel)
default:
ll.logger.SetLevel(logrus.InfoLevel)
}
}
func (ll *LogrusLogger) WithField(key string, value interface{}) Logger {
return ll.With(Fields{
key: value,
})
}
func (ll *LogrusLogger) WithErr(err error) Logger {
return ll.With(Fields{
"error": err,
})
}
func (ll *LogrusLogger) With(fields Fields) Logger {
newFields := make(Fields)
for k, v := range ll.fields {
newFields[k] = v
}
for k, v := range fields {
newFields[k] = v
}
return &LogrusLogger{
fields: newFields,
logger: ll.logger,
}
}
func (ll *LogrusLogger) Debug(message string) {
ll.logger.WithFields(logrus.Fields(ll.fields)).Debug(message)
}
func (ll *LogrusLogger) Info(message string) {
ll.logger.WithFields(logrus.Fields(ll.fields)).Info(message)
}
func (ll *LogrusLogger) Error(message string) {
ll.logger.WithFields(logrus.Fields(ll.fields)).Error(fmt.Errorf(message))
}

229
log/logrus_test.go Normal file
View File

@ -0,0 +1,229 @@
package log_test
import (
"errors"
"fmt"
"testing"
"git.sr.ht/~ewintr/go-kit/log"
"git.sr.ht/~ewintr/go-kit/test"
)
func TestLogrusLogger(t *testing.T) {
logline := "test line"
tw := &testWriter{}
logger := log.NewLogrusLogger(tw)
logger.SetLogLevel(log.LevelDebug)
for _, tc := range []struct {
name string
logfunc func(string)
}{
{
name: "debug",
logfunc: logger.Debug,
},
{
name: "info",
logfunc: logger.Info,
},
{
name: "error",
logfunc: logger.Error,
},
} {
t.Run(tc.name, func(t *testing.T) {
tw.Flush()
tc.logfunc(logline)
test.Includes(t, logline, tw.LogLines...)
})
}
}
func TestLogrusLoggerSetLogLevel(t *testing.T) {
loglines := map[log.LogLevel]string{
log.LevelDebug: "debug",
log.LevelInfo: "info",
log.LevelError: "error",
}
tw := &testWriter{}
logger := log.NewLogrusLogger(tw)
for _, tc := range []struct {
name string
level log.LogLevel
exp []string
}{
{
name: "debug",
level: log.LevelDebug,
exp: []string{
loglines[log.LevelDebug],
loglines[log.LevelInfo],
loglines[log.LevelError],
},
},
{
name: "info",
level: log.LevelInfo,
exp: []string{
loglines[log.LevelInfo],
loglines[log.LevelError],
},
},
{
name: "error",
level: log.LevelError,
exp: []string{
loglines[log.LevelError],
},
},
} {
t.Run(tc.name, func(t *testing.T) {
tw.Flush()
logger.SetLogLevel(tc.level)
logger.Debug(loglines[log.LevelDebug])
logger.Info(loglines[log.LevelInfo])
logger.Error(loglines[log.LevelError])
test.Equals(t, len(tc.exp), len(tw.LogLines))
for _, ll := range tc.exp {
test.Includes(t, ll, tw.LogLines...)
}
})
}
}
func TestLogrusLoggerWithField(t *testing.T) {
tw := &testWriter{}
logger := log.NewLogrusLogger(tw)
logger.SetLogLevel(log.LevelDebug)
// the following only tests whether the shortcut to With() works
// extensive testing of the fields in combination with levels is
// in the TestLogrusLoggerWith test
for _, tc := range []struct {
name string
value interface{}
}{
{
name: "string",
value: "value",
},
} {
t.Run(tc.name, func(t *testing.T) {
tw.Flush()
key, message := "key", "message"
fieldLogger := logger.WithField(key, tc.value)
fieldLogger.Info(message)
test.Equals(t, 1, len(tw.LogLines))
test.Includes(t, message, tw.LogLines[0])
test.Includes(t, key, tw.LogLines[0])
test.Includes(t, fmt.Sprintf("%v", tc.value), tw.LogLines[0])
})
}
}
func TestLogrusLoggerWithErr(t *testing.T) {
tw := &testWriter{}
logger := log.NewLogrusLogger(tw)
logger.SetLogLevel(log.LevelDebug)
// the following only tests whether the shortcut to With() works
// extensive testing of the fields in combination with levels is
// in the TestLogrusLoggerWith test
for _, tc := range []struct {
name string
value error
}{
{
name: "string",
value: errors.New("value"),
},
} {
t.Run(tc.name, func(t *testing.T) {
tw.Flush()
key, message := "key", "message"
fieldLogger := logger.WithField(key, tc.value)
fieldLogger.Info(message)
test.Equals(t, 1, len(tw.LogLines))
test.Includes(t, message, tw.LogLines[0])
test.Includes(t, key, tw.LogLines[0])
test.Includes(t, tc.value.Error(), tw.LogLines[0])
})
}
}
func TestLogrusLoggerWith(t *testing.T) {
tw := &testWriter{}
logger := log.NewLogrusLogger(tw)
logger.SetLogLevel(log.LevelDebug)
for _, tc := range []struct {
name string
value interface{}
}{
{
name: "string",
value: "value",
},
{
name: "int",
value: 3,
},
} {
t.Run(tc.name, func(t *testing.T) {
key := "key"
fieldLogger := logger.With(log.Fields{
key: tc.value,
})
for _, lf := range []struct {
name string
log func(string)
flog func(string)
}{
{
name: "debug",
log: logger.Debug,
flog: fieldLogger.Debug,
},
{
name: "info",
log: logger.Info,
flog: fieldLogger.Info,
},
{
name: "error",
log: logger.Error,
flog: fieldLogger.Error,
},
} {
t.Run(lf.name, func(t *testing.T) {
tw.Flush()
message := "normal"
fieldMessage := "field"
lf.log(message)
lf.flog(fieldMessage)
test.Equals(t, 2, len(tw.LogLines))
// first line is normal logger
test.Includes(t, message, tw.LogLines[0])
test.NotIncludes(t, key, tw.LogLines[0])
// second line is logger with fields
test.Includes(t, fieldMessage, tw.LogLines[1])
test.Includes(t, key, tw.LogLines[1])
test.Includes(t, fmt.Sprintf("%v", tc.value), tw.LogLines[1])
})
}
})
}
}

97
log/testlogger.go Normal file
View File

@ -0,0 +1,97 @@
package log
type TestLine struct {
Level LogLevel
Message string
Fields Fields
}
type TestOut struct {
Lines []TestLine
}
func NewTestOut() *TestOut {
return &TestOut{
Lines: make([]TestLine, 0),
}
}
func (to *TestOut) Append(tl TestLine) {
to.Lines = append(to.Lines, tl)
}
func (to *TestOut) Flush() {
to.Lines = make([]TestLine, 0)
}
type TestLogger struct {
fields Fields
level LogLevel
out *TestOut
}
func NewTestLogger(out *TestOut) Logger {
return &TestLogger{
fields: make(Fields),
level: LevelDebug,
out: out,
}
}
func (tl *TestLogger) SetLogLevel(level LogLevel) {
tl.level = level
}
func (tl *TestLogger) WithField(key string, value interface{}) Logger {
return tl.With(Fields{key: value})
}
func (tl *TestLogger) WithErr(err error) Logger {
return tl.With(Fields{"error": err})
}
func (tl *TestLogger) With(fields Fields) Logger {
newFields := make(Fields)
for k, v := range tl.fields {
newFields[k] = v
}
for k, v := range fields {
newFields[k] = v
}
return &TestLogger{
fields: newFields,
level: tl.level,
out: tl.out,
}
}
func (tl *TestLogger) Debug(message string) {
tl.out.Append(TestLine{
Level: LevelDebug,
Message: message,
Fields: tl.fields,
})
tl.fields = make(Fields)
}
func (tl *TestLogger) Info(message string) {
tl.out.Append(TestLine{
Level: LevelInfo,
Message: message,
Fields: tl.fields,
})
tl.fields = make(Fields)
}
func (tl *TestLogger) Error(message string) {
tl.out.Append(TestLine{
Level: LevelError,
Message: message,
Fields: tl.fields,
})
tl.fields = make(Fields)
}

102
log/testlogger_test.go Normal file
View File

@ -0,0 +1,102 @@
package log_test
import (
"errors"
"testing"
"git.sr.ht/~ewintr/go-kit/log"
"git.sr.ht/~ewintr/go-kit/test"
)
func TestTestLogger(t *testing.T) {
message := "test line"
out := log.NewTestOut()
logger := log.NewTestLogger(out)
for _, tc := range []struct {
name string
logfunc func(string)
exp log.TestLine
}{
{
name: "debug",
logfunc: logger.Debug,
exp: log.TestLine{
Level: log.LevelDebug,
Message: message,
Fields: log.Fields{},
},
},
{
name: "info",
logfunc: logger.Info,
exp: log.TestLine{
Level: log.LevelInfo,
Message: message,
Fields: log.Fields{},
},
},
{
name: "error",
logfunc: logger.Error,
exp: log.TestLine{
Level: log.LevelError,
Message: message,
Fields: log.Fields{},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
out.Flush()
tc.logfunc(message)
test.Equals(t, 1, len(out.Lines))
test.Equals(t, tc.exp, out.Lines[0])
})
}
}
func TestTestLoggerWithField(t *testing.T) {
key, value := "key", "value"
message := "message"
out := log.NewTestOut()
logger := log.NewTestLogger(out).WithField(key, value)
logger.Info(message)
test.Equals(t, 1, len(out.Lines))
test.Equals(t, log.TestLine{
Level: log.LevelInfo,
Fields: log.Fields{key: value},
Message: message,
}, out.Lines[0])
}
func TestTestLoggerWithErr(t *testing.T) {
err := errors.New("some err")
message := "message"
out := log.NewTestOut()
logger := log.NewTestLogger(out).WithErr(err)
logger.Error(message)
test.Equals(t, 1, len(out.Lines))
test.Equals(t, log.TestLine{
Level: log.LevelError,
Fields: log.Fields{"error": err},
Message: message,
}, out.Lines[0])
}
func TestTestLoggerWith(t *testing.T) {
key, value := "key", "value"
message := "message"
out := log.NewTestOut()
logger := log.NewTestLogger(out).With(log.Fields{key: value})
logger.Info(message)
test.Equals(t, 1, len(out.Lines))
test.Equals(t, log.TestLine{
Level: log.LevelInfo,
Fields: log.Fields{key: value},
Message: message,
}, out.Lines[0])
}