events

package
v1.0.3 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package events captures the server-emitted analytics events of milestone 07. Handlers build events with the typed constructors and hand them to a Writer, whose Emit never blocks the calling RPC: events are buffered in a bounded channel and batch-inserted by a background goroutine, with drop-on-overflow rather than back-pressure.

The package is self-contained: it defines its own narrow store interface (BatchInserter) and does not import the rest of the server.

Index

Constants

View Source
const (
	TypeUserSignup             = "user.signup"
	TypeUserLogin              = "user.login"
	TypeTokenRefresh           = "token.refresh"
	TypeUserLoginFailed        = "user.login_failed"
	TypePasswordResetCompleted = "password.reset_completed"
	TypeEmailVerified          = "email.verified"
	TypeUserDeleted            = "user.deleted"
	TypeIdentityLinked         = "identity.linked"
)

Event types emitted by the auth handlers. Authoritative — clients never send events, only ambient context in request metadata.

View Source
const (
	PlatformHeader   = "x-moth-platform"
	SDKVersionHeader = "x-moth-sdk-version"
)

Request-metadata headers attached by the SDK client interceptor (milestone 05) on every call.

View Source
const (
	PlatformIOS     = "ios"
	PlatformAndroid = "android"
	PlatformWeb     = "web"
	PlatformMacOS   = "macos"
	PlatformWindows = "windows"
	PlatformLinux   = "linux"
	PlatformOther   = "other"
)

Platforms recognized in x-moth-platform. Anything else non-empty (a future OS, garbage) is bucketed as "other"; a missing header stays "".

View Source
const DefaultTokenRefreshRate = 0.1

DefaultTokenRefreshRate is the default sampling rate for token.refresh events: 1 in 10.

Variables

This section is empty.

Functions

func ParsePlatform

func ParsePlatform(raw string) string

ParsePlatform validates a raw x-moth-platform value against the known enum (case-insensitively). Unknown non-empty values become "other" so a hostile client cannot inject arbitrary strings into analytics; an absent header stays "".

func ParseSDKVersion

func ParseSDKVersion(raw string) string

ParseSDKVersion sanitizes a raw x-moth-sdk-version value: trimmed, at most maxSDKVersionLen bytes, and restricted to the characters found in semver-ish strings ([0-9A-Za-z .+_-]). Anything else returns "" — a version header is best-effort context, never worth storing garbage.

func WithClientInfo

func WithClientInfo(ctx context.Context, info ClientInfo) context.Context

WithClientInfo stashes info in ctx for the event constructors to pick up. The auth interceptor calls this once per request.

Types

type BatchInserter

type BatchInserter interface {
	InsertEvents(ctx context.Context, events []Event) error
}

BatchInserter is the narrow store surface the writer needs. The store package implements it in milestone 07; tests use fakes. The events slice is owned by the callee — the writer never reuses or mutates it after the call.

type ClientInfo

type ClientInfo struct {
	Platform   string // one of the Platform* constants, or ""
	SDKVersion string // sanitized version string, or ""
}

ClientInfo is the ambient client context extracted from request headers.

func ClientInfoFromContext

func ClientInfoFromContext(ctx context.Context) ClientInfo

ClientInfoFromContext returns the info stored by WithClientInfo, or the zero value when none was stored.

func ClientInfoFromHeader

func ClientInfoFromHeader(h http.Header) ClientInfo

ClientInfoFromHeader parses and validates the SDK metadata headers. connect exposes request metadata as http.Header on both gRPC and gRPC-web transports.

type Config

type Config struct {
	// BufferSize bounds the in-flight event channel. When it is full,
	// Emit drops the event and increments Stats.Dropped — auth latency
	// is never traded for analytics completeness. Default 4096.
	BufferSize int
	// BatchSize triggers a flush once this many events are buffered.
	// Default 128.
	BatchSize int
	// FlushInterval flushes partial batches so a quiet server still
	// lands events promptly. Default 2s.
	FlushInterval time.Duration
	// SampleRates maps an event type to the fraction of its events kept
	// at Emit time, e.g. {TypeTokenRefresh: 0.1}. Types absent from the
	// map are always kept. A user's first sampled-type event of the UTC
	// day is always kept regardless of the rate: the DAU aggregation
	// counts distinct users, so purely random sampling would hide a
	// once-a-day refresher from DAU ~90% of their active days — keeping
	// the daily first preserves per-user presence while the rate still
	// thins the volume. Default samples token.refresh at
	// DefaultTokenRefreshRate.
	SampleRates map[string]float64
	// Logger receives insert-failure warnings and the rate-limited
	// lost-events warning. Default slog.Default().
	Logger *slog.Logger
	// LostLogInterval rate-limits the warning emitted when events were
	// dropped (full buffer) or failed to insert since the last report, so
	// silent analytics loss is visible in production logs. Default 1m.
	LostLogInterval time.Duration
}

Config tunes a Writer. The zero value gets sensible defaults from NewWriter.

type Event

type Event struct {
	Type       string
	ProjectID  string
	UserID     string
	Provider   string
	Platform   string
	SDKVersion string
	// Metadata is a small bag of extra fields (e.g. the login-failure
	// reason). It is JSON-encoded at write time via MetadataJSON.
	Metadata  map[string]string
	CreatedAt time.Time
}

Event is one analytics event, mirroring the events table: who did what, on which platform, when. UserID, Provider, Platform, SDKVersion and Metadata are all optional.

func EmailVerified

func EmailVerified(ctx context.Context, projectID, userID string) Event

EmailVerified records a confirmed email address.

func IdentityLinked

func IdentityLinked(ctx context.Context, projectID, userID, provider string) Event

IdentityLinked records a social identity being attached to an existing account.

func Login

func Login(ctx context.Context, projectID, userID, provider string) Event

Login records a successful sign-in.

func LoginFailed

func LoginFailed(ctx context.Context, projectID, provider string, reason FailureReason) Event

LoginFailed records a failed sign-in. By construction it carries no user ID — only the coarse reason bucket in metadata — so failure analytics can never identify an account.

func PasswordResetCompleted

func PasswordResetCompleted(ctx context.Context, projectID, userID string) Event

PasswordResetCompleted records a finished password reset.

func Signup

func Signup(ctx context.Context, projectID, userID, provider string) Event

Signup records a new account. Provider is "" for email/password.

func TokenRefresh

func TokenRefresh(ctx context.Context, projectID, userID string) Event

TokenRefresh records a refresh-token exchange. Writers keep each user's first refresh of the day and sample the rest (see Config.SampleRates): the type exists to approximate DAU — which needs every active user, not every refresh.

func UserDeleted

func UserDeleted(ctx context.Context, projectID, userID string) Event

UserDeleted records an account deletion.

func (Event) MetadataJSON

func (e Event) MetadataJSON() string

MetadataJSON returns the metadata encoded as a JSON object, or "" when there is none. Encoding failures cannot happen for map[string]string.

type FailureReason

type FailureReason string

FailureReason buckets why a login failed. Only these coarse buckets are stored — never the raw error, never an identifier of the account.

const (
	ReasonInvalidCredentials FailureReason = "invalid_credentials"
	ReasonDisabled           FailureReason = "disabled"
	ReasonProviderDisabled   FailureReason = "provider_disabled"
	ReasonOther              FailureReason = "other"
)

type Stats

type Stats struct {
	// Enqueued counts events accepted into the buffer.
	Enqueued uint64
	// Written counts events successfully inserted.
	Written uint64
	// Dropped counts events lost to a full buffer (or emitted after
	// Close).
	Dropped uint64
	// SampledOut counts events discarded by the sampler.
	SampledOut uint64
	// Failed counts events lost because a batch insert errored.
	Failed uint64
}

Stats is a snapshot of the writer's atomic counters.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer batches analytics events into a BatchInserter from a background goroutine. Emit never blocks. Create with NewWriter, stop with Close.

func NewWriter

func NewWriter(dst BatchInserter, cfg Config) *Writer

NewWriter starts the background flusher and returns the writer.

func (*Writer) Close

func (w *Writer) Close(ctx context.Context) error

Close stops the writer, draining buffered events until ctx expires. After the deadline any in-flight insert is cancelled and remaining events are abandoned; ctx.Err() is returned. Emit after Close drops. Close is idempotent and safe to call concurrently.

func (*Writer) Emit

func (w *Writer) Emit(e Event)

Emit queues e for writing and returns immediately. It never blocks: when the buffer is full (e.g. the store is stalled) the event is dropped and counted. A zero CreatedAt is stamped with the current time. Emit is safe for concurrent use.

func (*Writer) Stats

func (w *Writer) Stats() Stats

Stats returns a snapshot of the counters.

Jump to

Keyboard shortcuts

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