Slack
patrol.Slack posts one message per channel and per project in the batch. Each event in that project becomes one attachment, coloured by type: blue for Info, red for Error, yellow for Debug.
An attachment carries, in order: the fields as bold name and value lines, the message in a code block, the stacktrace with an editor link per frame, links to any uploaded files, and the mentions.
The constructor
Section titled “The constructor”patrol.NewSlack(opts patrol.SlackOptions, clientOpts ...slack.Option) (*patrol.Slack, error)Token and Channels are required and are checked before the client is built. The variadic slack.Option values go straight to slack-go, which is how you point it at a test server or give it your own HTTP client.
Options
Section titled “Options”| Field | Type | JSON | YAML and TOML | Required |
|---|---|---|---|---|
Token |
string |
token |
Token |
yes |
Channels |
[]string |
channels |
Channels |
yes |
AcceptChannelEvent |
func(channel string, e *patrol.Event) bool |
- |
- |
no, set in code |
Channels holds channel ids (C0123456789), which is what the Slack UI calls “Copy link to channel” and what the API is happiest with. AcceptChannelEvent is tagged - in all three formats because it is a function: it cannot come from a config file. Return false and that event is left out of that channel’s message; a channel whose events are all filtered out gets no message at all.
There is no TimeZone option here, and no capture time in the message. Slack stamps its own posting time on every message and a second clock next to it reads as a bug.
Token: your_bot_token_tokenChannels: - your_channel_idWhat the bot needs
Section titled “What the bot needs”chat:write to post. files:write as well if any event carries a file. The bot has to be a member of every channel in Channels.
What Test does
Section titled “What Test does”producer.Test(ctx) calls Slack’s auth.test and checks that the response carries a bot id. A token that is valid but belongs to a user rather than a bot fails here, which is the mistake worth catching at startup.
A worked example
Section titled “A worked example”From _examples/basic/program, which loads each options struct from its own YAML file:
// source: _examples/basic/program/main.go#L61-L66 // Slack. slackFlusher, err := patrol.NewSlack(readConfig[patrol.SlackOptions]("slack.yml")) if err != nil { log.Fatal(err) } eventFlushers = append(eventFlushers, slackFlusher)Gotchas
Section titled “Gotchas”A long message is collapsed. Past 500 bytes the message is cut at a space, ... is appended, and a “Show more” button is added whose value is the event id. The button does nothing until you run an interactive endpoint that handles it and fetches the full text. If you do not have one, keep messages short and put the detail in fields.
Files are uploaded once per channel. WithFile costs an upload plus a file-info call for every channel in Channels, for every event that carries it. Three channels and two files is six uploads. Attach files on the events that need them rather than on all of them.
A failure stops that flush. Posting is a loop over channels and project groups, and the first error returns. Channels after the failing one do not get the message in that flush. The error reaches OnError prefixed with slack:, and the next batch starts over.
Mentions are Slack ids. WithMentions("U0123456789") renders as <@U0123456789>. A display name does not work, and no other destination renders mentions at all.