Appearance
Go
The github.com/XKD-Labs/simpledatalogs-go module sends events and log lines from Go services, workers and CLIs. It uses only the standard library and never panics into your app.
- Go 1.22+.
- A
slog.Handlerfor your logs. - A nil
*Clientis a no-op, so a missing key never crashes the app.
Install
sh
go get github.com/XKD-Labs/simpledatalogs-goEarly access
The module has no tagged release yet. Until it does, fetch the latest commit:
sh
go get github.com/XKD-Labs/simpledatalogs-go@mainQuick start
go
import simpledatalogs "github.com/XKD-Labs/simpledatalogs-go"
sdl, err := simpledatalogs.New(simpledatalogs.Config{}) // reads SIMPLEDATALOGS_KEY
if err != nil {
log.Printf("simpledatalogs is off: %v", err)
}
defer sdl.Close(context.Background())
sdl.Track("signup", simpledatalogs.Props{"plan": "pro"}, simpledatalogs.WithUserID("42"))
sdl.Log(simpledatalogs.LevelError, "Payment failed", simpledatalogs.Props{"order_id": 7})A background goroutine sends every 5 seconds. Close sends what is left; call it before main returns. Use a secret key (sdl_sec_…) on servers.
New returns an error only for a missing key or a malformed endpoint, and then a nil *Client whose methods do nothing.
Events
go
sdl.Track("checkout", simpledatalogs.Props{"plan": "pro", "seats": 3, "trial": false},
simpledatalogs.WithUserID(strconv.Itoa(user.ID)),
simpledatalogs.WithTime(finishedAt),
)- Every line gets a UUID
event_idand atswhen you callTrackorLog, so a retry never counts twice. - Props are strings, numbers, booleans and named types over them;
fmt.Stringers send their text. Anything else is skipped with a warning. At most 100 props.
Logs (slog)
go
logger := slog.New(simpledatalogs.NewHandler(sdl, &simpledatalogs.HandlerOptions{Level: slog.LevelWarn}))
logger.Error("Payment failed", "order_id", 7, "user_id", user.ID, "err", err)- Each record becomes one log line with its level, time and message.
- Attributes become props; groups become dotted names (
http.status). Errors andStringers send their text. - Levels map to
debug,info,warning,error, andcriticalfromslog.LevelError+4. - To keep your current output too, fan out to both handlers.
sdl.Log takes LevelDebug, LevelInfo, LevelNotice, LevelWarning, LevelError or LevelCritical.
Users
Identity is per call: WithUserID, WithDeviceID, WithSessionID and WithAnonymousID. In slog records, top-level user_id, device_id, session_id and anonymous_id attributes fill the same fields.
The opt-out
go
sdl.SetEnabled(false) // deletes every unsent line and ignores new ones
sdl.SetEnabled(true) // collects againTo keep a client silent in tests or local runs, set Disabled: true in Config.
Flush and Close
go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := sdl.Close(ctx); err != nil {
log.Printf("simpledatalogs: %v", err) // errors.Is(err, simpledatalogs.ErrUndelivered)
}- With a deadline,
FlushandCloseretry until the queue is empty or the deadline passes. - Without one, they try once and stop at the first failure.
- In an HTTP server, call
Closeafterserver.Shutdown, so the last requests' events go too. sdl.Pending()counts lines not yet confirmed.
Configuration
Config field | Default | What it does |
|---|---|---|
Key | SIMPLEDATALOGS_KEY | The ingest key |
Endpoint | SIMPLEDATALOGS_ENDPOINT, else https://edge.simpledatalogs.com | The ingest host; /v1/ingest is added |
FlushInterval | 5s | How often the background goroutine sends |
MaxBatchEvents | 50 | Lines per request |
MaxRequestBytes | 256000 | Bytes per request before gzip |
MaxQueueEvents / MaxQueueBytes | 10000 / 10000000 | The queue's caps |
RequestTimeout | 15s | Per request |
DisableGzip | false | Bodies of 1 KB and more are gzipped unless set |
DefaultProps | none | Props added to every line, under the call's own |
HTTPClient | its own | Copied; redirects are never followed |
Logger | text on stderr | Where the SDK's own warnings go, once per kind |
Disabled | false | Keeps the client silent |
Privacy
- The SDK sends only what you pass: no IP addresses, hostnames, environment variables or request data.
- slog attributes reach ingest as you logged them, so leave personal data out of them.
- See Privacy for senders for what belongs in
user_id.
How it behaves
Batching
Every 5 seconds, or at once when 50 lines wait. Each request stays under 256 KB before gzip. No call waits on the network.
Memory only
The queue lives in memory, so lines still queued when the process dies are lost. For log files that must survive crashes, ship the files with Vector or Fluent Bit.
Errors and offline
| Answer | What the SDK does |
|---|---|
202 | Removes the lines |
401 | Keeps every line and re-checks the key after 1 minute, then every 10 minutes |
413 | Halves the batch; a single line still too large is dropped |
415 | Drops the batch, with one warning |
| Anything else, a timeout, offline | Backs off with full jitter from 1 second to 5 minutes, never sooner than Retry-After |
Caps
The queue keeps at most 10,000 lines or 10 MB. Past that, the oldest unsent lines are dropped, with a warning.
Troubleshooting
The SDK's warnings go to stderr, once per kind; set Config.Logger to route them elsewhere. For local development, set SIMPLEDATALOGS_ENDPOINT=http://localhost:4001.
Package docs are on pkg.go.dev.
Next steps
- Go quickstart: the shortest path.
- Text log lines: how levels and messages are stored.
- Privacy for senders: ids, not emails.