From 6966efc9a5b9c7508cc75ef542b43335ef25a554 Mon Sep 17 00:00:00 2001 From: samber Date: Wed, 26 Apr 2023 20:59:34 +0000 Subject: [PATCH] bump v0.5.0 --- examples/failover/example.go | 39 -------------- examples/fanout/example.go | 66 ------------------------ examples/pipe/errors.go | 45 ---------------- examples/pipe/example.go | 58 --------------------- examples/pipe/gdpr.go | 97 ----------------------------------- examples/pool/example.go | 39 -------------- examples/router/example.go | 45 ---------------- go.mod | 7 --- go.sum | 20 -------- images/workflow.drawio | 43 ---------------- images/workflow.png | Bin 23769 -> 0 bytes 11 files changed, 459 deletions(-) delete mode 100644 examples/failover/example.go delete mode 100644 examples/fanout/example.go delete mode 100644 examples/pipe/errors.go delete mode 100644 examples/pipe/example.go delete mode 100644 examples/pipe/gdpr.go delete mode 100644 examples/pool/example.go delete mode 100644 examples/router/example.go delete mode 100644 images/workflow.drawio delete mode 100644 images/workflow.png diff --git a/examples/failover/example.go b/examples/failover/example.go deleted file mode 100644 index e7e637b..0000000 --- a/examples/failover/example.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "fmt" - "net" - "time" - - slogmulti "github.com/samber/slog-multi" - "golang.org/x/exp/slog" -) - -func main() { - // ncat -l 1000 -k - // ncat -l 1001 -k - // ncat -l 1002 -k - - logstash1, _ := net.Dial("tcp", "localhost:1000") - logstash2, _ := net.Dial("tcp", "localhost:1001") - logstash3, _ := net.Dial("tcp", "localhost:1002") - - logger := slog.New( - slogmulti.Failover()( - slog.HandlerOptions{}.NewJSONHandler(logstash1), - slog.HandlerOptions{}.NewJSONHandler(logstash2), - slog.HandlerOptions{}.NewJSONHandler(logstash3), - ), - ) - - logger. - With( - slog.Group("user", - slog.String("id", "user-123"), - slog.Time("created_at", time.Now().AddDate(0, 0, -1)), - ), - ). - With("environment", "dev"). - With("error", fmt.Errorf("an error")). - Error("A message") -} diff --git a/examples/fanout/example.go b/examples/fanout/example.go deleted file mode 100644 index 02ba44d..0000000 --- a/examples/fanout/example.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net" - "os" - "time" - - slogmulti "github.com/samber/slog-multi" - "golang.org/x/exp/slog" -) - -func connectLogstash() *net.TCPConn { - // ncat -l 4242 -k - addr, err := net.ResolveTCPAddr("tcp", "localhost:4242") - if err != nil { - log.Fatal("TCP connection failed:", err.Error()) - } - - conn, err := net.DialTCP("tcp", nil, addr) - if err != nil { - log.Fatal("TCP connection failed:", err.Error()) - } - - return conn -} - -func main() { - logstash := connectLogstash() - stderr := os.Stderr - - logger := slog.New( - slogmulti.Fanout( - slog.HandlerOptions{}.NewJSONHandler(logstash), - slog.HandlerOptions{}.NewTextHandler(stderr), - ), - ) - - logger. - With( - slog.Group("user", - slog.String("id", "user-123"), - slog.Time("created_at", time.Now().AddDate(0, 0, -1)), - ), - ). - With("environment", "dev"). - With("error", fmt.Errorf("an error")). - Error("A message") - - // stderr output: - // time=2023-04-10T14:00:0.000000+00:00 level=ERROR msg="A message" user.id=user-123 user.created_at=2023-04-10T14:00:0.000000+00:00 environment=dev error="an error" - - // netcat output: - // { - // "time":"2023-04-10T14:00:0.000000+00:00", - // "level":"ERROR", - // "msg":"A message", - // "user":{ - // "id":"user-123", - // "created_at":"2023-04-10T14:00:0.000000+00:00" - // }, - // "environment":"dev", - // "error":"an error" - // } -} diff --git a/examples/pipe/errors.go b/examples/pipe/errors.go deleted file mode 100644 index 69e8d33..0000000 --- a/examples/pipe/errors.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "context" - "reflect" - - "golang.org/x/exp/slog" -) - -func errorFormattingMiddleware(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error { - attrs := []slog.Attr{} - - record.Attrs(func(attr slog.Attr) bool { - key := attr.Key - value := attr.Value - kind := attr.Value.Kind() - - if key == "error" && kind == slog.KindAny { - if err, ok := value.Any().(error); ok { - errType := reflect.TypeOf(err).String() - msg := err.Error() - - attrs = append( - attrs, - slog.Group("error", - slog.String("type", errType), - slog.String("message", msg), - ), - ) - } else { - attrs = append(attrs, attr) - } - } else { - attrs = append(attrs, attr) - } - - return true - }) - - // new record with formatted error - record = slog.NewRecord(record.Time, record.Level, record.Message, record.PC) - record.AddAttrs(attrs...) - - return next(ctx, record) -} diff --git a/examples/pipe/example.go b/examples/pipe/example.go deleted file mode 100644 index fd9e443..0000000 --- a/examples/pipe/example.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "fmt" - "os" - "time" - - slogmulti "github.com/samber/slog-multi" - "golang.org/x/exp/slog" -) - -func main() { - // format go `error` type into an object {error: "*myCustomErrorType", message: "could not reach https://a.b/c"} - errorFormattingMiddleware := slogmulti.NewHandleInlineMiddleware(errorFormattingMiddleware) - - // remove PII - gdprMiddleware := NewGDPRMiddleware() - - sink := slog.HandlerOptions{}.NewJSONHandler(os.Stderr) - - logger := slog.New( - slogmulti. - Pipe(errorFormattingMiddleware). - Pipe(gdprMiddleware). - Handler(sink), - ) - - logger. - With( - slog.Group("user", - slog.String("id", "user-123"), - slog.String("email", "user-123"), - slog.Time("created_at", time.Now()), - ), - ). - With("environment", "dev"). - Error("A message", - slog.String("foo", "bar"), - slog.Any("error", fmt.Errorf("an error"))) - - // output: - // { - // "time":"2023-04-10T14:00:0.000000+00:00", - // "level":"ERROR", - // "msg":"A message", - // "user":{ - // "id":"*******", - // "email":"*******", - // "created_at":"*******" - // }, - // "environment":"dev", - // "foo":"bar", - // "error":{ - // "type":"*errors.errorString", - // "message":"an error" - // } - // } -} diff --git a/examples/pipe/gdpr.go b/examples/pipe/gdpr.go deleted file mode 100644 index cc8b809..0000000 --- a/examples/pipe/gdpr.go +++ /dev/null @@ -1,97 +0,0 @@ -package main - -import ( - "context" - "strings" - - slogmulti "github.com/samber/slog-multi" - "golang.org/x/exp/slog" -) - -func NewGDPRMiddleware() slogmulti.Middleware { - return func(next slog.Handler) slog.Handler { - return &gdprMiddleware{ - next: next, - anonymize: false, - } - } -} - -type gdprMiddleware struct { - next slog.Handler - anonymize bool -} - -func (h *gdprMiddleware) Enabled(ctx context.Context, level slog.Level) bool { - return h.next.Enabled(ctx, level) -} - -func (h *gdprMiddleware) Handle(ctx context.Context, record slog.Record) error { - attrs := []slog.Attr{} - - record.Attrs(func(attr slog.Attr) bool { - if mightContainPII(attr.Key) { - attrs = append(attrs, anonymize(attr)) - } else { - attrs = append(attrs, attr) - } - - return true - }) - - // new record with anonymized data - record = slog.NewRecord(record.Time, record.Level, record.Message, record.PC) - record.AddAttrs(attrs...) - - return h.next.Handle(ctx, record) -} - -func (h *gdprMiddleware) WithAttrs(attrs []slog.Attr) slog.Handler { - if h.anonymize { - for i := range attrs { - attrs[i] = anonymize(attrs[i]) - } - } - - for i := range attrs { - if mightContainPII(attrs[i].Key) { - attrs[i] = anonymize(attrs[i]) - } - } - - return &gdprMiddleware{ - next: h.next.WithAttrs(attrs), - anonymize: h.anonymize, - } -} - -func (h *gdprMiddleware) WithGroup(name string) slog.Handler { - return &gdprMiddleware{ - next: h.next.WithGroup(name), - anonymize: h.anonymize || mightContainPII(name), - } -} - -func mightContainPII(key string) bool { - return key == "user" || - strings.Index(key, "user_") == 0 || - strings.Index(key, "user-") == 0 || - strings.Index(key, "user.") == 0 -} - -func anonymize(attr slog.Attr) slog.Attr { - k := attr.Key - v := attr.Value - kind := attr.Value.Kind() - - switch kind { - case slog.KindGroup: - attrs := v.Group() - for i := range attrs { - attrs[i] = anonymize(attrs[i]) - } - return slog.Group(k, attrs...) - default: - return slog.String(k, "*******") - } -} diff --git a/examples/pool/example.go b/examples/pool/example.go deleted file mode 100644 index 94fe900..0000000 --- a/examples/pool/example.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "fmt" - "net" - "time" - - slogmulti "github.com/samber/slog-multi" - "golang.org/x/exp/slog" -) - -func main() { - // ncat -l 1000 -k - // ncat -l 1001 -k - // ncat -l 1002 -k - - logstash1, _ := net.Dial("tcp", "localhost:1000") - logstash2, _ := net.Dial("tcp", "localhost:1001") - logstash3, _ := net.Dial("tcp", "localhost:1002") - - logger := slog.New( - slogmulti.Pool()( - slog.HandlerOptions{}.NewJSONHandler(logstash1), - slog.HandlerOptions{}.NewJSONHandler(logstash2), - slog.HandlerOptions{}.NewJSONHandler(logstash3), - ), - ) - - logger. - With( - slog.Group("user", - slog.String("id", "user-123"), - slog.Time("created_at", time.Now().AddDate(0, 0, -1)), - ), - ). - With("environment", "dev"). - With("error", fmt.Errorf("an error")). - Error("A message") -} diff --git a/examples/router/example.go b/examples/router/example.go deleted file mode 100644 index cd2275e..0000000 --- a/examples/router/example.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "context" - - slogmulti "github.com/samber/slog-multi" - slogslack "github.com/samber/slog-slack" - "golang.org/x/exp/slog" -) - -func recordMatchRegion(region string) func(ctx context.Context, r slog.Record) bool { - return func(ctx context.Context, r slog.Record) bool { - ok := false - - r.Attrs(func(attr slog.Attr) bool { - if attr.Key == "region" && attr.Value.Kind() == slog.KindString && attr.Value.String() == region { - ok = true - return false - } - - return true - }) - - return ok - } -} - -func main() { - slackChannelUS := slogslack.Option{Level: slog.LevelError, WebhookURL: "xxx", Channel: "supervision-us"}.NewSlackHandler() - slackChannelEU := slogslack.Option{Level: slog.LevelError, WebhookURL: "xxx", Channel: "supervision-eu"}.NewSlackHandler() - slackChannelAPAC := slogslack.Option{Level: slog.LevelError, WebhookURL: "xxx", Channel: "supervision-apac"}.NewSlackHandler() - - logger := slog.New( - slogmulti.Router(). - Add(slackChannelUS, recordMatchRegion("us")). - Add(slackChannelEU, recordMatchRegion("eu")). - Add(slackChannelAPAC, recordMatchRegion("apac")). - Handler(), - ) - - logger. - With("region", "us"). - With("pool", "us-west-1"). - Error("Server desynchronized") -} diff --git a/go.mod b/go.mod index e5b02f9..87b2f68 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,5 @@ go 1.20 require ( github.com/samber/lo v1.38.1 - github.com/samber/slog-slack v0.1.1 golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 ) - -require ( - github.com/gorilla/websocket v1.4.2 // indirect - github.com/slack-go/slack v0.12.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum index d12eea3..0dac19d 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,4 @@ -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/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= -github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -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/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/samber/slog-slack v0.1.1 h1:h36tHCIeNSX3lPD7wtIfblOem1lanWIh77p0zszrq3I= -github.com/samber/slog-slack v0.1.1/go.mod h1:rXkQmNKcjKhMAAl0cfbH0cnlgveqGXUdoIyYnEmnguQ= -github.com/slack-go/slack v0.12.1 h1:X97b9g2hnITDtNsNe5GkGx6O2/Sz/uC20ejRZN6QxOw= -github.com/slack-go/slack v0.12.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/images/workflow.drawio b/images/workflow.drawio deleted file mode 100644 index 710abad..0000000 --- a/images/workflow.drawio +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/images/workflow.png b/images/workflow.png deleted file mode 100644 index c44d45dbc54990d0ca496d69ae47baba02912b38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23769 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