Skip to content

Commit 8411239

Browse files
authored
add statsig option to eb (#269)
1 parent 9a2477a commit 8411239

File tree

4 files changed

+149
-10
lines changed

4 files changed

+149
-10
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99

1010
jobs:
1111
test:
12-
runs-on: ubuntu-20.04
12+
runs-on: ubuntu-22.04
1313
timeout-minutes: 15
1414
steps:
1515
- uses: actions/checkout@v3

error_boundary.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ type errorBoundary struct {
2222
}
2323

2424
type logExceptionRequestBody struct {
25-
Exception string `json:"exception"`
26-
Info string `json:"info"`
27-
StatsigMetadata statsigMetadata `json:"statsigMetadata"`
28-
Extra errorContext `json:"extra"`
29-
Tag string `json:"tag"`
25+
Exception string `json:"exception"`
26+
Info string `json:"info"`
27+
StatsigMetadata statsigMetadata `json:"statsigMetadata"`
28+
Extra errorContext `json:"extra"`
29+
Tag string `json:"tag"`
30+
StatsigOptions map[string]interface{} `json:"statsigOptions"`
3031
}
3132

3233
type logExceptionResponse struct {
@@ -181,6 +182,9 @@ func (e *errorBoundary) logExceptionWithContext(exception error, context errorCo
181182
Extra: context,
182183
Tag: context.Caller,
183184
}
185+
if e.options != nil {
186+
body.StatsigOptions = GetOptionLoggingCopy(*e.options)
187+
}
184188
bodyString, err := json.Marshal(body)
185189
if err != nil {
186190
return

error_boundary_test.go

+61-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import (
55
"errors"
66
"net/http"
77
"net/http/httptest"
8+
"reflect"
89
"strings"
910
"testing"
1011
)
1112

12-
func mock_server(t *testing.T, expectedError error, hit *bool) *httptest.Server {
13+
func mock_server(t *testing.T, expectedError error, hit *bool, statsigOptions *map[string]interface{}) *httptest.Server {
1314
return httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
1415
if strings.Contains(req.URL.Path, "/download_config_specs") {
1516
res.WriteHeader(500)
@@ -18,6 +19,9 @@ func mock_server(t *testing.T, expectedError error, hit *bool) *httptest.Server
1819
if strings.Contains(req.URL.Path, "/sdk_exception") {
1920
var body *logExceptionRequestBody
2021
_ = json.NewDecoder(req.Body).Decode(&body)
22+
for key, value := range body.StatsigOptions {
23+
(*statsigOptions)[key] = value
24+
}
2125
if body.Exception != "" && (expectedError == nil || body.Exception == expectedError.Error()) {
2226
*hit = true
2327
success := &logExceptionResponse{Success: true}
@@ -34,7 +38,8 @@ func mock_server(t *testing.T, expectedError error, hit *bool) *httptest.Server
3438
func TestLogException(t *testing.T) {
3539
err := errors.New("test error boundary log exception")
3640
hit := false
37-
testServer := mock_server(t, err, &hit)
41+
statsigOption := make(map[string]interface{})
42+
testServer := mock_server(t, err, &hit, &statsigOption)
3843
defer testServer.Close()
3944
opt := &Options{
4045
API: testServer.URL,
@@ -45,28 +50,44 @@ func TestLogException(t *testing.T) {
4550
if !hit {
4651
t.Error("Expected sdk_exception endpoint to be hit")
4752
}
53+
if statsigOption["API"] != testServer.URL {
54+
t.Error("Expected statsigOptions to be set")
55+
}
4856
}
4957

5058
func TestDCSError(t *testing.T) {
5159
hit := false
52-
testServer := mock_server(t, nil, &hit)
60+
statsigOption := make(map[string]interface{})
61+
testServer := mock_server(t, nil, &hit, &statsigOption)
5362
defer testServer.Close()
63+
dataadapter := dataAdapterExample{}
5464
opt := &Options{
5565
API: testServer.URL,
5666
OutputLoggerOptions: getOutputLoggerOptionsForTest(t),
5767
StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t),
68+
DataAdapter: &dataadapter,
5869
}
5970
InitializeWithOptions("secret-key", opt)
6071
defer ShutdownAndDangerouslyClearInstance()
6172
if !hit {
6273
t.Error("Expected sdk_exception endpoint to be hit")
6374
}
75+
expectedStatsigLoggerOptions := map[string]interface{}{
76+
"DisableInitDiagnostics": true,
77+
"DisableSyncDiagnostics": true,
78+
"DisableApiDiagnostics": true,
79+
"DisableAllLogging": false,
80+
}
81+
if !reflect.DeepEqual(statsigOption["StatsigLoggerOptions"], expectedStatsigLoggerOptions) {
82+
t.Error("Expected StatsigLoggerOptions in statsigOptions to be set")
83+
}
6484
}
6585

6686
func TestRepeatedError(t *testing.T) {
6787
err := errors.New("common error")
6888
hit := false
69-
testServer := mock_server(t, err, &hit)
89+
statsigOption := make(map[string]interface{})
90+
testServer := mock_server(t, err, &hit, &statsigOption)
7091
defer testServer.Close()
7192
opt := &Options{
7293
API: testServer.URL,
@@ -83,3 +104,39 @@ func TestRepeatedError(t *testing.T) {
83104
t.Error("Expected sdk_exception endpoint to NOT be hit")
84105
}
85106
}
107+
108+
func TestStatsigOptions(t *testing.T) {
109+
hit := false
110+
statsigOption := make(map[string]interface{})
111+
testServer := mock_server(t, nil, &hit, &statsigOption)
112+
defer testServer.Close()
113+
opt := &Options{
114+
API: testServer.URL,
115+
APIOverrides: APIOverrides{
116+
DownloadConfigSpecs: testServer.URL,
117+
},
118+
DataAdapter: &dataAdapterExample{},
119+
IDListSyncInterval: 1,
120+
IPCountryOptions: IPCountryOptions{
121+
Disabled: true,
122+
LazyLoad: true,
123+
},
124+
}
125+
diagnostics := newDiagnostics(opt)
126+
errorBoundary := newErrorBoundary("client-key", opt, diagnostics)
127+
errorBoundary.logException(errors.New("test error"))
128+
expectedOptions := map[string]interface{}{
129+
"API": testServer.URL,
130+
"APIOverrides": map[string]interface{}{
131+
"DownloadConfigSpecs": testServer.URL,
132+
"GetIDLists": "",
133+
"LogEvent": "",
134+
},
135+
"DataAdapter": "set",
136+
"IDListSyncInterval": 1.0,
137+
"IPCountryOptions": "set",
138+
}
139+
if !reflect.DeepEqual(statsigOption, expectedOptions) {
140+
t.Error("Expected statsigOptions to be set")
141+
}
142+
}

statsig_options.go

+78
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package statsig
22

33
import (
44
"net/http"
5+
"reflect"
56
"time"
67
)
78

@@ -37,6 +38,83 @@ func (o *Options) GetSDKEnvironmentTier() string {
3738
return "production"
3839
}
3940

41+
func GetOptionLoggingCopy(options Options) map[string]interface{} {
42+
loggingCopy := make(map[string]interface{})
43+
val := reflect.ValueOf(options)
44+
45+
for i := 0; i < val.NumField(); i++ {
46+
field := val.Type().Field(i)
47+
fieldValue := val.Field(i)
48+
switch fieldValue.Kind() {
49+
case reflect.String:
50+
if fieldValue.String() != "" {
51+
if fieldValue.Len() < 50 {
52+
loggingCopy[field.Name] = fieldValue.String()
53+
} else {
54+
loggingCopy[field.Name] = "set"
55+
}
56+
}
57+
58+
case reflect.Bool:
59+
if fieldValue.Bool() {
60+
loggingCopy[field.Name] = true
61+
}
62+
63+
case reflect.Int, reflect.Int64, reflect.Int32:
64+
if fieldValue.Int() != 0 {
65+
loggingCopy[field.Name] = fieldValue.Int()
66+
}
67+
68+
case reflect.Float64, reflect.Float32:
69+
if fieldValue.Float() != 0 {
70+
loggingCopy[field.Name] = fieldValue.Float()
71+
}
72+
73+
case reflect.Struct:
74+
if fieldValue.Type().Name() == "Duration" {
75+
loggingCopy[field.Name] = fieldValue.Interface().(time.Duration).String()
76+
break
77+
}
78+
if field.Name == "StatsigLoggerOptions" && !fieldValue.IsZero() {
79+
statsigLoggerOptions := map[string]interface{}{
80+
"DisableInitDiagnostics": fieldValue.Interface().(StatsigLoggerOptions).DisableInitDiagnostics,
81+
"DisableSyncDiagnostics": fieldValue.Interface().(StatsigLoggerOptions).DisableSyncDiagnostics,
82+
"DisableApiDiagnostics": fieldValue.Interface().(StatsigLoggerOptions).DisableApiDiagnostics,
83+
"DisableAllLogging": fieldValue.Interface().(StatsigLoggerOptions).DisableAllLogging,
84+
}
85+
loggingCopy[field.Name] = statsigLoggerOptions
86+
break
87+
}
88+
if field.Name == "APIOverrides" && !fieldValue.IsZero() {
89+
APIOptionOverrides := map[string]interface{}{
90+
"DownloadConfigSpecs": fieldValue.Interface().(APIOverrides).DownloadConfigSpecs,
91+
"GetIDLists": fieldValue.Interface().(APIOverrides).GetIDLists,
92+
"LogEvent": fieldValue.Interface().(APIOverrides).LogEvent,
93+
}
94+
loggingCopy[field.Name] = APIOptionOverrides
95+
break
96+
}
97+
if !fieldValue.IsZero() {
98+
loggingCopy[field.Name] = "set"
99+
}
100+
101+
case reflect.Func:
102+
if !fieldValue.IsNil() {
103+
loggingCopy[field.Name] = "set"
104+
}
105+
106+
case reflect.Interface:
107+
if !fieldValue.IsNil() {
108+
loggingCopy[field.Name] = "set"
109+
}
110+
111+
default:
112+
// ignore other fields
113+
}
114+
}
115+
return loggingCopy
116+
}
117+
40118
type APIOverrides struct {
41119
DownloadConfigSpecs string `json:"download_config_specs"`
42120
GetIDLists string `json:"get_id_lists"`

0 commit comments

Comments
 (0)