telemetry

package
v1.10.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 2, 2026 License: MIT Imports: 30 Imported by: 0

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

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

func ResolveDataDir(p *props.Props) string

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

type Backend interface {
	Send(ctx context.Context, events []Event) error
	Close() error
}

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

func NewFileBackend(path string) Backend

NewFileBackend returns a backend that appends events as newline-delimited JSON.

func NewHTTPBackend

func NewHTTPBackend(endpoint string, log logger.Logger) Backend

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)
}

func NewOTelBackend

func NewOTelBackend(ctx context.Context, endpoint string, opts ...OTelOption) (Backend, error)

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

func NewStdoutBackend(w io.Writer) Backend

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())
}

func (*Collector) BackendInfo added in v1.9.4

func (c *Collector) BackendInfo() string

BackendInfo returns a human-readable description of the active backend.

func (*Collector) Close added in v1.9.1

func (c *Collector) Close(ctx context.Context) error

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

func (c *Collector) Drop() error

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

func (c *Collector) Flush(ctx context.Context) error

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

func (c *Collector) SetBackendInfo(info string)

SetBackendInfo sets the human-readable backend description. Called by the root command after backend selection.

func (*Collector) Track

func (c *Collector) Track(eventType props.EventType, name string, extra map[string]string)

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

type Config struct {
	Enabled   bool `yaml:"enabled"`
	LocalOnly bool `yaml:"local_only"`
}

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

type DeletionRequestor interface {
	RequestDeletion(ctx context.Context, machineID string) error
}

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.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL