Documentation
¶
Overview ¶
Package productgraph provides a client for sending events to ProductGraph and middleware for frontend-backend correlation.
Package productgraph provides a client for sending telemetry events to ProductGraph and middleware for frontend-backend correlation.
Overview ¶
ProductGraph is a product analytics platform that collects and analyzes user behavior events from both frontend and backend systems. This package enables Go services to:
- Send events to ProductGraph with batching and async dispatch
- Correlate backend requests with frontend sessions
- Track API calls, errors, and journey steps
Quick Start ¶
// Create client
client, err := productgraph.New(productgraph.Config{
ProjectID: "my-project",
Endpoint: "https://api.productgraph.io/v1/events",
APIKey: os.Getenv("PRODUCTGRAPH_API_KEY"),
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Use middleware for automatic tracking
r := chi.NewRouter()
r.Use(productgraph.ChainMiddleware(client))
// Or track events manually
client.TrackAPICall(ctx, "POST", "/checkout", 200, 150*time.Millisecond)
client.TrackJourneyStep(ctx, "checkout_flow", "payment", "Enter Payment")
client.TrackError(ctx, "ValidationError", "invalid card number")
Correlation ¶
The CorrelationMiddleware extracts frontend correlation headers and injects them into the request context:
- X-Session-ID: Frontend session identifier
- X-Request-ID: Per-request identifier (generated if not provided)
The user ID is never taken from a request header. Set it from the authenticated session or token with WithUserID, in middleware that runs after authentication and before the request tracker.
Use SessionIDFromContext, RequestIDFromContext, and UserIDFromContext to retrieve these values in your handlers.
RequestTrackerMiddleware records the matched route pattern (http.ServeMux's Request.Pattern or chi's route pattern, or "unmatched"), never the raw request path, which can carry tokens.
Event Types ¶
ProductGraph supports various event types following OTel semantic conventions:
- page.view, page.leave: Page navigation
- ui.click, ui.input, ui.scroll, ui.submit: User interactions
- state.change: Application state changes
- api.request, api.response: API calls
- journey.step: User journey progression
- error: Application errors
- performance: Performance metrics
Configuration ¶
Use ConfigFromEnv to load configuration from environment variables:
- PRODUCTGRAPH_PROJECT_ID: Project identifier
- PRODUCTGRAPH_ENDPOINT: API endpoint
- PRODUCTGRAPH_API_KEY: Authentication key
- PRODUCTGRAPH_BATCH_SIZE: Events per batch (default: 50)
- PRODUCTGRAPH_BATCH_INTERVAL: Flush interval in seconds (default: 5)
- PRODUCTGRAPH_DEBUG: Enable debug logging
Index ¶
- Constants
- Variables
- func ChainMiddleware(client *Client) func(http.Handler) http.Handler
- func CorrelationMiddleware(next http.Handler) http.Handler
- func RequestIDFromContext(ctx context.Context) string
- func RequestTrackerMiddleware(client *Client) func(http.Handler) http.Handler
- func SessionIDFromContext(ctx context.Context) string
- func UserIDFromContext(ctx context.Context) string
- func WithRequestID(ctx context.Context, requestID string) context.Context
- func WithSessionID(ctx context.Context, sessionID string) context.Context
- func WithUserID(ctx context.Context, userID string) context.Context
- type Client
- func (c *Client) Close() error
- func (c *Client) Config() Config
- func (c *Client) Flush(ctx context.Context) error
- func (c *Client) IsEnabled() bool
- func (c *Client) SetLogger(logger *slog.Logger)
- func (c *Client) Track(ctx context.Context, event Event) error
- func (c *Client) TrackAPICall(ctx context.Context, method, path string, statusCode int, ...) error
- func (c *Client) TrackError(ctx context.Context, errType, message string) error
- func (c *Client) TrackJourneyStep(ctx context.Context, journeyID, stepID, stepName string) error
- type Config
- type ContextKey
- type Event
- type EventType
- type Payload
- type PayloadError
- type Response
Constants ¶
const ( // HeaderSessionID is the HTTP header for session ID from frontend. HeaderSessionID = "X-Session-ID" // HeaderRequestID is the HTTP header for request ID. HeaderRequestID = "X-Request-ID" // HeaderUserID is the HTTP header a frontend may send with its user ID. // // Deprecated: CorrelationMiddleware no longer reads it, because any // caller could attribute events to another user. Set the user ID from // the authenticated session with WithUserID instead. HeaderUserID = "X-User-ID" )
const RouteUnmatched = "unmatched"
RouteUnmatched is the path recorded for a request that no route pattern matched.
Variables ¶
var ( ErrMissingProjectID = errors.New("productgraph: missing project_id") ErrMissingEndpoint = errors.New("productgraph: missing endpoint") ErrClientClosed = errors.New("productgraph: client is closed") )
Common errors.
Functions ¶
func ChainMiddleware ¶
ChainMiddleware chains correlation and request tracking middleware. This is a convenience function that applies both middlewares in the correct order: 1. CorrelationMiddleware - extracts session/request IDs 2. RequestTrackerMiddleware - tracks API calls with correlation
func CorrelationMiddleware ¶
CorrelationMiddleware extracts correlation headers from incoming requests and injects them into the request context. It also generates a request ID if not provided and echoes it back in the response.
It does not take the user ID from the request: a header is set by the caller and proves nothing. After authentication, set the user ID from the verified session or token with WithUserID.
func RequestIDFromContext ¶
RequestIDFromContext extracts the request ID from context. Returns empty string if not present.
func RequestTrackerMiddleware ¶
RequestTrackerMiddleware creates middleware that tracks HTTP requests to ProductGraph. It records api.response events with method, route pattern, status code, and duration. The route is the pattern http.ServeMux or chi matched, or RouteUnmatched, never the request path.
func SessionIDFromContext ¶
SessionIDFromContext extracts the session ID from context. Returns empty string if not present.
func UserIDFromContext ¶
UserIDFromContext extracts the user ID from context. Returns empty string if not present.
func WithRequestID ¶
WithRequestID returns a new context with the request ID set.
func WithSessionID ¶
WithSessionID returns a new context with the session ID set.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client sends events to ProductGraph with batching and async dispatch.
func (*Client) Track ¶
Track queues an event for dispatch to ProductGraph. The event is buffered and sent asynchronously in batches.
func (*Client) TrackAPICall ¶
func (c *Client) TrackAPICall(ctx context.Context, method, path string, statusCode int, duration time.Duration) error
TrackAPICall is a convenience method for tracking API calls.
func (*Client) TrackError ¶
TrackError is a convenience method for tracking errors.
type Config ¶
type Config struct {
// ProjectID is the ProductGraph project identifier (required).
ProjectID string
// Endpoint is the ProductGraph API endpoint (required).
// Example: "https://api.productgraph.io/v1/events"
Endpoint string
// APIKey is the API key for authentication (optional).
// Sent as X-PG-API-Key header.
APIKey string
// BatchSize is the number of events to buffer before flushing.
// Default: 50
BatchSize int
// BatchInterval is the maximum time to wait before flushing buffered events.
// Default: 5 seconds
BatchInterval time.Duration
// HTTPClient is the HTTP client to use for requests.
// Default: http.DefaultClient with 10s timeout
HTTPClient *http.Client
// Debug enables debug logging.
Debug bool
}
Config holds ProductGraph client configuration.
func ConfigFromEnv ¶
func ConfigFromEnv() Config
ConfigFromEnv creates a Config from environment variables.
Environment variables:
- PRODUCTGRAPH_PROJECT_ID: Project identifier (required)
- PRODUCTGRAPH_ENDPOINT: API endpoint (required)
- PRODUCTGRAPH_API_KEY: API key for authentication
- PRODUCTGRAPH_BATCH_SIZE: Events per batch (default: 50)
- PRODUCTGRAPH_BATCH_INTERVAL: Flush interval in seconds (default: 5)
- PRODUCTGRAPH_DEBUG: Enable debug logging (true/false)
func (Config) WithDefaults ¶
WithDefaults returns a copy of the config with default values applied.
type ContextKey ¶
type ContextKey string
ContextKey is used for storing correlation values in context.
const ( // SessionIDKey is the context key for session ID. SessionIDKey ContextKey = "productgraph.session_id" // RequestIDKey is the context key for request ID. RequestIDKey ContextKey = "productgraph.request_id" // UserIDKey is the context key for user ID. UserIDKey ContextKey = "productgraph.user_id" )
type Event ¶
type Event struct {
// Identity
EventID string `json:"event_id"`
ProjectID string `json:"project_id"`
SessionID string `json:"session.id"`
UserID string `json:"user.id,omitempty"`
// Event classification
EventType EventType `json:"event.type"`
EventName string `json:"event.name,omitempty"`
Timestamp string `json:"event.timestamp"`
Sequence int `json:"event.sequence,omitempty"`
// Page context
PagePath string `json:"page.path,omitempty"`
PageTitle string `json:"page.title,omitempty"`
PageURL string `json:"page.url,omitempty"`
PageReferrer string `json:"page.referrer,omitempty"`
// UI context
UIComponentName string `json:"ui.component.name,omitempty"`
UIComponentPath string `json:"ui.component.path,omitempty"`
UIComponentType string `json:"ui.component.type,omitempty"`
UIAction string `json:"ui.action,omitempty"`
UIElement string `json:"ui.element,omitempty"`
UIElementText string `json:"ui.element.text,omitempty"`
// State changes
UIStateKey string `json:"ui.state.key,omitempty"`
UIStateBefore string `json:"ui.state.before,omitempty"`
UIStateAfter string `json:"ui.state.after,omitempty"`
// Journey tracking
JourneyID string `json:"gen_ai.journey.id,omitempty"`
JourneyStep string `json:"gen_ai.journey.step.id,omitempty"`
JourneyName string `json:"gen_ai.journey.step.name,omitempty"`
// API tracking
APIMethod string `json:"api.method,omitempty"`
APIPath string `json:"api.path,omitempty"`
APIStatusCode int `json:"api.status_code,omitempty"`
APIDurationMs int `json:"api.duration_ms,omitempty"`
// Error tracking
ErrorType string `json:"error.type,omitempty"`
ErrorMessage string `json:"error.message,omitempty"`
ErrorStack string `json:"error.stack,omitempty"`
// Performance
DurationMs int `json:"duration_ms,omitempty"`
// Organization
OrgID string `json:"org.id,omitempty"`
OrgName string `json:"org.name,omitempty"`
// Custom metadata
Metadata map[string]any `json:"metadata,omitempty"`
}
Event represents a ProductGraph event following OTel semantic conventions.
func APIResponseEvent ¶
APIResponseEvent creates an event for tracking an API response.
func ErrorEvent ¶
ErrorEvent creates an event for tracking an error.
func JourneyStepEvent ¶
JourneyStepEvent creates an event for tracking a journey step.
type EventType ¶
type EventType string
EventType represents the type of ProductGraph event.
const ( // Page events EventTypePageView EventType = "page.view" EventTypePageLeave EventType = "page.leave" // UI events EventTypeUIClick EventType = "ui.click" EventTypeUIInput EventType = "ui.input" EventTypeUIScroll EventType = "ui.scroll" EventTypeUIFocus EventType = "ui.focus" EventTypeUIBlur EventType = "ui.blur" EventTypeUISubmit EventType = "ui.submit" // State events EventTypeStateChange EventType = "state.change" // API events EventTypeAPIRequest EventType = "api.request" EventTypeAPIResponse EventType = "api.response" // Journey events EventTypeJourneyStep EventType = "journey.step" // Error events EventTypeError EventType = "error" // Performance events EventTypePerformance EventType = "performance" // Custom events EventTypeCustom EventType = "custom" )
type Payload ¶
type Payload struct {
Events []Event `json:"events"`
}
Payload represents the request body for POST /v1/events.
type PayloadError ¶
PayloadError represents an error for a specific event in a batch.
type Response ¶
type Response struct {
Accepted int `json:"accepted"`
Rejected int `json:"rejected"`
Errors []PayloadError `json:"errors,omitempty"`
}
Response represents the response from POST /v1/events.