diff --git a/README.md b/README.md index 7184288..e561523 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ one of them does not pull anything else in. | [prommetrics](./prommetrics) | Registration of the Prometheus collectors and the context-aware creation of the metrics. | | [protomap](./protomap) | Deserialization of protobuf messages by their descriptor name. | | [slogger](./slogger) | A `log/slog` logger with a configurable format, a hot-reloadable level and context helpers. | +| [slogger/grpcslog](./slogger/grpcslog) | A `grpclog.LoggerV2` implementation that writes the gRPC messages to the logger. | | [strcase](./strcase) | Detection of the case of a string and conversion between the case styles. | | [sync](./sync) | A `sync.Once` that keeps retrying until the guarded function succeeds. | | [time](./time) | A duration type that is serialized as a human-readable string. | diff --git a/closer/.golangci.yml b/closer/.golangci.yml index 7f28cfa..62d996f 100644 --- a/closer/.golangci.yml +++ b/closer/.golangci.yml @@ -79,6 +79,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' diff --git a/closer/go.mod b/closer/go.mod index 626d228..c39962f 100644 --- a/closer/go.mod +++ b/closer/go.mod @@ -3,13 +3,8 @@ module github.com/nbgrp/pkg/closer go 1.20 require ( - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.12.1 go.uber.org/goleak v1.3.0 ) -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) +require go.yaml.in/yaml/v3 v3.0.5 // indirect diff --git a/closer/go.sum b/closer/go.sum index 139b8e2..3569113 100644 --- a/closer/go.sum +++ b/closer/go.sum @@ -1,16 +1,4 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/ctxkey/.golangci.yml b/ctxkey/.golangci.yml index ac96fb5..164c260 100644 --- a/ctxkey/.golangci.yml +++ b/ctxkey/.golangci.yml @@ -79,6 +79,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' diff --git a/ctxkey/go.mod b/ctxkey/go.mod index a276442..37f7d70 100644 --- a/ctxkey/go.mod +++ b/ctxkey/go.mod @@ -2,4 +2,4 @@ module github.com/nbgrp/pkg/ctxkey go 1.14 -require github.com/stretchr/testify v1.9.0 +require github.com/stretchr/testify v1.12.1 diff --git a/ctxkey/go.sum b/ctxkey/go.sum index 8f0c6a1..617baf9 100644 --- a/ctxkey/go.sum +++ b/ctxkey/go.sum @@ -1,19 +1,2 @@ -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= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/deferred/.golangci.yml b/deferred/.golangci.yml index d6bf931..10dedd8 100644 --- a/deferred/.golangci.yml +++ b/deferred/.golangci.yml @@ -79,6 +79,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' diff --git a/dispatcher/.golangci.yml b/dispatcher/.golangci.yml index 7f28cfa..62d996f 100644 --- a/dispatcher/.golangci.yml +++ b/dispatcher/.golangci.yml @@ -79,6 +79,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' diff --git a/go.work b/go.work index 40f58de..dc76a88 100644 --- a/go.work +++ b/go.work @@ -8,6 +8,7 @@ use ( ./prommetrics ./protomap ./slogger + ./slogger/grpcslog ./strcase ./sync ./time diff --git a/go.work.sum b/go.work.sum index c989296..40dbcf3 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,14 +1,40 @@ +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.35.0 h1:bN1gA3of5bXtbnLsRPrwfmbbe7A5UWFlcTHseujLnpc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.35.0/go.mod h1:Yj5vHEz/aAepZGliRJsA6uvHAVAQyEwajq9ORCHPxzM= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= 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/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.39.1-0.20260819172001-e6e3fd93e4be h1:SWe0x6yfglnxuvOiYgTTnNq7QD/yvqthOh1RBI8Bj8w= +github.com/envoyproxy/go-control-plane/envoy v1.39.1-0.20260819172001-e6e3fd93e4be/go.mod h1:PYEOlng9XcrulfyWpm49jECTPV0LT4q8cO7fLW/xwgk= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -29,21 +55,49 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 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/spiffe/go-spiffe/v2 v2.8.1 h1:eXZMLsu+3MLEPJyGJkolqtVrteZfQdUpOWj6LTiDl/E= +github.com/spiffe/go-spiffe/v2 v2.8.1/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.45.0 h1:9jR0ZPRok9ryaOQ2Wx8rg5F7Aon59mxrqbVI60/vlBk= +go.opentelemetry.io/contrib/detectors/gcp v1.45.0/go.mod h1:VSme3o2fvSg5bVg0dRzyHaj4Z5EVhG+g2Fde6LKzmQA= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= 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= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1 h1:lrupDmKL3p5kEX1M92oan027eCKcouzjuPbH6YBK+Rs= +google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:q/3oV3jAi5vwelxsVAprMBC8BcM2zmNe+IjRGd+9/ks= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 h1:utmqiUzZAEINbLmRyAt1QjVkMyLQ+lvIwCpLaB1rZfw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/prommetrics/.golangci.yml b/prommetrics/.golangci.yml index 7f28cfa..62d996f 100644 --- a/prommetrics/.golangci.yml +++ b/prommetrics/.golangci.yml @@ -79,6 +79,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' diff --git a/protomap/.golangci.yml b/protomap/.golangci.yml index 61e915f..dd08290 100644 --- a/protomap/.golangci.yml +++ b/protomap/.golangci.yml @@ -80,6 +80,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' diff --git a/slogger/.golangci.yml b/slogger/.golangci.yml index e9e3af8..01e620c 100644 --- a/slogger/.golangci.yml +++ b/slogger/.golangci.yml @@ -80,6 +80,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' diff --git a/slogger/README.md b/slogger/README.md index 197b4fa..097dd13 100644 --- a/slogger/README.md +++ b/slogger/README.md @@ -10,6 +10,12 @@ The module is split into two packages: pass the logger and its attributes through the [context](https://pkg.go.dev/context). +There is also a separate module next to it: + +- [`github.com/nbgrp/pkg/slogger/grpcslog`](./grpcslog/) — the + [`grpclog.LoggerV2`](https://pkg.go.dev/google.golang.org/grpc/grpclog) + implementation that writes the gRPC messages to the logger. + ## Public API The `slogger` package provides: diff --git a/slogger/grpcslog/.golangci.yml b/slogger/grpcslog/.golangci.yml new file mode 100644 index 0000000..4955f26 --- /dev/null +++ b/slogger/grpcslog/.golangci.yml @@ -0,0 +1,353 @@ +version: "2" + +run: + timeout: 10m + +severity: + default: "critical" + +formatters: + enable: + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification. + - goimports # Check import statements are formatted according to the 'goimport' command. + + settings: + gofmt: + rewrite-rules: + - pattern: 'a[b:len(a)]' + replacement: 'a[b:]' + +linters: + default: none + enable: + - asasalint # Check for pass []any as any in variadic func(…any). + - asciicheck # Checks that all code identifiers does not have non-ASCII symbols in the name. + - bidichk # Checks for dangerous unicode character sequences. + - containedctx # Containedctx is a linter that detects struct contained context.Context field. + - contextcheck # Check whether the function uses a non-inherited context. + - copyloopvar # Copyloopvar is a linter detects places where loop variables are copied. + - depguard # Go linter that checks if package imports are in a list of acceptable packages. + - dupword # Checks for duplicate words in the source code. + - durationcheck # Check for two durations multiplied together. + - embeddedstructfieldcheck # Embedded types should be at the top of the field list of a struct, and there must be an empty line separating embedded fields from regular fields. + - errcheck # Errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases. + - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error. + - errorlint # Errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. + - exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions. + - fatcontext # Detects nested contexts in loops and function literals. + - forcetypeassert # Find forced type assertions. + - funcorder # Checks the order of functions, methods, and constructors. + - gocheckcompilerdirectives # Checks that go compiler directive comments (//go:) are valid. + - gocognit # Computes and checks the cognitive complexity of functions. + - goconst # Finds repeated strings that could be replaced by a constant. + - gocritic # Provides diagnostics that check for bugs, performance and style issues. + - goheader # Checks if file header matches to pattern. + - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. + - gosec # Inspects source code for security problems. + - govet # Vet examines Go source code and reports suspicious constructs. + - iface # Detect the incorrect use of interfaces, helping developers avoid interface pollution. + - inamedparam # Reports interfaces with unnamed method parameters. + - intrange # Intrange is a linter to find places where for loops could make use of an integer range. + - iotamixing # Checks if iotas are being used in const blocks with other non-iota declarations. + - makezero # Finds slice declarations with non-zero initial length. + - mirror # Reports wrong mirror patterns of bytes/strings usage. + - mnd # An analyzer to detect magic numbers. + - modernize # A suite of analyzers that suggest simplifications to Go code, using modern language and library features. + - nakedret # Checks that functions with naked returns are not longer than a maximum size (can be zero). + - nilnil # Checks that there is no simultaneous return of nil error and an invalid value. + - nolintlint # Reports ill-formed or insufficient nolint directives. + - nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. + - perfsprint # Checks that fmt.Sprintf can be replaced with a faster alternative. + - prealloc # Finds slice declarations that could potentially be pre-allocated. + - reassign # Checks that package variables are not reassigned. + - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. + - sloglint # Ensure consistent code style when using log/slog. + - staticcheck # It's a set of rules from staticcheck. + - testifylint # Checks usage of github.com/stretchr/testify. + - testpackage # Linter that makes you use a separate _test package. + - thelper # Thelper detects tests helpers which do not start with the t.Helper() method. + - tparallel # Tparallel detects inappropriate usage of t.Parallel() method in your Go test codes. + - unconvert # Remove unnecessary type conversions. + - unused # Checks Go code for unused constants, variables, functions and types. + - usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. + - usetesting # Reports uses of functions with replacement inside the testing package. + - wastedassign # Finds wasted assignment statements. + - whitespace # Checks for unnecessary newlines at the start and end of functions, if, for, etc. + + exclusions: + rules: + - text: 'Error return value of .((os\.)?std(out|err)\..*|.*print(f|ln)?). is not checked' + linters: [ errcheck ] + - path: (.+)_test\.go + linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' + - text: 'assigned to ctx' + linters: [ wastedassign ] + - text: 'ctx is never used' + linters: [ staticcheck ] + + paths: + - \\.git$ + - assets$ + - bench(mark)?s?$ + - bin$ + - build$ + - configs?$ + - deploy(ment)?s?$ + - docs$ + - githooks$ + - scripts$ + + generated: strict + + settings: + depguard: + rules: + main: + list-mode: lax + deny: + - pkg: 'math/rand$' + desc: 'use math/rand/v2' + - pkg: 'github.com/pkg/errors' + desc: 'Should be replaced by standard lib errors package' + + dupword: + comments-only: true + + embeddedstructfieldcheck: + forbid-mutex: true + + errcheck: + exclude-functions: + - encoding/json.Marshal + - encoding/json.MarshalIndent + + gocognit: + min-complexity: 10 + + goconst: + min-len: 2 + min-occurrences: 2 + + gocritic: + disable-all: true + enabled-checks: + ## DIAGNOSTIC + - appendAssign # Detects suspicious append result assignments. + - argOrder # Detects suspicious arguments order. + - badCall # Detects suspicious function calls. + - badCond # Detects suspicious condition expressions. + - badLock # Detects suspicious mutex lock/unlock operations. + - badRegexp # Detects suspicious regexp patterns. + - badSorting # Detects bad usage of sort package. + - badSyncOnceFunc # Detects bad usage of sync.OnceFunc. + - builtinShadowDecl # Detects top-level declarations that shadow the predeclared identifiers. + - caseOrder # Detects erroneous case order inside switch statements. + - codegenComment # Detects malformed ‘code generated’ file comments. + - commentedOutCode # Detects commented-out code inside function bodies. + - deferInLoop # Detects loops inside functions that use defer. + - deprecatedComment # Detects malformed ‘deprecated’ doc-comments. + - dupArg # Detects suspicious duplicated arguments. + - dupBranchBody # Detects duplicated branch bodies inside conditional statements. + - dupCase # Detects duplicated case clauses inside switch or select statements. + - dupOption # Detects duplicated option function arguments in variadic function calls. + - dupSubExpr # Detects suspicious duplicated sub-expressions. + - dynamicFmtString # Detects suspicious formatting strings usage. + - emptyDecl # Detects suspicious empty declarations blocks. + - evalOrder # Detects unwanted dependencies on the evaluation order. + - exitAfterDefer # Detects calls to exit/fatal inside functions that use defer. + - externalErrorReassign # Detects suspicious reassignment of error from another package. + - filepathJoin # Detects problems in filepath.Join() function calls. + - flagDeref # Detects immediate dereferencing of flag package pointers. + - flagName # Detects suspicious flag names. + - mapKey # Detects suspicious map literal keys. + - nilValReturn # Detects return statements those results evaluate to nil. + - offBy1 # Detects various off-by-one kind of errors. + - rangeAppendAll # Detects append all its data while range it. + - regexpPattern # Detects suspicious regexp patterns. + - returnAfterHttpError # Detects suspicious http.Error call without following return. + - sloppyLen # Detects usage of len when result is obvious or doesn’t make sense. + - sloppyReassign # Detects suspicious/confusing re-assignments. + - sloppyTypeAssert # Detects redundant type assertions. + - sortSlice # Detects suspicious sort.Slice calls. + - sprintfQuotedString # Detects “%s” formatting directives that can be replaced with %q. + - sqlQuery # Detects issue in Query() and Exec() calls. + - syncMapLoadAndDelete # Detects sync.Map load+delete operations that can be replaced with LoadAndDelete. + - truncateCmp # Detects potential truncation issues when comparing ints of different sizes. + - uncheckedInlineErr # Detects unchecked errors in if statements. + - unnecessaryDefer # Detects redundantly deferred calls. + - weakCond # Detects conditions that are unsafe due to not being exhaustive. + + ## STYLE + - assignOp # Detects assignments that can be simplified by using assignment operators. + - boolExprSimplify # Detects bool expressions that can be simplified. + - builtinShadow # Detects when predeclared identifiers are shadowed in assignments. + - captLocal # Detects capitalized names for local variables. + - commentFormatting # Detects comments with non-idiomatic formatting. + - commentedOutImport # Detects commented-out imports. + - defaultCaseOrder # Detects when default case in switch isn’t on 1st or last position. + - deferUnlambda # Detects deferred function literals that can be simplified. + - docStub # Detects comments that silence go lint complaints about doc-comment. + - dupImport # Detects multiple imports of the same package under different aliases. + - elseif # Detects else with nested if statement that can be replaced with else-if. + - emptyFallthrough # Detects fallthrough that can be avoided by using multi case values. + - emptyStringTest # Detects empty string checks that can be written more idiomatically. + - exposedSyncMutex # Detects exposed methods from sync.Mutex and sync.RWMutex. + - hexLiteral # Detects hex literals that have mixed case letter digits. + - httpNoBody # Detects nil usages in http.NewRequest calls, suggesting http.NoBody as an alternative. + - ifElseChain # Detects repeated if-else statements and suggests to replace them with switch statement. + - importShadow # Detects when imported package names shadowed in the assignments. + - initClause # Detects non-assignment statements inside if/switch init clause. + - methodExprCall # Detects method expression call that can be replaced with a method call. + - nestingReduce # Finds where nesting level could be reduced. + - newDeref # Detects immediate dereferencing of new expressions. + - octalLiteral # Detects old-style octal literals. + - paramTypeCombine # Detects if function parameters could be combined by type and suggest the way to do it. + - preferFilepathJoin # Detects concatenation with os.PathSeparator which can be replaced with filepath.Join. + - ptrToRefParam # Detects input and output parameters that have a type of pointer to referential type. + - redundantSprint # Detects redundant fmt.Sprint calls. + - regexpMust # Detects regexp.Compile* that can be replaced with regexp.MustCompile*. + - regexpSimplify # Detects regexp patterns that can be simplified. + - singleCaseSwitch # Detects switch statements that could be better written as if statement. + - stringConcatSimplify # Detects string concat operations that can be simplified. + - stringsCompare # Detects strings.Compare usage. + - switchTrue # Detects switch-over-bool statements that use explicit true tag value. + - timeExprSimplify # Detects manual conversion to milli- or microseconds. + - todoCommentWithoutDetail # Detects TODO comments without detail/assignee. + # tooManyResultsChecker # Detects function with too many results. + - typeAssertChain # Detects repeated type assertions and suggests to replace them with type switch statement. + - typeDefFirst # Detects method declarations preceding the type definition itself. + - typeSwitchVar # Detects type switches that can benefit from type guard clause with variable. + - typeUnparen # Detects unneeded parenthesis inside type expressions and suggests to remove them. + - underef # Detects dereference expressions that can be omitted. + - unlabelStmt # Detects redundant statement labels. + - unlambda # Detects function literals that can be simplified. + # unnamedResult # Detects unnamed results that may benefit from names. + # unnecessaryBlock # Detects unnecessary braced statement blocks. + - unslice # Detects slice expressions that can be simplified to sliced expression itself. + - valSwap # Detects value swapping code that are not using parallel assignment. + # whyNoLint # Ensures that //nolint comments include an explanation. + - wrapperFunc # Detects function calls that can be replaced with convenience wrappers. + - yodaStyleExpr # Detects Yoda style expressions and suggests to replace them. + + ## PERFORMANCE + - appendCombine # Detects append chains to the same slice that can be done in a single append call. + - equalFold # Detects unoptimal strings/bytes case-insensitive comparison. + - hugeParam # Detects params that incur excessive amount of copying. + - indexAlloc # Detects strings.Index calls that may cause unwanted allocs. + - preferDecodeRune # Detects expressions like []rune(s)[0] that may cause unwanted rune slice allocation. + - preferFprint # Detects fmt.Sprint(f/ln) calls which can be replaced with fmt.Fprint(f/ln). + - preferStringWriter # Detects w.Write or io.WriteString calls which can be replaced with w.WriteString. + - preferWriteByte # Detects WriteRune calls with rune literal argument that is single byte and reports to use WriteByte instead. + - rangeExprCopy # Detects expensive copies of for loop range expressions. + - rangeValCopy # Detects loops that copy big objects during each iteration. + - sliceClear # Detects slice clear loops, suggests an idiom that is recognized by the Go compiler. + - stringXbytes # Detects redundant conversions between string and []byte. + - zeroByteRepeat # Detects bytes.Repeat with 0 value. + + goheader: + template-path: ../../.github/goheader.tmpl + + gomoddirectives: + replace-local: true + + gosec: + excludes: + # _ instead of err checks + - G104 + + govet: + enable-all: true + + iface: + enable: + - identical + - opaque + - unused + + inamedparam: + skip-single-param: true + + iotamixing: + report-individual: true + + nakedret: + max-func-lines: 10 + + nilnil: + detect-opposite: true + + nolintlint: + require-specific: true + + prealloc: + range-loops: true + + reassign: + patterns: + - '.*' + + revive: + severity: error + rules: + - name: atomic # Check for commonly mistaken usages of the sync/atomic package. + - name: confusing-naming # Methods or fields of struct that have names different only by capitalization could be confusing. + - name: confusing-results # Function or methods that return multiple, no named, values of the same type could induce error. + - name: constant-logical-expr # The rule spots logical expressions that evaluate always to the same value. + - name: context-as-argument # By convention, context.Context should be the first parameter of a function. This rule spots function declarations that do not follow the convention. + - name: context-keys-type # Basic types should not be used as a key in context.WithValue. + - name: deep-exit # Packages exposing functions that can stop program execution by exiting are hard to reuse. This rule looks for program exits in functions other than main() or init(). + - name: defer # This rule warns on some common mistakes when using defer statement. + arguments: + - - 'call-chain' + - 'loop' + - 'method-call' + - 'recover' + - 'immediate-recover' + - 'return' + - name: duplicated-imports # It is possible to unintentionally import the same package twice. This rule looks for packages that are imported two or more times. + - name: early-return # In GO it is idiomatic to minimize nesting statements, a typical example is to avoid if-then-else constructions. + - name: empty-block # Empty blocks make code less readable and could be a symptom of a bug or unfinished refactoring. + - name: empty-lines # Sometimes gofmt is not enough to enforce a common formatting of a code-base; this rule warns when there are heading or trailing newlines in code blocks. + - name: error-return # By convention, for the sake of readability, the errors should be last in the list of returned values by a function. + - name: error-strings # By convention, for better readability, error messages should not be capitalized or end with punctuation or a newline. + - name: errorf # It is possible to get a simpler program by replacing errors.New(fmt.Sprintf()) with fmt.Errorf(). This rule spots that kind of simplification opportunities. + - name: forbidden-call-in-wg-go # Warns on forbidden calls inside calls to wg.Go. + - name: identical-branches # An if-then-else conditional with identical implementations in both branches is an error. + - name: identical-ifelseif-branches # An if ... else if chain with identical branches makes maintenance harder and might be a source of bugs. Duplicated branches should be consolidated in one. + - name: identical-ifelseif-conditions # An if ... else if chain with identical conditions can lead to unreachable code and is a potential source of bugs while making the code harder to read and maintain. + - name: identical-switch-branches # A switch with identical branches makes maintenance harder and might be a source of bugs. Duplicated branches should be consolidated in one case clause. + - name: identical-switch-conditions # A switch statement with cases with the same condition can lead to unreachable code and is a potential source of bugs while making the code harder to read and maintain. + - name: if-return # Checking if an error is nil to just after return the error or nil is redundant. + - name: increment-decrement # By convention, for better readability, incrementing an integer variable by 1 is recommended to be done using the ++ operator. This rule spots expressions like i += 1 and i -= 1 and proposes to change them into i++ and i--. + - name: inefficient-map-lookup # This rule identifies code that iteratively searches for a key in a map. + - name: optimize-operands-order # Conditional expressions can be written to take advantage of short circuit evaluation and speed up its average evaluation time by forcing the evaluation of less time-consuming terms before more costly ones. This rule spots logical expressions where the order of evaluation of terms seems non optimal. + - name: range # This rule suggests a shorter way of writing ranges that do not use the second value. + - name: redefines-builtin-id # Constant names like false, true, nil, function names like append, make, and basic type names like bool, and byte are not reserved words of the language; therefore the can be redefined. Even if possible, redefining these built in names can lead to bugs very difficult to detect. + - name: redundant-build-tag # This rule warns about redundant build tag comments // +build when //go:build is present. gofmt in Go 1.17+ automatically adds the //go:build constraint, making the // +build comment unnecessary. + - name: string-of-int # Explicit type conversion string(i) where i has an integer type other than rune might behave not as expected by the developer (e.g. string(42) is not "42"). This rule spot that kind of suspicious conversions. + - name: unchecked-type-assertion # This rule checks whether a type assertion result is checked (the ok value), preventing unexpected panics. + arguments: + - accept-ignored-assertion-result: true + - name: use-any # Since Go 1.18, interface{} has an alias: any. This rule proposes to replace instances of interface{} with any. + - name: use-errors-new # This rules identifies calls to fmt.Errorf that can be safely replaced by, the more efficient, errors.New. + - name: use-fmt-print # This rule proposes to replace calls to built-in print and println with their equivalents from fmt standard package. + - name: use-waitgroup-go # Since Go 1.25 the sync package proposes the WaitGroup.Go method. This method is a shorter and safer replacement for the idiom wg.Add ... go { ... wg.Done ... }. + - name: useless-break # This rule warns on useless break statements in case clauses of switch and select statements. + - name: useless-fallthrough # This rule warns on useless fallthrough statements in case clauses of switch statements. A fallthrough is considered useless if it's the single statement of a case clause block. + - name: unnecessary-if # Identifies `if-else` statements that can be replaced by simpler statements. + - name: unnecessary-stmt # This rule suggests to remove redundant statements like a break at the end of a case block, for improving the code's readability. + - name: unreachable-code # This rule spots and proposes to remove unreachable code. + - name: waitgroup-by-value # Function parameters that are passed by value, are in fact a copy of the original argument. Passing a copy of a sync.WaitGroup is usually not what the developer wants to do. This rule warns when a sync.WaitGroup expected as a by-value parameter in a function or method. + + sloglint: + attr-only: true + context: "scope" + msg-style: "capitalized" + key-naming-case: "snake" + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/slogger/grpcslog/README.md b/slogger/grpcslog/README.md new file mode 100644 index 0000000..e7d41bd --- /dev/null +++ b/slogger/grpcslog/README.md @@ -0,0 +1,80 @@ +# grpcslog + +A [`grpclog.LoggerV2`](https://pkg.go.dev/google.golang.org/grpc/grpclog) implementation +that writes the gRPC messages to a [`log/slog`](https://pkg.go.dev/log/slog) logger. + +The logger is installed as the global gRPC logger, so all the messages of gRPC +go to the same place as the rest of the application logs: + +```go +logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + +grpcLogger, err := grpcslog.New(logger) +if err != nil { + return err +} + +grpclog.SetLoggerV2(grpcLogger) +``` + +## Public API + +```go +func New(logger *slog.Logger, opts ...Option) (*GrpcLogger, error) + +func WithVerbosity(v int) Option +``` + +`New` returns an error if the `GRPC_GO_LOG_VERBOSITY_LEVEL` environment variable +contains an invalid value. + +### WithVerbosity + +Sets the verbosity level of the logger: the threshold of the `V` method. + +## Configuration + +The level and the format of the messages are set in the logger passed to `New`, +so only the verbosity level is configured by the package itself: + +| Variable | Values | Default | +|-----------------------------|-------------------------|---------| +| `GRPC_GO_LOG_VERBOSITY_LEVEL` | a non-negative integer | `0` | + +- `GRPC_GO_LOG_VERBOSITY_LEVEL` — the verbosity level of the logger. It sets + the threshold of the `V` method, so the verbose messages of gRPC are logged + only if their level is not greater than the verbosity level of the logger. + The variable takes precedence over the level passed to `WithVerbosity`. + +**`GRPC_GO_LOG_SEVERITY_LEVEL` and `GRPC_GO_LOG_FORMATTER` are not applied.** +They are read by the default gRPC logger only: the level and the format of the +records are defined by the logger, so the environment variables have no effect +on them. + +## Verbosity + +The verbosity level of gRPC is a threshold: the higher it is, the more verbose +messages are logged. The `V` method reports whether a message of the given +verbose level should be logged: + +```go +grpcLogger, err := grpcslog.New(logger, grpcslog.WithVerbosity(2)) +if err != nil { + return err +} + +grpcLogger.V(2) // true +grpcLogger.V(3) // false +``` + +The verbose messages of gRPC are written with the info severity, so they are +only logged if the level of the logger allows the info records. + +## Errors + +The constructor returns an error if the verbosity level is not a non-negative +integer: + +``` +invalid gRPC log verbosity level "bogus": strconv.Atoi: parsing "bogus": invalid syntax +``` diff --git a/slogger/grpcslog/go.mod b/slogger/grpcslog/go.mod new file mode 100644 index 0000000..aeb6f51 --- /dev/null +++ b/slogger/grpcslog/go.mod @@ -0,0 +1,10 @@ +module github.com/nbgrp/pkg/slogger/grpcslog + +go 1.26.0 + +require ( + github.com/stretchr/testify v1.12.1 + google.golang.org/grpc v1.85.0 +) + +require go.yaml.in/yaml/v3 v3.0.5 // indirect diff --git a/slogger/grpcslog/go.sum b/slogger/grpcslog/go.sum new file mode 100644 index 0000000..4dc1ec4 --- /dev/null +++ b/slogger/grpcslog/go.sum @@ -0,0 +1,6 @@ +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +google.golang.org/grpc v1.85.0 h1:BPBCfuVRQ1qvBhKDOq9uvQllMvO59rKsYhXpp7Dd8vw= +google.golang.org/grpc v1.85.0/go.mod h1:0rQrOD58yjal/0EaVbGBANindixsCZNQPDV3VKtaKuQ= diff --git a/slogger/grpcslog/grpcslog.go b/slogger/grpcslog/grpcslog.go new file mode 100644 index 0000000..14c4b42 --- /dev/null +++ b/slogger/grpcslog/grpcslog.go @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Package grpcslog provides a [grpclog.LoggerV2] implementation that writes +// the gRPC messages to a [log/slog.Logger]. +package grpcslog + +import ( + "fmt" + "log/slog" + "os" + "strconv" + + "google.golang.org/grpc/grpclog" +) + +var _ grpclog.LoggerV2 = (*GrpcLogger)(nil) + +// exit terminates the process after a fatal message. It is a variable to make +// it possible to test the fatal methods. +var exit = os.Exit + +// GrpcLogger writes the gRPC messages to a [slog.Logger]. +type GrpcLogger struct { + l *slog.Logger + v int +} + +// New initializes and returns a [GrpcLogger] that writes the gRPC messages to +// the given logger. +// +// The GRPC_GO_LOG_VERBOSITY_LEVEL environment variable takes precedence over +// the verbosity level set with [WithVerbosity]. +// +// It returns an error if the environment variable contains an invalid value. +func New(logger *slog.Logger, opts ...Option) (*GrpcLogger, error) { + l := &GrpcLogger{l: logger} + + for _, opt := range opts { + opt(l) + } + + verbosity, err := verbosityLevel(l.v) + if err != nil { + return nil, err + } + l.v = verbosity + + return l, nil +} + +func (l *GrpcLogger) Info(args ...any) { + l.l.Info(fmt.Sprint(args...)) +} + +func (l *GrpcLogger) Infoln(args ...any) { + l.l.Info(fmt.Sprintln(args...)) +} + +func (l *GrpcLogger) Infof(format string, args ...any) { + l.l.Info(fmt.Sprintf(format, args...)) +} + +func (l *GrpcLogger) Warning(args ...any) { + l.l.Warn(fmt.Sprint(args...)) +} + +func (l *GrpcLogger) Warningln(args ...any) { + l.l.Warn(fmt.Sprintln(args...)) +} + +func (l *GrpcLogger) Warningf(format string, args ...any) { + l.l.Warn(fmt.Sprintf(format, args...)) +} + +func (l *GrpcLogger) Error(args ...any) { + l.l.Error(fmt.Sprint(args...)) +} + +func (l *GrpcLogger) Errorln(args ...any) { + l.l.Error(fmt.Sprintln(args...)) +} + +func (l *GrpcLogger) Errorf(format string, args ...any) { + l.l.Error(fmt.Sprintf(format, args...)) +} + +func (l *GrpcLogger) Fatal(args ...any) { + l.l.Error(fmt.Sprint(args...)) + exit(1) +} + +func (l *GrpcLogger) Fatalln(args ...any) { + l.l.Error(fmt.Sprintln(args...)) + exit(1) +} + +func (l *GrpcLogger) Fatalf(format string, args ...any) { + l.l.Error(fmt.Sprintf(format, args...)) + exit(1) +} + +// V reports whether the verbosity level is at least the requested verbose +// level, like the default gRPC logger does. +// +// The verbose messages of gRPC are written with the info severity, so they are +// only logged if the level of the logger allows the info records. +func (l *GrpcLogger) V(level int) bool { + return level <= l.v +} + +// verbosityLevel returns the verbosity level of the logger based on the +// GRPC_GO_LOG_VERBOSITY_LEVEL environment variable. The environment variable +// takes precedence over the given level. +func verbosityLevel(v int) (int, error) { + value := os.Getenv("GRPC_GO_LOG_VERBOSITY_LEVEL") + if value == "" { + return v, nil + } + + parsed, err := strconv.Atoi(value) + if err != nil { + return 0, fmt.Errorf("invalid gRPC log verbosity level %q: %w", value, err) + } + + if parsed < 0 { + return 0, fmt.Errorf("invalid gRPC log verbosity level %q: the level must not be negative", value) + } + + return parsed, nil +} diff --git a/slogger/grpcslog/grpcslog_internal_test.go b/slogger/grpcslog/grpcslog_internal_test.go new file mode 100644 index 0000000..f473183 --- /dev/null +++ b/slogger/grpcslog/grpcslog_internal_test.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package grpcslog + +import ( + "bytes" + "encoding/json" + "log/slog" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestFatalMethods checks that the fatal methods log the message and exit the +// process with the code 1. +func TestFatalMethods(t *testing.T) { + tests := []struct { + name string + log func(l *GrpcLogger) + }{ + {name: "Fatal", log: func(l *GrpcLogger) { l.Fatal("fatal message") }}, + {name: "Fatalln", log: func(l *GrpcLogger) { l.Fatalln("fatal message") }}, + {name: "Fatalf", log: func(l *GrpcLogger) { l.Fatalf("fatal %s", "message") }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + original := exit + t.Cleanup(func() { exit = original }) + + var code int + exit = func(c int) { code = c } + + buf := &bytes.Buffer{} + l, err := New(slog.New(slog.NewJSONHandler(buf, nil))) + require.NoError(t, err) + + tt.log(l) + + assert.Equal(t, 1, code) + + records := parseRecords(t, buf.String()) + require.Len(t, records, 1) + assert.Equal(t, "ERROR", records[0].Level) + assert.Contains(t, records[0].Msg, "fatal message") + }) + } +} + +// record is a logged message. +type record struct { + Level string `json:"level"` + Msg string `json:"msg"` +} + +// parseRecords parses the JSON messages written to the buffer. +func parseRecords(t *testing.T, out string) []record { + t.Helper() + + var records []record + + for line := range strings.SplitSeq(strings.TrimSpace(out), "\n") { + if line == "" { + continue + } + + var r record + require.NoError(t, json.Unmarshal([]byte(line), &r)) + + records = append(records, r) + } + + return records +} diff --git a/slogger/grpcslog/grpcslog_test.go b/slogger/grpcslog/grpcslog_test.go new file mode 100644 index 0000000..3f86095 --- /dev/null +++ b/slogger/grpcslog/grpcslog_test.go @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package grpcslog_test + +import ( + "bytes" + "encoding/json" + "log/slog" + "strings" + "sync" + "testing" + + . "github.com/nbgrp/pkg/slogger/grpcslog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// record is a logged message. +type record struct { + Level string `json:"level"` + Msg string `json:"msg"` +} + +// safeBuffer is a buffer that is safe for concurrent use. +type safeBuffer struct { + buf bytes.Buffer + mu sync.Mutex +} + +func (b *safeBuffer) Write(p []byte) (int, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.buf.Write(p) +} + +func (b *safeBuffer) String() string { + b.mu.Lock() + defer b.mu.Unlock() + + return b.buf.String() +} + +// newLogger returns a logger that writes the JSON records to the given buffer. +func newLogger(buf *safeBuffer) *slog.Logger { + return slog.New(slog.NewJSONHandler(buf, nil)) +} + +// TestV checks the verbosity semantics of the logger. +// +// The verbosity level of gRPC is a threshold: the higher it is, the more +// verbose messages are logged. A message of the verbose level is logged if +// its level is not greater than the verbosity level of the logger. +func TestV(t *testing.T) { + tests := []struct { + name string + verbosity int + level int + want bool + }{ + {name: "level 0 with zero verbosity", verbosity: 0, level: 0, want: true}, + {name: "level 1 with zero verbosity", verbosity: 0, level: 1, want: false}, + {name: "level 2 with zero verbosity", verbosity: 0, level: 2, want: false}, + {name: "level 0 with verbosity 2", verbosity: 2, level: 0, want: true}, + {name: "level 1 with verbosity 2", verbosity: 2, level: 1, want: true}, + {name: "level 2 with verbosity 2", verbosity: 2, level: 2, want: true}, + {name: "level 3 with verbosity 2", verbosity: 2, level: 3, want: false}, + {name: "level 2 with verbosity 3", verbosity: 3, level: 2, want: true}, + {name: "level 4 with verbosity 3", verbosity: 3, level: 4, want: false}, + {name: "negative level with zero verbosity", verbosity: 0, level: -1, want: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("GRPC_GO_LOG_VERBOSITY_LEVEL", "") + + l, err := New(newLogger(&safeBuffer{}), WithVerbosity(tt.verbosity)) + require.NoError(t, err) + + assert.Equal(t, tt.want, l.V(tt.level)) + }) + } +} + +// TestNew_Verbosity checks that the verbosity level is taken from the +// environment variable. +func TestNew_Verbosity(t *testing.T) { + t.Setenv("GRPC_GO_LOG_VERBOSITY_LEVEL", "2") + + l, err := New(newLogger(&safeBuffer{})) + require.NoError(t, err) + + assert.True(t, l.V(0)) + assert.True(t, l.V(1)) + assert.True(t, l.V(2)) + assert.False(t, l.V(3)) +} + +// TestNew_VerbosityEnvPrecedence checks that the environment variable takes +// precedence over the verbosity level passed to the constructor. +func TestNew_VerbosityEnvPrecedence(t *testing.T) { + t.Setenv("GRPC_GO_LOG_VERBOSITY_LEVEL", "3") + + l, err := New(newLogger(&safeBuffer{}), WithVerbosity(1)) + require.NoError(t, err) + + assert.True(t, l.V(3)) + assert.False(t, l.V(4)) +} + +// TestNew_InvalidEnv checks that the invalid values of the environment +// variables are reported. +func TestNew_InvalidEnv(t *testing.T) { + tests := []struct { + name string + value string + want string + }{ + { + name: "invalid verbosity level", + value: "bogus", + want: `invalid gRPC log verbosity level "bogus"`, + }, + { + name: "negative verbosity level", + value: "-1", + want: `invalid gRPC log verbosity level "-1"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("GRPC_GO_LOG_VERBOSITY_LEVEL", tt.value) + + _, err := New(newLogger(&safeBuffer{})) + require.ErrorContains(t, err, tt.want) + }) + } +} + +// TestLogMethods checks that the messages are logged with the expected +// severity levels and contents. +func TestLogMethods(t *testing.T) { + tests := []struct { + name string + log func(l *GrpcLogger) + level string + msg string + }{ + { + name: "Info", + log: func(l *GrpcLogger) { l.Info("message") }, + level: "INFO", + msg: "message", + }, + { + name: "Infoln", + log: func(l *GrpcLogger) { l.Infoln("message") }, + level: "INFO", + msg: "message\n", + }, + { + name: "Infof", + log: func(l *GrpcLogger) { l.Infof("mes%s", "sage") }, + level: "INFO", + msg: "message", + }, + { + name: "Warning", + log: func(l *GrpcLogger) { l.Warning("message") }, + level: "WARN", + msg: "message", + }, + { + name: "Warningln", + log: func(l *GrpcLogger) { l.Warningln("message") }, + level: "WARN", + msg: "message\n", + }, + { + name: "Warningf", + log: func(l *GrpcLogger) { l.Warningf("mes%s", "sage") }, + level: "WARN", + msg: "message", + }, + { + name: "Error", + log: func(l *GrpcLogger) { l.Error("message") }, + level: "ERROR", + msg: "message", + }, + { + name: "Errorln", + log: func(l *GrpcLogger) { l.Errorln("message") }, + level: "ERROR", + msg: "message\n", + }, + { + name: "Errorf", + log: func(l *GrpcLogger) { l.Errorf("mes%s", "sage") }, + level: "ERROR", + msg: "message", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf := &safeBuffer{} + l, err := New(newLogger(buf)) + require.NoError(t, err) + + tt.log(l) + + records := parseRecords(t, buf.String()) + require.Len(t, records, 1) + assert.Equal(t, tt.level, records[0].Level) + assert.Equal(t, tt.msg, records[0].Msg) + }) + } +} + +// parseRecords parses the JSON messages written to the buffer. +func parseRecords(t *testing.T, out string) []record { + t.Helper() + + var records []record + + for line := range strings.SplitSeq(strings.TrimSpace(out), "\n") { + if line == "" { + continue + } + + var r record + require.NoError(t, json.Unmarshal([]byte(line), &r)) + + records = append(records, r) + } + + return records +} diff --git a/slogger/grpcslog/options.go b/slogger/grpcslog/options.go new file mode 100644 index 0000000..553c895 --- /dev/null +++ b/slogger/grpcslog/options.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package grpcslog + +type Option func(*GrpcLogger) + +// WithVerbosity sets the verbosity level of the logger: the threshold of the +// [GrpcLogger.V] method. +// +// The GRPC_GO_LOG_VERBOSITY_LEVEL environment variable takes precedence over +// the given level. +func WithVerbosity(v int) Option { + return func(l *GrpcLogger) { + l.v = v + } +} diff --git a/strcase/.golangci.yml b/strcase/.golangci.yml index 3e6f20b..3feaf3c 100644 --- a/strcase/.golangci.yml +++ b/strcase/.golangci.yml @@ -79,6 +79,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' @@ -256,8 +259,6 @@ linters: govet: enable-all: true - disable: - - fieldalignment iface: enable: diff --git a/sync/.golangci.yml b/sync/.golangci.yml index d6bf931..10dedd8 100644 --- a/sync/.golangci.yml +++ b/sync/.golangci.yml @@ -79,6 +79,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' diff --git a/time/.golangci.yml b/time/.golangci.yml index ea57071..199bcf6 100644 --- a/time/.golangci.yml +++ b/time/.golangci.yml @@ -81,6 +81,9 @@ linters: linters: [ errcheck ] - path: (.+)_test\.go linters: [ gocognit, goconst, mnd ] + - path: (.+)_test\.go + linters: [ govet ] + text: 'fieldalignment:' - text: 'assigned to ctx' linters: [ wastedassign ] - text: 'ctx is never used' @@ -262,8 +265,6 @@ linters: govet: enable-all: true - disable: - - fieldalignment iface: enable: