HTTP client
patrol.Client is a flusher that posts the batch to a patrol Server. POST / with the events as a JSON array; GET / for the connectivity check, which answers the literal body OK.
It is how a service keeps its credentials to itself. The Slack token, the SMTP password and the Twilio account live on the machine running the Server; every service that reports events holds one URL and one credential.
The constructor
Section titled “The constructor”patrol.NewClient(opts patrol.ClientOptions, clientOpts ...httpclient.Option) *patrol.ClientNo error. NewClient has no required field and validates nothing, so an empty BaseURL is not caught here: it fails at the first request. The variadic httpclient.Option values are applied after the defaults, which is where a timeout, a retry policy or a custom transport goes.
The returned *patrol.Client embeds *httpclient.Client, so its methods are available if you need to call the server yourself.
Options
Section titled “Options”| Field | Type | JSON | YAML and TOML | Required |
|---|---|---|---|---|
BaseURL |
string |
base_url |
BaseURL |
in practice, yes |
BasicAuth.Username |
string |
basic_auth.username |
BasicAuth.Username |
no |
BasicAuth.Password |
string |
basic_auth.password |
BasicAuth.Password |
no |
Token |
string |
token |
Token |
no |
Token is sent as Authorization: Bearer <token> on every request and the server accepts it when it is listed in its own Tokens. BasicAuth sends a username and password instead. When both are set, Token wins and the username and password are never sent. Set one.
A server with neither BasicAuth nor Tokens configured accepts anything, so a client with no credential is a valid setup on a private network.
BaseURL: http://patrol.internal:8080Token: pk_live_2f8c...A worked example
Section titled “A worked example”From the library’s README, with a username and password:
// source: README.md#L131-L135client := patrol.NewClient(patrol.ClientOptions{ BaseURL: "http://patrol.internal:8080", BasicAuth: patrol.BasicAuthOptions{Username: "user", Password: "pass"},})producer := patrol.NewProducer(patrol.ProducerOptions{ProjectName: "api"}, client)Reading the server’s errors
Section titled “Reading the server’s errors”A failed request comes back as a patrol.ServerError, which carries ErrorCode and Message. The codes are exported constants, so a sender can switch on them instead of on strings:
package main
import ( "context" "errors" "log" "os" "time"
"github.com/kataras/patrol")
func main() { ctx := context.Background()
client := patrol.NewClient(patrol.ClientOptions{ BaseURL: "https://api.patrol.hellenic.dev", Token: os.Getenv("PATROL_KEY"), })
producer := patrol.NewProducer(patrol.ProducerOptions{ ProjectName: "checkout-api", BufferInterval: 5 * time.Second, OnError: onError, }, client) defer producer.Close(ctx)
if err := producer.Test(ctx); err != nil { log.Println("patrol:", err) }}
func onError(err error) { serverErr, ok := errors.AsType[patrol.ServerError](err) if !ok { log.Println("patrol:", err) return } switch serverErr.ErrorCode { case patrol.ErrorCodeUnauthenticated: log.Println("patrol: the credential was refused") case patrol.ErrorCodePayloadTooLarge: log.Println("patrol: the batch is too large, lower MaxBatchSize") case patrol.ErrorCodeResourceExhausted: log.Println("patrol: over quota") default: log.Println("patrol:", serverErr.ErrorCode, serverErr.Message) }}errors.AsType reaches through both the flusher-name prefix the producer adds and the joined error it builds when several flushers fail at once.
What Test does
Section titled “What Test does”producer.Test(ctx) sends GET / and expects OK. When the server has credentials configured, that GET is authenticated too, so this check covers the credential as well as the route.
Gotchas
Section titled “Gotchas”An empty batch sends nothing. FlushEvents returns early on a zero-length batch, so an idle producer makes no requests at all.
A 200 is not a delivery. The server answers as soon as it has decoded the batch and delivers in the background. What you learn from a successful POST is that the server accepted the events, not that Slack got them. Errors on that side reach the server’s own OnError.
32 MiB is the ceiling. patrol.MaxRequestBody is 32 MiB and a larger body is refused with 413 and PAYLOAD_TOO_LARGE, before any decoding. Events carrying files are what gets a batch there; MaxBatchSize is the knob.
Retries are yours. Nothing is retried and nothing is buffered to disk. A flush that fails is reported to OnError and its events are gone. A sender that cannot lose events wraps the client in its own flusher with a queue.