Skip to content

Commit

Permalink
Added optional client hint headers, added structured logging using lo…
Browse files Browse the repository at this point in the history
…g/slog, removed DNT, changed package structure, fixed options to extend sessions, upgraded Go version to 1.21, updated dependencies.
  • Loading branch information
Kugelschieber committed Aug 19, 2023
1 parent 12fee5f commit b3d4f98
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 85 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 2.0.0

* added optional client hint headers
* added structured logging using `log/slog`
* removed DNT
* changed package structure
* fixed options to extend sessions
* upgraded Go version to 1.21
* updated dependencies

## 1.9.0

* added configuration options for request timeout and retries
Expand Down
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: deps test

deps:
go get -u -t ./...

test:
go test -cover -race github.com/pirsch-analytics/pirsch-go-sdk/v2/pkg
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module github.com/pirsch-analytics/pirsch-go-sdk
module github.com/pirsch-analytics/pirsch-go-sdk/v2

go 1.20
go 1.21

require (
github.com/emvi/null v1.3.1
github.com/stretchr/testify v1.8.2
github.com/stretchr/testify v1.8.4
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
138 changes: 71 additions & 67 deletions client.go → pkg/client.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package pirsch
package pkg

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"log/slog"
"net/http"
"net/url"
"os"
"strconv"
"sync"
"time"
Expand Down Expand Up @@ -67,7 +68,7 @@ var referrerQueryParams = []string{
// Client is used to access the Pirsch API.
type Client struct {
baseURL string
logger *log.Logger
logger *slog.Logger
clientID string
clientSecret string
accessToken string
Expand All @@ -90,19 +91,25 @@ type ClientConfig struct {
RequestRetries int

// Logger is an optional logger for debugging.
Logger *log.Logger
}

// HitOptions optional parameters to send with the hit request.
type HitOptions struct {
URL string
IP string
UserAgent string
AcceptLanguage string
Title string
Referrer string
ScreenWidth int
ScreenHeight int
Logger slog.Handler
}

// PageViewOptions optional parameters to send with the hit request.
type PageViewOptions struct {
URL string
IP string
UserAgent string
AcceptLanguage string
SecCHUA string
SecCHUAMobile string
SecCHUAPlatform string
SecCHUAPlatformVersion string
SecCHWidth string
SecCHViewportWidth string
Title string
Referrer string
ScreenWidth int
ScreenHeight int
}

// NewClient creates a new client for given client ID, client secret, hostname, and optional configuration.
Expand All @@ -128,9 +135,13 @@ func NewClient(clientID, clientSecret string, config *ClientConfig) *Client {
config.RequestRetries = defaultRequestRetries
}

if config.Logger == nil {
config.Logger = slog.NewTextHandler(os.Stderr, nil)
}

c := &Client{
baseURL: config.BaseURL,
logger: config.Logger,
logger: slog.New(config.Logger),
clientID: clientID,
clientSecret: clientSecret,
timeout: config.Timeout,
Expand All @@ -145,67 +156,54 @@ func NewClient(clientID, clientSecret string, config *ClientConfig) *Client {
return c
}

// Hit sends a page hit to Pirsch for given http.Request.
func (client *Client) Hit(r *http.Request) error {
return client.HitWithOptions(r, nil)
}

// HitWithOptions sends a page hit to Pirsch for given http.Request and options.
func (client *Client) HitWithOptions(r *http.Request, options *HitOptions) error {
if r.Header.Get("DNT") == "1" {
return nil
}

// PageView sends a page hit to Pirsch for given http.Request and options.
func (client *Client) PageView(r *http.Request, options *PageViewOptions) error {
if options == nil {
options = new(HitOptions)
options = new(PageViewOptions)
}

hit := client.getHit(r, options)
hit := client.getPageViewData(r, options)
return client.performPost(client.baseURL+hitEndpoint, &hit, client.requestRetries)
}

// Event sends an event to Pirsch for given http.Request.
func (client *Client) Event(name string, durationSeconds int, meta map[string]string, r *http.Request) error {
return client.EventWithOptions(name, durationSeconds, meta, r, nil)
}

// EventWithOptions sends an event to Pirsch for given http.Request and options.
func (client *Client) EventWithOptions(name string, durationSeconds int, meta map[string]string, r *http.Request, options *HitOptions) error {
// Event sends an event to Pirsch for given http.Request and options.
func (client *Client) Event(name string, durationSeconds int, meta map[string]string, r *http.Request, options *PageViewOptions) error {
if r.Header.Get("DNT") == "1" {
return nil
}

if options == nil {
options = new(HitOptions)
options = new(PageViewOptions)
}

return client.performPost(client.baseURL+eventEndpoint, &Event{
Name: name,
DurationSeconds: durationSeconds,
Metadata: meta,
Hit: client.getHit(r, options),
PageView: client.getPageViewData(r, options),
}, client.requestRetries)
}

// Session keeps a session alive for the given http.Request.
func (client *Client) Session(r *http.Request) error {
return client.HitWithOptions(r, nil)
}

// SessionWithOptions keeps a session alive for the given http.Request and options.
func (client *Client) SessionWithOptions(r *http.Request, options *HitOptions) error {
// Session keeps a session alive for the given http.Request and options.
func (client *Client) Session(r *http.Request, options *PageViewOptions) error {
if r.Header.Get("DNT") == "1" {
return nil
}

if options == nil {
options = new(HitOptions)
}

return client.performPost(client.baseURL+sessionEndpoint, &Hit{
URL: r.URL.String(),
IP: r.RemoteAddr,
UserAgent: r.Header.Get("User-Agent"),
options = new(PageViewOptions)
}

return client.performPost(client.baseURL+sessionEndpoint, &PageView{
URL: r.URL.String(),
IP: client.selectField(options.IP, r.RemoteAddr),
UserAgent: client.selectField(options.UserAgent, r.Header.Get("User-Agent")),
SecCHUA: client.selectField(options.SecCHUA, r.Header.Get("Sec-CH-UA")),
SecCHUAMobile: client.selectField(options.SecCHUAMobile, r.Header.Get("Sec-CH-UA-Mobile")),
SecCHUAPlatform: client.selectField(options.SecCHUAPlatform, r.Header.Get("Sec-CH-UA-Platform")),
SecCHUAPlatformVersion: client.selectField(options.SecCHUAPlatformVersion, r.Header.Get("Sec-CH-UA-Platform-Version")),
SecCHWidth: client.selectField(options.SecCHWidth, r.Header.Get("Sec-CH-Width")),
SecCHViewportWidth: client.selectField(options.SecCHViewportWidth, r.Header.Get("Sec-CH-Viewport-Width")),
}, client.requestRetries)
}

Expand Down Expand Up @@ -554,16 +552,22 @@ func (client *Client) Keywords(filter *Filter) ([]Keyword, error) {
return stats, nil
}

func (client *Client) getHit(r *http.Request, options *HitOptions) Hit {
return Hit{
URL: client.selectField(options.URL, r.URL.String()),
IP: client.selectField(options.IP, r.RemoteAddr),
UserAgent: client.selectField(options.UserAgent, r.Header.Get("User-Agent")),
AcceptLanguage: client.selectField(options.AcceptLanguage, r.Header.Get("Accept-Language")),
Title: options.Title,
Referrer: client.selectField(options.Referrer, client.getReferrerFromHeaderOrQuery(r)),
ScreenWidth: options.ScreenWidth,
ScreenHeight: options.ScreenHeight,
func (client *Client) getPageViewData(r *http.Request, options *PageViewOptions) PageView {
return PageView{
URL: client.selectField(options.URL, r.URL.String()),
IP: client.selectField(options.IP, r.RemoteAddr),
UserAgent: client.selectField(options.UserAgent, r.Header.Get("User-Agent")),
AcceptLanguage: client.selectField(options.AcceptLanguage, r.Header.Get("Accept-Language")),
SecCHUA: client.selectField(options.SecCHUA, r.Header.Get("Sec-CH-UA")),
SecCHUAMobile: client.selectField(options.SecCHUAMobile, r.Header.Get("Sec-CH-UA-Mobile")),
SecCHUAPlatform: client.selectField(options.SecCHUAPlatform, r.Header.Get("Sec-CH-UA-Platform")),
SecCHUAPlatformVersion: client.selectField(options.SecCHUAPlatformVersion, r.Header.Get("Sec-CH-UA-Platform-Version")),
SecCHWidth: client.selectField(options.SecCHWidth, r.Header.Get("Sec-CH-Width")),
SecCHViewportWidth: client.selectField(options.SecCHViewportWidth, r.Header.Get("Sec-CH-Viewport-Width")),
Title: options.Title,
Referrer: client.selectField(options.Referrer, client.getReferrerFromHeaderOrQuery(r)),
ScreenWidth: options.ScreenWidth,
ScreenHeight: options.ScreenHeight,
}
}

Expand Down Expand Up @@ -634,7 +638,7 @@ func (client *Client) performPost(url string, body interface{}, retry int) error

if err := client.refreshToken(); err != nil {
if client.logger != nil {
client.logger.Printf("error refreshing token: %s", err)
client.logger.Error("error refreshing token", "err", err)
}

return errors.New(fmt.Sprintf("error refreshing token (attempt %d/%d): %s", client.requestRetries-retry, client.requestRetries, err))
Expand Down Expand Up @@ -671,7 +675,7 @@ func (client *Client) performPost(url string, body interface{}, retry int) error

if err := client.refreshToken(); err != nil {
if client.logger != nil {
client.logger.Printf("error refreshing token: %s", err)
client.logger.Error("error refreshing token", "err", err)
}

return errors.New(fmt.Sprintf("error refreshing token (attempt %d/%d): %s", client.requestRetries-retry, client.requestRetries, err))
Expand All @@ -698,7 +702,7 @@ func (client *Client) performGet(url string, retry int, result interface{}) erro

if err := client.refreshToken(); err != nil {
if client.logger != nil {
client.logger.Printf("error refreshing token: %s", err)
client.logger.Error("error refreshing token", "err", err)
}

return errors.New(fmt.Sprintf("error refreshing token (attempt %d/%d): %s", client.requestRetries-retry, client.requestRetries, err))
Expand Down Expand Up @@ -730,7 +734,7 @@ func (client *Client) performGet(url string, retry int, result interface{}) erro

if err := client.refreshToken(); err != nil {
if client.logger != nil {
client.logger.Printf("error refreshing token: %s", err)
client.logger.Error("error refreshing token", "err", err)
}

return errors.New(fmt.Sprintf("error refreshing token (attempt %d/%d): %s", client.requestRetries-retry, client.requestRetries, err))
Expand Down
2 changes: 1 addition & 1 deletion client_test.go → pkg/client_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package pirsch
package pkg

import (
"fmt"
Expand Down
34 changes: 20 additions & 14 deletions types.go → pkg/types.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package pirsch
package pkg

import (
"github.com/emvi/null"
Expand All @@ -23,23 +23,29 @@ const (
// Use one of the constants ScaleDay, ScaleWeek, ScaleMonth, ScaleYear.
type Scale string

// Hit are the parameters to send a page hit to Pirsch.
type Hit struct {
Hostname string
URL string `json:"url"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
AcceptLanguage string `json:"accept_language"`
Title string `json:"title"`
Referrer string `json:"referrer"`
ScreenWidth int `json:"screen_width"`
ScreenHeight int `json:"screen_height"`
// PageView are the parameters to send a page hit to Pirsch.
type PageView struct {
Hostname string
URL string `json:"url"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
AcceptLanguage string `json:"accept_language"`
SecCHUA string `json:"sec_ch_ua"`
SecCHUAMobile string `json:"sec_ch_ua_mobile"`
SecCHUAPlatform string `json:"sec_ch_ua_platform"`
SecCHUAPlatformVersion string `json:"sec_ch_ua_platform_version"`
SecCHWidth string `json:"sec_ch_width"`
SecCHViewportWidth string `json:"sec_ch_viewport_width"`
Title string `json:"title"`
Referrer string `json:"referrer"`
ScreenWidth int `json:"screen_width"`
ScreenHeight int `json:"screen_height"`
}

// Event represents a single data point for custom events.
// It's basically the same as Hit, but with some additional fields (event name, time, and meta fields).
// It's basically the same as PageView, but with some additional fields (event name, time, and meta fields).
type Event struct {
Hit
PageView
Name string `json:"event_name"`
DurationSeconds int `json:"event_duration"`
Metadata map[string]string `json:"event_meta"`
Expand Down

0 comments on commit b3d4f98

Please sign in to comment.