Documentation
¶
Overview ¶
Package pug is the Pug server-side SDK for Go: event tracking, profile identify, and analytics reads.
A Client authenticates with a private project API key (prv_…). Ingestion never fails: Client.Track, Client.TrackWellKnown and Client.Identify validate their input, log and drop anything invalid, and hand events and identifies to background senders that batch them, so a tracking call can't take down a request path. Properties and traits are typed fields such as String and Int, passed in the same list as the call's options. Anything that can't be delivered is reported to Options.OnError.
Reads (Client.Profiles, Client.Activity and Client.Insights) run in the caller's control flow: they return errors of type *Error and are never retried. errors.Is matches an *Error to the sentinel for its status code, such as ErrNotFound.
Call Client.Close on shutdown so queued events and identifies are not lost.
Example ¶
package main
import (
"context"
"fmt"
"log"
"os"
"google.golang.org/protobuf/proto"
pug "github.com/pug-sh/sdk-go"
commoneventsv1 "github.com/pug-sh/sdk-go/gen/common/events/v1"
)
func main() {
client, err := pug.New(pug.Options{
APIKey: os.Getenv("PUG_API_KEY"),
Endpoint: "https://pug.example.com",
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
client.Track("user_42", "order.completed", pug.Float("amount", 49.0), pug.String("currency", "USD"))
client.TrackWellKnown("user_42", &commoneventsv1.FeatureUsedProperties{FeatureId: proto.String("export")})
client.Identify("user_42", pug.Email("ada@example.com"), pug.String("plan", "pro"))
profile, err := client.Profiles.GetByExternalID(ctx, "user_42")
if err != nil {
log.Print(err)
}
fmt.Println(profile.GetId())
if err := client.Close(ctx); err != nil {
log.Print(err)
}
}
Output:
Index ¶
- Constants
- Variables
- type ActivityClient
- func (a *ActivityClient) EventExplorer(ctx context.Context, req *activityv1.GetEventExplorerRequest) (*activityv1.GetEventExplorerResponse, error)
- func (a *ActivityClient) Feed(ctx context.Context, req *activityv1.GetActivityFeedRequest) (*activityv1.GetActivityFeedResponse, error)
- func (a *ActivityClient) FilterSchema(ctx context.Context, req *commonv1.GetFilterSchemaRequest) (*commonv1.GetFilterSchemaResponse, error)
- func (a *ActivityClient) Heatmap(ctx context.Context, req *activityv1.GetActivityHeatmapRequest) (*activityv1.GetActivityHeatmapResponse, error)
- func (a *ActivityClient) ProfileSessions(ctx context.Context, req *activityv1.GetProfileSessionsRequest) (*activityv1.GetProfileSessionsResponse, error)
- func (a *ActivityClient) ProfileStats(ctx context.Context, req *activityv1.GetProfileStatsRequest) (*activityv1.GetProfileStatsResponse, error)
- func (a *ActivityClient) PropertyValues(ctx context.Context, req *activityv1.GetPropertyValuesRequest) (*activityv1.GetPropertyValuesResponse, error)
- type BatchConfig
- type Client
- func (c *Client) Close(ctx context.Context) error
- func (c *Client) Flush(ctx context.Context) error
- func (c *Client) Identify(externalID string, opts ...IdentifyOption)
- func (c *Client) Track(distinctID string, kind Kind, opts ...TrackOption)
- func (c *Client) TrackWellKnown(distinctID string, event proto.Message, opts ...TrackOption)
- type Coordinates
- type Error
- type Event
- type Field
- func Any(key string, value any) Field
- func Bool(key string, value bool) Field
- func Duration(key string, value time.Duration) Field
- func Float(key string, value float64) Field
- func Int[T integer](key string, value T) Field
- func String(key, value string) Field
- func Time(key string, value time.Time) Field
- type GetOption
- type Identify
- type IdentifyOption
- type InsightsClient
- func (i *InsightsClient) FilterSchema(ctx context.Context, req *commonv1.GetFilterSchemaRequest) (*commonv1.GetFilterSchemaResponse, error)
- func (i *InsightsClient) PropertyValues(ctx context.Context, req *insightsv1.GetPropertyValuesRequest) (*insightsv1.GetPropertyValuesResponse, error)
- func (i *InsightsClient) Query(ctx context.Context, req *insightsv1.QueryRequest) (*insightsv1.QueryResponse, error)
- func (i *InsightsClient) SegmentUsers(ctx context.Context, req *insightsv1.SegmentUsersRequest) (*insightsv1.SegmentUsersResponse, error)
- type Kind
- type Location
- type OnError
- type Options
- type ProfilesClient
- func (p *ProfilesClient) Delete(ctx context.Context, id string) (*profilesv1.DeleteResponse, error)
- func (p *ProfilesClient) Get(ctx context.Context, id string, opts ...GetOption) (*profilesv1.Profile, error)
- func (p *ProfilesClient) GetByExternalID(ctx context.Context, externalID string, opts ...GetOption) (*profilesv1.Profile, error)
- func (p *ProfilesClient) List(ctx context.Context, req *profilesv1.ListRequest) iter.Seq2[*profilesv1.Profile, error]
- type Properties
- type TrackOption
- type Traits
- type Undelivered
Examples ¶
Constants ¶
const Version = "0.0.1"
Version is the SDK version, sent with every event as the $sdkVersion auto-property.
Variables ¶
var ( // ErrClosed is reported to OnError for events and identifies that arrived after Close or // were still undelivered when it finished. ErrClosed = ingest.ErrClosed // ErrQueueOverflow is reported to OnError for the oldest events or identifies, dropped to // make room when their queue is full. ErrQueueOverflow = ingest.ErrQueueOverflow )
var ( ErrCanceled error = codeError(connect.CodeCanceled) ErrUnknown error = codeError(connect.CodeUnknown) ErrInvalidArgument error = codeError(connect.CodeInvalidArgument) ErrDeadlineExceeded error = codeError(connect.CodeDeadlineExceeded) ErrNotFound error = codeError(connect.CodeNotFound) ErrAlreadyExists error = codeError(connect.CodeAlreadyExists) ErrPermissionDenied error = codeError(connect.CodePermissionDenied) ErrResourceExhausted error = codeError(connect.CodeResourceExhausted) ErrFailedPrecondition error = codeError(connect.CodeFailedPrecondition) ErrAborted error = codeError(connect.CodeAborted) ErrOutOfRange error = codeError(connect.CodeOutOfRange) ErrUnimplemented error = codeError(connect.CodeUnimplemented) ErrInternal error = codeError(connect.CodeInternal) ErrDataLoss error = codeError(connect.CodeDataLoss) ErrUnauthenticated error = codeError(connect.CodeUnauthenticated) )
Sentinels for the Connect status codes, so errors.Is(err, ErrNotFound) checks an *Error's Code.
Functions ¶
This section is empty.
Types ¶
type ActivityClient ¶
type ActivityClient struct {
// contains filtered or unexported fields
}
ActivityClient reads raw events and per-profile activity. Reads leave out traffic tagged as a bot at ingest unless the request sets include_bots.
func (*ActivityClient) EventExplorer ¶
func (a *ActivityClient) EventExplorer(ctx context.Context, req *activityv1.GetEventExplorerRequest) (*activityv1.GetEventExplorerResponse, error)
EventExplorer returns a page of events across every profile in the project.
func (*ActivityClient) Feed ¶
func (a *ActivityClient) Feed(ctx context.Context, req *activityv1.GetActivityFeedRequest) (*activityv1.GetActivityFeedResponse, error)
Feed returns a page of one profile's events, merged anonymous events included.
func (*ActivityClient) FilterSchema ¶
func (a *ActivityClient) FilterSchema(ctx context.Context, req *commonv1.GetFilterSchemaRequest) (*commonv1.GetFilterSchemaResponse, error)
FilterSchema returns the event kinds and property keys available to filter by; req may be nil.
func (*ActivityClient) Heatmap ¶
func (a *ActivityClient) Heatmap(ctx context.Context, req *activityv1.GetActivityHeatmapRequest) (*activityv1.GetActivityHeatmapResponse, error)
Heatmap returns one profile's per-day event counts.
func (*ActivityClient) ProfileSessions ¶
func (a *ActivityClient) ProfileSessions(ctx context.Context, req *activityv1.GetProfileSessionsRequest) (*activityv1.GetProfileSessionsResponse, error)
ProfileSessions returns a page of one profile's sessions.
func (*ActivityClient) ProfileStats ¶
func (a *ActivityClient) ProfileStats(ctx context.Context, req *activityv1.GetProfileStatsRequest) (*activityv1.GetProfileStatsResponse, error)
ProfileStats returns one profile's aggregate statistics and latest device and location context.
func (*ActivityClient) PropertyValues ¶
func (a *ActivityClient) PropertyValues(ctx context.Context, req *activityv1.GetPropertyValuesRequest) (*activityv1.GetPropertyValuesResponse, error)
PropertyValues returns the distinct values seen for a property key.
type BatchConfig ¶
type BatchConfig struct {
// MaxSize is the buffered-event count that triggers a send, and the most events sent in
// one request. Defaults to 100; capped at 1000.
MaxSize int
// MaxWait is the longest an event waits before its batch is sent, and the retry interval
// after a transient failure. Defaults to 5s.
MaxWait time.Duration
// MaxQueueSize bounds the buffer: when it is full, the oldest event is dropped and
// reported to OnError. Defaults to 10,000.
MaxQueueSize int
}
BatchConfig tunes the ingestion buffers. Events and identifies each get a buffer with these settings. Zero fields take their defaults.
type Client ¶
type Client struct {
// Profiles reads and deletes profiles.
Profiles *ProfilesClient
// Activity reads raw events and per-profile activity.
Activity *ActivityClient
// Insights runs analytics queries.
Insights *InsightsClient
// contains filtered or unexported fields
}
Client is a Pug server-side client, safe for concurrent use. Ingestion is buffered and sent by a background goroutine, so call Client.Close on shutdown.
func New ¶
New returns a Client. It fails only on unusable options: a missing, malformed or non-private API key, or an Endpoint that is not an absolute http(s) URL.
func (*Client) Close ¶
Close drains both queues and stops their background senders; later Track and Identify calls are dropped with a warning. Events and identifies that cannot be delivered, including any left when ctx ends, are reported to OnError. It returns ctx's error if ctx ended before the drain finished.
Example ¶
package main
import (
"context"
"log"
"os"
"time"
pug "github.com/pug-sh/sdk-go"
)
func main() {
client, _ := pug.New(pug.Options{
APIKey: os.Getenv("PUG_API_KEY"),
OnError: func(err error, lost pug.Undelivered) {
log.Printf("dropping %d event(s) and %d identify(s): %v", len(lost.Events), len(lost.Identifies), err)
},
})
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := client.Close(ctx); err != nil {
log.Print(err)
}
}
Output:
func (*Client) Flush ¶
Flush sends the queued events and identifies now. It returns once an attempt in progress and its own attempt have finished, or with ctx's error if ctx ends first. It does not guarantee empty queues: a transient failure keeps items queued for a later retry. Use Close to drain on shutdown.
func (*Client) Identify ¶
func (c *Client) Identify(externalID string, opts ...IdentifyOption)
Identify queues a create-or-update of the profile for externalID and returns at once; a background goroutine sends identifies in batches with BatchIdentify, or with one Identify call each on a server that predates it. Its traits are fields (String, Int, …), named traits (Name, FirstName, LastName, Email, Avatar) and Traits maps, in the same list as the With… options, and are shallow-merged into the stored properties. It never fails: invalid input is logged and dropped, and an identify that can't be delivered is reported to OnError.
func (*Client) Track ¶
func (c *Client) Track(distinctID string, kind Kind, opts ...TrackOption)
Track enqueues an event of kind for distinctID and returns at once; a background goroutine sends events in batches. Its properties are fields (String, Int, …) and Properties maps, in the same list as the With… options. It never fails: invalid input is logged and dropped.
For a well-known kind such as "purchase", properties are checked against the event's schema, keyed by the fields' JSON names ("productId"; "product_id" logs a warning), and other keys ride along as custom properties. Any other kind is a custom event with free-form properties. Client.TrackWellKnown takes the typed schema instead.
Example ¶
package main
import (
"context"
"os"
"time"
pug "github.com/pug-sh/sdk-go"
)
func main() {
client, _ := pug.New(pug.Options{APIKey: os.Getenv("PUG_API_KEY")})
defer client.Close(context.Background())
client.Track("user_123", "checkout_completed", pug.String("plan", "pro"),
pug.WithLocation(pug.Location{Country: "DE", City: "Berlin"}),
pug.WithTimestamp(time.Now().Add(-time.Minute)),
)
}
Output:
func (*Client) TrackWellKnown ¶
func (c *Client) TrackWellKnown(distinctID string, event proto.Message, opts ...TrackOption)
TrackWellKnown enqueues a well-known event from its typed properties message, such as &commoneventsv1.PurchaseProperties{...}; the kind comes from the message's schema. [Field]s and Properties among opts are sent as custom properties alongside the typed fields, except one keyed by a typed field's JSON name, which is dropped. Like Track, it never fails: invalid input is logged and dropped.
Example ¶
package main
import (
"context"
"os"
"google.golang.org/protobuf/proto"
pug "github.com/pug-sh/sdk-go"
commoneventsv1 "github.com/pug-sh/sdk-go/gen/common/events/v1"
)
func main() {
client, _ := pug.New(pug.Options{APIKey: os.Getenv("PUG_API_KEY")})
defer client.Close(context.Background())
client.TrackWellKnown("user_42", &commoneventsv1.PurchaseProperties{
Amount: proto.Float64(49),
Currency: proto.String("USD"),
OrderId: proto.String("ord_1"),
}, pug.String("coupon", "FALL25"))
}
Output:
type Coordinates ¶
Coordinates are a position in decimal degrees. Latitude and longitude are sent together or not at all.
type Error ¶
type Error struct {
// Code is the Connect status code, or zero when the failure did not come from the RPC
// layer.
Code connect.Code
// Message describes the failure, without the code.
Message string
// Err is the underlying error.
Err error
}
Error is the error returned by the read methods and handed to OnError when a send fails. Ingestion never returns errors. Under errors.Is it matches the sentinel for its Code, such as ErrNotFound.
func (*Error) Is ¶
Is reports whether target is the sentinel for e's Code, such as ErrNotFound. An Error without a Code matches none.
type Field ¶
type Field struct {
// contains filtered or unexported fields
}
Field is one event property or profile trait, made by String, Int, Float, Bool, Time, Duration or Any. It goes in the same list as the call's other options, so the key sits next to its value and the value's type is fixed at compile time.
func Float ¶
Float is a floating-point property, sent as a double even when it is a whole number. NaN and ±Inf are dropped with a warning.
func Int ¶
Int is an integer property of any integer type. One past the int64 range is dropped with a warning.
type GetOption ¶
type GetOption func(*getOptions)
GetOption customizes a single-profile lookup.
func IncludeBots ¶
func IncludeBots() GetOption
IncludeBots counts traffic tagged as a bot at ingest in the profile's activity summary. It is left out by default.
type Identify ¶
type Identify = sdkprofilesv1.IdentifyRequest
Identify is the wire form of a queued identify, as handed to OnError.
type IdentifyOption ¶
type IdentifyOption interface {
// contains filtered or unexported methods
}
IdentifyOption customizes one Client.Identify call: a Field, a named trait such as Email, the profile's Traits, or an option such as WithAnonymousID. Nil options are skipped.
func Avatar ¶
func Avatar(value string) IdentifyOption
Avatar is the URL of the profile's picture, sent as the "avatar" trait. The dashboard shows only an absolute https URL.
func Email ¶
func Email(value string) IdentifyOption
Email is the profile's email address, sent as the "email" trait.
func FirstName ¶
func FirstName(value string) IdentifyOption
FirstName is the profile's first name, sent as the "firstName" trait. With LastName, the dashboard composes a display name when Name is unset.
func LastName ¶
func LastName(value string) IdentifyOption
LastName is the profile's last name, sent as the "lastName" trait.
func Name ¶
func Name(value string) IdentifyOption
Name is the profile's display name, sent as the "name" trait.
func WithAnonymousID ¶
func WithAnonymousID(id string) IdentifyOption
WithAnonymousID names the SDK anonymous id (it must start with "anon-") whose profile is merged into the identified one.
func WithDeviceID ¶
func WithDeviceID(id string) IdentifyOption
WithDeviceID assigns a device to the profile.
type InsightsClient ¶
type InsightsClient struct {
// contains filtered or unexported fields
}
InsightsClient runs product-analytics queries. Queries leave out traffic tagged as a bot at ingest unless the spec sets include_bots.
func (*InsightsClient) FilterSchema ¶
func (i *InsightsClient) FilterSchema(ctx context.Context, req *commonv1.GetFilterSchemaRequest) (*commonv1.GetFilterSchemaResponse, error)
FilterSchema returns the event kinds and property keys available to filter and break down by; req may be nil.
func (*InsightsClient) PropertyValues ¶
func (i *InsightsClient) PropertyValues(ctx context.Context, req *insightsv1.GetPropertyValuesRequest) (*insightsv1.GetPropertyValuesResponse, error)
PropertyValues returns the observed values of an event or profile property.
func (*InsightsClient) Query ¶
func (i *InsightsClient) Query(ctx context.Context, req *insightsv1.QueryRequest) (*insightsv1.QueryResponse, error)
Query runs an insight (trends, segmentation, funnel, retention, user flow, top-K or map).
func (*InsightsClient) SegmentUsers ¶
func (i *InsightsClient) SegmentUsers(ctx context.Context, req *insightsv1.SegmentUsersRequest) (*insightsv1.SegmentUsersResponse, error)
SegmentUsers returns a page of the distinct ids behind a set of events.
type Kind ¶
type Kind string
Kind is an event's kind, such as "order.completed". An untyped string constant converts to it implicitly; a string variable needs Kind(s), so a distinct ID passed where the kind belongs fails to compile.
Example ¶
package main
import (
"context"
"os"
pug "github.com/pug-sh/sdk-go"
)
func main() {
client, _ := pug.New(pug.Options{APIKey: os.Getenv("PUG_API_KEY")})
defer client.Close(context.Background())
kind := os.Getenv("CHECKOUT_EVENT") // a kind held in a variable needs the conversion
client.Track("user_42", pug.Kind(kind), pug.Float("amount", 49))
}
Output:
type Location ¶
type Location struct {
Continent string
// Country is an ISO 3166-1 alpha-2 code such as "DE". Case and surrounding spaces are
// normalized.
Country string
Region string
City string
PostalCode string
MetroCode string
// Timezone is an IANA name such as "Europe/Berlin".
Timezone string
Coordinates *Coordinates
}
Location is where the visitor is, for an event your backend sends on their behalf. Strings are trimmed and empty ones omitted. A field that looks like a mistake (an unknown country, a coordinate out of range) is dropped with a warning and the rest is still sent.
type OnError ¶
type OnError func(err error, lost Undelivered)
OnError is a dead-letter hook for queued events and identifies that could not be delivered. err matches ErrQueueOverflow or ErrClosed under errors.Is, or is an *Error for a failed send. It runs on the goroutine that called Track or Identify or on a background sender, so it must be safe for concurrent use, return promptly, and not call Flush or Close.
type Options ¶
type Options struct {
// APIKey is the project's private API key (prv_…).
APIKey string
// Endpoint is the Pug server origin. Defaults to the hosted endpoint.
Endpoint string
// Batch tunes the ingestion buffer.
Batch BatchConfig
// OnError receives queued events and identifies that could not be delivered: a permanent
// send failure, a queue overflow, or ones still undelivered at Close.
OnError OnError
// Logger receives the SDK's warnings and errors. Defaults to slog.Default().
Logger *slog.Logger
// HTTPClient sends the requests. Defaults to a client using http.DefaultTransport.
HTTPClient *http.Client
// Timeout bounds each request whose context has no deadline. Defaults to 10s.
Timeout time.Duration
}
Options configures a Client. Only APIKey is required.
type ProfilesClient ¶
type ProfilesClient struct {
// contains filtered or unexported fields
}
ProfilesClient reads and deletes profiles. Reads leave out traffic tagged as a bot at ingest unless asked to include it.
func (*ProfilesClient) Delete ¶
func (p *ProfilesClient) Delete(ctx context.Context, id string) (*profilesv1.DeleteResponse, error)
Delete erases the profile with the given id. Erasure completes asynchronously; the response carries the request id to track it by.
func (*ProfilesClient) Get ¶
func (p *ProfilesClient) Get(ctx context.Context, id string, opts ...GetOption) (*profilesv1.Profile, error)
Get returns the profile with the given Pug profile id.
func (*ProfilesClient) GetByExternalID ¶
func (p *ProfilesClient) GetByExternalID(ctx context.Context, externalID string, opts ...GetOption) (*profilesv1.Profile, error)
GetByExternalID returns the profile with the external id your application assigned it.
func (*ProfilesClient) List ¶
func (p *ProfilesClient) List(ctx context.Context, req *profilesv1.ListRequest) iter.Seq2[*profilesv1.Profile, error]
List iterates over the profiles matching req, fetching pages as it goes; a nil req lists every profile. An error ends the iteration. The whole iteration runs under one request, so without a deadline on ctx it is bounded by Options.Timeout.
Example ¶
package main
import (
"context"
"fmt"
"log"
"os"
"time"
pug "github.com/pug-sh/sdk-go"
)
func main() {
client, _ := pug.New(pug.Options{APIKey: os.Getenv("PUG_API_KEY")})
defer client.Close(context.Background())
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
for profile, err := range client.Profiles.List(ctx, nil) {
if err != nil {
log.Print(err)
break
}
fmt.Println(profile.GetExternalId())
}
}
Output:
type Properties ¶
Properties are an event's custom properties as a map, for keys only known at run time, passed to Client.Track or Client.TrackWellKnown among its options like a Field; given more than once, they are merged, later keys winning. Strings, booleans, integers and floats keep their type; time.Time becomes a timestamp and time.Duration a duration string like "1.5s"; json.Number is an integer when it has no fraction; a value implementing encoding.TextMarshaler is sent as its text; other maps, slices and structs are sent as JSON strings. Nil values are omitted, and a value that can't be represented (a func, NaN, a uint64 past the int64 range) is dropped with a warning.
type TrackOption ¶
type TrackOption interface {
// contains filtered or unexported methods
}
TrackOption customizes one tracked event: a Field, its Properties, or an option such as WithTimestamp. Nil options are skipped.
func WithLocation ¶
func WithLocation(loc Location) TrackOption
WithLocation records the visitor's location. Pug derives geo only from browser requests, so an event sent with a private key carries the location set here or none at all.
func WithSessionID ¶
func WithSessionID(id string) TrackOption
WithSessionID overrides the client's per-instance session id. It must be a UUID; an empty id keeps the default, and any other id is ignored with a warning.
func WithTimestamp ¶
func WithTimestamp(t time.Time) TrackOption
WithTimestamp sets when the event happened; it defaults to now. A time before the Unix epoch or after year 9999 is almost certainly a unit mistake, so it is ignored with a warning.
type Traits ¶
Traits are profile traits as a map, for keys only known at run time, passed to Client.Identify among its options like a Field and shallow-merged into the stored ones; given more than once, they are merged, later keys winning. They must be JSON-serializable.
type Undelivered ¶
Undelivered is what one failure lost. Either slice may be empty.