Skip to content

Instantly share code, notes, and snippets.

@imjasonh
Created January 7, 2014 17:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save imjasonh/8303394 to your computer and use it in GitHub Desktop.
Save imjasonh/8303394 to your computer and use it in GitHub Desktop.
Simple client for the Google Analytics Measurement Protocol (https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters), to report events to Google Analytics. Also a half-baked example of using reflection and struct tags to generate URL query parameters.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
//
// Usage:
// req, _ := analytics.Request{
// HitType: analytics.PageView,
// TrackingID: "UA-XXXX-Y",
// ClientID: "35009a79-1a05-49d7-b876-2b884d0f825b",
// // ...
// }.Request()
// resp, _ := req.Do()
package analytics
import (
"fmt"
"net/http"
"net/url"
"reflect"
"time"
)
var required = []string{"t", "tid", "cid"}
type HitType string
const (
PageView HitType = "pageview"
AppView HitType = "appview"
Event HitType = "event"
Transaction HitType = "transaction"
Item HitType = "item"
Social HitType = "social"
Exception HitType = "exception"
Timing HitType = "timing"
)
type SessionEvent string
const (
Start SessionEvent = "start"
End SessionEvent = "end"
)
type Request struct {
// TODO: Custom dimensions and metrics
Version string `url:"v"`
TrackingID string `url:"tid"`
ClientID string `url:"cid"`
HitType HitType `url:"t"`
AnonymizeIP bool `url:"aip"`
QueueTimeMilliseconds time.Duration `url:"qt"`
CacheBuster string `url:"z"`
SessionControl SessionEvent `url:"sc"`
DocumentReferrer string `url:"dr"`
CampaignName string `url:"cn"`
CampaignSource string `url:"cs"`
CampaignMedium string `url:"cm"`
CampaignKeyword string `url:"ck"`
CampaignContent string `url:"cc"`
CampaignID string `url:"ci"`
GoogleAdwordsID string `url:"gclid"`
GoogleDisplayAdsID string `url:"gclid"`
ScreenResolution string `url:"sc"`
ViewportSize string `url:"vp"`
DocumentEncoding string `url:"de"`
ScreenColors string `url:"sd"`
UserLanguage string `url:"ul"`
JavaEnabled bool `url:"je"`
FlashVersion string `url:"fl"`
NonInteractionHit bool `url:"ni"`
DocumentLocation string `url:"dl"`
DocumentHost string `url:"dh"`
DocumentPath string `url:"dp"`
DocumentTitle string `url:"dt"`
ContentDescription string `url:"cd"`
LinkID string `url:"linkid"`
ApplicationName string `url:"an"`
ApplicationVersion string `url:"av"`
EventCategory string `url:"ec"`
EventAction string `url:"ea"`
EventLabel string `url:"el"`
EventValue int `url:"ev"`
TransactionID string `url:"ti"`
TransactionAffiliation string `url:"ta"`
TransactionRevenue float64 `url:"tr"`
TransactionShipping float64 `url:"ts"`
TransactionTax float64 `url:"tt"`
ItemName string `url:"in"`
ItemPrice float64 `url:"ip"`
ItemQuantity int `url:"iq"`
ItemCode string `url:"ic"`
ItemCategory string `url:"iv"`
CurrencyCode string `url:"cc"`
SocialNetwork string `url:"sn"`
SocialAction string `url:"sa"`
SocialActionTarget string `url:"st"`
UserTimingCategory string `url:"utc"`
UserTimingVariable string `url:"utv"`
UserTimingTimeMilliseconds time.Duration `url:"utt"`
UserTimingLabel string `url:"utl"`
PageLoadTimeMilliseconds time.Duration `url:"ptl"`
DNSTimeMilliseconds time.Duration `url:"dns"`
PageDownloadTimeMilliseconds time.Duration `url:"pdt"`
RedirectResponseTimeMilliseconds time.Duration `url:"rrt"`
TCPConnectTimeMilliseconds time.Duration `url:"tcp"`
ServerResponseTimeMilliseconds time.Duration `url:"srt"`
ExceptionDescription string `url:"exd"`
ExceptionFatal bool `url:"exf"`
ExperimentID string `url:"xid"`
ExperimentVariant string `url:"xvar"`
}
func NewRequest(tid, cid string, t HitType) Request {
return Request{
TrackingID: tid,
ClientID: cid,
HitType: t,
}
}
func (r Request) encode() url.Values {
vals := url.Values{}
rt := reflect.TypeOf(r)
rv := reflect.ValueOf(r)
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
fv := rv.FieldByName(f.Name)
sv := ""
switch f.Type.Kind() {
case reflect.Bool:
if fv.Bool() {
sv = "1"
}
case reflect.Int:
fi := fv.Int()
if fi == 0 {
continue
}
sv = string(fi)
case reflect.Float64:
ff := fv.Float()
if ff == 0 {
continue
}
sv = fmt.Sprintf("%d", ff)
case reflect.Int64:
// Fields of type int64 are probably time.Durations, and we want the millisecond value
fi := fv.Int()
if fi == 0 {
continue
}
sv = string(fv.Int() / 1000)
default:
sv = fv.String()
}
if sv != "" {
vals.Set(f.Tag.Get("url"), sv)
}
}
return vals
}
func (r Request) Validate() error {
v := r.encode()
if v.Get("v") == "" {
v.Set("v", "1")
}
for _, req := range required {
if v.Get(req) == "" {
return fmt.Errorf("%s is required\n", req)
}
}
return nil
}
func (r Request) Request() (*http.Request, error) {
u := url.URL{
Scheme: "https",
Host: "www.google-analytics.com",
Path: "collect",
RawQuery: r.encode().Encode(),
}
req, err := http.NewRequest("POST", u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
return req, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment