From a3fbba1b2665e488a97ab5678ea1b8cd33aba39a Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 16 Dec 2019 11:07:30 -0600 Subject: [PATCH 1/6] Remove Value interface and ForceJSON Realized we can replace ForceJSON now that we only marshal structs as JSON if they have a json struct tag and json.Marshaller can be used in place of Value as well so its not necessary. --- example_forcejson_test.go | 30 -------- example_value_test.go | 32 --------- go.mod | 24 ++++--- go.sum | 102 ++++++++++++++++++++++----- internal/assert/assert.go | 15 +++- map.go | 107 +++++++++++++---------------- map_test.go | 38 +++------- slog.go | 8 --- sloggers/slogtest/assert/assert.go | 29 ++------ 9 files changed, 173 insertions(+), 212 deletions(-) delete mode 100644 example_forcejson_test.go delete mode 100644 example_value_test.go diff --git a/example_forcejson_test.go b/example_forcejson_test.go deleted file mode 100644 index 89e2f06..0000000 --- a/example_forcejson_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package slog_test - -import ( - "context" - "fmt" - "os" - - "cdr.dev/slog" - "cdr.dev/slog/sloggers/sloghuman" -) - -type stringer struct { - X int `json:"x"` -} - -func (s *stringer) String() string { - return fmt.Sprintf("string method: %v", s.X) -} - -func (s *stringer) SlogValue() interface{} { - return slog.ForceJSON(s) -} - -func ExampleForceJSON() { - l := sloghuman.Make(os.Stdout) - - l.Info(context.Background(), "hello", slog.F("stringer", &stringer{X: 3})) - - // 2019-12-06 23:33:40.263 [INFO] hello {"stringer": {"x": 3}} -} diff --git a/example_value_test.go b/example_value_test.go deleted file mode 100644 index e889900..0000000 --- a/example_value_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package slog_test - -import ( - "context" - "os" - - "cdr.dev/slog" - "cdr.dev/slog/sloggers/sloghuman" -) - -type vals struct { - first int - second int -} - -func (s *vals) SlogValue() interface{} { - return slog.M( - slog.F("total", s.first+s.second), - slog.F("first", s.first), - slog.F("second", s.second), - ) -} - -func ExampleValue() { - l := sloghuman.Make(os.Stdout) - l.Info(context.Background(), "hello", slog.F("val", &vals{ - first: 3, - second: 6, - })) - - // 2019-12-07 21:06:14.636 [INFO] hello {"val": {"total": 9, "first": 3, "second": 6}} -} diff --git a/go.mod b/go.mod index 9247148..c965c2f 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,19 @@ module cdr.dev/slog go 1.13 require ( - cloud.google.com/go v0.43.0 - github.com/alecthomas/chroma v0.6.6 + cloud.google.com/go v0.49.0 + github.com/alecthomas/chroma v0.7.0 + github.com/dlclark/regexp2 v1.2.0 // indirect github.com/fatih/color v1.7.0 - github.com/kylelemons/godebug v1.1.0 - github.com/mattn/go-colorable v0.1.2 // indirect - github.com/mattn/go-isatty v0.0.9 // indirect - go.opencensus.io v0.22.1 - golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 - golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect - golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 - google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 + github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect + github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.11 // indirect + go.opencensus.io v0.22.2 + golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 + golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect + golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 + google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 + google.golang.org/grpc v1.25.1 // indirect ) diff --git a/go.sum b/go.sum index 38f1ff5..6bd2924 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,17 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ= +cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= @@ -10,8 +19,8 @@ github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtix github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= -github.com/alecthomas/chroma v0.6.6 h1:AwWMP1sWgMNgEiptNtV/T5GWOLtZFDdrc2ZfWx1ogmg= -github.com/alecthomas/chroma v0.6.6/go.mod h1:zVlgtbRS7BJDrDY9SB238RmpoCBCYFlLmcfZ3durxTk= +github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw= +github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= @@ -19,6 +28,7 @@ github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUS github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= @@ -29,12 +39,19 @@ 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= github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg= github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -48,10 +65,13 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e h1:4WfjkTUTsO6siF8ghDQQk6t7x/FPsv3w6MXkc47do7Q= +github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -64,16 +84,20 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= @@ -82,25 +106,36 @@ 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= -go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -108,7 +143,12 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISg golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -120,6 +160,8 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -142,9 +184,9 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -160,14 +202,25 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -179,14 +232,29 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/assert/assert.go b/internal/assert/assert.go index 2f8d2eb..71ae94f 100644 --- a/internal/assert/assert.go +++ b/internal/assert/assert.go @@ -5,15 +5,24 @@ import ( "reflect" "testing" - "github.com/kylelemons/godebug/pretty" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) +// Diff returns a diff between exp and act. +func Diff(exp, act interface{}, opts ...cmp.Option) string { + opts = append(opts, cmpopts.EquateErrors(), cmp.Exporter(func(r reflect.Type) bool { + return true + })) + return cmp.Diff(exp, act, opts...) +} + // Equal asserts exp == act. func Equal(t testing.TB, name string, exp, act interface{}) { t.Helper() - if !reflect.DeepEqual(exp, act) { + if diff := Diff(exp, act); diff != "" { t.Fatalf(`unexpected %v: diff: -%v`, name, pretty.Compare(exp, act)) +%v`, name, diff) } } diff --git a/map.go b/map.go index c810d60..c2e7dd6 100644 --- a/map.go +++ b/map.go @@ -13,11 +13,6 @@ import ( // Map represents an ordered map of fields. type Map []Field -// SlogValue implements Value. -func (m Map) SlogValue() interface{} { - return ForceJSON(m) -} - var _ json.Marshaler = Map(nil) // MarshalJSON implements json.Marshaler. @@ -27,23 +22,20 @@ var _ json.Marshaler = Map(nil) // // Every field value is encoded with the following process: // -// 1. slog.Value is checked to allow any type to replace its representation for logging. -// -// 2. json.Marshaller is handled. +// 1. json.Marshaller is handled. // // 2. xerrors.Formatter is handled. // -// 3. Protobufs are encoded with json.Marshal. +// 3. structs that have a field with a json tag are encoded with json.Marshal. // -// 4. error and fmt.Stringer are used if possible. +// 4. error and fmt.Stringer is handled. // // 5. slices and arrays go through the encode function for every element. // -// 6. If the value is a struct without exported fields or a type that -// cannot be encoded with json.Marshal (like channels) then -// fmt.Sprintf("%+v") is used. +// 6. If the value cannot be encoded directly with json.Marshal (like channels) +// then fmt.Sprintf("%+v") is used. // -// 8. json.Marshal(v) is used for all other values. +// 7. json.Marshal(v) is used for all other values. func (m Map) MarshalJSON() ([]byte, error) { b := &bytes.Buffer{} b.WriteByte('{') @@ -62,19 +54,6 @@ func (m Map) MarshalJSON() ([]byte, error) { return b.Bytes(), nil } -// ForceJSON ensures the value is logged via json.Marshal. -// -// Use it when implementing SlogValue to ensure a structured -// representation of a struct if you know it's capable even -// when it implements fmt.Stringer or error. -func ForceJSON(v interface{}) interface{} { - return jsonVal{v: v} -} - -type jsonVal struct { - v interface{} -} - func marshalList(rv reflect.Value) []byte { b := &bytes.Buffer{} b.WriteByte('[') @@ -93,51 +72,57 @@ func marshalList(rv reflect.Value) []byte { func encode(v interface{}) []byte { switch v := v.(type) { - case Value: - return encode(v.SlogValue()) case json.Marshaler: return encodeJSON(v) - case jsonVal: - return encodeJSON(v.v) case xerrors.Formatter: return encode(errorChain(v)) - case interface { - ProtoMessage() - }: - return encode(ForceJSON(v)) + } + + rv := reflect.Indirect(reflect.ValueOf(v)) + if !rv.IsValid() { + return encodeJSON(v) + } + + if rv.Kind() == reflect.Struct { + b, ok := encodeStruct(rv) + if ok { + return b + } + } + + switch v.(type) { case error, fmt.Stringer: return encode(fmt.Sprint(v)) - default: - rv := reflect.Indirect(reflect.ValueOf(v)) - if !rv.IsValid() { - return encodeJSON(v) - } + } - switch rv.Type().Kind() { - case reflect.Slice: - if !rv.IsNil() { - return marshalList(rv) - } - case reflect.Array: + switch rv.Type().Kind() { + case reflect.Slice: + if !rv.IsNil() { return marshalList(rv) - case reflect.Struct: - typ := rv.Type() - for i := 0; i < rv.NumField(); i++ { - // Found an exported field. - if typ.Field(i).PkgPath == "" { - return encodeJSON(v) - } - } - - return encodeJSON(fmt.Sprintf("%+v", v)) - case reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func: - // These types cannot be directly encoded with json.Marshal. - // See https://golang.org/pkg/encoding/json/#Marshal - return encodeJSON(fmt.Sprintf("%+v", v)) } + case reflect.Array: + return marshalList(rv) + case reflect.Struct, reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func: + // These types cannot be directly encoded with json.Marshal. + // See https://golang.org/pkg/encoding/json/#Marshal + return encodeJSON(fmt.Sprintf("%+v", v)) + } - return encodeJSON(v) + return encodeJSON(v) +} + +func encodeStruct(rv reflect.Value) ([]byte, bool) { + if rv.Kind() == reflect.Struct { + for i := 0; i < rv.NumField(); i++ { + ft := rv.Type().Field(i) + // Found a field with a json tag. + if ft.Tag.Get("json") != "" { + return encodeJSON(rv.Interface()), true + } + } } + + return nil, false } func encodeJSON(v interface{}) []byte { diff --git a/map_test.go b/map_test.go index 3b98d7a..db8e5ea 100644 --- a/map_test.go +++ b/map_test.go @@ -86,18 +86,18 @@ func TestMap(t *testing.T) { mapTestFile := strings.Replace(mapTestFile, "_test", "", 1) test(t, slog.M( - slog.F("meow", slog.ForceJSON(complex(10, 10))), + slog.F("meow", complexJSON(complex(10, 10))), ), `{ "meow": { "error": [ { "msg": "failed to marshal to JSON", "fun": "cdr.dev/slog.encodeJSON", - "loc": "`+mapTestFile+`:147" + "loc": "`+mapTestFile+`:132" }, - "json: unsupported type: complex128" + "json: error calling MarshalJSON for type slog_test.complexJSON: json: unsupported type: complex128" ], - "type": "complex128", + "type": "slog_test.complexJSON", "value": "(10+10i)" } }`) @@ -163,26 +163,6 @@ func TestMap(t *testing.T) { }`) }) - t.Run("forceJSON", func(t *testing.T) { - t.Parallel() - - test(t, slog.M( - slog.F("error", slog.ForceJSON(io.EOF)), - ), `{ - "error": {} - }`) - }) - - t.Run("value", func(t *testing.T) { - t.Parallel() - - test(t, slog.M( - slog.F("error", meow{1}), - ), `{ - "error": "xdxd" - }`) - }) - t.Run("nilSlice", func(t *testing.T) { t.Parallel() @@ -246,10 +226,6 @@ type meow struct { a int } -func (m meow) SlogValue() interface{} { - return "xdxd" -} - func indentJSON(t *testing.T, j string) string { b := &bytes.Buffer{} err := json.Indent(b, []byte(j), "", strings.Repeat(" ", 4)) @@ -263,3 +239,9 @@ func marshalJSON(t *testing.T, m slog.Map) string { assert.Success(t, "marshal map to JSON", err) return indentJSON(t, string(actb)) } + +type complexJSON complex128 + +func (c complexJSON) MarshalJSON() ([]byte, error) { + return json.Marshal(complex128(c)) +} diff --git a/slog.go b/slog.go index d2cf1b3..a291f27 100644 --- a/slog.go +++ b/slog.go @@ -226,14 +226,6 @@ func M(fs ...Field) Map { return fs } -// Value represents a log value. -// -// Implement SlogValue in your own types to override -// the value encoded when logging. -type Value interface { - SlogValue() interface{} -} - // Error is the standard key used for logging a Go error value. func Error(err error) Field { return F("error", err) diff --git a/sloggers/slogtest/assert/assert.go b/sloggers/slogtest/assert/assert.go index 0989db5..e246a2f 100644 --- a/sloggers/slogtest/assert/assert.go +++ b/sloggers/slogtest/assert/assert.go @@ -6,14 +6,13 @@ package assert // import "cdr.dev/slog/sloggers/slogtest/assert" import ( - "errors" - "reflect" "strings" "testing" - "github.com/kylelemons/godebug/pretty" + "github.com/google/go-cmp/cmp" "cdr.dev/slog" + "cdr.dev/slog/internal/assert" "cdr.dev/slog/sloggers/slogtest" ) @@ -22,17 +21,14 @@ import ( // If they are not equal, it will fatal the test with a diff of the // two objects. // -// If act or exp is an error it will be unwrapped. -func Equal(t testing.TB, name string, exp, act interface{}) { +// Errors will be compared with errors.Is. +func Equal(t testing.TB, name string, exp, act interface{}, opts ...cmp.Option) { slog.Helper() - exp = unwrapErr(exp) - act = unwrapErr(act) - - if !reflect.DeepEqual(exp, act) { + if diff := assert.Diff(exp, act, opts...); diff != "" { slogtest.Fatal(t, "unexpected value", slog.F("name", name), - slog.F("diff", pretty.Compare(exp, act)), + slog.F("diff", diff), ) } } @@ -64,19 +60,6 @@ func Error(t testing.TB, name string, err error) { } } -func unwrapErr(v interface{}) interface{} { - err, ok := v.(error) - if !ok { - return v - } - uerr := errors.Unwrap(err) - for uerr != nil { - err = uerr - uerr = errors.Unwrap(uerr) - } - return err -} - // ErrorContains asserts err != nil and err.Error() contains sub. // // The match will be case insensitive. From 56c5fd9ffa13a5d08baf001ce1f89a36c9c5b979 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 16 Dec 2019 11:20:42 -0600 Subject: [PATCH 2/6] Update docs --- README.md | 8 +++----- example_test.go | 5 +++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 85d77d2..9fd2562 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ log := sloghuman.Make(os.Stdout) log.Info(context.Background(), "my message here", slog.F("field_name", "something or the other"), slog.F("some_map", slog.M( - slog.F("nested_fields", "wowow"), + slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)), )), slog.Error( xerrors.Errorf("wrap1: %w", @@ -52,7 +52,7 @@ log.Info(context.Background(), "my message here", ) ``` -![Example output screenshot](https://i.imgur.com/7MJM0VE.png) +![Example output screenshot](https://i.imgur.com/KGRmQFo.png) ## Why? @@ -97,9 +97,7 @@ Here is a list of reasons how we improved on zap with slog. - zap is hard and confusing to extend. There are too many structures and configuration options. 1. Structured logging of Go structures with `json.Marshal` - - All values will be logged with `json.Marshal` unless they implement `fmt.Stringer` or `error`. - - You can force JSON by using [`slog.ForceJSON`](https://godoc.org/cdr.dev/slog#ForceJSON). - - One may implement [`slog.Value`](https://godoc.org/cdr.dev/slog#Value) to override the representation completely. + - Entire encoding process is documented on [godoc](https://godoc.org/cdr.dev/slog#Map.MarshalJSON). - With zap, We found ourselves often implementing zap's [ObjectMarshaler](https://godoc.org/go.uber.org/zap/zapcore#ObjectMarshaler) to log Go structures. This was verbose and most of the time we ended up only implementing `fmt.Stringer` and using `zap.Stringer` instead. diff --git a/example_test.go b/example_test.go index e32b687..d0123bb 100644 --- a/example_test.go +++ b/example_test.go @@ -6,6 +6,7 @@ import ( "net" "os" "testing" + "time" "go.opencensus.io/trace" "golang.org/x/xerrors" @@ -22,7 +23,7 @@ func Example() { log.Info(context.Background(), "my message here", slog.F("field_name", "something or the other"), slog.F("some_map", slog.M( - slog.F("nested_fields", "wowow"), + slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)), )), slog.Error( xerrors.Errorf("wrap1: %w", @@ -33,7 +34,7 @@ func Example() { ), ) - // 2019-12-09 05:04:53.398 [INFO] my message here {"field_name": "something or the other", "some_map": {"nested_fields": "wowow"}} ... + // 2019-12-09 05:04:53.398 [INFO] my message here {"field_name": "something or the other", "some_map": {"nested_fields": "2000-02-05T04:04:04Z"}} ... // "error": wrap1: // main.main // /Users/nhooyr/src/cdr/scratch/example.go:22 From f7a90d8c4e3665558b8c161a9c69bcb2ec1edfb9 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 16 Dec 2019 11:32:40 -0600 Subject: [PATCH 3/6] Improve docs and add more examples --- example_marshaller_test.go | 34 ++++++++++++++++++++++++++++++++++ example_test.go | 25 ++++++++++++++++++++++--- map.go | 3 +-- map_test.go | 2 +- 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 example_marshaller_test.go diff --git a/example_marshaller_test.go b/example_marshaller_test.go new file mode 100644 index 0000000..e4e6992 --- /dev/null +++ b/example_marshaller_test.go @@ -0,0 +1,34 @@ +package slog_test + +import ( + "context" + "os" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" +) + +type myStruct struct { + foo int + bar int +} + +func (s myStruct) MarshalJSON() ([]byte, error) { + return slog.M( + slog.F("foo", s.foo), + slog.F("bar", s.foo), + ).MarshalJSON() +} + +func Example_marshaller() { + l := sloghuman.Make(os.Stdout) + + l.Info(context.Background(), "wow", + slog.F("myStruct", myStruct{ + foo: 1, + bar: 2, + }), + ) + + // 2019-12-16 17:31:37.120 [INFO] wow {"myStruct": {"foo": 1, "bar": 1}} +} diff --git a/example_test.go b/example_test.go index d0123bb..f3577ac 100644 --- a/example_test.go +++ b/example_test.go @@ -44,6 +44,26 @@ func Example() { // - EOF } +func Example_struct() { + l := sloghuman.Make(os.Stdout) + + type hello struct { + Meow int `json:"meow"` + Bar string `json:"bar"` + M time.Time `json:"m"` + } + + l.Info(context.Background(), "check out my structure", + slog.F("hello", hello{ + Meow: 1, + Bar: "barbar", + M: time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC), + }), + ) + + // 2019-12-16 17:31:51.769 [INFO] check out my structure {"hello": {"meow": 1, "bar": "barbar", "m": "2000-02-05T04:04:04Z"}} +} + func Example_testing() { // Provided by the testing package in tests. var t testing.TB @@ -66,17 +86,16 @@ func Example_tracing() { } func Example_multiple() { - ctx := context.Background() l := sloghuman.Make(os.Stdout) f, err := os.OpenFile("stackdriver", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) if err != nil { - l.Fatal(ctx, "failed to open stackdriver log file", slog.Error(err)) + l.Fatal(context.Background(), "failed to open stackdriver log file", slog.Error(err)) } l = slog.Make(l, slogstackdriver.Make(f)) - l.Info(ctx, "log to stdout and stackdriver") + l.Info(context.Background(), "log to stdout and stackdriver") // 2019-12-07 20:59:55.790 [INFO] log to stdout and stackdriver } diff --git a/map.go b/map.go index c2e7dd6..636f4ee 100644 --- a/map.go +++ b/map.go @@ -32,8 +32,7 @@ var _ json.Marshaler = Map(nil) // // 5. slices and arrays go through the encode function for every element. // -// 6. If the value cannot be encoded directly with json.Marshal (like channels) -// then fmt.Sprintf("%+v") is used. +// 6. For values that cannot be encoded with json.Marshal, fmt.Sprintf("%+v") is used. // // 7. json.Marshal(v) is used for all other values. func (m Map) MarshalJSON() ([]byte, error) { diff --git a/map_test.go b/map_test.go index db8e5ea..e15a6ee 100644 --- a/map_test.go +++ b/map_test.go @@ -93,7 +93,7 @@ func TestMap(t *testing.T) { { "msg": "failed to marshal to JSON", "fun": "cdr.dev/slog.encodeJSON", - "loc": "`+mapTestFile+`:132" + "loc": "`+mapTestFile+`:131" }, "json: error calling MarshalJSON for type slog_test.complexJSON: json: unsupported type: complex128" ], From 2b49514fd5bbc6081a1d863ccbc3161d488e419b Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 31 Mar 2020 13:25:08 -0500 Subject: [PATCH 4/6] Refactor for Context only logging --- README.md | 6 +- context.go | 27 ++++ example_helper_test.go | 4 +- example_marshaller_test.go | 4 +- example_test.go | 51 ++++---- export_test.go | 9 +- map.go | 2 +- map_test.go | 6 +- s.go | 17 ++- s_test.go | 9 +- slog.go | 117 ++++++++++++------ slog_test.go | 54 ++++---- sloggers/sloghuman/sloghuman.go | 6 +- sloggers/sloghuman/sloghuman_test.go | 11 +- sloggers/slogjson/slogjson.go | 6 +- sloggers/slogjson/slogjson_test.go | 6 +- sloggers/slogstackdriver/slogstackdriver.go | 8 +- .../slogstackdriver/slogstackdriver_test.go | 6 +- sloggers/slogtest/assert/assert.go | 2 +- sloggers/slogtest/t.go | 20 +-- sloggers/slogtest/t_test.go | 9 +- 21 files changed, 230 insertions(+), 150 deletions(-) create mode 100644 context.go diff --git a/README.md b/README.md index 9fd2562..50550b8 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ go get cdr.dev/slog Many more examples available at [godoc](https://godoc.org/cdr.dev/slog#pkg-examples). ```go -log := sloghuman.Make(os.Stdout) +ctx := sloghuman.Make(ctx, os.Stdout) -log.Info(context.Background(), "my message here", +slog.Info(ctx, "my message here", slog.F("field_name", "something or the other"), slog.F("some_map", slog.M( slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)), @@ -87,6 +87,8 @@ Here is a list of reasons how we improved on zap with slog. 1. Full [context.Context](https://blog.golang.org/context) support - `slog` lets you set fields in a `context.Context` such that any log with the context prints those fields. + - `slog` stores the actual logger in the `context.Context`, following the example of + [the Go trace library](https://golang.org/pkg/runtime/trace/). Our logger doesn't bloat type and function signatures. - We wanted to be able to pull up all relevant logs for a given trace, user or request. With zap, we were plugging these fields in for every relevant log or passing around a logger with the fields set. This became very verbose. diff --git a/context.go b/context.go new file mode 100644 index 0000000..e3f20c1 --- /dev/null +++ b/context.go @@ -0,0 +1,27 @@ +package slog + +import "context" + +type loggerCtxKey = struct{} + +// SinkContext is used by slog.Make to compose many loggers together. +type SinkContext struct { + Sink + context.Context +} + +func contextWithLogger(ctx context.Context, l logger) SinkContext { + ctx = context.WithValue(ctx, loggerCtxKey{}, l) + return SinkContext{ + Context: ctx, + Sink: l, + } +} + +func loggerFromContext(ctx context.Context) (logger, bool) { + v := ctx.Value(loggerCtxKey{}) + if v == nil { + return logger{}, false + } + return v.(logger), true +} diff --git a/example_helper_test.go b/example_helper_test.go index e06d372..853341d 100644 --- a/example_helper_test.go +++ b/example_helper_test.go @@ -12,12 +12,12 @@ import ( func httpLogHelper(ctx context.Context, status int) { slog.Helper() - l.Info(ctx, "sending HTTP response", + slog.Info(ctx, "sending HTTP response", slog.F("status", status), ) } -var l = sloghuman.Make(os.Stdout) +var l = sloghuman.Make(context.Background(), os.Stdout) func ExampleHelper() { ctx := context.Background() diff --git a/example_marshaller_test.go b/example_marshaller_test.go index e4e6992..514ab62 100644 --- a/example_marshaller_test.go +++ b/example_marshaller_test.go @@ -21,9 +21,9 @@ func (s myStruct) MarshalJSON() ([]byte, error) { } func Example_marshaller() { - l := sloghuman.Make(os.Stdout) + ctx := sloghuman.Make(context.Background(), os.Stdout) - l.Info(context.Background(), "wow", + slog.Info(ctx, "wow", slog.F("myStruct", myStruct{ foo: 1, bar: 2, diff --git a/example_test.go b/example_test.go index f3577ac..3627532 100644 --- a/example_test.go +++ b/example_test.go @@ -18,14 +18,14 @@ import ( ) func Example() { - log := sloghuman.Make(os.Stdout) + ctx := sloghuman.Make(context.Background(), os.Stdout) - log.Info(context.Background(), "my message here", + slog.Info(ctx, "my message here", slog.F("field_name", "something or the other"), slog.F("some_map", slog.M( slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)), )), - slog.Error( + slog.Err( xerrors.Errorf("wrap1: %w", xerrors.Errorf("wrap2: %w", io.EOF, @@ -45,7 +45,7 @@ func Example() { } func Example_struct() { - l := sloghuman.Make(os.Stdout) + ctx := sloghuman.Make(context.Background(), os.Stdout) type hello struct { Meow int `json:"meow"` @@ -53,7 +53,7 @@ func Example_struct() { M time.Time `json:"m"` } - l.Info(context.Background(), "check out my structure", + slog.Info(ctx, "check out my structure", slog.F("hello", hello{ Meow: 1, Bar: "barbar", @@ -76,26 +76,27 @@ func Example_testing() { } func Example_tracing() { - log := sloghuman.Make(os.Stdout) + var ctx context.Context + ctx = sloghuman.Make(context.Background(), os.Stdout) - ctx, _ := trace.StartSpan(context.Background(), "spanName") + ctx, _ = trace.StartSpan(ctx, "spanName") - log.Info(ctx, "my msg", slog.F("hello", "hi")) + slog.Info(ctx, "my msg", slog.F("hello", "hi")) // 2019-12-09 21:59:48.110 [INFO] my msg {"trace": "f143d018d00de835688453d8dc55c9fd", "span": "f214167bf550afc3", "hello": "hi"} } func Example_multiple() { - l := sloghuman.Make(os.Stdout) + ctx := sloghuman.Make(context.Background(), os.Stdout) f, err := os.OpenFile("stackdriver", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) if err != nil { - l.Fatal(context.Background(), "failed to open stackdriver log file", slog.Error(err)) + slog.Fatal(ctx, "failed to open stackdriver log file", slog.Err(err)) } - l = slog.Make(l, slogstackdriver.Make(f)) + ctx = slog.Make(l, slogstackdriver.Make(ctx, f)) - l.Info(context.Background(), "log to stdout and stackdriver") + slog.Info(ctx, "log to stdout and stackdriver") // 2019-12-07 20:59:55.790 [INFO] log to stdout and stackdriver } @@ -103,41 +104,41 @@ func Example_multiple() { func ExampleWith() { ctx := slog.With(context.Background(), slog.F("field", 1)) - l := sloghuman.Make(os.Stdout) - l.Info(ctx, "msg") + ctx = sloghuman.Make(ctx, os.Stdout) + slog.Info(ctx, "msg") // 2019-12-07 20:54:23.986 [INFO] msg {"field": 1} } func ExampleStdlib() { ctx := slog.With(context.Background(), slog.F("field", 1)) - l := slog.Stdlib(ctx, sloghuman.Make(os.Stdout)) + l := slog.Stdlib(sloghuman.Make(ctx, os.Stdout)) l.Print("msg") // 2019-12-07 20:54:23.986 [INFO] (stdlib) msg {"field": 1} } -func ExampleLogger_Named() { +func ExampleNamed() { ctx := context.Background() - l := sloghuman.Make(os.Stdout) - l = l.Named("http") - l.Info(ctx, "received request", slog.F("remote address", net.IPv4(127, 0, 0, 1))) + ctx = sloghuman.Make(ctx, os.Stdout) + ctx = slog.Named(ctx, "http") + slog.Info(ctx, "received request", slog.F("remote address", net.IPv4(127, 0, 0, 1))) // 2019-12-07 21:20:56.974 [INFO] (http) received request {"remote address": "127.0.0.1"} } -func ExampleLogger_Leveled() { +func ExampleLeveled() { ctx := context.Background() - l := sloghuman.Make(os.Stdout) - l.Debug(ctx, "testing1") - l.Info(ctx, "received request") + ctx = sloghuman.Make(ctx, os.Stdout) + slog.Debug(ctx, "testing1") + slog.Info(ctx, "received request") - l = l.Leveled(slog.LevelDebug) + ctx = slog.Leveled(ctx, slog.LevelDebug) - l.Debug(ctx, "testing2") + slog.Debug(ctx, "testing2") // 2019-12-07 21:26:20.945 [INFO] received request // 2019-12-07 21:26:20.945 [DEBUG] testing2 diff --git a/export_test.go b/export_test.go index 1266811..a682e20 100644 --- a/export_test.go +++ b/export_test.go @@ -1,5 +1,12 @@ package slog -func (l *Logger) SetExit(fn func(int)) { +import "context" + +func SetExit(ctx context.Context, fn func(int)) context.Context { + l, ok := loggerFromContext(ctx) + if !ok { + return ctx + } l.exit = fn + return contextWithLogger(ctx, l) } diff --git a/map.go b/map.go index 636f4ee..a1b594b 100644 --- a/map.go +++ b/map.go @@ -128,7 +128,7 @@ func encodeJSON(v interface{}) []byte { b, err := json.Marshal(v) if err != nil { return encode(M( - Error(xerrors.Errorf("failed to marshal to JSON: %w", err)), + Err(xerrors.Errorf("failed to marshal to JSON: %w", err)), F("type", reflect.TypeOf(v)), F("value", fmt.Sprintf("%+v", v)), )) diff --git a/map_test.go b/map_test.go index e15a6ee..03d7886 100644 --- a/map_test.go +++ b/map_test.go @@ -37,7 +37,7 @@ func TestMap(t *testing.T) { } test(t, slog.M( - slog.Error( + slog.Err( xerrors.Errorf("wrap1: %w", xerrors.Errorf("wrap2: %w", io.EOF, @@ -222,10 +222,6 @@ func TestMap(t *testing.T) { }) } -type meow struct { - a int -} - func indentJSON(t *testing.T, j string) string { b := &bytes.Buffer{} err := json.Indent(b, []byte(j), "", strings.Repeat(" ", 4)) diff --git a/s.go b/s.go index fa8917e..e85195c 100644 --- a/s.go +++ b/s.go @@ -3,6 +3,7 @@ package slog import ( "context" "log" + "os" "strings" ) @@ -15,14 +16,19 @@ import ( // You can redirect the stdlib default logger with log.SetOutput // to the Writer on the logger returned by this function. // See the example. -func Stdlib(ctx context.Context, l Logger) *log.Logger { - l.skip += 3 +func Stdlib(ctx context.Context) *log.Logger { + ctx = Named(ctx, "stdlib") - l = l.Named("stdlib") + l, ok := loggerFromContext(ctx) + if !ok { + // Give stderr logger if no slog. + return log.New(os.Stderr, "", 0) + } + l.skip += 3 + ctx = contextWithLogger(ctx, l) w := &stdlogWriter{ ctx: ctx, - l: l, } return log.New(w, "", 0) @@ -30,7 +36,6 @@ func Stdlib(ctx context.Context, l Logger) *log.Logger { type stdlogWriter struct { ctx context.Context - l Logger } func (w stdlogWriter) Write(p []byte) (n int, err error) { @@ -39,7 +44,7 @@ func (w stdlogWriter) Write(p []byte) (n int, err error) { // we do not want. msg = strings.TrimSuffix(msg, "\n") - w.l.Info(w.ctx, msg) + Info(w.ctx, msg) return len(p), nil } diff --git a/s_test.go b/s_test.go index a589460..5006c04 100644 --- a/s_test.go +++ b/s_test.go @@ -2,6 +2,7 @@ package slog_test import ( "bytes" + "context" "testing" "cdr.dev/slog" @@ -14,14 +15,16 @@ func TestStdlib(t *testing.T) { t.Parallel() b := &bytes.Buffer{} - l := slog.Make(sloghuman.Make(b)).With( + ctx := context.Background() + ctx = slog.Make(sloghuman.Make(ctx, b)) + ctx = slog.With(ctx, slog.F("hi", "we"), ) - stdlibLog := slog.Stdlib(bg, l) + stdlibLog := slog.Stdlib(ctx) stdlibLog.Println("stdlib") et, rest, err := entryhuman.StripTimestamp(b.String()) assert.Success(t, "strip timestamp", err) assert.False(t, "timestamp", et.IsZero()) - assert.Equal(t, "entry", " [INFO]\t(stdlib)\t\tstdlib\t{\"hi\": \"we\"}\n", rest) + assert.Equal(t, "entry", " [INFO]\t(stdlib)\t\tstdlib\t{\"hi\": \"we\"}\n", rest) } diff --git a/slog.go b/slog.go index a291f27..bfbecde 100644 --- a/slog.go +++ b/slog.go @@ -4,8 +4,8 @@ // // The examples are the best way to understand how to use this library effectively. // -// The Logger type implements a high level API around the Sink interface. -// Logger implements Sink as well to allow composition. +// The logger type implements a high level API around the Sink interface. +// logger implements Sink as well to allow composition. // // Implementations of the Sink interface are available in the sloggers subdirectory. package slog // import "cdr.dev/slog" @@ -21,7 +21,7 @@ import ( "go.opencensus.io/trace" ) -// Sink is the destination of a Logger. +// Sink is the destination of a logger. // // All sinks must be safe for concurrent use. type Sink interface { @@ -33,7 +33,7 @@ type Sink interface { // underlying sinks. // // It extends the entry with the set fields and names. -func (l Logger) LogEntry(ctx context.Context, e SinkEntry) { +func (l logger) LogEntry(ctx context.Context, e SinkEntry) { if e.Level < l.level { return } @@ -46,17 +46,27 @@ func (l Logger) LogEntry(ctx context.Context, e SinkEntry) { } } -// Sync calls Sync on all the underlying sinks. -func (l Logger) Sync() { +func (l logger) Sync() { for _, s := range l.sinks { s.Sync() } } -// Logger wraps Sink with a nice API to log entries. +// Sync calls Sync on all the underlying sinks. +func Sync(ctx context.Context) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } + l.Sync() + return +} + +// logger wraps Sink with a nice API to log entries. // -// Logger is safe for concurrent use. -type Logger struct { +// logger is safe for concurrent use. +// It is unexported because callers should only log via a context. +type logger struct { sinks []Sink level Level @@ -68,34 +78,53 @@ type Logger struct { } // Make creates a logger that writes logs to the passed sinks at LevelInfo. -func Make(sinks ...Sink) Logger { - return Logger{ - sinks: sinks, - level: LevelInfo, - - exit: os.Exit, +func Make(ctx context.Context, sinks ...Sink) SinkContext { + // Just in case the ctx has a logger, start with it. + l, _ := loggerFromContext(ctx) + l.sinks = append(l.sinks, sinks...) + if l.level == 0 { + l.level = LevelInfo } + l.exit = os.Exit + + return contextWithLogger(ctx, l) } // Debug logs the msg and fields at LevelDebug. -func (l Logger) Debug(ctx context.Context, msg string, fields ...Field) { +func Debug(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelDebug, msg, fields) } // Info logs the msg and fields at LevelInfo. -func (l Logger) Info(ctx context.Context, msg string, fields ...Field) { +func Info(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelInfo, msg, fields) } // Warn logs the msg and fields at LevelWarn. -func (l Logger) Warn(ctx context.Context, msg string, fields ...Field) { +func Warn(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelWarn, msg, fields) } // Error logs the msg and fields at LevelError. // // It will then Sync(). -func (l Logger) Error(ctx context.Context, msg string, fields ...Field) { +func Error(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelError, msg, fields) l.Sync() } @@ -103,7 +132,11 @@ func (l Logger) Error(ctx context.Context, msg string, fields ...Field) { // Critical logs the msg and fields at LevelCritical. // // It will then Sync(). -func (l Logger) Critical(ctx context.Context, msg string, fields ...Field) { +func Critical(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelCritical, msg, fields) l.Sync() } @@ -111,41 +144,47 @@ func (l Logger) Critical(ctx context.Context, msg string, fields ...Field) { // Fatal logs the msg and fields at LevelFatal. // // It will then Sync() and os.Exit(1). -func (l Logger) Fatal(ctx context.Context, msg string, fields ...Field) { +func Fatal(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + os.Stderr.WriteString("Fatal called but no Logger in context") + // The caller expects the program to terminate after Fatal no matter what. + l.exit(1) + return + } l.log(ctx, LevelFatal, msg, fields) l.Sync() l.exit(1) } -// With returns a Logger that prepends the given fields on every -// logged entry. -// -// It will append to any fields already in the Logger. -func (l Logger) With(fields ...Field) Logger { - l.fields = l.fields.append(fields) - return l -} - // Named appends the name to the set names // on the logger. -func (l Logger) Named(name string) Logger { +func Named(ctx context.Context, name string) context.Context { + l, ok := loggerFromContext(ctx) + if !ok { + return ctx + } l.names = appendNames(l.names, name) - return l + return contextWithLogger(ctx, l) } -// Leveled returns a Logger that only logs entries +// Leveled returns a logger that only logs entries // equal to or above the given level. -func (l Logger) Leveled(level Level) Logger { +func Leveled(ctx context.Context, level Level) context.Context { + l, ok := loggerFromContext(ctx) + if !ok { + return ctx + } l.level = level - return l + return contextWithLogger(ctx, l) } -func (l Logger) log(ctx context.Context, level Level, msg string, fields Map) { +func (l logger) log(ctx context.Context, level Level, msg string, fields Map) { ent := l.entry(ctx, level, msg, fields) l.LogEntry(ctx, ent) } -func (l Logger) entry(ctx context.Context, level Level, msg string, fields Map) SinkEntry { +func (l logger) entry(ctx context.Context, level Level, msg string, fields Map) SinkEntry { ent := SinkEntry{ Time: time.Now().UTC(), Level: level, @@ -226,8 +265,8 @@ func M(fs ...Field) Map { return fs } -// Error is the standard key used for logging a Go error value. -func Error(err error) Field { +// Err is the standard key used for logging a Go error value. +func Err(err error) Field { return F("error", err) } diff --git a/slog_test.go b/slog_test.go index a55def2..3c88989 100644 --- a/slog_test.go +++ b/slog_test.go @@ -28,8 +28,6 @@ func (s *fakeSink) Sync() { s.syncs++ } -var bg = context.Background() - func TestLogger(t *testing.T) { t.Parallel() @@ -38,11 +36,12 @@ func TestLogger(t *testing.T) { s1 := &fakeSink{} s2 := &fakeSink{} - l := slog.Make(s1, s2) - l = l.Leveled(slog.LevelError) + var ctx context.Context + ctx = slog.Make(context.Background(), s1, s2) + ctx = slog.Leveled(ctx, slog.LevelError) - l.Info(bg, "wow", slog.Error(io.EOF)) - l.Error(bg, "meow", slog.Error(io.ErrUnexpectedEOF)) + slog.Info(ctx, "wow", slog.Err(io.EOF)) + slog.Error(ctx, "meow", slog.Err(io.ErrUnexpectedEOF)) assert.Equal(t, "syncs", 1, s1.syncs) assert.Len(t, "entries", 1, s1.entries) @@ -54,13 +53,14 @@ func TestLogger(t *testing.T) { t.Parallel() s := &fakeSink{} - l := slog.Make(s) + var ctx context.Context + ctx = slog.Make(context.Background(), s) h := func(ctx context.Context) { slog.Helper() - l.Info(ctx, "logging in helper") + slog.Info(ctx, "logging in helper") } - ctx := slog.With(bg, slog.F( + ctx = slog.With(ctx, slog.F( "ctx", 1024), ) h(ctx) @@ -86,15 +86,16 @@ func TestLogger(t *testing.T) { t.Parallel() s := &fakeSink{} - l := slog.Make(s) - l = l.Named("hello") - l = l.Named("hello2") + var ctx context.Context + ctx = slog.Make(context.Background(), s) + ctx = slog.Named(ctx, "hello") + ctx = slog.Named(ctx, "hello2") - ctx, span := trace.StartSpan(bg, "trace") + ctx, span := trace.StartSpan(ctx, "trace") ctx = slog.With(ctx, slog.F("ctx", io.EOF)) - l = l.With(slog.F("with", 2)) + ctx = slog.With(ctx, slog.F("with", 2)) - l.Info(ctx, "meow", slog.F("hi", "xd")) + slog.Info(ctx, "meow", slog.F("hi", "xd")) assert.Len(t, "entries", 1, s.entries) assert.Equal(t, "entry", slog.SinkEntry{ @@ -107,13 +108,13 @@ func TestLogger(t *testing.T) { File: slogTestFile, Func: "cdr.dev/slog_test.TestLogger.func3", - Line: 97, + Line: 98, SpanContext: span.SpanContext(), Fields: slog.M( - slog.F("with", 2), slog.F("ctx", io.EOF), + slog.F("with", 2), slog.F("hi", "xd"), ), }, s.entries[0]) @@ -123,20 +124,21 @@ func TestLogger(t *testing.T) { t.Parallel() s := &fakeSink{} - l := slog.Make(s) + var ctx context.Context + ctx = slog.Make(context.Background(), s) exits := 0 - l.SetExit(func(int) { + ctx = slog.SetExit(ctx, func(int) { exits++ }) - l = l.Leveled(slog.LevelDebug) - l.Debug(bg, "") - l.Info(bg, "") - l.Warn(bg, "") - l.Error(bg, "") - l.Critical(bg, "") - l.Fatal(bg, "") + ctx = slog.Leveled(ctx, slog.LevelDebug) + slog.Debug(ctx, "") + slog.Info(ctx, "") + slog.Warn(ctx, "") + slog.Error(ctx, "") + slog.Critical(ctx, "") + slog.Fatal(ctx, "") assert.Len(t, "entries", 6, s.entries) assert.Equal(t, "syncs", 3, s.syncs) diff --git a/sloggers/sloghuman/sloghuman.go b/sloggers/sloghuman/sloghuman.go index 719db7c..18cbb58 100644 --- a/sloggers/sloghuman/sloghuman.go +++ b/sloggers/sloghuman/sloghuman.go @@ -17,8 +17,8 @@ import ( // // If the writer implements Sync() error then // it will be called when syncing. -func Make(w io.Writer) slog.Logger { - return slog.Make(&humanSink{ +func Make(ctx context.Context, w io.Writer) slog.SinkContext { + return slog.Make(ctx, &humanSink{ w: syncwriter.New(w), w2: w, }) @@ -29,7 +29,7 @@ type humanSink struct { w2 io.Writer } -func (s humanSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { +func (s humanSink) LogEntry(_ context.Context, ent slog.SinkEntry) { str := entryhuman.Fmt(s.w2, ent) lines := strings.Split(str, "\n") diff --git a/sloggers/sloghuman/sloghuman_test.go b/sloggers/sloghuman/sloghuman_test.go index 5c28afb..86fe888 100644 --- a/sloggers/sloghuman/sloghuman_test.go +++ b/sloggers/sloghuman/sloghuman_test.go @@ -11,18 +11,17 @@ import ( "cdr.dev/slog/sloggers/sloghuman" ) -var bg = context.Background() - func TestMake(t *testing.T) { t.Parallel() b := &bytes.Buffer{} - l := sloghuman.Make(b) - l.Info(bg, "line1\n\nline2", slog.F("wowow", "me\nyou")) - l.Sync() + ctx := context.Background() + ctx = sloghuman.Make(ctx, b) + slog.Info(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) + slog.Sync(ctx) et, rest, err := entryhuman.StripTimestamp(b.String()) assert.Success(t, "strip timestamp", err) assert.False(t, "timestamp", et.IsZero()) - assert.Equal(t, "entry", " [INFO]\t\t...\t{\"wowow\": \"me\\nyou\"}\n \"msg\": line1\n\n line2\n", rest) + assert.Equal(t, "entry", " [INFO]\t\t...\t{\"wowow\": \"me\\nyou\"}\n \"msg\": line1\n\n line2\n", rest) } diff --git a/sloggers/slogjson/slogjson.go b/sloggers/slogjson/slogjson.go index 5069ddc..06c0446 100644 --- a/sloggers/slogjson/slogjson.go +++ b/sloggers/slogjson/slogjson.go @@ -34,8 +34,8 @@ import ( // for the format. // If the writer implements Sync() error then // it will be called when syncing. -func Make(w io.Writer) slog.Logger { - return slog.Make(jsonSink{ +func Make(ctx context.Context, w io.Writer) context.Context { + return slog.Make(ctx, jsonSink{ w: syncwriter.New(w), }) } @@ -44,7 +44,7 @@ type jsonSink struct { w *syncwriter.Writer } -func (s jsonSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { +func (s jsonSink) LogEntry(_ context.Context, ent slog.SinkEntry) { m := slog.M( slog.F("ts", ent.Time), slog.F("level", ent.Level), diff --git a/sloggers/slogjson/slogjson_test.go b/sloggers/slogjson/slogjson_test.go index 77ee4e4..103be7a 100644 --- a/sloggers/slogjson/slogjson_test.go +++ b/sloggers/slogjson/slogjson_test.go @@ -24,9 +24,9 @@ func TestMake(t *testing.T) { ctx, s := trace.StartSpan(bg, "meow") b := &bytes.Buffer{} - l := slogjson.Make(b) - l = l.Named("named") - l.Error(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) + ctx = slogjson.Make(ctx, b) + ctx = slog.Named(ctx, "named") + slog.Error(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) j := entryjson.Filter(b.String(), "ts") exp := fmt.Sprintf(`{"level":"ERROR","msg":"line1\n\nline2","caller":"%v:29","func":"cdr.dev/slog/sloggers/slogjson_test.TestMake","logger_names":["named"],"trace":"%v","span":"%v","fields":{"wowow":"me\nyou"}} diff --git a/sloggers/slogstackdriver/slogstackdriver.go b/sloggers/slogstackdriver/slogstackdriver.go index 3b4045e..bac1294 100644 --- a/sloggers/slogstackdriver/slogstackdriver.go +++ b/sloggers/slogstackdriver/slogstackdriver.go @@ -17,14 +17,14 @@ import ( "cdr.dev/slog/internal/syncwriter" ) -// Make creates a slog.Logger configured to write JSON logs +// Make creates a slog.logger configured to write JSON logs // to stdout for stackdriver. // // See https://cloud.google.com/logging/docs/agent -func Make(w io.Writer) slog.Logger { +func Make(ctx context.Context, w io.Writer) slog.SinkContext { projectID, _ := metadata.ProjectID() - return slog.Make(stackdriverSink{ + return slog.Make(ctx, stackdriverSink{ projectID: projectID, w: syncwriter.New(w), }) @@ -35,7 +35,7 @@ type stackdriverSink struct { w *syncwriter.Writer } -func (s stackdriverSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { +func (s stackdriverSink) LogEntry(_ context.Context, ent slog.SinkEntry) { // https://cloud.google.com/logging/docs/agent/configuration#special-fields e := slog.M( slog.F("severity", sev(ent.Level)), diff --git a/sloggers/slogstackdriver/slogstackdriver_test.go b/sloggers/slogstackdriver/slogstackdriver_test.go index 3a4a295..025ad9d 100644 --- a/sloggers/slogstackdriver/slogstackdriver_test.go +++ b/sloggers/slogstackdriver/slogstackdriver_test.go @@ -24,9 +24,9 @@ func TestStackdriver(t *testing.T) { ctx, s := trace.StartSpan(bg, "meow") b := &bytes.Buffer{} - l := slogstackdriver.Make(b) - l = l.Named("meow") - l.Error(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) + ctx = slogstackdriver.Make(ctx, b) + ctx = slog.Named(ctx, "meow") + slog.Error(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) j := entryjson.Filter(b.String(), "timestamp") exp := fmt.Sprintf(`{"severity":"ERROR","message":"line1\n\nline2","logging.googleapis.com/sourceLocation":{"file":"%v","line":29,"function":"cdr.dev/slog/sloggers/slogstackdriver_test.TestStackdriver"},"logging.googleapis.com/operation":{"producer":"meow"},"logging.googleapis.com/trace":"projects//traces/%v","logging.googleapis.com/spanId":"%v","logging.googleapis.com/trace_sampled":false,"wowow":"me\nyou"} diff --git a/sloggers/slogtest/assert/assert.go b/sloggers/slogtest/assert/assert.go index e246a2f..7aafdd4 100644 --- a/sloggers/slogtest/assert/assert.go +++ b/sloggers/slogtest/assert/assert.go @@ -39,7 +39,7 @@ func Success(t testing.TB, name string, err error) { if err != nil { slogtest.Fatal(t, "unexpected error", slog.F("name", name), - slog.Error(err), + slog.Err(err), ) } } diff --git a/sloggers/slogtest/t.go b/sloggers/slogtest/t.go index e315184..ffb972a 100644 --- a/sloggers/slogtest/t.go +++ b/sloggers/slogtest/t.go @@ -18,8 +18,8 @@ import ( // Ensure all stdlib logs go through slog. func init() { - l := sloghuman.Make(os.Stderr) - log.SetOutput(slog.Stdlib(context.Background(), l).Writer()) + ctx := sloghuman.Make(ctx, os.Stderr) + log.SetOutput(slog.Stdlib(ctx).Writer()) } // Options represents the options for the logger returned @@ -30,12 +30,12 @@ type Options struct { IgnoreErrors bool } -// Make creates a Logger that writes logs to tb in a human readable format. -func Make(tb testing.TB, opts *Options) slog.Logger { +// Make creates a logger that writes logs to tb in a human readable format. +func Make(tb testing.TB, opts *Options) slog.SinkContext { if opts == nil { opts = &Options{} } - return slog.Make(testSink{ + return slog.Make(context.Background(), testSink{ tb: tb, opts: opts, }) @@ -72,30 +72,30 @@ func (ts testSink) Sync() {} var ctx = context.Background() -func l(t testing.TB) slog.Logger { +func l(t testing.TB) context.Context { return Make(t, nil) } // Debug logs the given msg and fields to t via t.Log at the debug level. func Debug(t testing.TB, msg string, fields ...slog.Field) { slog.Helper() - l(t).Debug(ctx, msg, fields...) + slog.Debug(l(t), msg, fields...) } // Info logs the given msg and fields to t via t.Log at the info level. func Info(t testing.TB, msg string, fields ...slog.Field) { slog.Helper() - l(t).Info(ctx, msg, fields...) + slog.Info(l(t), msg, fields...) } // Error logs the given msg and fields to t via t.Error at the error level. func Error(t testing.TB, msg string, fields ...slog.Field) { slog.Helper() - l(t).Error(ctx, msg, fields...) + slog.Error(l(t), msg, fields...) } // Fatal logs the given msg and fields to t via t.Fatal at the fatal level. func Fatal(t testing.TB, msg string, fields ...slog.Field) { slog.Helper() - l(t).Fatal(ctx, msg, fields...) + slog.Fatal(l(t), msg, fields...) } diff --git a/sloggers/slogtest/t_test.go b/sloggers/slogtest/t_test.go index 9169cbe..77942a5 100644 --- a/sloggers/slogtest/t_test.go +++ b/sloggers/slogtest/t_test.go @@ -31,11 +31,12 @@ func TestIgnoreErrors(t *testing.T) { t.Parallel() tb := &fakeTB{} - l := slog.Make(slogtest.Make(tb, &slogtest.Options{ + ctx := context.Background() + ctx = slog.Make(ctx, slogtest.Make(tb, &slogtest.Options{ IgnoreErrors: true, })) - l.Error(bg, "hello") + slog.Error(ctx, "hello") assert.Equal(t, "errors", 0, tb.errors) defer func() { @@ -43,11 +44,9 @@ func TestIgnoreErrors(t *testing.T) { assert.Equal(t, "fatals", 0, tb.fatals) }() - l.Fatal(bg, "hello") + slog.Fatal(ctx, "hello") } -var bg = context.Background() - type fakeTB struct { testing.TB From 6a41f9d85660abd7fa70daad28a4731a59b358d0 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 31 Mar 2020 14:54:42 -0500 Subject: [PATCH 5/6] Revert "Make SinkContext a struct" It's better as an interface so users don't have to call the `Sink` method on the struct during Make. - Move SinkContext into context.go --- context.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index e3f20c1..d44a049 100644 --- a/context.go +++ b/context.go @@ -4,15 +4,21 @@ import "context" type loggerCtxKey = struct{} -// SinkContext is used by slog.Make to compose many loggers together. -type SinkContext struct { +type sinkContext struct { + context.Context + Sink +} + +// SinkContext is a context that implements Sink. +// It may be returned by log creators to allow for composition. +type SinkContext interface { Sink context.Context } func contextWithLogger(ctx context.Context, l logger) SinkContext { ctx = context.WithValue(ctx, loggerCtxKey{}, l) - return SinkContext{ + return &sinkContext{ Context: ctx, Sink: l, } From 0d999259b6559610bb00e2adadc253806c75a9d7 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 31 Mar 2020 15:11:45 -0500 Subject: [PATCH 6/6] Add /v2 to import path --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c965c2f..bfca1fb 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module cdr.dev/slog +module cdr.dev/slog/v2 go 1.13