Documentation
¶
Overview ¶
Package telemetry provides an opt-in telemetry framework with pluggable backends, privacy controls, bounded buffering, and GDPR-compliant data deletion for CLI tools built on GTB.
Architecture ¶
Telemetry is gated at two levels: the tool author enables the TelemetryCmd feature flag, and the user opts in via the telemetry enable command or TELEMETRY_ENABLED environment variable. When either gate is inactive, the collector is a silent noop — callers never need to check.
Events are buffered in memory (capped at 1000) and flushed on process exit via Cobra's OnFinalize callback. If the buffer fills before flush, events are spilled to disk and recovered on the next flush.
Backends ¶
The Backend interface allows tool authors to supply custom analytics platforms. Built-in backends:
- NewNoopBackend — silently discards events (used when disabled)
- NewStdoutBackend — pretty-printed JSON to a writer (debugging)
- NewFileBackend — newline-delimited JSON to a file (local-only mode)
- NewHTTPBackend — JSON POST to an HTTP endpoint
- NewOTelBackend — OpenTelemetry log records via OTLP/HTTP
Vendor-specific backends for Datadog and PostHog are in subpackages.
Privacy ¶
No personally identifiable information is collected. Machine IDs are SHA-256 hashed from multiple system signals. Command arguments and file contents are never recorded unless ExtendedCollection is explicitly enabled by the tool author for enterprise environments.
Usage ¶
// Track a command invocation
p.Collector.TrackCommand("generate", durationMs, exitCode, nil)
// Track a feature usage event
p.Collector.Track(props.EventFeatureUsed, "ai.chat", map[string]string{"provider": "claude"})
Package telemetry provides an opt-in telemetry framework with pluggable backends, privacy controls, and bounded buffering with disk spill.
Index ¶
- func HashedMachineID() string
- func ResolveDataDir(p *props.Props) string
- type Backend
- type Collector
- func (c *Collector) BackendInfo() string
- func (c *Collector) Close(ctx context.Context) error
- func (c *Collector) Drop() error
- func (c *Collector) Flush(ctx context.Context) error
- func (c *Collector) SetBackendInfo(info string)
- func (c *Collector) Track(eventType props.EventType, name string, extra map[string]string)
- func (c *Collector) TrackCommand(name string, durationMs int64, exitCode int, extra map[string]string)
- func (c *Collector) TrackCommandExtended(name string, args []string, durationMs int64, exitCode int, errMsg string, ...)
- type Config
- type DeletionRequestor
- type EmailDeletionRequestor
- type Event
- type EventDeletionRequestor
- type EventType
- type HTTPDeletionRequestor
- type OTelOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func HashedMachineID ¶
func HashedMachineID() string
HashedMachineID returns a privacy-preserving machine identifier derived from multiple system signals: OS machine ID, first non-loopback MAC address, hostname, and username. Each signal degrades gracefully if unavailable. The result is the first 8 bytes of the SHA-256 hash, encoded as 16 hex chars. Computed fresh on every invocation — not persisted to config.
func ResolveDataDir ¶
ResolveDataDir determines the directory for telemetry data files (spill files, local-only logs). Uses the config directory if it exists and is writable, otherwise falls back to os.TempDir().
Types ¶
type Backend ¶
Backend is the interface for telemetry data sinks. Implementations must be safe for concurrent use. Send must be non-blocking or short-timeout to avoid impacting CLI performance.
func NewFileBackend ¶
NewFileBackend returns a backend that appends events as newline-delimited JSON.
func NewHTTPBackend ¶
NewHTTPBackend returns a backend that POSTs events as JSON to the given endpoint. Non-2xx responses are logged at debug level via the provided logger. Network errors are silently dropped — telemetry must never block the user.
func NewNoopBackend ¶
func NewNoopBackend() Backend
NewNoopBackend returns a backend that silently discards all events.
Example ¶
package main
import (
"context"
"github.com/phpboyscout/go-tool-base/pkg/telemetry"
)
func main() {
// The noop backend silently discards all events.
// Used when telemetry is disabled.
backend := telemetry.NewNoopBackend()
_ = backend.Send(context.Background(), nil)
}
Output:
func NewOTelBackend ¶
NewOTelBackend creates a Backend that exports events as OTel log records via OTLP/HTTP. endpoint is the full URL of the OTLP HTTP endpoint (e.g. "https://otlp-gateway-prod-eu-west-0.grafana.net/otlp"). The URL is parsed into host and path components — the SDK appends /v1/logs to the path automatically. A custom OTel error handler is registered to route SDK errors to the provided logger.
func NewStdoutBackend ¶
NewStdoutBackend returns a backend that writes events as pretty-printed JSON.
type Collector ¶
type Collector struct {
// contains filtered or unexported fields
}
Collector accumulates events and flushes to the backend. All methods are safe for concurrent use. When disabled, all operations are no-ops — callers do not need to check whether telemetry is enabled.
func NewCollector ¶
func NewCollector(cfg Config, backend Backend, toolName, version string, metadata map[string]string, log logger.Logger, dataDir string, deliveryMode props.DeliveryMode, extendedCollection bool) *Collector
NewCollector creates a Collector. When cfg.Enabled is false, returns a noop collector so callers never need to nil-check.
Example ¶
package main
import (
"context"
"os"
"github.com/phpboyscout/go-tool-base/pkg/logger"
"github.com/phpboyscout/go-tool-base/pkg/props"
"github.com/phpboyscout/go-tool-base/pkg/telemetry"
)
func main() {
cfg := telemetry.Config{Enabled: true}
backend := telemetry.NewStdoutBackend(os.Stdout)
c := telemetry.NewCollector(cfg, backend, "mytool", "1.0.0",
map[string]string{"env": "production"},
logger.NewNoop(), "", props.DeliveryAtLeastOnce, false)
c.Track(props.EventCommandInvocation, "generate", nil)
c.TrackCommand("build", 1500, 0, map[string]string{"target": "linux"})
_ = c.Flush(context.Background())
}
Output:
func (*Collector) BackendInfo ¶ added in v1.9.4
BackendInfo returns a human-readable description of the active backend.
func (*Collector) Close ¶ added in v1.9.1
Close flushes pending events and shuts down the backend gracefully. Must be called on process exit to ensure backends like OTLP flush their internal batch queues.
func (*Collector) Drop ¶
Drop clears all buffered events and deletes any spill files without sending. Called when the user disables telemetry to ensure immediate consent withdrawal.
func (*Collector) Flush ¶
Flush sends all buffered events to the backend, then clears the buffer. Checks for and sends spill files before flushing the current buffer.
func (*Collector) SetBackendInfo ¶ added in v1.9.4
SetBackendInfo sets the human-readable backend description. Called by the root command after backend selection.
func (*Collector) Track ¶
Track records a telemetry event. No-op when collector is disabled. When the in-memory buffer reaches maxBuffer, events are spilled to disk.
func (*Collector) TrackCommand ¶
func (c *Collector) TrackCommand(name string, durationMs int64, exitCode int, extra map[string]string)
TrackCommand records a command invocation event with duration and exit code. This is a convenience wrapper around Track for command lifecycle events.
func (*Collector) TrackCommandExtended ¶ added in v1.9.1
func (c *Collector) TrackCommandExtended(name string, args []string, durationMs int64, exitCode int, errMsg string, extra map[string]string)
TrackCommandExtended records a command invocation with full context. When ExtendedCollection is disabled on the collector, args and errMsg are silently dropped — callers do not need to check the flag themselves.
type Config ¶
Config holds runtime telemetry configuration read from the user's config file. Endpoints are not included — they are tool-author concerns set in props.TelemetryConfig, not user-configurable.
type DeletionRequestor ¶
DeletionRequestor sends a GDPR data deletion request for a given machine ID. Implementations should be best-effort — deletion cannot be guaranteed for all backend types.
func NewEmailDeletionRequestor ¶
func NewEmailDeletionRequestor(address, toolName string) DeletionRequestor
NewEmailDeletionRequestor creates a requestor that opens a pre-filled mailto: link for the user to send a deletion request.
func NewEventDeletionRequestor ¶
func NewEventDeletionRequestor(backend Backend) DeletionRequestor
NewEventDeletionRequestor creates a requestor that emits a deletion request event through the provided backend.
func NewHTTPDeletionRequestor ¶
func NewHTTPDeletionRequestor(endpoint string, log logger.Logger) DeletionRequestor
NewHTTPDeletionRequestor creates a requestor that POSTs a JSON deletion request to the given endpoint.
type EmailDeletionRequestor ¶
type EmailDeletionRequestor struct {
// contains filtered or unexported fields
}
EmailDeletionRequestor composes a deletion request email by opening the user's default mail client via a mailto: URL.
func (*EmailDeletionRequestor) RequestDeletion ¶
func (e *EmailDeletionRequestor) RequestDeletion(ctx context.Context, machineID string) error
type Event ¶
type Event struct {
Timestamp time.Time `json:"timestamp"`
Type EventType `json:"type"`
Name string `json:"name"`
MachineID string `json:"machine_id"`
ToolName string `json:"tool_name"`
Version string `json:"version"`
OS string `json:"os"`
Arch string `json:"arch"`
GoVersion string `json:"go_version"`
OSVersion string `json:"os_version"`
DurationMs int64 `json:"duration_ms,omitempty"`
ExitCode int `json:"exit_code,omitempty"`
Args []string `json:"args,omitempty"` // only populated when ExtendedCollection is enabled
Error string `json:"error,omitempty"` // only populated when ExtendedCollection is enabled
Metadata map[string]string `json:"metadata,omitempty"`
}
Event represents a single telemetry event.
type EventDeletionRequestor ¶
type EventDeletionRequestor struct {
// contains filtered or unexported fields
}
EventDeletionRequestor sends a data.deletion_request event through the existing telemetry backend. This is the universal fallback — works with any backend type.
func (*EventDeletionRequestor) RequestDeletion ¶
func (e *EventDeletionRequestor) RequestDeletion(ctx context.Context, machineID string) error
type EventType ¶
type EventType string
EventType identifies the category of telemetry event. Mirrored from pkg/props — since both resolve to string, values are interchangeable at the interface boundary.
const ( EventCommandInvocation EventType = "command.invocation" EventCommandError EventType = "command.error" EventFeatureUsed EventType = "feature.used" EventUpdateCheck EventType = "update.check" EventUpdateApplied EventType = "update.applied" EventDeletionRequest EventType = "data.deletion_request" )
type HTTPDeletionRequestor ¶
type HTTPDeletionRequestor struct {
// contains filtered or unexported fields
}
HTTPDeletionRequestor sends a deletion request via HTTP POST.
func (*HTTPDeletionRequestor) RequestDeletion ¶
func (h *HTTPDeletionRequestor) RequestDeletion(ctx context.Context, machineID string) error
type OTelOption ¶
type OTelOption func(*otelConfig)
OTelOption configures the OTLP backend.
func WithOTelHeaders ¶
func WithOTelHeaders(headers map[string]string) OTelOption
WithOTelHeaders sets HTTP headers sent with every OTLP request (e.g. auth tokens).
func WithOTelInsecure ¶
func WithOTelInsecure() OTelOption
WithOTelInsecure disables TLS — use only for local collectors.
func WithOTelLogger ¶
func WithOTelLogger(l logger.Logger) OTelOption
WithOTelLogger sets the logger for routing OTel SDK errors.
func WithOTelService ¶
func WithOTelService(name, version string) OTelOption
WithOTelService sets the service.name and service.version resource attributes. These appear as labels in Grafana/Loki and identify the tool in the telemetry data.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package datadog provides a telemetry backend that sends events to Datadog's HTTP Logs Intake API.
|
Package datadog provides a telemetry backend that sends events to Datadog's HTTP Logs Intake API. |
|
Package posthog provides a telemetry backend that sends events to PostHog's Capture API using the batch endpoint.
|
Package posthog provides a telemetry backend that sends events to PostHog's Capture API using the batch endpoint. |