Skip to content

Commit

Permalink
add statsig metadata to sdk_exception logs
Browse files Browse the repository at this point in the history
  • Loading branch information
kenny-statsig committed Oct 27, 2023
1 parent 071d44d commit f1d79bb
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/kong.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env:

jobs:
KONG:
timeout-minutes: 5
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Get KONG
Expand Down
2 changes: 1 addition & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func (c *Client) GetClientInitializeResponse(user User, clientKey string) Client
func (c *Client) verifyUser(user User) bool {
if user.UserID == "" && len(user.CustomIDs) == 0 {
err := errors.New(EmptyUserError)
global.Logger().LogError(err)
Logger().LogError(err)
return false
}
return true
Expand Down
2 changes: 1 addition & 1 deletion diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (d *diagnosticsBase) logProcess(msg string) {
case ConfigSyncContext:
process = StatsigProcessSync
}
global.Logger().LogStep(process, msg)
Logger().LogStep(process, msg)
}

func (d *diagnosticsBase) serializeWithSampling() map[string]interface{} {
Expand Down
15 changes: 9 additions & 6 deletions error_boundary.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ type errorBoundary struct {
}

type logExceptionRequestBody struct {
Exception string `json:"exception"`
Info string `json:"info"`
Exception string `json:"exception"`
Info string `json:"info"`
StatsigMetadata statsigMetadata `json:"statsigMetadata"`
}

type logExceptionResponse struct {
Expand Down Expand Up @@ -106,7 +107,7 @@ func (e *errorBoundary) captureVoid(task func()) {
func (e *errorBoundary) ebRecover(recoverCallback func()) {
if err := recover(); err != nil {
e.logException(toError(err))
global.Logger().LogError(err)
Logger().LogError(err)
recoverCallback()
}
}
Expand All @@ -123,15 +124,16 @@ func (e *errorBoundary) logException(exception error) {
}
stack := make([]byte, 1024)
runtime.Stack(stack, false)
metadata := getStatsigMetadata()
body := &logExceptionRequestBody{
Exception: exceptionString,
Info: string(stack),
Exception: exceptionString,
Info: string(stack),
StatsigMetadata: metadata,
}
bodyString, err := json.Marshal(body)
if err != nil {
return
}
metadata := getStatsigMetadata()

req, err := http.NewRequest("POST", e.api+e.endpoint, bytes.NewBuffer(bodyString))
if err != nil {
Expand All @@ -142,6 +144,7 @@ func (e *errorBoundary) logException(exception error) {
req.Header.Add("STATSIG-CLIENT-TIME", strconv.FormatInt(getUnixMilli(), 10))
req.Header.Add("STATSIG-SDK-TYPE", metadata.SDKType)
req.Header.Add("STATSIG-SDK-VERSION", metadata.SDKVersion)
req.Header.Add("STATSIG-SERVER-SESSION-ID", metadata.SessionID)

_, _ = e.client.Do(req)
}
2 changes: 1 addition & 1 deletion evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func newEvaluator(
defer func() {
if err := recover(); err != nil {
errorBoundary.logException(toError(err))
global.Logger().LogError(err)
Logger().LogError(err)
}
}()

Expand Down
25 changes: 21 additions & 4 deletions global_state.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package statsig

import "sync"
import (
"sync"

"github.com/google/uuid"
)

// Using global state variables directly will lead to race conditions
// Instead, define an accessor below using the Mutex lock
type GlobalState struct {
logger *OutputLogger
mu sync.RWMutex
logger *OutputLogger
sessionID string
mu sync.RWMutex
}

var global GlobalState

func (g *GlobalState) Logger() *OutputLogger {
func Logger() *OutputLogger {
global.mu.RLock()
defer global.mu.RUnlock()
return global.logger
Expand All @@ -24,3 +29,15 @@ func InitializeGlobalOutputLogger(options OutputLoggerOptions) {
options: options,
}
}

func SessionID() string {
global.mu.RLock()
defer global.mu.RUnlock()
return global.sessionID
}

func InitializeGlobalSessionID() {
global.mu.Lock()
defer global.mu.Unlock()
global.sessionID = uuid.NewString()
}
8 changes: 5 additions & 3 deletions statsig.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ var instance *Client
// Initializes the global Statsig instance with the given sdkKey
func Initialize(sdkKey string) {
InitializeGlobalOutputLogger(OutputLoggerOptions{})
InitializeGlobalSessionID()
if IsInitialized() {
global.Logger().Log("Statsig is already initialized.", nil)
Logger().Log("Statsig is already initialized.", nil)
return
}

Expand Down Expand Up @@ -66,8 +67,9 @@ func IsInitialized() bool {
// Initializes the global Statsig instance with the given sdkKey and options
func InitializeWithOptions(sdkKey string, options *Options) {
InitializeGlobalOutputLogger(options.OutputLoggerOptions)
InitializeGlobalSessionID()
if IsInitialized() {
global.Logger().Log("Statsig is already initialized.", nil)
Logger().Log("Statsig is already initialized.", nil)
return
}

Expand All @@ -82,7 +84,7 @@ func InitializeWithOptions(sdkKey string, options *Options) {
case res := <-channel:
instance = res
case <-time.After(options.InitTimeout):
global.Logger().LogStep(StatsigProcessInitialize, "Timed out")
Logger().LogStep(StatsigProcessInitialize, "Timed out")
return
}
} else {
Expand Down
2 changes: 2 additions & 0 deletions statsig_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ type statsigMetadata struct {
SDKType string `json:"sdkType"`
SDKVersion string `json:"sdkVersion"`
LanguageVersion string `json:"languageVersion"`
SessionID string `json:"sessionID"`
}

func getStatsigMetadata() statsigMetadata {
return statsigMetadata{
SDKType: "go-sdk",
SDKVersion: "1.12.2",
LanguageVersion: runtime.Version()[2:],
SessionID: SessionID(),
}
}
53 changes: 53 additions & 0 deletions statsig_metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package statsig

import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)

func makeTestServer(reqCallback func(req *http.Request)) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
reqCallback(req)
}))
}

func TestStatsigMetadata(t *testing.T) {
sessionID := ""
InitializeWithOptions("secret-key", &Options{
API: makeTestServer(func(req *http.Request) {
reqSessionID := req.Header.Get("STATSIG-SERVER-SESSION-ID")
if strings.Contains(req.URL.Path, "download_config_specs") {
sessionID = reqSessionID
}
if strings.Contains(req.URL.Path, "log_event") {
if reqSessionID != sessionID {
t.Error("Inconsistent SessionID")
}
}
}).URL,
OutputLoggerOptions: getOutputLoggerOptionsForTest(t),
StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t),
LoggingMaxBufferSize: 1,
})
if sessionID == "" {
t.Error("Missing SessionID in statsig metadata")
}
CheckGate(User{UserID: "first"}, "non-existent")
Shutdown()
InitializeWithOptions("secret-key", &Options{
API: makeTestServer(func(req *http.Request) {
reqSessionID := req.Header.Get("STATSIG-SERVER-SESSION-ID")
if strings.Contains(req.URL.Path, "download_config_specs") {
if reqSessionID == sessionID {
t.Error("SessionID not reset on Initialize")
}
}
}).URL,
OutputLoggerOptions: getOutputLoggerOptionsForTest(t),
StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t),
})
ShutdownAndDangerouslyClearInstance()
}
33 changes: 12 additions & 21 deletions transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"strconv"
"strings"
"time"

"github.com/google/uuid"
)

const (
Expand All @@ -18,35 +16,28 @@ const (
)

type transport struct {
api string
sdkKey string
metadata statsigMetadata // Safe to read from but not thread safe to write into. If value needs to change, please ensure thread safety.
client *http.Client
options *Options
sessionID string
}

func getSessionID() string {
return uuid.NewString()
api string
sdkKey string
metadata statsigMetadata // Safe to read from but not thread safe to write into. If value needs to change, please ensure thread safety.
client *http.Client
options *Options
}

func newTransport(secret string, options *Options) *transport {
api := defaultString(options.API, DefaultEndpoint)
api = strings.TrimSuffix(api, "/")
defer func() {
if err := recover(); err != nil {
global.Logger().LogError(err)
Logger().LogError(err)
}
}()
sid := getSessionID()

return &transport{
api: api,
metadata: getStatsigMetadata(),
sdkKey: secret,
client: &http.Client{Timeout: time.Second * 3},
options: options,
sessionID: sid,
api: api,
metadata: getStatsigMetadata(),
sdkKey: secret,
client: &http.Client{Timeout: time.Second * 3},
options: options,
}
}

Expand Down Expand Up @@ -106,7 +97,7 @@ func (transport *transport) doRequest(endpoint string, body []byte) (*http.Respo
req.Header.Add("STATSIG-API-KEY", transport.sdkKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Add("STATSIG-CLIENT-TIME", strconv.FormatInt(getUnixMilli(), 10))
req.Header.Add("STATSIG-SERVER-SESSION-ID", transport.sessionID)
req.Header.Add("STATSIG-SERVER-SESSION-ID", transport.metadata.SessionID)
req.Header.Add("STATSIG-SDK-TYPE", transport.metadata.SDKType)
req.Header.Add("STATSIG-SDK-VERSION", transport.metadata.SDKVersion)

Expand Down

0 comments on commit f1d79bb

Please sign in to comment.