MM-23568: Add rudder to server diagnostics. (#14151)
* MM-23568: Add rudder to server diagnostics. * Add unit test. * Go mod tidy. * CSP Header fix. * Fix review comments. * Update web/handlers.go Co-Authored-By: Jesse Hallam <jesse.hallam@gmail.com> * Partially address review comments. * fix tests. * Finish implementing review suggestions and then fixing tests. * Fix CSP Header tests. Co-authored-by: Jesse Hallam <jesse.hallam@gmail.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
26310720be
Коммит
6cabc40e62
@@ -8,6 +8,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
rudder "github.com/rudderlabs/analytics-go"
|
||||
"github.com/segmentio/analytics-go"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
@@ -16,7 +17,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SEGMENT_KEY = "placeholder_segment_key"
|
||||
SEGMENT_KEY = "placeholder_segment_key"
|
||||
RUDDER_KEY = "placeholder_rudder_key"
|
||||
RUDDER_DATAPLANE_URL = "placeholder_rudder_dataplane_url"
|
||||
|
||||
TRACK_CONFIG_SERVICE = "config_service"
|
||||
TRACK_CONFIG_TEAM = "config_team"
|
||||
@@ -80,14 +83,37 @@ func (a *App) sendDailyDiagnostics(override bool) {
|
||||
a.trackGroups()
|
||||
a.trackChannelModeration()
|
||||
}
|
||||
|
||||
if *a.Config().LogSettings.EnableDiagnostics && a.IsLeader() && ((!strings.Contains(RUDDER_KEY, "placeholder") && !strings.Contains(RUDDER_DATAPLANE_URL, "placeholder")) || override) {
|
||||
a.Srv().initRudder(RUDDER_DATAPLANE_URL)
|
||||
a.trackActivity()
|
||||
a.trackConfig()
|
||||
a.trackLicense()
|
||||
a.trackPlugins()
|
||||
a.trackServer()
|
||||
a.trackPermissions()
|
||||
a.trackElasticsearch()
|
||||
a.trackGroups()
|
||||
a.trackChannelModeration()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) SendDiagnostic(event string, properties map[string]interface{}) {
|
||||
a.Srv().diagnosticClient.Enqueue(analytics.Track{
|
||||
Event: event,
|
||||
UserId: a.DiagnosticId(),
|
||||
Properties: properties,
|
||||
})
|
||||
if a.Srv().diagnosticClient != nil {
|
||||
a.Srv().diagnosticClient.Enqueue(analytics.Track{
|
||||
Event: event,
|
||||
UserId: a.DiagnosticId(),
|
||||
Properties: properties,
|
||||
})
|
||||
}
|
||||
|
||||
if a.Srv().rudderClient != nil {
|
||||
a.Srv().rudderClient.Enqueue(rudder.Track{
|
||||
Event: event,
|
||||
UserId: a.DiagnosticId(),
|
||||
Properties: properties,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func isDefault(setting interface{}, defaultValue interface{}) bool {
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestPluginVersion(t *testing.T) {
|
||||
assert.Empty(t, pluginVersion(plugins, "unknown.plugin"))
|
||||
}
|
||||
|
||||
func TestDiagnostics(t *testing.T) {
|
||||
func TestSegmentDiagnostics(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
@@ -275,3 +275,210 @@ func TestDiagnostics(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRudderDiagnostics(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := SetupWithCustomConfig(t, func(config *model.Config) {
|
||||
*config.PluginSettings.Enable = false
|
||||
})
|
||||
defer th.TearDown()
|
||||
|
||||
type payload struct {
|
||||
MessageId string
|
||||
SentAt time.Time
|
||||
Batch []struct {
|
||||
MessageId string
|
||||
UserId string
|
||||
Event string
|
||||
Timestamp time.Time
|
||||
Properties map[string]interface{}
|
||||
}
|
||||
Context struct {
|
||||
Library struct {
|
||||
Name string
|
||||
Version string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data := make(chan payload, 100)
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var p payload
|
||||
err = json.Unmarshal(body, &p)
|
||||
require.NoError(t, err)
|
||||
|
||||
data <- p
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
diagnosticID := "test-diagnostic-id-12345"
|
||||
th.App.SetDiagnosticId(diagnosticID)
|
||||
th.Server.initRudder(server.URL)
|
||||
|
||||
assertPayload := func(t *testing.T, actual payload, event string, properties map[string]interface{}) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, actual.MessageId)
|
||||
assert.False(t, actual.SentAt.IsZero())
|
||||
if assert.Len(t, actual.Batch, 1) {
|
||||
assert.NotEmpty(t, actual.Batch[0].MessageId, "message id should not be empty")
|
||||
assert.Equal(t, diagnosticID, actual.Batch[0].UserId)
|
||||
if event != "" {
|
||||
assert.Equal(t, event, actual.Batch[0].Event)
|
||||
}
|
||||
assert.False(t, actual.Batch[0].Timestamp.IsZero(), "batch timestamp should not be the zero value")
|
||||
if properties != nil {
|
||||
assert.Equal(t, properties, actual.Batch[0].Properties)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, "analytics-go", actual.Context.Library.Name)
|
||||
assert.Equal(t, "3.0.0", actual.Context.Library.Version)
|
||||
}
|
||||
|
||||
collectInfo := func(info *[]string) {
|
||||
t.Helper()
|
||||
for {
|
||||
select {
|
||||
case result := <-data:
|
||||
assertPayload(t, result, "", nil)
|
||||
*info = append(*info, result.Batch[0].Event)
|
||||
case <-time.After(time.Second * 1):
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Should send a client identify message
|
||||
select {
|
||||
case identifyMessage := <-data:
|
||||
assertPayload(t, identifyMessage, "", nil)
|
||||
case <-time.After(time.Second * 1):
|
||||
require.Fail(t, "Did not receive ID message")
|
||||
}
|
||||
|
||||
t.Run("Send", func(t *testing.T) {
|
||||
testValue := "test-send-value-6789"
|
||||
th.App.SendDiagnostic("Testing Diagnostic", map[string]interface{}{
|
||||
"hey": testValue,
|
||||
})
|
||||
select {
|
||||
case result := <-data:
|
||||
assertPayload(t, result, "Testing Diagnostic", map[string]interface{}{
|
||||
"hey": testValue,
|
||||
})
|
||||
case <-time.After(time.Second * 1):
|
||||
require.Fail(t, "Did not receive diagnostic")
|
||||
}
|
||||
})
|
||||
|
||||
// Plugins remain disabled at this point
|
||||
t.Run("SendDailyDiagnosticsPluginsDisabled", func(t *testing.T) {
|
||||
th.App.sendDailyDiagnostics(true)
|
||||
|
||||
var info []string
|
||||
// Collect the info sent.
|
||||
collectInfo(&info)
|
||||
|
||||
for _, item := range []string{
|
||||
TRACK_CONFIG_SERVICE,
|
||||
TRACK_CONFIG_TEAM,
|
||||
TRACK_CONFIG_SQL,
|
||||
TRACK_CONFIG_LOG,
|
||||
TRACK_CONFIG_NOTIFICATION_LOG,
|
||||
TRACK_CONFIG_FILE,
|
||||
TRACK_CONFIG_RATE,
|
||||
TRACK_CONFIG_EMAIL,
|
||||
TRACK_CONFIG_PRIVACY,
|
||||
TRACK_CONFIG_OAUTH,
|
||||
TRACK_CONFIG_LDAP,
|
||||
TRACK_CONFIG_COMPLIANCE,
|
||||
TRACK_CONFIG_LOCALIZATION,
|
||||
TRACK_CONFIG_SAML,
|
||||
TRACK_CONFIG_PASSWORD,
|
||||
TRACK_CONFIG_CLUSTER,
|
||||
TRACK_CONFIG_METRICS,
|
||||
TRACK_CONFIG_SUPPORT,
|
||||
TRACK_CONFIG_NATIVEAPP,
|
||||
TRACK_CONFIG_EXPERIMENTAL,
|
||||
TRACK_CONFIG_ANALYTICS,
|
||||
TRACK_CONFIG_PLUGIN,
|
||||
TRACK_ACTIVITY,
|
||||
TRACK_SERVER,
|
||||
TRACK_CONFIG_MESSAGE_EXPORT,
|
||||
// TRACK_PLUGINS,
|
||||
} {
|
||||
require.Contains(t, info, item)
|
||||
}
|
||||
})
|
||||
|
||||
// Enable plugins for the remainder of the tests.
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
|
||||
t.Run("SendDailyDiagnostics", func(t *testing.T) {
|
||||
th.App.sendDailyDiagnostics(true)
|
||||
|
||||
var info []string
|
||||
// Collect the info sent.
|
||||
collectInfo(&info)
|
||||
|
||||
for _, item := range []string{
|
||||
TRACK_CONFIG_SERVICE,
|
||||
TRACK_CONFIG_TEAM,
|
||||
TRACK_CONFIG_SQL,
|
||||
TRACK_CONFIG_LOG,
|
||||
TRACK_CONFIG_NOTIFICATION_LOG,
|
||||
TRACK_CONFIG_FILE,
|
||||
TRACK_CONFIG_RATE,
|
||||
TRACK_CONFIG_EMAIL,
|
||||
TRACK_CONFIG_PRIVACY,
|
||||
TRACK_CONFIG_OAUTH,
|
||||
TRACK_CONFIG_LDAP,
|
||||
TRACK_CONFIG_COMPLIANCE,
|
||||
TRACK_CONFIG_LOCALIZATION,
|
||||
TRACK_CONFIG_SAML,
|
||||
TRACK_CONFIG_PASSWORD,
|
||||
TRACK_CONFIG_CLUSTER,
|
||||
TRACK_CONFIG_METRICS,
|
||||
TRACK_CONFIG_SUPPORT,
|
||||
TRACK_CONFIG_NATIVEAPP,
|
||||
TRACK_CONFIG_EXPERIMENTAL,
|
||||
TRACK_CONFIG_ANALYTICS,
|
||||
TRACK_CONFIG_PLUGIN,
|
||||
TRACK_ACTIVITY,
|
||||
TRACK_SERVER,
|
||||
TRACK_CONFIG_MESSAGE_EXPORT,
|
||||
TRACK_PLUGINS,
|
||||
} {
|
||||
require.Contains(t, info, item)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SendDailyDiagnosticsNoRudderKey", func(t *testing.T) {
|
||||
th.App.SendDailyDiagnostics()
|
||||
|
||||
select {
|
||||
case <-data:
|
||||
require.Fail(t, "Should not send diagnostics when the rudder key is not set")
|
||||
case <-time.After(time.Second * 1):
|
||||
// Did not receive diagnostics
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SendDailyDiagnosticsDisabled", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.LogSettings.EnableDiagnostics = false })
|
||||
|
||||
th.App.sendDailyDiagnostics(true)
|
||||
|
||||
select {
|
||||
case <-data:
|
||||
require.Fail(t, "Should not send diagnostics when they are disabled")
|
||||
case <-time.After(time.Second * 1):
|
||||
// Did not receive diagnostics
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/cors"
|
||||
rudder "github.com/rudderlabs/analytics-go"
|
||||
analytics "github.com/segmentio/analytics-go"
|
||||
"github.com/throttled/throttled"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
@@ -115,6 +116,7 @@ type Server struct {
|
||||
|
||||
diagnosticId string
|
||||
diagnosticClient analytics.Client
|
||||
rudderClient rudder.Client
|
||||
|
||||
phase2PermissionsMigrationComplete bool
|
||||
|
||||
@@ -874,13 +876,47 @@ func (s *Server) initDiagnostics(endpoint string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) initRudder(endpoint string) {
|
||||
if s.rudderClient == nil {
|
||||
config := rudder.Config{}
|
||||
config.Logger = rudder.StdLogger(s.Log.StdLog(mlog.String("source", "rudder")))
|
||||
config.Endpoint = endpoint
|
||||
// For testing
|
||||
if endpoint != RUDDER_DATAPLANE_URL {
|
||||
config.Verbose = true
|
||||
config.BatchSize = 1
|
||||
}
|
||||
client, err := rudder.NewWithConfig(RUDDER_KEY, config)
|
||||
if err != nil {
|
||||
mlog.Error("Failed to create Rudder instance", mlog.Err(err))
|
||||
return
|
||||
}
|
||||
client.Enqueue(rudder.Identify{
|
||||
UserId: s.diagnosticId,
|
||||
})
|
||||
|
||||
s.rudderClient = client
|
||||
}
|
||||
}
|
||||
|
||||
// shutdownDiagnostics closes the diagnostic client.
|
||||
func (s *Server) shutdownDiagnostics() error {
|
||||
var segmentErr, rudderErr error
|
||||
if s.diagnosticClient != nil {
|
||||
return s.diagnosticClient.Close()
|
||||
segmentErr = s.diagnosticClient.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
if s.rudderClient != nil {
|
||||
rudderErr = s.rudderClient.Close()
|
||||
}
|
||||
|
||||
if segmentErr != nil && rudderErr != nil {
|
||||
return errors.New(fmt.Sprintf("%s, %s", segmentErr.Error(), rudderErr.Error()))
|
||||
} else if segmentErr != nil {
|
||||
return segmentErr
|
||||
}
|
||||
|
||||
return rudderErr
|
||||
}
|
||||
|
||||
// GetHubs returns the list of hubs. This method is safe
|
||||
|
||||
3
go.mod
3
go.mod
@@ -72,7 +72,8 @@ require (
|
||||
github.com/prometheus/client_golang v1.4.0
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/rs/cors v1.7.0
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 // indirect
|
||||
github.com/rudderlabs/analytics-go v3.1.0+incompatible
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/segmentio/analytics-go v3.1.0+incompatible
|
||||
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect
|
||||
|
||||
3
go.sum
3
go.sum
@@ -282,7 +282,6 @@ github.com/mattermost/gosaml2 v0.3.2 h1:kq2dY5qUe6fPPHra171GVlgo+ycBsEog0gZMetxL
|
||||
github.com/mattermost/gosaml2 v0.3.2/go.mod h1:Z429EIOiEi9kbq6yHoApfzlcXpa6dzRDc6pO+Vy2Ksk=
|
||||
github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d h1:2DV7VIlEv6J5R5o6tUcb3ZMKJYeeZuWZL7Rv1m23TgQ=
|
||||
github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ=
|
||||
github.com/mattermost/mattermost-server v5.11.1+incompatible h1:LPzKY0+2Tic/ik67qIg6VrydRCgxNXZQXOeaiJ2rMBY=
|
||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o=
|
||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
|
||||
github.com/mattermost/viper v1.0.4 h1:cMYOz4PhguscGSPxrSokUtib5HrG4gCpiUh27wyA3d0=
|
||||
@@ -412,6 +411,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rudderlabs/analytics-go v3.1.0+incompatible h1:raJgBJMxxnCe9CWHL1zJq4rccap4yG7lvk4Fs6B7v+o=
|
||||
github.com/rudderlabs/analytics-go v3.1.0+incompatible/go.mod h1:LF8/ty9kUX4PTY3l5c97K3nZZaX5Hwsvt+NBaRL/f30=
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 h1:J4AOUcOh/t1XbQcJfkEqhzgvMJ2tDxdCVvmHxW5QXao=
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
|
||||
32
vendor/github.com/rudderlabs/analytics-go/.gitignore
сгенерированный
поставляемый
Обычный файл
32
vendor/github.com/rudderlabs/analytics-go/.gitignore
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,32 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# Emacs
|
||||
*~
|
||||
\#*
|
||||
.\#*
|
||||
|
||||
# Artifacts
|
||||
tmp/*
|
||||
6
vendor/github.com/rudderlabs/analytics-go/.gitmodules
сгенерированный
поставляемый
Обычный файл
6
vendor/github.com/rudderlabs/analytics-go/.gitmodules
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,6 @@
|
||||
[submodule "vendor/github.com/segmentio/backo-go"]
|
||||
path = vendor/github.com/segmentio/backo-go
|
||||
url = https://github.com/segmentio/backo-go
|
||||
[submodule "vendor/github.com/xtgo/uuid"]
|
||||
path = vendor/github.com/xtgo/uuid
|
||||
url = https://github.com/xtgo/uuid
|
||||
88
vendor/github.com/rudderlabs/analytics-go/History.md
сгенерированный
поставляемый
Обычный файл
88
vendor/github.com/rudderlabs/analytics-go/History.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,88 @@
|
||||
|
||||
v3.1.0 / 2019-09-20
|
||||
===================
|
||||
|
||||
* add consistent panic error message
|
||||
* Expose the Message interface Validate method
|
||||
* return error if a custom type is enqueued
|
||||
* Handle pointer types in Enqueue()
|
||||
* message: update maxMessageBytes to 32KB
|
||||
|
||||
v3.0.1 / 2018-10-02
|
||||
===================
|
||||
|
||||
* Migrate from Circle V1 format to Circle V2
|
||||
* Adds CLI for sending segment events
|
||||
* Vendor packages back-go and uuid instead of using gitsubmodules
|
||||
|
||||
|
||||
v3.0.0 / 2016-06-02
|
||||
===================
|
||||
|
||||
* 3.0 is a significant rewrite with multiple breaking changes.
|
||||
* [Quickstart](https://segment.com/docs/sources/server/go/quickstart/).
|
||||
* [Documentation](https://segment.com/docs/sources/server/go/).
|
||||
* [GoDocs](https://godoc.org/gopkg.in/segmentio/analytics-go.v3).
|
||||
* [What's New in v3](https://segment.com/docs/sources/server/go/#what-s-new-in-v3).
|
||||
|
||||
|
||||
v2.1.0 / 2015-12-28
|
||||
===================
|
||||
|
||||
* Add ability to set custom timestamps for messages.
|
||||
* Add ability to set a custom `net/http` client.
|
||||
* Add ability to set a custom logger.
|
||||
* Fix edge case when client would try to upload no messages.
|
||||
* Properly upload in-flight messages when client is asked to shutdown.
|
||||
* Add ability to set `.integrations` field on messages.
|
||||
* Fix resource leak with interval ticker after shutdown.
|
||||
* Add retries and back-off when uploading messages.
|
||||
* Add ability to set custom flush interval.
|
||||
|
||||
v2.0.0 / 2015-02-03
|
||||
===================
|
||||
|
||||
* rewrite with breaking API changes
|
||||
|
||||
v1.2.0 / 2014-09-03
|
||||
==================
|
||||
|
||||
* add public .Flush() method
|
||||
* rename .Stop() to .Close()
|
||||
|
||||
v1.1.0 / 2014-09-02
|
||||
==================
|
||||
|
||||
* add client.Stop() to flash/wait. Closes #7
|
||||
|
||||
v1.0.0 / 2014-08-26
|
||||
==================
|
||||
|
||||
* fix response close
|
||||
* change comments to be more go-like
|
||||
* change uuid libraries
|
||||
|
||||
0.1.2 / 2014-06-11
|
||||
==================
|
||||
|
||||
* add runnable example
|
||||
* fix: close body
|
||||
|
||||
0.1.1 / 2014-05-31
|
||||
==================
|
||||
|
||||
* refactor locking
|
||||
|
||||
0.1.0 / 2014-05-22
|
||||
==================
|
||||
|
||||
* replace Debug option with debug package
|
||||
|
||||
0.0.2 / 2014-05-20
|
||||
==================
|
||||
|
||||
* add .Start()
|
||||
* add mutexes
|
||||
* rename BufferSize to FlushAt and FlushInterval to FlushAfter
|
||||
* lower FlushInterval to 5 seconds
|
||||
* lower BufferSize to 20 to match other clients
|
||||
21
vendor/github.com/rudderlabs/analytics-go/License.md
сгенерированный
поставляемый
Обычный файл
21
vendor/github.com/rudderlabs/analytics-go/License.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Segment, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
31
vendor/github.com/rudderlabs/analytics-go/Makefile
сгенерированный
поставляемый
Обычный файл
31
vendor/github.com/rudderlabs/analytics-go/Makefile
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,31 @@
|
||||
ifndef CIRCLE_ARTIFACTS
|
||||
CIRCLE_ARTIFACTS=tmp
|
||||
endif
|
||||
|
||||
bootstrap:
|
||||
.buildscript/bootstrap.sh
|
||||
|
||||
dependencies:
|
||||
@go get -v -t ./...
|
||||
|
||||
vet:
|
||||
@go vet ./...
|
||||
|
||||
test: vet
|
||||
@mkdir -p ${CIRCLE_ARTIFACTS}
|
||||
@go test -race -coverprofile=${CIRCLE_ARTIFACTS}/cover.out .
|
||||
@go tool cover -func ${CIRCLE_ARTIFACTS}/cover.out -o ${CIRCLE_ARTIFACTS}/cover.txt
|
||||
@go tool cover -html ${CIRCLE_ARTIFACTS}/cover.out -o ${CIRCLE_ARTIFACTS}/cover.html
|
||||
|
||||
build: test
|
||||
@go build ./...
|
||||
|
||||
e2e:
|
||||
@if [ "$(RUN_E2E_TESTS)" != "true" ]; then \
|
||||
echo "Skipping end to end tests."; else \
|
||||
go get github.com/segmentio/library-e2e-tester/cmd/tester; \
|
||||
tester -segment-write-key=$(SEGMENT_WRITE_KEY) -webhook-auth-username=$(WEBHOOK_AUTH_USERNAME) -webhook-bucket=$(WEBHOOK_BUCKET) -path='cli' -concurrency=2 -skip='advance|alias'; fi
|
||||
|
||||
ci: dependencies test e2e
|
||||
|
||||
.PHONY: bootstrap dependencies vet test e2e ci
|
||||
55
vendor/github.com/rudderlabs/analytics-go/Readme.md
сгенерированный
поставляемый
Обычный файл
55
vendor/github.com/rudderlabs/analytics-go/Readme.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,55 @@
|
||||
# analytics-go [](https://circleci.com/gh/segmentio/analytics-go/tree/master) [](https://godoc.org/github.com/segmentio/analytics-go)
|
||||
|
||||
Segment analytics client for Go.
|
||||
|
||||
## Installation
|
||||
|
||||
The package can be simply installed via go get, we recommend that you use a
|
||||
package version management system like the Go vendor directory or a tool like
|
||||
Godep to avoid issues related to API breaking changes introduced between major
|
||||
versions of the library.
|
||||
|
||||
To install it in the GOPATH:
|
||||
```
|
||||
go get https://github.com/segmentio/analytics-go
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
The links bellow should provide all the documentation needed to make the best
|
||||
use of the library and the Segment API:
|
||||
|
||||
- [Documentation](https://segment.com/docs/libraries/go/)
|
||||
- [godoc](https://godoc.org/gopkg.in/segmentio/analytics-go.v3)
|
||||
- [API](https://segment.com/docs/libraries/http/)
|
||||
- [Specs](https://segment.com/docs/spec/)
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/segmentio/analytics-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Instantiates a client to use send messages to the segment API.
|
||||
client := analytics.New(os.Getenv("SEGMENT_WRITE_KEY"))
|
||||
|
||||
// Enqueues a track event that will be sent asynchronously.
|
||||
client.Enqueue(analytics.Track{
|
||||
UserId: "test-user",
|
||||
Event: "test-snippet",
|
||||
})
|
||||
|
||||
// Flushes any queued messages and closes the client.
|
||||
client.Close()
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
The library is released under the [MIT license](License.md).
|
||||
44
vendor/github.com/rudderlabs/analytics-go/alias.go
сгенерированный
поставляемый
Обычный файл
44
vendor/github.com/rudderlabs/analytics-go/alias.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,44 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Alias)(nil)
|
||||
|
||||
// This type represents object sent in a alias call as described in
|
||||
// https://segment.com/docs/libraries/http/#alias
|
||||
type Alias struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
PreviousId string `json:"previousId"`
|
||||
UserId string `json:"userId"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Alias) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Alias) Validate() error {
|
||||
if len(msg.UserId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Alias",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
if len(msg.PreviousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Alias",
|
||||
Name: "PreviousId",
|
||||
Value: msg.PreviousId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
431
vendor/github.com/rudderlabs/analytics-go/analytics.go
сгенерированный
поставляемый
Обычный файл
431
vendor/github.com/rudderlabs/analytics-go/analytics.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,431 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Version of the client.
|
||||
const Version = "3.0.0"
|
||||
const unimplementedError = "not implemented"
|
||||
|
||||
// This interface is the main API exposed by the analytics package.
|
||||
// Values that satsify this interface are returned by the client constructors
|
||||
// provided by the package and provide a way to send messages via the HTTP API.
|
||||
type Client interface {
|
||||
io.Closer
|
||||
|
||||
// Queues a message to be sent by the client when the conditions for a batch
|
||||
// upload are met.
|
||||
// This is the main method you'll be using, a typical flow would look like
|
||||
// this:
|
||||
//
|
||||
// client := analytics.New(writeKey)
|
||||
// ...
|
||||
// client.Enqueue(analytics.Track{ ... })
|
||||
// ...
|
||||
// client.Close()
|
||||
//
|
||||
// The method returns an error if the message queue not be queued, which
|
||||
// happens if the client was already closed at the time the method was
|
||||
// called or if the message was malformed.
|
||||
Enqueue(Message) error
|
||||
}
|
||||
|
||||
type client struct {
|
||||
Config
|
||||
key string
|
||||
|
||||
// This channel is where the `Enqueue` method writes messages so they can be
|
||||
// picked up and pushed by the backend goroutine taking care of applying the
|
||||
// batching rules.
|
||||
msgs chan Message
|
||||
|
||||
// These two channels are used to synchronize the client shutting down when
|
||||
// `Close` is called.
|
||||
// The first channel is closed to signal the backend goroutine that it has
|
||||
// to stop, then the second one is closed by the backend goroutine to signal
|
||||
// that it has finished flushing all queued messages.
|
||||
quit chan struct{}
|
||||
shutdown chan struct{}
|
||||
|
||||
// This HTTP client is used to send requests to the backend, it uses the
|
||||
// HTTP transport provided in the configuration.
|
||||
http http.Client
|
||||
}
|
||||
|
||||
// Instantiate a new client that uses the write key passed as first argument to
|
||||
// send messages to the backend.
|
||||
// The client is created with the default configuration.
|
||||
func New(writeKey string) Client {
|
||||
// Here we can ignore the error because the default config is always valid.
|
||||
c, _ := NewWithConfig(writeKey, Config{})
|
||||
return c
|
||||
}
|
||||
|
||||
// Instantiate a new client that uses the write key and configuration passed as
|
||||
// arguments to send messages to the backend.
|
||||
// The function will return an error if the configuration contained impossible
|
||||
// values (like a negative flush interval for example).
|
||||
// When the function returns an error the returned client will always be nil.
|
||||
func NewWithConfig(writeKey string, config Config) (cli Client, err error) {
|
||||
if err = config.validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c := &client{
|
||||
Config: makeConfig(config),
|
||||
key: writeKey,
|
||||
msgs: make(chan Message, 100),
|
||||
quit: make(chan struct{}),
|
||||
shutdown: make(chan struct{}),
|
||||
http: makeHttpClient(config.Transport),
|
||||
}
|
||||
|
||||
go c.loop()
|
||||
|
||||
cli = c
|
||||
return
|
||||
}
|
||||
|
||||
func makeHttpClient(transport http.RoundTripper) http.Client {
|
||||
httpClient := http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
if supportsTimeout(transport) {
|
||||
httpClient.Timeout = 10 * time.Second
|
||||
}
|
||||
return httpClient
|
||||
}
|
||||
|
||||
func dereferenceMessage(msg Message) Message {
|
||||
switch m := msg.(type) {
|
||||
case *Alias:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Group:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Identify:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Page:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Screen:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Track:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (c *client) Enqueue(msg Message) (err error) {
|
||||
msg = dereferenceMessage(msg)
|
||||
if err = msg.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var id = c.uid()
|
||||
var ts = c.now()
|
||||
|
||||
switch m := msg.(type) {
|
||||
case Alias:
|
||||
m.Type = "alias"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Group:
|
||||
m.Type = "group"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Identify:
|
||||
m.Type = "identify"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Page:
|
||||
m.Type = "page"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Screen:
|
||||
m.Type = "screen"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Track:
|
||||
m.Type = "track"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("messages with custom types cannot be enqueued: %T", msg)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// When the `msgs` channel is closed writing to it will trigger a panic.
|
||||
// To avoid letting the panic propagate to the caller we recover from it
|
||||
// and instead report that the client has been closed and shouldn't be
|
||||
// used anymore.
|
||||
if recover() != nil {
|
||||
err = ErrClosed
|
||||
}
|
||||
}()
|
||||
|
||||
c.msgs <- msg
|
||||
return
|
||||
}
|
||||
|
||||
// Close and flush metrics.
|
||||
func (c *client) Close() (err error) {
|
||||
defer func() {
|
||||
// Always recover, a panic could be raised if `c`.quit was closed which
|
||||
// means the method was called more than once.
|
||||
if recover() != nil {
|
||||
err = ErrClosed
|
||||
}
|
||||
}()
|
||||
close(c.quit)
|
||||
<-c.shutdown
|
||||
return
|
||||
}
|
||||
|
||||
// Asychronously send a batched requests.
|
||||
func (c *client) sendAsync(msgs []message, wg *sync.WaitGroup, ex *executor) {
|
||||
wg.Add(1)
|
||||
|
||||
if !ex.do(func() {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
// In case a bug is introduced in the send function that triggers
|
||||
// a panic, we don't want this to ever crash the application so we
|
||||
// catch it here and log it instead.
|
||||
if err := recover(); err != nil {
|
||||
c.errorf("panic - %s", err)
|
||||
}
|
||||
}()
|
||||
c.send(msgs)
|
||||
}) {
|
||||
wg.Done()
|
||||
c.errorf("sending messages failed - %s", ErrTooManyRequests)
|
||||
c.notifyFailure(msgs, ErrTooManyRequests)
|
||||
}
|
||||
}
|
||||
|
||||
// Send batch request.
|
||||
func (c *client) send(msgs []message) {
|
||||
const attempts = 10
|
||||
|
||||
b, err := json.Marshal(batch{
|
||||
MessageId: c.uid(),
|
||||
SentAt: c.now(),
|
||||
Messages: msgs,
|
||||
Context: c.DefaultContext,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.errorf("marshalling messages - %s", err)
|
||||
c.notifyFailure(msgs, err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i != attempts; i++ {
|
||||
if err = c.upload(b); err == nil {
|
||||
c.notifySuccess(msgs)
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for either a retry timeout or the client to be closed.
|
||||
select {
|
||||
case <-time.After(c.RetryAfter(i)):
|
||||
case <-c.quit:
|
||||
c.errorf("%d messages dropped because they failed to be sent and the client was closed", len(msgs))
|
||||
c.notifyFailure(msgs, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.errorf("%d messages dropped because they failed to be sent after %d attempts", len(msgs), attempts)
|
||||
c.notifyFailure(msgs, err)
|
||||
}
|
||||
|
||||
// Upload serialized batch message.
|
||||
func (c *client) upload(b []byte) error {
|
||||
url := c.Endpoint + "/v1/batch"
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
c.errorf("creating request - %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Add("User-Agent", "analytics-go (version: "+Version+")")
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Content-Length", string(len(b)))
|
||||
req.SetBasicAuth(c.key, "")
|
||||
|
||||
res, err := c.http.Do(req)
|
||||
|
||||
if err != nil {
|
||||
c.errorf("sending request - %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
return c.report(res)
|
||||
}
|
||||
|
||||
// Report on response body.
|
||||
func (c *client) report(res *http.Response) (err error) {
|
||||
var body []byte
|
||||
|
||||
if res.StatusCode < 300 {
|
||||
c.debugf("response %s", res.Status)
|
||||
return
|
||||
}
|
||||
|
||||
if body, err = ioutil.ReadAll(res.Body); err != nil {
|
||||
c.errorf("response %d %s - %s", res.StatusCode, res.Status, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.logf("response %d %s – %s", res.StatusCode, res.Status, string(body))
|
||||
return fmt.Errorf("%d %s", res.StatusCode, res.Status)
|
||||
}
|
||||
|
||||
// Batch loop.
|
||||
func (c *client) loop() {
|
||||
defer close(c.shutdown)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait()
|
||||
|
||||
tick := time.NewTicker(c.Interval)
|
||||
defer tick.Stop()
|
||||
|
||||
ex := newExecutor(c.maxConcurrentRequests)
|
||||
defer ex.close()
|
||||
|
||||
mq := messageQueue{
|
||||
maxBatchSize: c.BatchSize,
|
||||
maxBatchBytes: c.maxBatchBytes(),
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-c.msgs:
|
||||
c.push(&mq, msg, wg, ex)
|
||||
|
||||
case <-tick.C:
|
||||
c.flush(&mq, wg, ex)
|
||||
|
||||
case <-c.quit:
|
||||
c.debugf("exit requested – draining messages")
|
||||
|
||||
// Drain the msg channel, we have to close it first so no more
|
||||
// messages can be pushed and otherwise the loop would never end.
|
||||
close(c.msgs)
|
||||
for msg := range c.msgs {
|
||||
c.push(&mq, msg, wg, ex)
|
||||
}
|
||||
|
||||
c.flush(&mq, wg, ex)
|
||||
c.debugf("exit")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) push(q *messageQueue, m Message, wg *sync.WaitGroup, ex *executor) {
|
||||
var msg message
|
||||
var err error
|
||||
|
||||
if msg, err = makeMessage(m, maxMessageBytes); err != nil {
|
||||
c.errorf("%s - %v", err, m)
|
||||
c.notifyFailure([]message{{m, nil}}, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.debugf("buffer (%d/%d) %v", len(q.pending), c.BatchSize, m)
|
||||
|
||||
if msgs := q.push(msg); msgs != nil {
|
||||
c.debugf("exceeded messages batch limit with batch of %d messages – flushing", len(msgs))
|
||||
c.sendAsync(msgs, wg, ex)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) flush(q *messageQueue, wg *sync.WaitGroup, ex *executor) {
|
||||
if msgs := q.flush(); msgs != nil {
|
||||
c.debugf("flushing %d messages", len(msgs))
|
||||
c.sendAsync(msgs, wg, ex)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) debugf(format string, args ...interface{}) {
|
||||
if c.Verbose {
|
||||
c.logf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) logf(format string, args ...interface{}) {
|
||||
c.Logger.Logf(format, args...)
|
||||
}
|
||||
|
||||
func (c *client) errorf(format string, args ...interface{}) {
|
||||
c.Logger.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (c *client) maxBatchBytes() int {
|
||||
b, _ := json.Marshal(batch{
|
||||
MessageId: c.uid(),
|
||||
SentAt: c.now(),
|
||||
Context: c.DefaultContext,
|
||||
})
|
||||
return maxBatchBytes - len(b)
|
||||
}
|
||||
|
||||
func (c *client) notifySuccess(msgs []message) {
|
||||
if c.Callback != nil {
|
||||
for _, m := range msgs {
|
||||
c.Callback.Success(m.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) notifyFailure(msgs []message, err error) {
|
||||
if c.Callback != nil {
|
||||
for _, m := range msgs {
|
||||
c.Callback.Failure(m.msg, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
173
vendor/github.com/rudderlabs/analytics-go/config.go
сгенерированный
поставляемый
Обычный файл
173
vendor/github.com/rudderlabs/analytics-go/config.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,173 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/segmentio/backo-go"
|
||||
"github.com/xtgo/uuid"
|
||||
)
|
||||
|
||||
// Instances of this type carry the different configuration options that may
|
||||
// be set when instantiating a client.
|
||||
//
|
||||
// Each field's zero-value is either meaningful or interpreted as using the
|
||||
// default value defined by the library.
|
||||
type Config struct {
|
||||
|
||||
// The endpoint to which the client connect and send their messages, set to
|
||||
// `DefaultEndpoint` by default.
|
||||
Endpoint string
|
||||
|
||||
// The flushing interval of the client. Messages will be sent when they've
|
||||
// been queued up to the maximum batch size or when the flushing interval
|
||||
// timer triggers.
|
||||
Interval time.Duration
|
||||
|
||||
// The HTTP transport used by the client, this allows an application to
|
||||
// redefine how requests are being sent at the HTTP level (for example,
|
||||
// to change the connection pooling policy).
|
||||
// If none is specified the client uses `http.DefaultTransport`.
|
||||
Transport http.RoundTripper
|
||||
|
||||
// The logger used by the client to output info or error messages when that
|
||||
// are generated by background operations.
|
||||
// If none is specified the client uses a standard logger that outputs to
|
||||
// `os.Stderr`.
|
||||
Logger Logger
|
||||
|
||||
// The callback object that will be used by the client to notify the
|
||||
// application when messages sends to the backend API succeeded or failed.
|
||||
Callback Callback
|
||||
|
||||
// The maximum number of messages that will be sent in one API call.
|
||||
// Messages will be sent when they've been queued up to the maximum batch
|
||||
// size or when the flushing interval timer triggers.
|
||||
// Note that the API will still enforce a 500KB limit on each HTTP request
|
||||
// which is independent from the number of embedded messages.
|
||||
BatchSize int
|
||||
|
||||
// When set to true the client will send more frequent and detailed messages
|
||||
// to its logger.
|
||||
Verbose bool
|
||||
|
||||
// The default context set on each message sent by the client.
|
||||
DefaultContext *Context
|
||||
|
||||
// The retry policy used by the client to resend requests that have failed.
|
||||
// The function is called with how many times the operation has been retried
|
||||
// and is expected to return how long the client should wait before trying
|
||||
// again.
|
||||
// If not set the client will fallback to use a default retry policy.
|
||||
RetryAfter func(int) time.Duration
|
||||
|
||||
// A function called by the client to generate unique message identifiers.
|
||||
// The client uses a UUID generator if none is provided.
|
||||
// This field is not exported and only exposed internally to let unit tests
|
||||
// mock the id generation.
|
||||
uid func() string
|
||||
|
||||
// A function called by the client to get the current time, `time.Now` is
|
||||
// used by default.
|
||||
// This field is not exported and only exposed internally to let unit tests
|
||||
// mock the current time.
|
||||
now func() time.Time
|
||||
|
||||
// The maximum number of goroutines that will be spawned by a client to send
|
||||
// requests to the backend API.
|
||||
// This field is not exported and only exposed internally to let unit tests
|
||||
// mock the current time.
|
||||
maxConcurrentRequests int
|
||||
}
|
||||
|
||||
// This constant sets the default endpoint to which client instances send
|
||||
// messages if none was explictly set.
|
||||
const DefaultEndpoint = "https://api.segment.io"
|
||||
|
||||
// This constant sets the default flush interval used by client instances if
|
||||
// none was explicitly set.
|
||||
const DefaultInterval = 5 * time.Second
|
||||
|
||||
// This constant sets the default batch size used by client instances if none
|
||||
// was explicitly set.
|
||||
const DefaultBatchSize = 250
|
||||
|
||||
// Verifies that fields that don't have zero-values are set to valid values,
|
||||
// returns an error describing the problem if a field was invalid.
|
||||
func (c *Config) validate() error {
|
||||
if c.Interval < 0 {
|
||||
return ConfigError{
|
||||
Reason: "negative time intervals are not supported",
|
||||
Field: "Interval",
|
||||
Value: c.Interval,
|
||||
}
|
||||
}
|
||||
|
||||
if c.BatchSize < 0 {
|
||||
return ConfigError{
|
||||
Reason: "negative batch sizes are not supported",
|
||||
Field: "BatchSize",
|
||||
Value: c.BatchSize,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Given a config object as argument the function will set all zero-values to
|
||||
// their defaults and return the modified object.
|
||||
func makeConfig(c Config) Config {
|
||||
if len(c.Endpoint) == 0 {
|
||||
c.Endpoint = DefaultEndpoint
|
||||
}
|
||||
|
||||
if c.Interval == 0 {
|
||||
c.Interval = DefaultInterval
|
||||
}
|
||||
|
||||
if c.Transport == nil {
|
||||
c.Transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
if c.Logger == nil {
|
||||
c.Logger = newDefaultLogger()
|
||||
}
|
||||
|
||||
if c.BatchSize == 0 {
|
||||
c.BatchSize = DefaultBatchSize
|
||||
}
|
||||
|
||||
if c.DefaultContext == nil {
|
||||
c.DefaultContext = &Context{}
|
||||
}
|
||||
|
||||
if c.RetryAfter == nil {
|
||||
c.RetryAfter = backo.DefaultBacko().Duration
|
||||
}
|
||||
|
||||
if c.uid == nil {
|
||||
c.uid = uid
|
||||
}
|
||||
|
||||
if c.now == nil {
|
||||
c.now = time.Now
|
||||
}
|
||||
|
||||
if c.maxConcurrentRequests == 0 {
|
||||
c.maxConcurrentRequests = 1000
|
||||
}
|
||||
|
||||
// We always overwrite the 'library' field of the default context set on the
|
||||
// client because we want this information to be accurate.
|
||||
c.DefaultContext.Library = LibraryInfo{
|
||||
Name: "analytics-go",
|
||||
Version: Version,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// This function returns a string representation of a UUID, it's the default
|
||||
// function used for generating unique IDs.
|
||||
func uid() string {
|
||||
return uuid.NewRandom().String()
|
||||
}
|
||||
148
vendor/github.com/rudderlabs/analytics-go/context.go
сгенерированный
поставляемый
Обычный файл
148
vendor/github.com/rudderlabs/analytics-go/context.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,148 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// This type provides the representation of the `context` object as defined in
|
||||
// https://segment.com/docs/spec/common/#context
|
||||
type Context struct {
|
||||
App AppInfo `json:"app,omitempty"`
|
||||
Campaign CampaignInfo `json:"campaign,omitempty"`
|
||||
Device DeviceInfo `json:"device,omitempty"`
|
||||
Library LibraryInfo `json:"library,omitempty"`
|
||||
Location LocationInfo `json:"location,omitempty"`
|
||||
Network NetworkInfo `json:"network,omitempty"`
|
||||
OS OSInfo `json:"os,omitempty"`
|
||||
Page PageInfo `json:"page,omitempty"`
|
||||
Referrer ReferrerInfo `json:"referrer,omitempty"`
|
||||
Screen ScreenInfo `json:"screen,omitempty"`
|
||||
IP net.IP `json:"ip,omitempty"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
UserAgent string `json:"userAgent,omitempty"`
|
||||
Traits Traits `json:"traits,omitempty"`
|
||||
|
||||
// This map is used to allow extensions to the context specifications that
|
||||
// may not be documented or could be introduced in the future.
|
||||
// The fields of this map are inlined in the serialized context object,
|
||||
// there is no actual "extra" field in the JSON representation.
|
||||
Extra map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.app` object as defined
|
||||
// in https://segment.com/docs/spec/common/#context
|
||||
type AppInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Build string `json:"build,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.campaign` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type CampaignInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Medium string `json:"medium,omitempty"`
|
||||
Term string `json:"term,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.device` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type DeviceInfo struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
AdvertisingID string `json:"advertisingId,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.library` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type LibraryInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.location` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type LocationInfo struct {
|
||||
City string `json:"city,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
Latitude float64 `json:"latitude,omitempty"`
|
||||
Longitude float64 `json:"longitude,omitempty"`
|
||||
Speed float64 `json:"speed,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.network` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type NetworkInfo struct {
|
||||
Bluetooth bool `json:"bluetooth,omitempty"`
|
||||
Cellular bool `json:"cellular,omitempty"`
|
||||
WIFI bool `json:"wifi,omitempty"`
|
||||
Carrier string `json:"carrier,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.os` object as defined
|
||||
// in https://segment.com/docs/spec/common/#context
|
||||
type OSInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.page` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type PageInfo struct {
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Referrer string `json:"referrer,omitempty"`
|
||||
Search string `json:"search,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.referrer` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type ReferrerInfo struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.screen` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type ScreenInfo struct {
|
||||
Density int `json:"density,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
}
|
||||
|
||||
// Satisfy the `json.Marshaler` interface. We have to flatten out the `Extra`
|
||||
// field but the standard json package doesn't support it yet.
|
||||
// Implementing this interface allows us to override the default marshaling of
|
||||
// the context object and to the inlining ourselves.
|
||||
//
|
||||
// Related discussion: https://github.com/golang/go/issues/6213
|
||||
func (ctx Context) MarshalJSON() ([]byte, error) {
|
||||
v := reflect.ValueOf(ctx)
|
||||
n := v.NumField()
|
||||
m := make(map[string]interface{}, n+len(ctx.Extra))
|
||||
|
||||
// Copy the `Extra` map into the map representation of the context, it is
|
||||
// important to do this operation before going through the actual struct
|
||||
// fields so the latter take precendence and override duplicated values
|
||||
// that would be set in the extensions.
|
||||
for name, value := range ctx.Extra {
|
||||
m[name] = value
|
||||
}
|
||||
|
||||
return json.Marshal(structToMap(v, m))
|
||||
}
|
||||
60
vendor/github.com/rudderlabs/analytics-go/error.go
сгенерированный
поставляемый
Обычный файл
60
vendor/github.com/rudderlabs/analytics-go/error.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,60 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Returned by the `NewWithConfig` function when the one of the configuration
|
||||
// fields was set to an impossible value (like a negative duration).
|
||||
type ConfigError struct {
|
||||
|
||||
// A human-readable message explaining why the configuration field's value
|
||||
// is invalid.
|
||||
Reason string
|
||||
|
||||
// The name of the configuration field that was carrying an invalid value.
|
||||
Field string
|
||||
|
||||
// The value of the configuration field that caused the error.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (e ConfigError) Error() string {
|
||||
return fmt.Sprintf("analytics.NewWithConfig: %s (analytics.Config.%s: %#v)", e.Reason, e.Field, e.Value)
|
||||
}
|
||||
|
||||
// Instances of this type are used to represent errors returned when a field was
|
||||
// no initialize properly in a structure passed as argument to one of the
|
||||
// functions of this package.
|
||||
type FieldError struct {
|
||||
|
||||
// The human-readable representation of the type of structure that wasn't
|
||||
// initialized properly.
|
||||
Type string
|
||||
|
||||
// The name of the field that wasn't properly initialized.
|
||||
Name string
|
||||
|
||||
// The value of the field that wasn't properly initialized.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (e FieldError) Error() string {
|
||||
return fmt.Sprintf("%s.%s: invalid field value: %#v", e.Type, e.Name, e.Value)
|
||||
}
|
||||
|
||||
var (
|
||||
// This error is returned by methods of the `Client` interface when they are
|
||||
// called after the client was already closed.
|
||||
ErrClosed = errors.New("the client was already closed")
|
||||
|
||||
// This error is used to notify the application that too many requests are
|
||||
// already being sent and no more messages can be accepted.
|
||||
ErrTooManyRequests = errors.New("too many requests are already in-flight")
|
||||
|
||||
// This error is used to notify the client callbacks that a message send
|
||||
// failed because the JSON representation of a message exceeded the upper
|
||||
// limit.
|
||||
ErrMessageTooBig = errors.New("the message exceeds the maximum allowed size")
|
||||
)
|
||||
53
vendor/github.com/rudderlabs/analytics-go/executor.go
сгенерированный
поставляемый
Обычный файл
53
vendor/github.com/rudderlabs/analytics-go/executor.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,53 @@
|
||||
package analytics
|
||||
|
||||
import "sync"
|
||||
|
||||
type executor struct {
|
||||
queue chan func()
|
||||
mutex sync.Mutex
|
||||
size int
|
||||
cap int
|
||||
}
|
||||
|
||||
func newExecutor(cap int) *executor {
|
||||
e := &executor{
|
||||
queue: make(chan func(), 1),
|
||||
cap: cap,
|
||||
}
|
||||
go e.loop()
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *executor) do(task func()) (ok bool) {
|
||||
e.mutex.Lock()
|
||||
|
||||
if e.size != e.cap {
|
||||
e.queue <- task
|
||||
e.size++
|
||||
ok = true
|
||||
}
|
||||
|
||||
e.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (e *executor) close() {
|
||||
close(e.queue)
|
||||
}
|
||||
|
||||
func (e *executor) loop() {
|
||||
for task := range e.queue {
|
||||
go e.run(task)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) run(task func()) {
|
||||
defer e.done()
|
||||
task()
|
||||
}
|
||||
|
||||
func (e *executor) done() {
|
||||
e.mutex.Lock()
|
||||
e.size--
|
||||
e.mutex.Unlock()
|
||||
}
|
||||
46
vendor/github.com/rudderlabs/analytics-go/group.go
сгенерированный
поставляемый
Обычный файл
46
vendor/github.com/rudderlabs/analytics-go/group.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,46 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Group)(nil)
|
||||
|
||||
// This type represents object sent in a group call as described in
|
||||
// https://segment.com/docs/libraries/http/#group
|
||||
type Group struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
GroupId string `json:"groupId"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Traits Traits `json:"traits,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Group) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Group) Validate() error {
|
||||
if len(msg.GroupId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Group",
|
||||
Name: "GroupId",
|
||||
Value: msg.GroupId,
|
||||
}
|
||||
}
|
||||
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Group",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
37
vendor/github.com/rudderlabs/analytics-go/identify.go
сгенерированный
поставляемый
Обычный файл
37
vendor/github.com/rudderlabs/analytics-go/identify.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,37 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Identify)(nil)
|
||||
|
||||
// This type represents object sent in an identify call as described in
|
||||
// https://segment.com/docs/libraries/http/#identify
|
||||
type Identify struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Traits Traits `json:"traits,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Identify) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Identify) Validate() error {
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Identify",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
44
vendor/github.com/rudderlabs/analytics-go/integrations.go
сгенерированный
поставляемый
Обычный файл
44
vendor/github.com/rudderlabs/analytics-go/integrations.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,44 @@
|
||||
package analytics
|
||||
|
||||
// This type is used to represent integrations in messages that support it.
|
||||
// It is a free-form where values are most often booleans that enable or
|
||||
// disable integrations.
|
||||
// Here's a quick example of how this type is meant to be used:
|
||||
//
|
||||
// analytics.Track{
|
||||
// UserId: "0123456789",
|
||||
// Integrations: analytics.NewIntegrations()
|
||||
// .EnableAll()
|
||||
// .Disable("Salesforce")
|
||||
// .Disable("Marketo"),
|
||||
// }
|
||||
//
|
||||
// The specifications can be found at https://segment.com/docs/spec/common/#integrations
|
||||
type Integrations map[string]interface{}
|
||||
|
||||
func NewIntegrations() Integrations {
|
||||
return make(Integrations, 10)
|
||||
}
|
||||
|
||||
func (i Integrations) EnableAll() Integrations {
|
||||
return i.Enable("all")
|
||||
}
|
||||
|
||||
func (i Integrations) DisableAll() Integrations {
|
||||
return i.Disable("all")
|
||||
}
|
||||
|
||||
func (i Integrations) Enable(name string) Integrations {
|
||||
return i.Set(name, true)
|
||||
}
|
||||
|
||||
func (i Integrations) Disable(name string) Integrations {
|
||||
return i.Set(name, false)
|
||||
}
|
||||
|
||||
// Sets an integration named by the first argument to the specified value, any
|
||||
// value other than `false` will be interpreted as enabling the integration.
|
||||
func (i Integrations) Set(name string, value interface{}) Integrations {
|
||||
i[name] = value
|
||||
return i
|
||||
}
|
||||
87
vendor/github.com/rudderlabs/analytics-go/json.go
сгенерированный
поставляемый
Обычный файл
87
vendor/github.com/rudderlabs/analytics-go/json.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,87 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Imitate what what the JSON package would do when serializing a struct value,
|
||||
// the only difference is we we don't serialize zero-value struct fields as well.
|
||||
// Note that this function doesn't recursively convert structures to maps, only
|
||||
// the value passed as argument is transformed.
|
||||
func structToMap(v reflect.Value, m map[string]interface{}) map[string]interface{} {
|
||||
t := v.Type()
|
||||
n := t.NumField()
|
||||
|
||||
if m == nil {
|
||||
m = make(map[string]interface{}, n)
|
||||
}
|
||||
|
||||
for i := 0; i != n; i++ {
|
||||
field := t.Field(i)
|
||||
value := v.Field(i)
|
||||
name, omitempty := parseJsonTag(field.Tag.Get("json"), field.Name)
|
||||
|
||||
if name != "-" && !(omitempty && isZeroValue(value)) {
|
||||
m[name] = value.Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Parses a JSON tag the way the json package would do it, returing the expected
|
||||
// name of the field once serialized and if empty values should be omitted.
|
||||
func parseJsonTag(tag string, defName string) (name string, omitempty bool) {
|
||||
args := strings.Split(tag, ",")
|
||||
|
||||
if len(args) == 0 || len(args[0]) == 0 {
|
||||
name = defName
|
||||
} else {
|
||||
name = args[0]
|
||||
}
|
||||
|
||||
if len(args) > 1 && args[1] == "omitempty" {
|
||||
omitempty = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Checks if the value given as argument is a zero-value, it is based on the
|
||||
// isEmptyValue function in https://golang.org/src/encoding/json/encode.go
|
||||
// but also checks struct types recursively.
|
||||
func isZeroValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
|
||||
case reflect.Struct:
|
||||
for i, n := 0, v.NumField(); i != n; i++ {
|
||||
if !isZeroValue(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
47
vendor/github.com/rudderlabs/analytics-go/logger.go
сгенерированный
поставляемый
Обычный файл
47
vendor/github.com/rudderlabs/analytics-go/logger.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,47 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Instances of types implementing this interface can be used to define where
|
||||
// the analytics client logs are written.
|
||||
type Logger interface {
|
||||
|
||||
// Analytics clients call this method to log regular messages about the
|
||||
// operations they perform.
|
||||
// Messages logged by this method are usually tagged with an `INFO` log
|
||||
// level in common logging libraries.
|
||||
Logf(format string, args ...interface{})
|
||||
|
||||
// Analytics clients call this method to log errors they encounter while
|
||||
// sending events to the backend servers.
|
||||
// Messages logged by this method are usually tagged with an `ERROR` log
|
||||
// level in common logging libraries.
|
||||
Errorf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// This function instantiate an object that statisfies the analytics.Logger
|
||||
// interface and send logs to standard logger passed as argument.
|
||||
func StdLogger(logger *log.Logger) Logger {
|
||||
return stdLogger{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type stdLogger struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (l stdLogger) Logf(format string, args ...interface{}) {
|
||||
l.logger.Printf("INFO: "+format, args...)
|
||||
}
|
||||
|
||||
func (l stdLogger) Errorf(format string, args ...interface{}) {
|
||||
l.logger.Printf("ERROR: "+format, args...)
|
||||
}
|
||||
|
||||
func newDefaultLogger() Logger {
|
||||
return StdLogger(log.New(os.Stderr, "segment ", log.LstdFlags))
|
||||
}
|
||||
131
vendor/github.com/rudderlabs/analytics-go/message.go
сгенерированный
поставляемый
Обычный файл
131
vendor/github.com/rudderlabs/analytics-go/message.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,131 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Values implementing this interface are used by analytics clients to notify
|
||||
// the application when a message send succeeded or failed.
|
||||
//
|
||||
// Callback methods are called by a client's internal goroutines, there are no
|
||||
// guarantees on which goroutine will trigger the callbacks, the calls can be
|
||||
// made sequentially or in parallel, the order doesn't depend on the order of
|
||||
// messages were queued to the client.
|
||||
//
|
||||
// Callback methods must return quickly and not cause long blocking operations
|
||||
// to avoid interferring with the client's internal work flow.
|
||||
type Callback interface {
|
||||
|
||||
// This method is called for every message that was successfully sent to
|
||||
// the API.
|
||||
Success(Message)
|
||||
|
||||
// This method is called for every message that failed to be sent to the
|
||||
// API and will be discarded by the client.
|
||||
Failure(Message, error)
|
||||
}
|
||||
|
||||
// This interface is used to represent analytics objects that can be sent via
|
||||
// a client.
|
||||
//
|
||||
// Types like analytics.Track, analytics.Page, etc... implement this interface
|
||||
// and therefore can be passed to the analytics.Client.Send method.
|
||||
type Message interface {
|
||||
|
||||
// Validate validates the internal structure of the message, the method must return
|
||||
// nil if the message is valid, or an error describing what went wrong.
|
||||
Validate() error
|
||||
|
||||
// internal is an unexposed interface function to ensure only types defined within this package can satisfy the Message interface. Invoking this method will panic.
|
||||
internal()
|
||||
}
|
||||
|
||||
// Takes a message id as first argument and returns it, unless it's the zero-
|
||||
// value, in that case the default id passed as second argument is returned.
|
||||
func makeMessageId(id string, def string) string {
|
||||
if len(id) == 0 {
|
||||
return def
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Returns the time value passed as first argument, unless it's the zero-value,
|
||||
// in that case the default value passed as second argument is returned.
|
||||
func makeTimestamp(t time.Time, def time.Time) time.Time {
|
||||
if t == (time.Time{}) {
|
||||
return def
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// This structure represents objects sent to the /v1/batch endpoint. We don't
|
||||
// export this type because it's only meant to be used internally to send groups
|
||||
// of messages in one API call.
|
||||
type batch struct {
|
||||
MessageId string `json:"messageId"`
|
||||
SentAt time.Time `json:"sentAt"`
|
||||
Messages []message `json:"batch"`
|
||||
Context *Context `json:"context"`
|
||||
}
|
||||
|
||||
type message struct {
|
||||
msg Message
|
||||
json []byte
|
||||
}
|
||||
|
||||
func makeMessage(m Message, maxBytes int) (msg message, err error) {
|
||||
if msg.json, err = json.Marshal(m); err == nil {
|
||||
if len(msg.json) > maxBytes {
|
||||
err = ErrMessageTooBig
|
||||
} else {
|
||||
msg.msg = m
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m message) MarshalJSON() ([]byte, error) {
|
||||
return m.json, nil
|
||||
}
|
||||
|
||||
func (m message) size() int {
|
||||
// The `+ 1` is for the comma that sits between each items of a JSON array.
|
||||
return len(m.json) + 1
|
||||
}
|
||||
|
||||
type messageQueue struct {
|
||||
pending []message
|
||||
bytes int
|
||||
maxBatchSize int
|
||||
maxBatchBytes int
|
||||
}
|
||||
|
||||
func (q *messageQueue) push(m message) (b []message) {
|
||||
if (q.bytes + m.size()) > q.maxBatchBytes {
|
||||
b = q.flush()
|
||||
}
|
||||
|
||||
if q.pending == nil {
|
||||
q.pending = make([]message, 0, q.maxBatchSize)
|
||||
}
|
||||
|
||||
q.pending = append(q.pending, m)
|
||||
q.bytes += len(m.json)
|
||||
|
||||
if b == nil && len(q.pending) == q.maxBatchSize {
|
||||
b = q.flush()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (q *messageQueue) flush() (msgs []message) {
|
||||
msgs, q.pending, q.bytes = q.pending, nil, 0
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
maxBatchBytes = 500000
|
||||
maxMessageBytes = 32000
|
||||
)
|
||||
38
vendor/github.com/rudderlabs/analytics-go/page.go
сгенерированный
поставляемый
Обычный файл
38
vendor/github.com/rudderlabs/analytics-go/page.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,38 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Page)(nil)
|
||||
|
||||
// This type represents object sent in a page call as described in
|
||||
// https://segment.com/docs/libraries/http/#page
|
||||
type Page struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Properties Properties `json:"properties,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Page) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Page) Validate() error {
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Page",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
117
vendor/github.com/rudderlabs/analytics-go/properties.go
сгенерированный
поставляемый
Обычный файл
117
vendor/github.com/rudderlabs/analytics-go/properties.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,117 @@
|
||||
package analytics
|
||||
|
||||
// This type is used to represent properties in messages that support it.
|
||||
// It is a free-form object so the application can set any value it sees fit but
|
||||
// a few helper method are defined to make it easier to instantiate properties with
|
||||
// common fields.
|
||||
// Here's a quick example of how this type is meant to be used:
|
||||
//
|
||||
// analytics.Page{
|
||||
// UserId: "0123456789",
|
||||
// Properties: analytics.NewProperties()
|
||||
// .SetRevenue(10.0)
|
||||
// .SetCurrency("USD"),
|
||||
// }
|
||||
//
|
||||
type Properties map[string]interface{}
|
||||
|
||||
func NewProperties() Properties {
|
||||
return make(Properties, 10)
|
||||
}
|
||||
|
||||
func (p Properties) SetRevenue(revenue float64) Properties {
|
||||
return p.Set("revenue", revenue)
|
||||
}
|
||||
|
||||
func (p Properties) SetCurrency(currency string) Properties {
|
||||
return p.Set("currency", currency)
|
||||
}
|
||||
|
||||
func (p Properties) SetValue(value float64) Properties {
|
||||
return p.Set("value", value)
|
||||
}
|
||||
|
||||
func (p Properties) SetPath(path string) Properties {
|
||||
return p.Set("path", path)
|
||||
}
|
||||
|
||||
func (p Properties) SetReferrer(referrer string) Properties {
|
||||
return p.Set("referrer", referrer)
|
||||
}
|
||||
|
||||
func (p Properties) SetTitle(title string) Properties {
|
||||
return p.Set("title", title)
|
||||
}
|
||||
|
||||
func (p Properties) SetURL(url string) Properties {
|
||||
return p.Set("url", url)
|
||||
}
|
||||
|
||||
func (p Properties) SetName(name string) Properties {
|
||||
return p.Set("name", name)
|
||||
}
|
||||
|
||||
func (p Properties) SetCategory(category string) Properties {
|
||||
return p.Set("category", category)
|
||||
}
|
||||
|
||||
func (p Properties) SetSKU(sku string) Properties {
|
||||
return p.Set("sku", sku)
|
||||
}
|
||||
|
||||
func (p Properties) SetPrice(price float64) Properties {
|
||||
return p.Set("price", price)
|
||||
}
|
||||
|
||||
func (p Properties) SetProductId(id string) Properties {
|
||||
return p.Set("id", id)
|
||||
}
|
||||
|
||||
func (p Properties) SetOrderId(id string) Properties {
|
||||
return p.Set("orderId", id)
|
||||
}
|
||||
|
||||
func (p Properties) SetTotal(total float64) Properties {
|
||||
return p.Set("total", total)
|
||||
}
|
||||
|
||||
func (p Properties) SetSubtotal(subtotal float64) Properties {
|
||||
return p.Set("subtotal", subtotal)
|
||||
}
|
||||
|
||||
func (p Properties) SetShipping(shipping float64) Properties {
|
||||
return p.Set("shipping", shipping)
|
||||
}
|
||||
|
||||
func (p Properties) SetTax(tax float64) Properties {
|
||||
return p.Set("tax", tax)
|
||||
}
|
||||
|
||||
func (p Properties) SetDiscount(discount float64) Properties {
|
||||
return p.Set("discount", discount)
|
||||
}
|
||||
|
||||
func (p Properties) SetCoupon(coupon string) Properties {
|
||||
return p.Set("coupon", coupon)
|
||||
}
|
||||
|
||||
func (p Properties) SetProducts(products ...Product) Properties {
|
||||
return p.Set("products", products)
|
||||
}
|
||||
|
||||
func (p Properties) SetRepeat(repeat bool) Properties {
|
||||
return p.Set("repeat", repeat)
|
||||
}
|
||||
|
||||
func (p Properties) Set(name string, value interface{}) Properties {
|
||||
p[name] = value
|
||||
return p
|
||||
}
|
||||
|
||||
// This type represents products in the E-commerce API.
|
||||
type Product struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
SKU string `json:"sky,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Price float64 `json:"price"`
|
||||
}
|
||||
38
vendor/github.com/rudderlabs/analytics-go/screen.go
сгенерированный
поставляемый
Обычный файл
38
vendor/github.com/rudderlabs/analytics-go/screen.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,38 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Screen)(nil)
|
||||
|
||||
// This type represents object sent in a screen call as described in
|
||||
// https://segment.com/docs/libraries/http/#screen
|
||||
type Screen struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Properties Properties `json:"properties,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Screen) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Screen) Validate() error {
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Screen",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
16
vendor/github.com/rudderlabs/analytics-go/timeout_15.go
сгенерированный
поставляемый
Обычный файл
16
vendor/github.com/rudderlabs/analytics-go/timeout_15.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,16 @@
|
||||
// +build !go1.6
|
||||
|
||||
package analytics
|
||||
|
||||
import "net/http"
|
||||
|
||||
// http clients on versions of go before 1.6 only support timeout if the
|
||||
// transport implements the `CancelRequest` method.
|
||||
func supportsTimeout(transport http.RoundTripper) bool {
|
||||
_, ok := transport.(requestCanceler)
|
||||
return ok
|
||||
}
|
||||
|
||||
type requestCanceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
10
vendor/github.com/rudderlabs/analytics-go/timeout_16.go
сгенерированный
поставляемый
Обычный файл
10
vendor/github.com/rudderlabs/analytics-go/timeout_16.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,10 @@
|
||||
// +build go1.6
|
||||
|
||||
package analytics
|
||||
|
||||
import "net/http"
|
||||
|
||||
// http clients on versions of go after 1.6 always support timeout.
|
||||
func supportsTimeout(transport http.RoundTripper) bool {
|
||||
return true
|
||||
}
|
||||
46
vendor/github.com/rudderlabs/analytics-go/track.go
сгенерированный
поставляемый
Обычный файл
46
vendor/github.com/rudderlabs/analytics-go/track.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,46 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Track)(nil)
|
||||
|
||||
// This type represents object sent in a track call as described in
|
||||
// https://segment.com/docs/libraries/http/#track
|
||||
type Track struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
Event string `json:"event"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Properties Properties `json:"properties,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Track) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Track) Validate() error {
|
||||
if len(msg.Event) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Track",
|
||||
Name: "Event",
|
||||
Value: msg.Event,
|
||||
}
|
||||
}
|
||||
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Track",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
89
vendor/github.com/rudderlabs/analytics-go/traits.go
сгенерированный
поставляемый
Обычный файл
89
vendor/github.com/rudderlabs/analytics-go/traits.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,89 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
// This type is used to represent traits in messages that support it.
|
||||
// It is a free-form object so the application can set any value it sees fit but
|
||||
// a few helper method are defined to make it easier to instantiate traits with
|
||||
// common fields.
|
||||
// Here's a quick example of how this type is meant to be used:
|
||||
//
|
||||
// analytics.Identify{
|
||||
// UserId: "0123456789",
|
||||
// Traits: analytics.NewTraits()
|
||||
// .SetFirstName("Luke")
|
||||
// .SetLastName("Skywalker")
|
||||
// .Set("Role", "Jedi"),
|
||||
// }
|
||||
//
|
||||
// The specifications can be found at https://segment.com/docs/spec/identify/#traits
|
||||
type Traits map[string]interface{}
|
||||
|
||||
func NewTraits() Traits {
|
||||
return make(Traits, 10)
|
||||
}
|
||||
|
||||
func (t Traits) SetAddress(address string) Traits {
|
||||
return t.Set("address", address)
|
||||
}
|
||||
|
||||
func (t Traits) SetAge(age int) Traits {
|
||||
return t.Set("age", age)
|
||||
}
|
||||
|
||||
func (t Traits) SetAvatar(url string) Traits {
|
||||
return t.Set("avatar", url)
|
||||
}
|
||||
|
||||
func (t Traits) SetBirthday(date time.Time) Traits {
|
||||
return t.Set("birthday", date)
|
||||
}
|
||||
|
||||
func (t Traits) SetCreatedAt(date time.Time) Traits {
|
||||
return t.Set("createdAt", date)
|
||||
}
|
||||
|
||||
func (t Traits) SetDescription(desc string) Traits {
|
||||
return t.Set("description", desc)
|
||||
}
|
||||
|
||||
func (t Traits) SetEmail(email string) Traits {
|
||||
return t.Set("email", email)
|
||||
}
|
||||
|
||||
func (t Traits) SetFirstName(firstName string) Traits {
|
||||
return t.Set("firstName", firstName)
|
||||
}
|
||||
|
||||
func (t Traits) SetGender(gender string) Traits {
|
||||
return t.Set("gender", gender)
|
||||
}
|
||||
|
||||
func (t Traits) SetLastName(lastName string) Traits {
|
||||
return t.Set("lastName", lastName)
|
||||
}
|
||||
|
||||
func (t Traits) SetName(name string) Traits {
|
||||
return t.Set("name", name)
|
||||
}
|
||||
|
||||
func (t Traits) SetPhone(phone string) Traits {
|
||||
return t.Set("phone", phone)
|
||||
}
|
||||
|
||||
func (t Traits) SetTitle(title string) Traits {
|
||||
return t.Set("title", title)
|
||||
}
|
||||
|
||||
func (t Traits) SetUsername(username string) Traits {
|
||||
return t.Set("username", username)
|
||||
}
|
||||
|
||||
func (t Traits) SetWebsite(url string) Traits {
|
||||
return t.Set("website", url)
|
||||
}
|
||||
|
||||
func (t Traits) Set(field string, value interface{}) Traits {
|
||||
t[field] = value
|
||||
return t
|
||||
}
|
||||
3
vendor/modules.txt
поставляемый
3
vendor/modules.txt
поставляемый
@@ -289,6 +289,9 @@ github.com/prometheus/procfs/internal/util
|
||||
# github.com/rs/cors v1.7.0
|
||||
## explicit
|
||||
github.com/rs/cors
|
||||
# github.com/rudderlabs/analytics-go v3.1.0+incompatible
|
||||
## explicit
|
||||
github.com/rudderlabs/analytics-go
|
||||
# github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7
|
||||
## explicit
|
||||
github.com/russellhaering/goxmldsig
|
||||
|
||||
@@ -152,7 +152,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
// Set content security policy. This is also specified in the root.html of the webapp in a meta tag.
|
||||
w.Header().Set("Content-Security-Policy", fmt.Sprintf(
|
||||
"frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/%s",
|
||||
"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com cdn.segment.com/analytics.js/%s",
|
||||
h.cspShaDirective,
|
||||
))
|
||||
} else {
|
||||
|
||||
@@ -296,7 +296,7 @@ func TestHandlerServeCSPHeader(t *testing.T) {
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, 200, response.Code)
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/"})
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com cdn.segment.com/analytics.js/"})
|
||||
})
|
||||
|
||||
t.Run("static, with subpath", func(t *testing.T) {
|
||||
@@ -333,7 +333,7 @@ func TestHandlerServeCSPHeader(t *testing.T) {
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, 200, response.Code)
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/"})
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com cdn.segment.com/analytics.js/"})
|
||||
|
||||
// TODO: It's hard to unit test this now that the CSP directive is effectively
|
||||
// decided in Setup(). Circle back to this in master once the memory store is
|
||||
@@ -348,7 +348,7 @@ func TestHandlerServeCSPHeader(t *testing.T) {
|
||||
response = httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, 200, response.Code)
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/"})
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com cdn.segment.com/analytics.js/"})
|
||||
// TODO: See above.
|
||||
// assert.Contains(t, response.Header()["Content-Security-Policy"], "frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/ 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='", "csp header incorrectly changed after subpath changed")
|
||||
})
|
||||
|
||||
Ссылка в новой задаче
Block a user