diff --git a/go.mod b/go.mod index d103e6c..65a19aa 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 09bf09b..f04d093 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/log/example_interface_test.go b/log/example_interface_test.go deleted file mode 100644 index 877ea0d..0000000 --- a/log/example_interface_test.go +++ /dev/null @@ -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":"-"} -} diff --git a/log/gokit.go b/log/gokit.go deleted file mode 100644 index 04e38a0..0000000 --- a/log/gokit.go +++ /dev/null @@ -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, " ") -} diff --git a/log/gokit_test.go b/log/gokit_test.go deleted file mode 100644 index cd1ba9d..0000000 --- a/log/gokit_test.go +++ /dev/null @@ -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 -} diff --git a/log/gokitio.go b/log/gokitio.go new file mode 100644 index 0000000..7a8fbf8 --- /dev/null +++ b/log/gokitio.go @@ -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...) +} diff --git a/log/gokitio_test.go b/log/gokitio_test.go new file mode 100644 index 0000000..21d9cc8 --- /dev/null +++ b/log/gokitio_test.go @@ -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]) + }) + } + }) + } +} diff --git a/log/log.go b/log/log.go index af2bd21..4b9a376 100644 --- a/log/log.go +++ b/log/log.go @@ -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) } diff --git a/log/log_test.go b/log/log_test.go index e8c5165..46e42a5 100644 --- a/log/log_test.go +++ b/log/log_test.go @@ -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{} } diff --git a/log/logctx.go b/log/logctx.go deleted file mode 100644 index 8aec2b5..0000000 --- a/log/logctx.go +++ /dev/null @@ -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()) -} diff --git a/log/logctx_test.go b/log/logctx_test.go deleted file mode 100644 index d4050af..0000000 --- a/log/logctx_test.go +++ /dev/null @@ -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()) - }) - } - }) -} diff --git a/log/logrus.go b/log/logrus.go new file mode 100644 index 0000000..2458822 --- /dev/null +++ b/log/logrus.go @@ -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)) +} diff --git a/log/logrus_test.go b/log/logrus_test.go new file mode 100644 index 0000000..d3c913d --- /dev/null +++ b/log/logrus_test.go @@ -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]) + }) + } + }) + } +} diff --git a/log/testlogger.go b/log/testlogger.go new file mode 100644 index 0000000..e6ad10e --- /dev/null +++ b/log/testlogger.go @@ -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) +} diff --git a/log/testlogger_test.go b/log/testlogger_test.go new file mode 100644 index 0000000..2729f90 --- /dev/null +++ b/log/testlogger_test.go @@ -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]) +}