pax_global_header00006660000000000000000000000064147410304350014513gustar00rootroot0000000000000052 comment=c754b60f06075c830867aa06817e6b05ffa2eeac slog-slack-2.7.3/000077500000000000000000000000001474103043500135635ustar00rootroot00000000000000slog-slack-2.7.3/.github/000077500000000000000000000000001474103043500151235ustar00rootroot00000000000000slog-slack-2.7.3/.github/FUNDING.yml000066400000000000000000000000211474103043500167310ustar00rootroot00000000000000github: [samber] slog-slack-2.7.3/.github/dependabot.yml000066400000000000000000000003031474103043500177470ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly - package-ecosystem: gomod directory: / schedule: interval: weekly slog-slack-2.7.3/.github/workflows/000077500000000000000000000000001474103043500171605ustar00rootroot00000000000000slog-slack-2.7.3/.github/workflows/lint.yml000066400000000000000000000006761474103043500206620ustar00rootroot00000000000000name: Lint on: push: pull_request: jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/setup-go@v5 with: go-version: 1.21 stable: false - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: args: --timeout 120s --max-same-issues 50 - name: Bearer uses: bearer/bearer-action@v2 slog-slack-2.7.3/.github/workflows/release.yml000066400000000000000000000025211474103043500213230ustar00rootroot00000000000000name: Release on: workflow_dispatch: inputs: semver: type: string description: 'Semver (eg: v1.2.3)' required: true jobs: release: if: github.triggering_actor == 'samber' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: 1.21 stable: false - name: Test run: make test # remove tests in order to clean dependencies - name: Remove xxx_test.go files run: rm -rf *_test.go ./examples ./images # cleanup test dependencies - name: Cleanup dependencies run: go mod tidy - name: List files run: tree -Cfi - name: Write new go.mod into logs run: cat go.mod - name: Write new go.sum into logs run: cat go.sum - name: Create tag run: | git config --global user.name '${{ github.triggering_actor }}' git config --global user.email "${{ github.triggering_actor}}@users.noreply.github.com" git add . git commit --allow-empty -m 'bump ${{ inputs.semver }}' git tag ${{ inputs.semver }} git push origin ${{ inputs.semver }} - name: Release uses: softprops/action-gh-release@v2 with: name: ${{ inputs.semver }} tag_name: ${{ inputs.semver }} slog-slack-2.7.3/.github/workflows/test.yml000066400000000000000000000013121474103043500206570ustar00rootroot00000000000000name: Tests on: push: tags: branches: pull_request: jobs: test: runs-on: ubuntu-latest strategy: matrix: go: - '1.21' - '1.22' - '1.x' steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} stable: false - name: Build run: make build - name: Test run: make test - name: Test run: make coverage - name: Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./cover.out flags: unittests verbose: true if: matrix.go == '1.21' slog-slack-2.7.3/.gitignore000066400000000000000000000013651474103043500155600ustar00rootroot00000000000000 # Created by https://www.toptal.com/developers/gitignore/api/go # Edit at https://www.toptal.com/developers/gitignore?templates=go ### Go ### # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # Go workspace file go.work ### Go Patch ### /vendor/ /Godeps/ # End of https://www.toptal.com/developers/gitignore/api/go cover.out cover.html .vscode .idea/ slog-slack-2.7.3/LICENSE000066400000000000000000000020561474103043500145730ustar00rootroot00000000000000MIT License Copyright (c) 2023 Samuel Berthe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. slog-slack-2.7.3/Makefile000066400000000000000000000020101474103043500152140ustar00rootroot00000000000000 build: go build -v ./... test: go test -race -v ./... watch-test: reflex -t 50ms -s -- sh -c 'gotest -race -v ./...' bench: go test -benchmem -count 3 -bench ./... watch-bench: reflex -t 50ms -s -- sh -c 'go test -benchmem -count 3 -bench ./...' coverage: go test -v -coverprofile=cover.out -covermode=atomic ./... go tool cover -html=cover.out -o cover.html tools: go install github.com/cespare/reflex@latest go install github.com/rakyll/gotest@latest go install github.com/psampaz/go-mod-outdated@latest go install github.com/jondot/goweight@latest go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest go get -t -u golang.org/x/tools/cmd/cover go install github.com/sonatype-nexus-community/nancy@latest go mod tidy lint: golangci-lint run --timeout 60s --max-same-issues 50 ./... lint-fix: golangci-lint run --timeout 60s --max-same-issues 50 --fix ./... audit: go list -json -m all | nancy sleuth outdated: go list -u -m -json all | go-mod-outdated -update -direct weight: goweight slog-slack-2.7.3/README.md000066400000000000000000000176661474103043500150620ustar00rootroot00000000000000 # slog: Slack handler [![tag](https://img.shields.io/github/tag/samber/slog-slack.svg)](https://github.com/samber/slog-slack/releases) ![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.21-%23007d9c) [![GoDoc](https://godoc.org/github.com/samber/slog-slack?status.svg)](https://pkg.go.dev/github.com/samber/slog-slack) ![Build Status](https://github.com/samber/slog-slack/actions/workflows/test.yml/badge.svg) [![Go report](https://goreportcard.com/badge/github.com/samber/slog-slack)](https://goreportcard.com/report/github.com/samber/slog-slack) [![Coverage](https://img.shields.io/codecov/c/github/samber/slog-slack)](https://codecov.io/gh/samber/slog-slack) [![Contributors](https://img.shields.io/github/contributors/samber/slog-slack)](https://github.com/samber/slog-slack/graphs/contributors) [![License](https://img.shields.io/github/license/samber/slog-slack)](./LICENSE) A [Slack](https://slack.com) Handler for [slog](https://pkg.go.dev/golang.org/log/slog) Go library.

Sponsored by:
Quickwit
Cloud-native search engine for observability - An OSS alternative to Splunk, Elasticsearch, Loki, and Tempo.

**See also:** - [slog-multi](https://github.com/samber/slog-multi): `slog.Handler` chaining, fanout, routing, failover, load balancing... - [slog-formatter](https://github.com/samber/slog-formatter): `slog` attribute formatting - [slog-sampling](https://github.com/samber/slog-sampling): `slog` sampling policy - [slog-mock](https://github.com/samber/slog-mock): `slog.Handler` for test purposes **HTTP middlewares:** - [slog-gin](https://github.com/samber/slog-gin): Gin middleware for `slog` logger - [slog-echo](https://github.com/samber/slog-echo): Echo middleware for `slog` logger - [slog-fiber](https://github.com/samber/slog-fiber): Fiber middleware for `slog` logger - [slog-chi](https://github.com/samber/slog-chi): Chi middleware for `slog` logger - [slog-http](https://github.com/samber/slog-http): `net/http` middleware for `slog` logger **Loggers:** - [slog-zap](https://github.com/samber/slog-zap): A `slog` handler for `Zap` - [slog-zerolog](https://github.com/samber/slog-zerolog): A `slog` handler for `Zerolog` - [slog-logrus](https://github.com/samber/slog-logrus): A `slog` handler for `Logrus` **Log sinks:** - [slog-datadog](https://github.com/samber/slog-datadog): A `slog` handler for `Datadog` - [slog-betterstack](https://github.com/samber/slog-betterstack): A `slog` handler for `Betterstack` - [slog-rollbar](https://github.com/samber/slog-rollbar): A `slog` handler for `Rollbar` - [slog-loki](https://github.com/samber/slog-loki): A `slog` handler for `Loki` - [slog-sentry](https://github.com/samber/slog-sentry): A `slog` handler for `Sentry` - [slog-syslog](https://github.com/samber/slog-syslog): A `slog` handler for `Syslog` - [slog-logstash](https://github.com/samber/slog-logstash): A `slog` handler for `Logstash` - [slog-fluentd](https://github.com/samber/slog-fluentd): A `slog` handler for `Fluentd` - [slog-graylog](https://github.com/samber/slog-graylog): A `slog` handler for `Graylog` - [slog-quickwit](https://github.com/samber/slog-quickwit): A `slog` handler for `Quickwit` - [slog-slack](https://github.com/samber/slog-slack): A `slog` handler for `Slack` - [slog-telegram](https://github.com/samber/slog-telegram): A `slog` handler for `Telegram` - [slog-mattermost](https://github.com/samber/slog-mattermost): A `slog` handler for `Mattermost` - [slog-microsoft-teams](https://github.com/samber/slog-microsoft-teams): A `slog` handler for `Microsoft Teams` - [slog-webhook](https://github.com/samber/slog-webhook): A `slog` handler for `Webhook` - [slog-kafka](https://github.com/samber/slog-kafka): A `slog` handler for `Kafka` - [slog-nats](https://github.com/samber/slog-nats): A `slog` handler for `NATS` - [slog-parquet](https://github.com/samber/slog-parquet): A `slog` handler for `Parquet` + `Object Storage` - [slog-channel](https://github.com/samber/slog-channel): A `slog` handler for Go channels ## 🚀 Install ```sh go get github.com/samber/slog-slack/v2 ``` **Compatibility**: go >= 1.21 No breaking changes will be made to exported APIs before v3.0.0. ## 💡 Usage GoDoc: [https://pkg.go.dev/github.com/samber/slog-slack/v2](https://pkg.go.dev/github.com/samber/slog-slack/v2) ### Handler options ```go type Option struct { // log level (default: debug) Level slog.Leveler // slack webhook url WebhookURL string // slack bot token BotToken string // slack channel (default: webhook channel) Channel string // bot username (default: webhook username) Username string // bot emoji (default: webhook emoji) IconEmoji string // bot emoji (default: webhook emoji) IconURL string // Not implemented yet, but we would like your feedback here: #7 // ThreadTimestamp string // API request timeout (default: 10s) Timeout time.Duration // optional: customize Slack message builder Converter Converter // optional: see slog.HandlerOptions AddSource bool ReplaceAttr func(groups []string, a slog.Attr) slog.Attr } ``` Attributes will be injected in message attachments. Other global parameters: ```go slogslack.SourceKey = "source" slogslack.ColorMapping = map[slog.Level]string{...} ``` ![screenshot](./screenshot.png) ### Example #### Using webhook Generate a webhook [here](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks). ```go import ( slogslack "github.com/samber/slog-slack/v2" "log/slog" ) func main() { webhook := "https://hooks.slack.com/services/xxx/yyy/zzz" channel := "alerts" logger := slog.New(slogslack.Option{Level: slog.LevelError, WebhookURL: webhook, Channel: channel}.NewSlackHandler()) logger = logger. With("environment", "dev"). With("release", "v1.0.0") // log error logger. With("category", "sql"). With("query.statement", "SELECT COUNT(*) FROM users;"). With("query.duration", 1*time.Second). With("error", fmt.Errorf("could not count users")). Error("caramba!") // log user signup logger. With( slog.Group("user", slog.String("id", "user-123"), slog.Time("created_at", time.Now()), ), ). Info("user registration") // push record to a thread logger.ErrorContext( slogslack.WithThreadTimestamp(context.Background(), "1714929099.4238"), "An error", ) } ``` #### Using bot token Use [Bot token](https://api.slack.com/authentication/token-types#bot). ```go import ( slogslack "github.com/samber/slog-slack/v2" "log/slog" ) func main() { token := "xoxb-" channel := "alerts" logger := slog.New(slogslack.Option{Level: slog.LevelError, BotToken: token, Channel: channel}.NewSlackHandler()) logger = logger. With("environment", "dev"). With("release", "v1.0.0") } ``` ## 🤝 Contributing - Ping me on twitter [@samuelberthe](https://twitter.com/samuelberthe) (DMs, mentions, whatever :)) - Fork the [project](https://github.com/samber/slog-slack) - Fix [open issues](https://github.com/samber/slog-slack/issues) or request new features Don't hesitate ;) ```bash # Install some dev dependencies make tools # Run tests make test # or make watch-test ``` ## 👤 Contributors ![Contributors](https://contrib.rocks/image?repo=samber/slog-slack) ## 💫 Show your support Give a ⭐️ if this project helped you! [![GitHub Sponsors](https://img.shields.io/github/sponsors/samber?style=for-the-badge)](https://github.com/sponsors/samber) ## 📝 License Copyright © 2023 [Samuel Berthe](https://github.com/samber). This project is [MIT](./LICENSE) licensed. slog-slack-2.7.3/context.go000066400000000000000000000013561474103043500156030ustar00rootroot00000000000000package slogslack import "context" type threadTimestampCtxKey struct{} // reply in thread to posts with timestamps set this func WithThreadTimestamp(ctx context.Context, ts string) context.Context { return context.WithValue(ctx, threadTimestampCtxKey{}, ts) } func contextThreadTimestamp(ctx context.Context) string { if v, ok := ctx.Value(threadTimestampCtxKey{}).(string); ok { return v } return "" } type replyBroadcastCtxKey struct{} // broadcast to channel when replies to thread if set func WithReplyBroadcast(ctx context.Context) context.Context { return context.WithValue(ctx, replyBroadcastCtxKey{}, true) } func contextReplyBroadcast(ctx context.Context) bool { _, ok := ctx.Value(replyBroadcastCtxKey{}).(bool) return ok } slog-slack-2.7.3/converter.go000066400000000000000000000030721474103043500161230ustar00rootroot00000000000000package slogslack import ( "log/slog" slogcommon "github.com/samber/slog-common" "github.com/slack-go/slack" ) var SourceKey = "source" type Converter func(addSource bool, replaceAttr func(groups []string, a slog.Attr) slog.Attr, loggerAttr []slog.Attr, groups []string, record *slog.Record) *slack.WebhookMessage func DefaultConverter(addSource bool, replaceAttr func(groups []string, a slog.Attr) slog.Attr, loggerAttr []slog.Attr, groups []string, record *slog.Record) *slack.WebhookMessage { // aggregate all attributes attrs := slogcommon.AppendRecordAttrsToAttrs(loggerAttr, groups, record) // developer formatters if addSource { attrs = append(attrs, slogcommon.Source(SourceKey, record)) } attrs = slogcommon.ReplaceAttrs(replaceAttr, []string{}, attrs...) attrs = slogcommon.RemoveEmptyAttrs(attrs) // handler formatter message := &slack.WebhookMessage{} message.Text = record.Message message.Attachments = []slack.Attachment{ { Color: ColorMapping[record.Level], Fields: []slack.AttachmentField{}, }, } attrToSlackMessage("", attrs, message) return message } func attrToSlackMessage(base string, attrs []slog.Attr, message *slack.WebhookMessage) { for i := range attrs { attr := attrs[i] k := attr.Key v := attr.Value kind := attr.Value.Kind() if kind == slog.KindGroup { attrToSlackMessage(base+k+".", v.Group(), message) } else { field := slack.AttachmentField{} field.Title = base + k field.Value = slogcommon.ValueToString(v) message.Attachments[0].Fields = append(message.Attachments[0].Fields, field) } } } slog-slack-2.7.3/example/000077500000000000000000000000001474103043500152165ustar00rootroot00000000000000slog-slack-2.7.3/example/example.go000066400000000000000000000011241474103043500171760ustar00rootroot00000000000000package main import ( "fmt" "time" "log/slog" slogslack "github.com/samber/slog-slack/v2" ) func main() { webhook := "https://hooks.slack.com/services/xxx/yyy/zzz" channel := "alerts" logger := slog.New(slogslack.Option{Level: slog.LevelDebug, WebhookURL: webhook, Channel: channel}.NewSlackHandler()) logger = logger.With("release", "v1.0.0") 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") } slog-slack-2.7.3/go.mod000066400000000000000000000005641474103043500146760ustar00rootroot00000000000000module github.com/samber/slog-slack/v2 go 1.21 require ( github.com/samber/slog-common v0.18.1 github.com/slack-go/slack v0.15.0 ) require ( github.com/google/go-cmp v0.5.9 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/samber/lo v1.47.0 // indirect github.com/stretchr/testify v1.10.0 // indirect golang.org/x/text v0.16.0 // indirect ) slog-slack-2.7.3/go.sum000066400000000000000000000041271474103043500147220ustar00rootroot00000000000000github.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/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/samber/slog-common v0.18.1 h1:c0EipD/nVY9HG5shgm/XAs67mgpWDMF+MmtptdJNCkQ= github.com/samber/slog-common v0.18.1/go.mod h1:QNZiNGKakvrfbJ2YglQXLCZauzkI9xZBjOhWFKS3IKk= github.com/slack-go/slack v0.15.0 h1:LE2lj2y9vqqiOf+qIIy0GvEoxgF1N5yLGZffmEZykt0= github.com/slack-go/slack v0.15.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= slog-slack-2.7.3/handler.go000066400000000000000000000066071474103043500155400ustar00rootroot00000000000000package slogslack import ( "context" "time" "log/slog" slogcommon "github.com/samber/slog-common" "github.com/slack-go/slack" ) type Option struct { // log level (default: debug) Level slog.Leveler // slack webhook url WebhookURL string // slack bot token BotToken string // slack channel (default: webhook channel) Channel string // bot username (default: webhook username) Username string // bot emoji (default: webhook emoji) IconEmoji string // bot emoji (default: webhook emoji) IconURL string // API request timeout (default: 10s) Timeout time.Duration // optional: customize Slack message builder Converter Converter // optional: see slog.HandlerOptions AddSource bool ReplaceAttr func(groups []string, a slog.Attr) slog.Attr } func (o Option) NewSlackHandler() slog.Handler { if o.Level == nil { o.Level = slog.LevelDebug } if o.WebhookURL == "" && o.BotToken == "" { panic("missing Slack webhook url and bot token") } if o.Timeout == 0 { o.Timeout = 10 * time.Second } if o.Converter == nil { o.Converter = DefaultConverter } return &SlackHandler{ option: o, attrs: []slog.Attr{}, groups: []string{}, } } var _ slog.Handler = (*SlackHandler)(nil) type SlackHandler struct { option Option attrs []slog.Attr groups []string } func (h *SlackHandler) Enabled(_ context.Context, level slog.Level) bool { return level >= h.option.Level.Level() } func (h *SlackHandler) Handle(ctx context.Context, record slog.Record) error { message := h.option.Converter(h.option.AddSource, h.option.ReplaceAttr, h.attrs, h.groups, &record) if h.option.Channel != "" { message.Channel = h.option.Channel } if h.option.Username != "" { message.Username = h.option.Username } if h.option.IconEmoji != "" { message.IconEmoji = h.option.IconEmoji } if h.option.IconURL != "" { message.IconURL = h.option.IconURL } if ts := contextThreadTimestamp(ctx); ts != "" { message.ThreadTimestamp = ts if contextReplyBroadcast(ctx) { message.ReplyBroadcast = true } } // non-blocking go func() { _ = h.postMessage(ctx, message) }() return nil } func (h *SlackHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return &SlackHandler{ option: h.option, attrs: slogcommon.AppendAttrsToGroup(h.groups, h.attrs, attrs...), groups: h.groups, } } func (h *SlackHandler) WithGroup(name string) slog.Handler { // https://cs.opensource.google/go/x/exp/+/46b07846:slog/handler.go;l=247 if name == "" { return h } return &SlackHandler{ option: h.option, attrs: h.attrs, groups: append(h.groups, name), } } func (h *SlackHandler) postMessage(ctx context.Context, message *slack.WebhookMessage) error { ctx, cancel := context.WithTimeout(ctx, h.option.Timeout) defer cancel() if h.option.WebhookURL != "" { return slack.PostWebhookContext(ctx, h.option.WebhookURL, message) } options := []slack.MsgOption{ slack.MsgOptionText(message.Text, true), slack.MsgOptionAttachments(message.Attachments...), slack.MsgOptionUsername(message.Username), slack.MsgOptionIconURL(message.IconURL), slack.MsgOptionIconEmoji(message.IconEmoji), } if message.ThreadTimestamp != "" { options = append(options, slack.MsgOptionTS(message.ThreadTimestamp)) } if message.ReplyBroadcast { options = append(options, slack.MsgOptionBroadcast()) } _, _, err := slack.New(h.option.BotToken).PostMessageContext(ctx, message.Channel, options...) return err } slog-slack-2.7.3/screenshot.png000066400000000000000000000560371474103043500164610ustar00rootroot00000000000000PNG  IHDRwq;Ul iCCPICC ProfileHPS{oz$!RB M"@J$PbL(veqׂ]Qpud-ۢ \ņʻ#޼3sr99)w?s.PL k%͖GY>F@/PȸIWyw@cX M(RPN*Y(7J gC&ٲ12C6rM1NgxLt/ʦ(|< g<B({ |!h]0#+kl2P)g_i*k\~,|4[25بRH2י(DҔIY d7q|̰INTyyѓ,RGM|QVܗ;|T]eF/Tq# dEFTT/WFI}fRi^ Ou6[?տHʝʩW&OĨe>ZU(3PWDf/3LGL2Dmg-Z=6"R$M7NI3Xv߉Wm) )ES4iFF0 f H@00& ܁7 Dd9P}`?8ep#zK0ށC:!dYCP(J4H ):*ʠ :B@` Xf3a̅Chx>/|x\ WGz<|w/a d!6E‘D$#+BDj&t!G Cǰ06wL&#,ƬlĔaa11w0ݘ!W,ƺayxl6[-VaOb/aa{p8gspe=:\3׃:xk>g G2@ $aYmBaA4#ÉBRfb&8B$4#qf;Z޳qv9<ϼbE]K%2r-+JlUnuvXn:C:rF ņkcScm˴ ]k`ję[gjdiw=>~}+C]Gc*F׳gfDwީ鋳ܹy%eKl\qźr=-6g>0؃QL޳ȋU[]ǵsp_}Nu]c :. lm jPK!gVЦ99<3 5p^'#~1|H呭QQEDo~ciUM}W?3~EIBc">16*qxz 7d 2YD269.pg~8?Kٝ2$z DbQ_GjqjGڶD<(I^Kq0c43..uJJfH/.2XdQZV Zx!yJ)+tCiFٝS!76%%7Z-ݰ// eee-ˍY޽b%2ee*UzW>&c/kc]ܺ|=~SSV /X~߷o%߶mpܰkBa"}g]wR7mv޼w nt^[kl~;k{?v,qdVɾʝ]LwmL\vܧn {o>}E>}/"]Y?gZP][UT`סC]\(k$uhcMmE8<9rs'v,5Ojiro:O.?ufY 7˚ϧiYB^lrZx\9}kk םpq_N9txVSnw]ߏّ)Ü#V?>.|_-r:}YԳG=)~ܛϰߡ@^,?vx޿}-=f[GpwYF~p#c맸O}#KX~ih訌/珯pj*oq[MM|O<3U^a@7F{QED.rEG%_:FGGFGT>^?&7F@el#74-PuO d@IDATx.4ݝ!݊(JaR"( " H.v{.yfn͝wιw L  E 8F OHH@ PB  H=v*OH(y @ $@;D$@HH pS"  w^$@$ PN) ; (c`HH @1SyJ$@$@kHb  ة<% ܺuKΟ?/RRv)I&MjgcΜ9#7oޔ)RH ܒaɓ6mZ rK;a۳K.Ʌ $\\'O.I&<I ӧO˱cڵkCBB$C :ujk׮'0q4loaeOk c4jHOtU[|yi_%$vK;a۳ÇG`놀Ϝ9o=r`߷o_O܌n:}2?$+8}L3H00C8uTTiRnGd]!܏:]6 ^<} xGΔύ7d͚5RhQI>wݲ~=Fʔ)5 }b M)wܒ)S&+٢$K,R|y_ٳ2K/ / Ӭy{T>( ,:'O'xB0bH9z:tH|IU<)ԩ u8q3(XbҦMۥKپms_7*)R ~|3f~:y=7|iҥ2:m/6FpFѵk֪G^lݫu}*d)O=OuzJaǎnߥCz.y weyНeƍG 6h>oFO#%K`ۧ۱dU>jԨ{=#~Wl߾]NǤ:Ѽyse'(p+Tܹ{˖-Rd)Yf.nvXp)[FKyt7sIni-o޼&,y2Wtre}M6{vvC6?ȭDJ+UR u[5k ~PEE/M,;eH? | 0kҤ2a"Ho͓Ye;cDKۣ&o+dΝ#FtUs*-Z*Uꮺu2x`qPA=-[6eڵڥN:!aU;˔sș3a r谡*POWh]u=CxVRE0^#ÚYj\==fYb%mWBx"}ZR%7` AD7@C2e4/gT“_֭d5NZՒ ʉ'ӥ>ĵ}ZiOPuӂ m2qDU1=|nF'H*إ`VU\$>~h`nIƼ$IG$ѽ[!^ .EnJge/[wQ#G ]F|m!:|'MwBQ?tx$ _pmSO>}OMj-[H,Ux+V̩.sھ5kgUǵ<}:Q toSFѸ1օIF0-#w{tFg yPs+^ٸqw[N7,k ؟{9ge0'ѾnCx* } V}q\Kq7=o71cȕW4?T4v3pq! CGkN7 |nAJVrl>9(H~8p@[d/Woqs?5kV=Ft0ݻ477<#(:hӶ(P0ēg|Ǔ ӑ5B(aXadγkNȏ6 3& 1E:T"7,{|ͷoرc5 RʕU`W_}uO퀾DxUdG7n?>]A/yZ߫UUU׀zΝM5Q#G鍬 4;I"_Cr ΔJM܌4~G}@dH)1X~X"'ܡ=4i̝#m[I˖-%w 3Co+VuKRD *?rɞ=栣J  `ԏ:J VajW>qDCj<9 XrP{A=4F}WZUG֮<z` O:!:mnݺU$IkAx ~@7*^|0l0sgϩp**,v~|#'t 3zXB>(s*˖ (!SLsd__ǁ#^⫼0(_늌o8{dgXrgۛdً%]jaIX5۫/@zɕ+NP8wL 8~{L_rLv츮UƫXqnqӥU+xu]IH|ZfVak ޱOr ۴Kb Xym7O {?>mzI̍!Ҹu릱,^X'_%O)$@$@"4r`Чݺl;(y+Eb]4X3`_pa]k  ^:sf޶"xp3ޓ"IH; w7 %A0W^] e.! '*f^ 2[[%&+1f&R=F[zIjժ`Vχg  O Bo6bt J!3.-376W38x$ʚV#\31N嗍fN5 }< gam\rƍS'Oos @ "nIOwKv@BNCrUXQ<59vl4/qUo1eR wN? 0"$ap  q'urIh{{,_.gN/ExmoL>7qӀ-k8_:v/_kM'P==VCܯa5:wڭ 8op*=vq%KT_2d:-v1u h'kTy ;*wi9# @u(6Fxy B1:͕;:6tػϼ߮fQ1u;5qϓ'61ňV1ό}BÉ43Z7$')?7\e挙N6pCzgϞ/.rzuG%RJZ=X9Dk8T?1B @RT p%!.]:t 1pI}gFx΄k<5 5pxsiF3JS8'>.#d[غ66&RQVcyN>'VkieݥkzjU؞F3ZF6BN*zC@vC\9u5ntx@ܹ4HHeP/FP 'Co 0ЯɓWt,kt a]c@??of?A| ̈́ t>HMR-5@$@C#T~}] i6 (gVDǨx` 5`ďѿk<<0 2D['+M>͇*FRIhv* @'n"Xa7%U<_p5l$eʖB əg䛯FZylM$sDHǜӏgXP[am`-j"j(ܣZ=$@$ "Z&*HH Ւ $@0$@$pP'H& I& D>e$@$0 8n=X) HH q!to Ub=VOH|p_4#t A=߱s喱` ,F$@c˚IHp\0_yY]2H+.o p%@JcϞ=RXqc#$iF$W\RZuYl+?gҋ/IE` ;vxf*~I[lQبB k G$@app^%kiӶJ}@2|^tͫ'nݺqx=bL2E6\ko%y2vX:Y/4wQi9ҧOIf\ "Uf? @}QWHߠI|mب#iS#jU9 ?Iׯ/ҥr9ޯv"~ܹyfiּ;{o+WqƇl\yUS7HH w09rpbJN4݉!ܵSaۙK,rO 7HH ai۶,ZH^xysriYx 8T̙*Of~"r咜9rʪի$8(Xwh/ǏʏVִ|Vv℉Rd 룞'NHX! o8Jh\2ed۶mҢy _/͚5 1y $@$I<_z̟?_&MfNX~\b$LP7o}O^_u0H\ pJ6?iae}mk2Ixǥb/ʂ  O^0H0.bT|ʑ=el8yKtҖ11a# F(ܣQg$@$( @I1 D#ѨT {H hYl* J=PRG$@ш{4,6H%@()# hD=uJ$@pCw uC Ds_}U)Y;vV={Vr-C'$@$?B1rH"@Ȭ,6eY/ |\Np: qCw-} @x pJ~SIsu֫[Of#~Pի'r * ǎ+˕W5J-dݚ6g)[ZJ{aCiٳK%RjUyͷ;_ZnMa6j(I6,TPL3?HH ,E%_W:u$eJ~O:%{_?kVQsJȑqI0 v_yO$@=Y#\v-4VZqFYn 8HU( 6L2I4idڵk:Ǎ"I$1H"{(&!X֬Y#8~8qDٴi(P@2g,RҴ!-OϻJ^*-#\si)a}츱ǎJ:u/yZd̔QۧO1q ' ?@wz=.\7'%K{0H v=~$ɓ't:a =ctBsÆ 2sL_Rzu 38a%6yd1du1yd]tO|ɤWxQ$@1Gk6,[h}Μ9*Ӧ BѼv\)[nngϖD9eO-Zpd"  G#>O:ȃw_6\<0.SN`cRÆ u۷m;"ccС:oȩӧGѪqeʔa^~e.nŋL2|-+VիWϭ-[Hʕʳ]iThC&@Np+K^z]CXdgغeg޺al aT$ poIdܹJ#ڵk[l8ȕ3TPA!lرR\yɝ+hB8! nʖ)+V=z ({Ilسg:W#oVZچժ˲e˴.ǿ?^*V(9sTMe۶m?HJeҤ;U}Յ u>NMܹszE a4 F=͛7U}ytݫǜsO>,i21Jd7^\ygǏK^ZjnQÉ{'uՕg|,ʕ.jٳgJ{IYM6qFy@2|^tMۧ~]!7V._,/|Ah?6N(5j/JNt>VZɥKw w)0H ^ /_,P\rU7n,CO?Tg.%JP7wUV|K֭9ҬY$o޼[*Uh]A}}*tqđ(N:M֬Y#;w~e3fͪf)}J4,;>e̘1\mQ I"{ _o:M*:ƈ{ߩbŊ@ێȑ#|C%s1yǜ8 r >N6/ş/;wHqO!طCXuݻWe˖wxpH$!8|z jՒ6ʺudAҿiа:N&J<˭]3هhСCN\XУO>?6t [#<Թ(_:0y eӦMΜ9֑ؾ}{?dۣu2O>{CzٵkNbV]V~Tu#GtS1U6& } @ Gё/&(;?Y5k9C2۵2ѣGeZkʔQ#GIlYkݸqCGh^v?ƍ%k֬EeJ˸Ƒ)Rh)S0Z~}vݺGr'r)֛&Ϝ>#-ZO|V H!d9^)xX); :t\I0=E IJ xyI<|]\Q>^xnyrz]Xd/dgum8kV&s ?~|6x} N355@ɝ~^YsO"#H  % K:˗ C!@P$@$p P_H  % Ke$@$PP?<( _/k' B\%9U+-Ϻu=sOm؁ɔ9<3;wnʝQʗ/ߚYd] ^+N̂ 3:u'dذa>~C+V[͛7Wv{8#; o"~j5 9c6,=>A>'.]Z}YZxjGUvs/Q ۿm6I6<+!i'IDn+^;5VDq̛;Oz}Ov vc}L L xM&}8IoTTQ^|EY`w,yzL)SHfMM8alne萡7 )K Lx[` Vxv5+_4w 86 0P` m5yRxq5&(D5m(< ]˹nCW 䩧 4T/ݵ-؆eU Tnߺ- 4TǏWHߵsOZ'sHVpM~ժUʗOp`eV{@M+`[&&Y'ءkeZzM3j׶[\k\狉[1BwOR~,ě*˸j# _sЍŋ+۷%yr83It{ܽ{e|h~@Aaե]a EͥI&.)ׂ2k,I^crYdQu&Ȯh>y!SXLPGGaÆz]#.,f1o<9`v~([&#uˍltºLl:wwx܆vgF'˔) ӧNK4?}6 ?\R.xGQ8<CAK$I\yFppp6.ؕ96~'3ͺo>Uuz O̓-۫6Qg]kvU]떭>tP $z}F'd}îRZji+N:g<'OJܒ2f1,fs Y:z_8sz.YZD <9ˢj 2 pmk@.]:A(J׀µޛ0ҥyY;wNu{^Z8&nt]}Y)ZsOJ'Nt0yN'\ 3CzLڽKuǀSpSƢBֵ.|3Yjװr-34jH IF:}fٵI?BǰAQ;'@KpW<_7tpȂ 8%KԤ:uޮ_8} ]7]L+cx;5;BWzKj ڊcǪ79sc7K`!_yLj׀}ȡ.~xTJdŊx*u*ILCwQEiӦUƍCcr豣:RĈa믿Q7 7߫#UWiQQ߹W,nGP[6Iov]mꫯdԨQ2o<}:Aˮ+Yި9F Kܹ^1 vboȘw>U4;1Z7&?\_{oxyc_sgǵS@;1w` u'IޫW(j-xD+&U]o;(=%3'%rR15`~,BC2C qͪ)<;٣lL3]5ߓO>sZp ,BK?x=Q"uB% Inݺ! )|kcGb$G^A3#P3qv' j*Wlgq]LLHȔ/>NY\ .]*Ç - O]k[1ŋ>?7kyO`Ÿ) 5t޼yjaa,~ rRA%Z~vpXReXreyB ?~3fշO_9x ?*,-Ĺc~N63RV3GNdN<ٲfs[*4n:F*Z2-{Ik]f0a5g m X Xet暆XֈΛ'F ~| *̐7X,QD<[SsÓg ZFoh /y\n Ob d ܽyo2s8:W7q^cp'&@71A課 ab :@| &u* v]7z\gIDATMY4{:{tAHKHH/ wl9 $@ HH p}ǖ O>0H/ wl9 $@ HH p}ǖ O>0H/ wl9 $@ HH p}g1*p݃Ry$P^xʠB>VoJG@$ > Vx`vx+oTH˖-qQg8,`۳,ۻ3I(]:>3C 10|B`"b]}=mwT8T qF~ͷrAiаO7ԮgbܛI֭ԁk<ү=׺u} 2t4h@/ZH]H.%4@8^ռۍ}`TQ]u7Ϝ9#&M'|B ټyے-{61nִڵko8H. оzb\IuNƌ%gΜJ)-Y6.T b Q:¢i *翴iE 7TƯtMGԨYC 0`iܸc;IUDp ԀUVR*ɸSO@pʌ*UMg͚5N]OhOlP 5OB^z:Mj 7HC-ZnNZr'I7*]J/|V8s̎90[)| *ٯiuU-nl7tI$Uu mGӝ< 7  G=0JEx嗤Zj6L&O@B̙4tI `ivsUdߜzc& ujvzjYvNVb2Ьy3ue/u2@$PO,d$ I$gYx_KڷkPd`O{{G '\G1g0qm6iѼSč#hUtX]3ٱ_Y OҥKj!ȑ#ŹM$@{(j'ء$NXXF!,;NF6B֮[+0ܷO_ɑ#ZHD`=$adto xP(CIy޽{s֬Yԙ3gJzuvۧN6VZ;t sul98 xX8JΨXI̜1IС:nҤ.((Hg7h  x(C#ҵ|ϒ%ԬQ爼y2wΝ;<ŏg]L  MjP%KxZda4|ڡE2uT9vF J݅/jJ+%Kq9ʣUd兞/h:z۽{kN / Ŋ^z(s̑eʪ|ңGsH"pႴiF2e$SMSNI>}%SLҢe ǏB I֭$}%ID4n"qƕW*p7m49y̚=K9~ :uH2#'Y pүٳ/^<ɘ1̙S-[ҦM+ -Tm3t[pF6B:uY'|I4z~{TPQ&LpH$@A Ȭ{QwdaH-qg;<׮]%% O1ׯ_8qC$@2^(x _T„ WIH  P-0Y DQ' DU @T!@Uz  H$@0Y DQ' DU @T!@Uz  H$@0Y DQ' DU @T!@/V7ytӚaׯ̞5[V**VStemVv>W_IdҤI=nܸ!oԨ^Cc ի[Om&M6Oux# 7 ,xJW۷[GZhiUXʑ=Z'NK˘S{~+O<֒%K,#5kXF[W\PqmuZnu!qZ4o۶m:vhnZue=iX+WF z0m-_ٳ-CP.Q[uNh"8?nڵ]G$a: @& ߰~)\DW.Ǘ_6+èddŊqF-Q… xS̜9S_Ο?/>$R P^M4͛7KFL^Jܹ$88x'cǎ_|Qʔ.#X1믿h>m䨑oP5kԔ *Ⱦ}d)B}d> PuN)Rj%)BfStD2t7oT}{B$@{z8 @L&~CLs# B=t$OH\ P6 1#y$@$JݕIH p!  W4M$@1@Z;tOHC ƽg^ b=ϋHA=H  Sl' {8`1+ DѥN  pbV .(ܣKO$@$Ŭ$@$]PGb;IH (YIH p.=v @8P @t!HUIENDB`slog-slack-2.7.3/slack.go000066400000000000000000000003131474103043500152040ustar00rootroot00000000000000package slogslack import ( "log/slog" ) var ColorMapping = map[slog.Level]string{ slog.LevelDebug: "#63C5DA", slog.LevelInfo: "#63C5DA", slog.LevelWarn: "#FFA500", slog.LevelError: "#FF0000", }