Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@ on:
branches: [ "master" ]

jobs:

build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go: [stable, oldstable]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
check-latest: true
cache: true

- name: Build
run: go build -v ./...
- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
- name: Test
run: go test -race -v ./...
6 changes: 2 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ all: test
prepare:
# needed for `make fmt`
go get golang.org/x/tools/cmd/goimports
# linters
go get github.com/alecthomas/gometalinter
gometalinter --install
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# needed for `make cover`
go get golang.org/x/tools/cmd/cover
@echo Now you should be ready to run "make"
Expand All @@ -18,7 +16,7 @@ fmt:
find . -name "*.go" -exec goimports -w {} \;

lint:
gometalinter
golangci-lint run

cover:
go test -cover -coverprofile cover.out
Expand Down
72 changes: 67 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ Don't forget to destroy the Heroku app after you're done so that you aren't char

The code for the sample app is [available on Github](https://github.com/honeybadger-io/crywolf-go), in case you'd like to read through it, or run it locally.

## Supported Go Versions

This library supports the last two major Go releases, consistent with the Go team's [release policy](https://go.dev/doc/devel/release):

- Go 1.25.x
- Go 1.24.x

Older versions may work but are not officially supported or tested.

## Configuration

Expand All @@ -99,18 +107,22 @@ honeybadger.Configure(honeybadger.Configuration{
```
The following options are available to you:

| Name | Type | Default | Example | Environment variable |
| ----- | ---- | ------- | ------- | -------------------- |
| Name | Type | Default | Example | Environment variable |
|------|------|----------|----------|----------------------|
| APIKey | `string` | `""` | `"badger01"` | `HONEYBADGER_API_KEY` |
| Root | `string` | The current working directory | `"/path/to/project"` | `HONEYBADGER_ROOT` |
| Env | `string` | `""` | `"production"` | `HONEYBADGER_ENV` |
| Hostname | `string` | The hostname of the current server. | `"badger01"` | `HONEYBADGER_HOSTNAME` |
| Hostname | `string` | The hostname of current server | `"badger01"` | `HONEYBADGER_HOSTNAME` |
| Endpoint | `string` | `"https://api.honeybadger.io"` | `"https://honeybadger.example.com/"` | `HONEYBADGER_ENDPOINT` |
| Sync | `bool` | false | `true` | `HONEYBADGER_SYNC` |
| Sync | `bool` | `false` | `true` | `HONEYBADGER_SYNC` |
| Timeout | `time.Duration` | 3 seconds | `10 * time.Second` | `HONEYBADGER_TIMEOUT` (nanoseconds) |
| Logger | `honeybadger.Logger` | Logs to stderr | `CustomLogger{}` | n/a |
| Backend | `honeybadger.Backend` | HTTP backend | `CustomBackend{}` | n/a |

| EventsBatchSize | `int` | 1000 | `500` | `HONEYBADGER_EVENTS_BATCH_SIZE` |
| EventsTimeout | `time.Duration` | 30 seconds | `10 * time.Second` | `HONEYBADGER_EVENTS_TIMEOUT` (nanoseconds) |
| EventsMaxQueueSize | `int` | 100000 | `50000` | `HONEYBADGER_EVENTS_MAX_QUEUE_SIZE` |
| EventsMaxRetries | `int` | 3 | `5` | `HONEYBADGER_EVENTS_MAX_RETRIES` |
| EventsThrottleWait | `time.Duration` | 60 seconds | `30 * time.Second` | `HONEYBADGER_EVENTS_THROTTLE_WAIT` (nanoseconds)|

## Public Interface

Expand Down Expand Up @@ -228,6 +240,56 @@ honeybadger.BeforeNotify(

---

### `honeybadger.Event()`: Send events to Honeybadger Insights.

Send custom events to Honeybadger Insights for tracking application behavior and metrics.

#### Examples:

```go
honeybadger.Event("user_login", map[string]any{
"user_id": 123,
"email": "[email protected]",
})
```

Events are batched and sent asynchronously. Configuration options are available
for batching, retries, and throttling. See [Configuration](#configuration) for details.

---

### `honeybadger.BeforeEvent()`: Add a callback to skip or modify event data.

Similar to `BeforeNotify()`, you can add callbacks to modify event data or skip events entirely before they are sent.

#### Examples:

To modify or augment event data:

```go
honeybadger.BeforeEvent(
func(event map[string]any) error {
event["environment"] = "production"
return nil
}
)
```

To skip events, use `honeybadger.ErrEventDropped`:

```go
honeybadger.BeforeEvent(
func(event map[string]any) error {
if event["event_type"] == "debug_event" {
return honeybadger.ErrEventDropped
}
return nil
}
)
```

---

### ``honeybadger.NewNullBackend()``: Disable data reporting.

`NewNullBackend` creates a backend which swallows all errors and does not send them to Honeybadger. This is useful for development and testing to disable sending unnecessary errors.
Expand Down
50 changes: 47 additions & 3 deletions client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package honeybadger

import (
"errors"
"net/http"
)

Expand All @@ -14,9 +15,13 @@ type Payload interface {
// custom implementation may be configured by the user.
type Backend interface {
Notify(feature Feature, payload Payload) error
Event(events []*eventPayload) error
}

var ErrEventDropped = errors.New("event dropped by handler")

type noticeHandler func(*Notice) error
type eventHandler func(map[string]any) error

// Client is the manager for interacting with the Honeybadger service. It holds
// the configuration and implements the public API.
Expand All @@ -25,11 +30,22 @@ type Client struct {
context *contextSync
worker worker
beforeNotifyHandlers []noticeHandler
eventsWorker *EventsWorker
beforeEventHandlers []eventHandler
}

func eventsConfigChanged(config *Configuration) bool {
return config.EventsBatchSize > 0 || config.EventsTimeout > 0 || config.EventsMaxQueueSize > 0 || config.EventsMaxRetries >= 0 || config.Backend != nil || config.Context != nil
}

// Configure updates the client configuration with the supplied config.
func (client *Client) Configure(config Configuration) {
client.Config.update(&config)

if eventsConfigChanged(&config) && client.eventsWorker != nil {
client.eventsWorker.Stop()
client.eventsWorker = NewEventsWorker(client.Config)
}
}

// SetContext updates the client context with supplied context.
Expand All @@ -40,6 +56,7 @@ func (client *Client) SetContext(context Context) {
// Flush blocks until the worker has processed its queue.
func (client *Client) Flush() {
client.worker.Flush()
client.eventsWorker.Flush()
}

// BeforeNotify adds a callback function which is run before a notice is
Expand All @@ -49,6 +66,10 @@ func (client *Client) BeforeNotify(handler func(notice *Notice) error) {
client.beforeNotifyHandlers = append(client.beforeNotifyHandlers, handler)
}

func (client *Client) BeforeEvent(handler func(event map[string]any) error) {
client.beforeEventHandlers = append(client.beforeEventHandlers, handler)
}

// Notify reports the error err to the Honeybadger service.
func (client *Client) Notify(err interface{}, extra ...interface{}) (string, error) {
extra = append([]interface{}{client.context.internal}, extra...)
Expand Down Expand Up @@ -78,6 +99,27 @@ func (client *Client) Notify(err interface{}, extra ...interface{}) (string, err
return notice.Token, nil
}

func (client *Client) Event(eventType string, eventData map[string]interface{}) error {
event := newEventPayload(eventType, eventData)

for _, handler := range client.beforeEventHandlers {
err := handler(event.data)

if err == ErrEventDropped {
return nil
} else if err != nil {
return err
}
}

if client.Config.Sync {
return client.Config.Backend.Event([]*eventPayload{event})
}

client.eventsWorker.Push(event)
return nil
}

// Monitor automatically reports panics which occur in the function it's called
// from. Must be deferred.
func (client *Client) Monitor() {
Expand Down Expand Up @@ -110,11 +152,13 @@ func (client *Client) Handler(h http.Handler) http.Handler {
func New(c Configuration) *Client {
config := newConfig(c)
worker := newBufferedWorker(config)
eventsWorker := NewEventsWorker(config)

client := Client{
Config: config,
worker: worker,
context: newContextSync(),
Config: config,
worker: worker,
context: newContextSync(),
eventsWorker: eventsWorker,
}

return &client
Expand Down
4 changes: 4 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ func (b *mockBackend) Notify(_ Feature, n Payload) error {
return nil
}

func (b *mockBackend) Event(events []*eventPayload) error {
return nil
}

func mockClient(c Configuration) (Client, *mockWorker, *mockBackend) {
worker := &mockWorker{}
backend := &mockBackend{}
Expand Down
Loading