MM-12389 Update segment library to v3 (#11472)
* Update analytics-go dependency * Migrate from segment library v2 to v3 * Reinclude gorilla/handlers module * Fix missing module * Remove reference to outdated analytics module
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
c9e289f828
Коммит
d844c52f06
3
Makefile
3
Makefile
@@ -576,9 +576,6 @@ update-dependencies: ## Uses go get -u to update all the dependencies while hold
|
|||||||
# Update all dependencies (does not update across major versions)
|
# Update all dependencies (does not update across major versions)
|
||||||
go get -u
|
go get -u
|
||||||
|
|
||||||
# Keep back because of breaking API changes
|
|
||||||
go get -u github.com/segmentio/analytics-go@2.1.1
|
|
||||||
|
|
||||||
# Tidy up
|
# Tidy up
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (a *App) sendDailyDiagnostics(override bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SendDiagnostic(event string, properties map[string]interface{}) {
|
func (a *App) SendDiagnostic(event string, properties map[string]interface{}) {
|
||||||
a.Srv.diagnosticClient.Track(&analytics.Track{
|
a.Srv.diagnosticClient.Enqueue(&analytics.Track{
|
||||||
Event: event,
|
Event: event,
|
||||||
UserId: a.DiagnosticId(),
|
UserId: a.DiagnosticId(),
|
||||||
Properties: properties,
|
Properties: properties,
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ type Server struct {
|
|||||||
limitedClientConfig map[string]string
|
limitedClientConfig map[string]string
|
||||||
|
|
||||||
diagnosticId string
|
diagnosticId string
|
||||||
diagnosticClient *analytics.Client
|
diagnosticClient analytics.Client
|
||||||
|
|
||||||
phase2PermissionsMigrationComplete bool
|
phase2PermissionsMigrationComplete bool
|
||||||
|
|
||||||
@@ -745,15 +745,16 @@ func (s *Server) StartElasticsearch() {
|
|||||||
|
|
||||||
func (s *Server) initDiagnostics(endpoint string) {
|
func (s *Server) initDiagnostics(endpoint string) {
|
||||||
if s.diagnosticClient == nil {
|
if s.diagnosticClient == nil {
|
||||||
client := analytics.New(SEGMENT_KEY)
|
config := analytics.Config{}
|
||||||
client.Logger = s.Log.StdLog(mlog.String("source", "segment"))
|
config.Logger = analytics.StdLogger(s.Log.StdLog(mlog.String("source", "segment")))
|
||||||
// For testing
|
// For testing
|
||||||
if endpoint != "" {
|
if endpoint != "" {
|
||||||
client.Endpoint = endpoint
|
config.Endpoint = endpoint
|
||||||
client.Verbose = true
|
config.Verbose = true
|
||||||
client.Size = 1
|
config.BatchSize = 1
|
||||||
}
|
}
|
||||||
client.Identify(&analytics.Identify{
|
client, _ := analytics.NewWithConfig(SEGMENT_KEY, config)
|
||||||
|
client.Enqueue(&analytics.Identify{
|
||||||
UserId: s.diagnosticId,
|
UserId: s.diagnosticId,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -761,7 +762,7 @@ func (s *Server) initDiagnostics(endpoint string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShutdownDiagnostics closes the diagnostic client.
|
// shutdownDiagnostics closes the diagnostic client.
|
||||||
func (s *Server) shutdownDiagnostics() error {
|
func (s *Server) shutdownDiagnostics() error {
|
||||||
if s.diagnosticClient != nil {
|
if s.diagnosticClient != nil {
|
||||||
return s.diagnosticClient.Close()
|
return s.diagnosticClient.Close()
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -35,7 +35,6 @@ require (
|
|||||||
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428
|
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053
|
github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053
|
||||||
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
|
|
||||||
github.com/jmoiron/sqlx v1.2.0
|
github.com/jmoiron/sqlx v1.2.0
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
github.com/lib/pq v1.0.0
|
github.com/lib/pq v1.0.0
|
||||||
@@ -61,7 +60,7 @@ require (
|
|||||||
github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573 // indirect
|
github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573 // indirect
|
||||||
github.com/rs/cors v1.6.0
|
github.com/rs/cors v1.6.0
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||||
github.com/segmentio/analytics-go v2.0.1-0.20160426181448-2d840d861c32+incompatible
|
github.com/segmentio/analytics-go v3.0.1+incompatible
|
||||||
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect
|
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect
|
||||||
github.com/sirupsen/logrus v1.4.0
|
github.com/sirupsen/logrus v1.4.0
|
||||||
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect
|
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -171,8 +171,6 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
|
|||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053 h1:vAR93++rxlMlJRMK0hKD3l5La7FjpmUIxO1jnJmgTbI=
|
github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053 h1:vAR93++rxlMlJRMK0hKD3l5La7FjpmUIxO1jnJmgTbI=
|
||||||
github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||||
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
|
|
||||||
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
|
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||||
@@ -304,8 +302,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
|
|||||||
github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/segmentio/analytics-go v2.0.1-0.20160426181448-2d840d861c32+incompatible h1:VcfKSKy3OxvnkQzsZPD1170acL2qldNDZCiRJ7CzZwE=
|
github.com/segmentio/analytics-go v3.0.1+incompatible h1:W7T3ieNQjPFMb+SE8SAVYo6mPkKK/Y37wYdiNf5lCVg=
|
||||||
github.com/segmentio/analytics-go v2.0.1-0.20160426181448-2d840d861c32+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48=
|
github.com/segmentio/analytics-go v3.0.1+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48=
|
||||||
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c h1:rsRTAcCR5CeNLkvgBVSjQoDGRRt6kggsE6XYBqCv2KQ=
|
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c h1:rsRTAcCR5CeNLkvgBVSjQoDGRRt6kggsE6XYBqCv2KQ=
|
||||||
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=
|
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
|||||||
26
vendor/github.com/jehiah/go-strftime/README.md
сгенерированный
поставляемый
26
vendor/github.com/jehiah/go-strftime/README.md
сгенерированный
поставляемый
@@ -1,26 +0,0 @@
|
|||||||
go-strftime
|
|
||||||
===========
|
|
||||||
|
|
||||||
go implementation of strftime
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
strftime "github.com/jehiah/go-strftime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
t := time.Unix(1340244776, 0)
|
|
||||||
utc, _ := time.LoadLocation("UTC")
|
|
||||||
t = t.In(utc)
|
|
||||||
fmt.Println(strftime.Format("%Y-%m-%d %H:%M:%S", t))
|
|
||||||
// Output:
|
|
||||||
// 2012-06-21 02:12:56
|
|
||||||
}
|
|
||||||
```
|
|
||||||
72
vendor/github.com/jehiah/go-strftime/strftime.go
сгенерированный
поставляемый
72
vendor/github.com/jehiah/go-strftime/strftime.go
сгенерированный
поставляемый
@@ -1,72 +0,0 @@
|
|||||||
// go implementation of strftime
|
|
||||||
package strftime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// taken from time/format.go
|
|
||||||
var conversion = map[rune]string{
|
|
||||||
/*stdLongMonth */ 'B': "January",
|
|
||||||
/*stdMonth */ 'b': "Jan",
|
|
||||||
// stdNumMonth */ 'm': "1",
|
|
||||||
/*stdZeroMonth */ 'm': "01",
|
|
||||||
/*stdLongWeekDay */ 'A': "Monday",
|
|
||||||
/*stdWeekDay */ 'a': "Mon",
|
|
||||||
// stdDay */ 'd': "2",
|
|
||||||
// stdUnderDay */ 'd': "_2",
|
|
||||||
/*stdZeroDay */ 'd': "02",
|
|
||||||
/*stdHour */ 'H': "15",
|
|
||||||
// stdHour12 */ 'I': "3",
|
|
||||||
/*stdZeroHour12 */ 'I': "03",
|
|
||||||
// stdMinute */ 'M': "4",
|
|
||||||
/*stdZeroMinute */ 'M': "04",
|
|
||||||
// stdSecond */ 'S': "5",
|
|
||||||
/*stdZeroSecond */ 'S': "05",
|
|
||||||
/*stdLongYear */ 'Y': "2006",
|
|
||||||
/*stdYear */ 'y': "06",
|
|
||||||
/*stdPM */ 'p': "PM",
|
|
||||||
// stdpm */ 'p': "pm",
|
|
||||||
/*stdTZ */ 'Z': "MST",
|
|
||||||
// stdISO8601TZ */ 'z': "Z0700", // prints Z for UTC
|
|
||||||
// stdISO8601ColonTZ */ 'z': "Z07:00", // prints Z for UTC
|
|
||||||
/*stdNumTZ */ 'z': "-0700", // always numeric
|
|
||||||
// stdNumShortTZ */ 'b': "-07", // always numeric
|
|
||||||
// stdNumColonTZ */ 'b': "-07:00", // always numeric
|
|
||||||
/* nonStdMilli */ 'L': ".000",
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is an alternative to time.Format because no one knows
|
|
||||||
// what date 040305 is supposed to create when used as a 'layout' string
|
|
||||||
// this takes standard strftime format options. For a complete list
|
|
||||||
// of format options see http://strftime.org/
|
|
||||||
func Format(format string, t time.Time) string {
|
|
||||||
retval := make([]byte, 0, len(format))
|
|
||||||
for i, ni := 0, 0; i < len(format); i = ni + 2 {
|
|
||||||
ni = strings.IndexByte(format[i:], '%')
|
|
||||||
if ni < 0 {
|
|
||||||
ni = len(format)
|
|
||||||
} else {
|
|
||||||
ni += i
|
|
||||||
}
|
|
||||||
retval = append(retval, []byte(format[i:ni])...)
|
|
||||||
if ni+1 < len(format) {
|
|
||||||
c := format[ni+1]
|
|
||||||
if c == '%' {
|
|
||||||
retval = append(retval, '%')
|
|
||||||
} else {
|
|
||||||
if layoutCmd, ok := conversion[rune(c)]; ok {
|
|
||||||
retval = append(retval, []byte(t.Format(layoutCmd))...)
|
|
||||||
} else {
|
|
||||||
retval = append(retval, '%', c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ni < len(format) {
|
|
||||||
retval = append(retval, '%')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(retval)
|
|
||||||
}
|
|
||||||
10
vendor/github.com/jehiah/go-strftime/.gitignore → vendor/github.com/segmentio/analytics-go/.gitignore
сгенерированный
поставляемый
10
vendor/github.com/jehiah/go-strftime/.gitignore → vendor/github.com/segmentio/analytics-go/.gitignore
сгенерированный
поставляемый
@@ -20,3 +20,13 @@ _cgo_export.*
|
|||||||
_testmain.go
|
_testmain.go
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
# Emacs
|
||||||
|
*~
|
||||||
|
\#*
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# Artifacts
|
||||||
|
tmp/*
|
||||||
6
vendor/github.com/segmentio/analytics-go/.gitmodules
сгенерированный
поставляемый
Обычный файл
6
vendor/github.com/segmentio/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
|
||||||
19
vendor/github.com/segmentio/analytics-go/History.md
сгенерированный
поставляемый
19
vendor/github.com/segmentio/analytics-go/History.md
сгенерированный
поставляемый
@@ -1,9 +1,20 @@
|
|||||||
|
v3.0.1 / 2018-10-02
|
||||||
v2.1.1 / 2016-04-26
|
|
||||||
===================
|
===================
|
||||||
|
|
||||||
* Fix blocking the goroutine when Close is the first call.
|
* Migrate from Circle V1 format to Circle V2
|
||||||
* Fix blocking the goroutine when the message queue fills up.
|
* 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
|
v2.1.0 / 2015-12-28
|
||||||
===================
|
===================
|
||||||
|
|||||||
12
vendor/github.com/jehiah/go-strftime/LICENSE → vendor/github.com/segmentio/analytics-go/License.md
сгенерированный
поставляемый
12
vendor/github.com/jehiah/go-strftime/LICENSE → vendor/github.com/segmentio/analytics-go/License.md
сгенерированный
поставляемый
@@ -1,4 +1,6 @@
|
|||||||
Copyright (c) 2012 Jehiah Czebotar
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Segment, Inc.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -7,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
The above copyright notice and this permission notice shall be included in all
|
||||||
all copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
THE SOFTWARE.
|
SOFTWARE.
|
||||||
33
vendor/github.com/segmentio/analytics-go/Makefile
сгенерированный
поставляемый
33
vendor/github.com/segmentio/analytics-go/Makefile
сгенерированный
поставляемый
@@ -1,10 +1,31 @@
|
|||||||
|
ifndef CIRCLE_ARTIFACTS
|
||||||
|
CIRCLE_ARTIFACTS=tmp
|
||||||
|
endif
|
||||||
|
|
||||||
|
bootstrap:
|
||||||
|
.buildscript/bootstrap.sh
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
@go get -v -t ./...
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
@godep go vet ./...
|
@go vet ./...
|
||||||
|
|
||||||
build:
|
test: vet
|
||||||
@godep go build
|
@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
|
||||||
|
|
||||||
test:
|
build: test
|
||||||
@godep go test -race -cover ./...
|
@go build ./...
|
||||||
|
|
||||||
.PHONY: vet build test
|
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'; fi
|
||||||
|
|
||||||
|
ci: dependencies test e2e
|
||||||
|
|
||||||
|
.PHONY: bootstrap dependencies vet test e2e ci
|
||||||
|
|||||||
55
vendor/github.com/segmentio/analytics-go/Readme.md
сгенерированный
поставляемый
55
vendor/github.com/segmentio/analytics-go/Readme.md
сгенерированный
поставляемый
@@ -1,8 +1,55 @@
|
|||||||
# analytics-go
|
# analytics-go [](https://circleci.com/gh/segmentio/analytics-go/tree/master) [](https://godoc.org/github.com/segmentio/analytics-go)
|
||||||
|
|
||||||
Segment analytics client for Go. For additional documentation
|
Segment analytics client for Go.
|
||||||
visit [https://segment.com/docs/libraries/go](https://segment.com/docs/libraries/go/) or view the [godocs](http://godoc.org/github.com/segmentio/analytics-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
|
## License
|
||||||
|
|
||||||
MIT
|
The library is released under the [MIT license](License.md).
|
||||||
|
|||||||
38
vendor/github.com/segmentio/analytics-go/alias.go
сгенерированный
поставляемый
Обычный файл
38
vendor/github.com/segmentio/analytics-go/alias.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,38 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// 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) 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
|
||||||
|
}
|
||||||
587
vendor/github.com/segmentio/analytics-go/analytics.go
сгенерированный
поставляемый
587
vendor/github.com/segmentio/analytics-go/analytics.go
сгенерированный
поставляемый
@@ -2,307 +2,248 @@ package analytics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jehiah/go-strftime"
|
|
||||||
"github.com/segmentio/backo-go"
|
|
||||||
"github.com/xtgo/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version of the client.
|
// Version of the client.
|
||||||
const Version = "2.1.0"
|
const Version = "3.0.0"
|
||||||
|
|
||||||
// Endpoint for the Segment API.
|
// This interface is the main API exposed by the analytics package.
|
||||||
const Endpoint = "https://api.segment.io"
|
// 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
|
||||||
|
|
||||||
// DefaultContext of message batches.
|
// Queues a message to be sent by the client when the conditions for a batch
|
||||||
var DefaultContext = map[string]interface{}{
|
// upload are met.
|
||||||
"library": map[string]interface{}{
|
// This is the main method you'll be using, a typical flow would look like
|
||||||
"name": "analytics-go",
|
// this:
|
||||||
"version": Version,
|
//
|
||||||
},
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backoff policy.
|
type client struct {
|
||||||
var Backo = backo.DefaultBacko()
|
Config
|
||||||
|
key string
|
||||||
|
|
||||||
// Message interface.
|
// This channel is where the `Enqueue` method writes messages so they can be
|
||||||
type message interface {
|
// picked up and pushed by the backend goroutine taking care of applying the
|
||||||
setMessageId(string)
|
// batching rules.
|
||||||
setTimestamp(string)
|
msgs chan Message
|
||||||
}
|
|
||||||
|
|
||||||
// Message fields common to all.
|
// These two channels are used to synchronize the client shutting down when
|
||||||
type Message struct {
|
// `Close` is called.
|
||||||
Type string `json:"type,omitempty"`
|
// The first channel is closed to signal the backend goroutine that it has
|
||||||
MessageId string `json:"messageId,omitempty"`
|
// to stop, then the second one is closed by the backend goroutine to signal
|
||||||
Timestamp string `json:"timestamp,omitempty"`
|
// that it has finished flushing all queued messages.
|
||||||
SentAt string `json:"sentAt,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batch message.
|
|
||||||
type Batch struct {
|
|
||||||
Context map[string]interface{} `json:"context,omitempty"`
|
|
||||||
Messages []interface{} `json:"batch"`
|
|
||||||
Message
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identify message.
|
|
||||||
type Identify struct {
|
|
||||||
Context map[string]interface{} `json:"context,omitempty"`
|
|
||||||
Integrations map[string]interface{} `json:"integrations,omitempty"`
|
|
||||||
Traits map[string]interface{} `json:"traits,omitempty"`
|
|
||||||
AnonymousId string `json:"anonymousId,omitempty"`
|
|
||||||
UserId string `json:"userId,omitempty"`
|
|
||||||
Message
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group message.
|
|
||||||
type Group struct {
|
|
||||||
Context map[string]interface{} `json:"context,omitempty"`
|
|
||||||
Integrations map[string]interface{} `json:"integrations,omitempty"`
|
|
||||||
Traits map[string]interface{} `json:"traits,omitempty"`
|
|
||||||
AnonymousId string `json:"anonymousId,omitempty"`
|
|
||||||
UserId string `json:"userId,omitempty"`
|
|
||||||
GroupId string `json:"groupId"`
|
|
||||||
Message
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track message.
|
|
||||||
type Track struct {
|
|
||||||
Context map[string]interface{} `json:"context,omitempty"`
|
|
||||||
Integrations map[string]interface{} `json:"integrations,omitempty"`
|
|
||||||
Properties map[string]interface{} `json:"properties,omitempty"`
|
|
||||||
AnonymousId string `json:"anonymousId,omitempty"`
|
|
||||||
UserId string `json:"userId,omitempty"`
|
|
||||||
Event string `json:"event"`
|
|
||||||
Message
|
|
||||||
}
|
|
||||||
|
|
||||||
// Page message.
|
|
||||||
type Page struct {
|
|
||||||
Context map[string]interface{} `json:"context,omitempty"`
|
|
||||||
Integrations map[string]interface{} `json:"integrations,omitempty"`
|
|
||||||
Traits map[string]interface{} `json:"properties,omitempty"`
|
|
||||||
AnonymousId string `json:"anonymousId,omitempty"`
|
|
||||||
UserId string `json:"userId,omitempty"`
|
|
||||||
Category string `json:"category,omitempty"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Message
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alias message.
|
|
||||||
type Alias struct {
|
|
||||||
PreviousId string `json:"previousId"`
|
|
||||||
UserId string `json:"userId"`
|
|
||||||
Message
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client which batches messages and flushes at the given Interval or
|
|
||||||
// when the Size limit is exceeded. Set Verbose to true to enable
|
|
||||||
// logging output.
|
|
||||||
type Client struct {
|
|
||||||
Endpoint string
|
|
||||||
// Interval represents the duration at which messages are flushed. It may be
|
|
||||||
// configured only before any messages are enqueued.
|
|
||||||
Interval time.Duration
|
|
||||||
Size int
|
|
||||||
Logger *log.Logger
|
|
||||||
Verbose bool
|
|
||||||
Client http.Client
|
|
||||||
key string
|
|
||||||
msgs chan interface{}
|
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
shutdown chan struct{}
|
shutdown chan struct{}
|
||||||
uid func() string
|
|
||||||
now func() time.Time
|
|
||||||
once sync.Once
|
|
||||||
wg sync.WaitGroup
|
|
||||||
|
|
||||||
// These synchronization primitives are used to control how many goroutines
|
// This HTTP client is used to send requests to the backend, it uses the
|
||||||
// are spawned by the client for uploads.
|
// HTTP transport provided in the configuration.
|
||||||
upmtx sync.Mutex
|
http http.Client
|
||||||
upcond sync.Cond
|
|
||||||
upcount int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New client with write key.
|
// Instantiate a new client that uses the write key passed as first argument to
|
||||||
func New(key string) *Client {
|
// send messages to the backend.
|
||||||
c := &Client{
|
// The client is created with the default configuration.
|
||||||
Endpoint: Endpoint,
|
func New(writeKey string) Client {
|
||||||
Interval: 5 * time.Second,
|
// Here we can ignore the error because the default config is always valid.
|
||||||
Size: 250,
|
c, _ := NewWithConfig(writeKey, Config{})
|
||||||
Logger: log.New(os.Stderr, "segment ", log.LstdFlags),
|
|
||||||
Verbose: false,
|
|
||||||
Client: *http.DefaultClient,
|
|
||||||
key: key,
|
|
||||||
msgs: make(chan interface{}, 100),
|
|
||||||
quit: make(chan struct{}),
|
|
||||||
shutdown: make(chan struct{}),
|
|
||||||
now: time.Now,
|
|
||||||
uid: uid,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.upcond.L = &c.upmtx
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alias buffers an "alias" message.
|
// Instantiate a new client that uses the write key and configuration passed as
|
||||||
func (c *Client) Alias(msg *Alias) error {
|
// arguments to send messages to the backend.
|
||||||
if msg.UserId == "" {
|
// The function will return an error if the configuration contained impossible
|
||||||
return errors.New("You must pass a 'userId'.")
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.PreviousId == "" {
|
c := &client{
|
||||||
return errors.New("You must pass a 'previousId'.")
|
Config: makeConfig(config),
|
||||||
|
key: writeKey,
|
||||||
|
msgs: make(chan Message, 100),
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
shutdown: make(chan struct{}),
|
||||||
|
http: makeHttpClient(config.Transport),
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Type = "alias"
|
|
||||||
c.queue(msg)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Page buffers an "page" message.
|
|
||||||
func (c *Client) Page(msg *Page) error {
|
|
||||||
if msg.UserId == "" && msg.AnonymousId == "" {
|
|
||||||
return errors.New("You must pass either an 'anonymousId' or 'userId'.")
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Type = "page"
|
|
||||||
c.queue(msg)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group buffers an "group" message.
|
|
||||||
func (c *Client) Group(msg *Group) error {
|
|
||||||
if msg.GroupId == "" {
|
|
||||||
return errors.New("You must pass a 'groupId'.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.UserId == "" && msg.AnonymousId == "" {
|
|
||||||
return errors.New("You must pass either an 'anonymousId' or 'userId'.")
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Type = "group"
|
|
||||||
c.queue(msg)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identify buffers an "identify" message.
|
|
||||||
func (c *Client) Identify(msg *Identify) error {
|
|
||||||
if msg.UserId == "" && msg.AnonymousId == "" {
|
|
||||||
return errors.New("You must pass either an 'anonymousId' or 'userId'.")
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Type = "identify"
|
|
||||||
c.queue(msg)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track buffers an "track" message.
|
|
||||||
func (c *Client) Track(msg *Track) error {
|
|
||||||
if msg.Event == "" {
|
|
||||||
return errors.New("You must pass 'event'.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.UserId == "" && msg.AnonymousId == "" {
|
|
||||||
return errors.New("You must pass either an 'anonymousId' or 'userId'.")
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Type = "track"
|
|
||||||
c.queue(msg)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) startLoop() {
|
|
||||||
go c.loop()
|
go c.loop()
|
||||||
|
|
||||||
|
cli = c
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue message.
|
func makeHttpClient(transport http.RoundTripper) http.Client {
|
||||||
func (c *Client) queue(msg message) {
|
httpClient := http.Client{
|
||||||
c.once.Do(c.startLoop)
|
Transport: transport,
|
||||||
msg.setMessageId(c.uid())
|
}
|
||||||
msg.setTimestamp(timestamp(c.now()))
|
if supportsTimeout(transport) {
|
||||||
|
httpClient.Timeout = 10 * time.Second
|
||||||
|
}
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Enqueue(msg Message) (err error) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
c.msgs <- msg
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close and flush metrics.
|
// Close and flush metrics.
|
||||||
func (c *Client) Close() error {
|
func (c *client) Close() (err error) {
|
||||||
c.once.Do(c.startLoop)
|
defer func() {
|
||||||
c.quit <- struct{}{}
|
// Always recover, a panic could be raised if `c`.quit was closed which
|
||||||
close(c.msgs)
|
// means the method was called more than once.
|
||||||
|
if recover() != nil {
|
||||||
|
err = ErrClosed
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
close(c.quit)
|
||||||
<-c.shutdown
|
<-c.shutdown
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) sendAsync(msgs []interface{}) {
|
// Asychronously send a batched requests.
|
||||||
c.upmtx.Lock()
|
func (c *client) sendAsync(msgs []message, wg *sync.WaitGroup, ex *executor) {
|
||||||
for c.upcount == 1000 {
|
wg.Add(1)
|
||||||
c.upcond.Wait()
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
c.upcount++
|
|
||||||
c.upmtx.Unlock()
|
|
||||||
c.wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
err := c.send(msgs)
|
|
||||||
if err != nil {
|
|
||||||
c.logf(err.Error())
|
|
||||||
}
|
|
||||||
c.upmtx.Lock()
|
|
||||||
c.upcount--
|
|
||||||
c.upcond.Signal()
|
|
||||||
c.upmtx.Unlock()
|
|
||||||
c.wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send batch request.
|
// Send batch request.
|
||||||
func (c *Client) send(msgs []interface{}) error {
|
func (c *client) send(msgs []message) {
|
||||||
if len(msgs) == 0 {
|
const attempts = 10
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
batch := new(Batch)
|
b, err := json.Marshal(batch{
|
||||||
batch.Messages = msgs
|
MessageId: c.uid(),
|
||||||
batch.MessageId = c.uid()
|
SentAt: c.now(),
|
||||||
batch.SentAt = timestamp(c.now())
|
Messages: msgs,
|
||||||
batch.Context = DefaultContext
|
Context: c.DefaultContext,
|
||||||
|
})
|
||||||
|
|
||||||
b, err := json.Marshal(batch)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error marshalling msgs: %s", err)
|
c.errorf("marshalling messages - %s", err)
|
||||||
|
c.notifyFailure(msgs, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i != attempts; i++ {
|
||||||
if err = c.upload(b); err == nil {
|
if err = c.upload(b); err == nil {
|
||||||
return 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
|
||||||
}
|
}
|
||||||
Backo.Sleep(i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
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.
|
// Upload serialized batch message.
|
||||||
func (c *Client) upload(b []byte) error {
|
func (c *client) upload(b []byte) error {
|
||||||
url := c.Endpoint + "/v1/batch"
|
url := c.Endpoint + "/v1/batch"
|
||||||
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating request: %s", err)
|
c.errorf("creating request - %s", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("User-Agent", "analytics-go (version: "+Version+")")
|
req.Header.Add("User-Agent", "analytics-go (version: "+Version+")")
|
||||||
@@ -310,98 +251,138 @@ func (c *Client) upload(b []byte) error {
|
|||||||
req.Header.Add("Content-Length", string(len(b)))
|
req.Header.Add("Content-Length", string(len(b)))
|
||||||
req.SetBasicAuth(c.key, "")
|
req.SetBasicAuth(c.key, "")
|
||||||
|
|
||||||
res, err := c.Client.Do(req)
|
res, err := c.http.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error sending request: %s", err)
|
c.errorf("sending request - %s", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
return c.report(res)
|
||||||
|
}
|
||||||
|
|
||||||
if res.StatusCode < 400 {
|
// Report on response body.
|
||||||
c.verbose("response %s", res.Status)
|
func (c *client) report(res *http.Response) (err error) {
|
||||||
return nil
|
var body []byte
|
||||||
|
|
||||||
|
if res.StatusCode < 300 {
|
||||||
|
c.debugf("response %s", res.Status)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
if body, err = ioutil.ReadAll(res.Body); err != nil {
|
||||||
if err != nil {
|
c.errorf("response %d %s - %s", res.StatusCode, res.Status, err)
|
||||||
return fmt.Errorf("error reading response body: %s", err)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("response %s: %d – %s", res.Status, res.StatusCode, string(body))
|
c.logf("response %d %s – %s", res.StatusCode, res.Status, string(body))
|
||||||
|
return fmt.Errorf("%d %s", res.StatusCode, res.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch loop.
|
// Batch loop.
|
||||||
func (c *Client) loop() {
|
func (c *client) loop() {
|
||||||
var msgs []interface{}
|
defer close(c.shutdown)
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
defer wg.Wait()
|
||||||
|
|
||||||
tick := time.NewTicker(c.Interval)
|
tick := time.NewTicker(c.Interval)
|
||||||
|
defer tick.Stop()
|
||||||
|
|
||||||
|
ex := newExecutor(c.maxConcurrentRequests)
|
||||||
|
defer ex.close()
|
||||||
|
|
||||||
|
mq := messageQueue{
|
||||||
|
maxBatchSize: c.BatchSize,
|
||||||
|
maxBatchBytes: c.maxBatchBytes(),
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg := <-c.msgs:
|
case msg := <-c.msgs:
|
||||||
c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg)
|
c.push(&mq, msg, wg, ex)
|
||||||
msgs = append(msgs, msg)
|
|
||||||
if len(msgs) == c.Size {
|
|
||||||
c.verbose("exceeded %d messages – flushing", c.Size)
|
|
||||||
c.sendAsync(msgs)
|
|
||||||
msgs = make([]interface{}, 0, c.Size)
|
|
||||||
}
|
|
||||||
case <-tick.C:
|
case <-tick.C:
|
||||||
if len(msgs) > 0 {
|
c.flush(&mq, wg, ex)
|
||||||
c.verbose("interval reached - flushing %d", len(msgs))
|
|
||||||
c.sendAsync(msgs)
|
|
||||||
msgs = make([]interface{}, 0, c.Size)
|
|
||||||
} else {
|
|
||||||
c.verbose("interval reached – nothing to send")
|
|
||||||
}
|
|
||||||
case <-c.quit:
|
case <-c.quit:
|
||||||
tick.Stop()
|
c.debugf("exit requested – draining messages")
|
||||||
c.verbose("exit requested – draining msgs")
|
|
||||||
// drain the msg channel.
|
// 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 {
|
for msg := range c.msgs {
|
||||||
c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg)
|
c.push(&mq, msg, wg, ex)
|
||||||
msgs = append(msgs, msg)
|
|
||||||
}
|
}
|
||||||
c.verbose("exit requested – flushing %d", len(msgs))
|
|
||||||
c.sendAsync(msgs)
|
c.flush(&mq, wg, ex)
|
||||||
c.wg.Wait()
|
c.debugf("exit")
|
||||||
c.verbose("exit")
|
|
||||||
c.shutdown <- struct{}{}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verbose log.
|
func (c *client) push(q *messageQueue, m Message, wg *sync.WaitGroup, ex *executor) {
|
||||||
func (c *Client) verbose(msg string, args ...interface{}) {
|
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 {
|
if c.Verbose {
|
||||||
c.Logger.Printf(msg, args...)
|
c.logf(format, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unconditional log.
|
func (c *client) logf(format string, args ...interface{}) {
|
||||||
func (c *Client) logf(msg string, args ...interface{}) {
|
c.Logger.Logf(format, args...)
|
||||||
c.Logger.Printf(msg, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set message timestamp if one is not already set.
|
func (c *client) errorf(format string, args ...interface{}) {
|
||||||
func (m *Message) setTimestamp(s string) {
|
c.Logger.Errorf(format, args...)
|
||||||
if m.Timestamp == "" {
|
}
|
||||||
m.Timestamp = s
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set message id.
|
func (c *client) notifyFailure(msgs []message, err error) {
|
||||||
func (m *Message) setMessageId(s string) {
|
if c.Callback != nil {
|
||||||
if m.MessageId == "" {
|
for _, m := range msgs {
|
||||||
m.MessageId = s
|
c.Callback.Failure(m.msg, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return formatted timestamp.
|
|
||||||
func timestamp(t time.Time) string {
|
|
||||||
return strftime.Format("%Y-%m-%dT%H:%M:%S%z", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return uuid string.
|
|
||||||
func uid() string {
|
|
||||||
return uuid.NewRandom().String()
|
|
||||||
}
|
|
||||||
|
|||||||
18
vendor/github.com/segmentio/analytics-go/circle.yml
сгенерированный
поставляемый
18
vendor/github.com/segmentio/analytics-go/circle.yml
сгенерированный
поставляемый
@@ -1,18 +0,0 @@
|
|||||||
machine:
|
|
||||||
environment:
|
|
||||||
IMPORT_PATH: "github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
pre:
|
|
||||||
- go get github.com/tools/godep
|
|
||||||
|
|
||||||
override:
|
|
||||||
- mkdir -p "$GOPATH/src/$IMPORT_PATH"
|
|
||||||
- rsync -azC --delete ./ "$GOPATH/src/$IMPORT_PATH/"
|
|
||||||
|
|
||||||
test:
|
|
||||||
pre:
|
|
||||||
- make vet
|
|
||||||
|
|
||||||
override:
|
|
||||||
- make test
|
|
||||||
173
vendor/github.com/segmentio/analytics-go/config.go
сгенерированный
поставляемый
Обычный файл
173
vendor/github.com/segmentio/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/segmentio/analytics-go/context.go
сгенерированный
поставляемый
Обычный файл
148
vendor/github.com/segmentio/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/segmentio/analytics-go/error.go
сгенерированный
поставляемый
Обычный файл
60
vendor/github.com/segmentio/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/segmentio/analytics-go/executor.go
сгенерированный
поставляемый
Обычный файл
53
vendor/github.com/segmentio/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()
|
||||||
|
}
|
||||||
40
vendor/github.com/segmentio/analytics-go/group.go
сгенерированный
поставляемый
Обычный файл
40
vendor/github.com/segmentio/analytics-go/group.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,40 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// 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) 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
|
||||||
|
}
|
||||||
31
vendor/github.com/segmentio/analytics-go/identify.go
сгенерированный
поставляемый
Обычный файл
31
vendor/github.com/segmentio/analytics-go/identify.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,31 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// 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) 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/segmentio/analytics-go/integrations.go
сгенерированный
поставляемый
Обычный файл
44
vendor/github.com/segmentio/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/segmentio/analytics-go/json.go
сгенерированный
поставляемый
Обычный файл
87
vendor/github.com/segmentio/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/segmentio/analytics-go/logger.go
сгенерированный
поставляемый
Обычный файл
47
vendor/github.com/segmentio/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))
|
||||||
|
}
|
||||||
128
vendor/github.com/segmentio/analytics-go/message.go
сгенерированный
поставляемый
Обычный файл
128
vendor/github.com/segmentio/analytics-go/message.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,128 @@
|
|||||||
|
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 {
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = 15000
|
||||||
|
)
|
||||||
32
vendor/github.com/segmentio/analytics-go/page.go
сгенерированный
поставляемый
Обычный файл
32
vendor/github.com/segmentio/analytics-go/page.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,32 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// 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) 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/segmentio/analytics-go/properties.go
сгенерированный
поставляемый
Обычный файл
117
vendor/github.com/segmentio/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"`
|
||||||
|
}
|
||||||
32
vendor/github.com/segmentio/analytics-go/screen.go
сгенерированный
поставляемый
Обычный файл
32
vendor/github.com/segmentio/analytics-go/screen.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,32 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// 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) 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/segmentio/analytics-go/timeout_15.go
сгенерированный
поставляемый
Обычный файл
16
vendor/github.com/segmentio/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/segmentio/analytics-go/timeout_16.go
сгенерированный
поставляемый
Обычный файл
10
vendor/github.com/segmentio/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
|
||||||
|
}
|
||||||
40
vendor/github.com/segmentio/analytics-go/track.go
сгенерированный
поставляемый
Обычный файл
40
vendor/github.com/segmentio/analytics-go/track.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,40 @@
|
|||||||
|
package analytics
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// 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) 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/segmentio/analytics-go/traits.go
сгенерированный
поставляемый
Обычный файл
89
vendor/github.com/segmentio/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
|
||||||
|
}
|
||||||
4
vendor/modules.txt
поставляемый
4
vendor/modules.txt
поставляемый
@@ -99,8 +99,6 @@ github.com/icrowley/fake
|
|||||||
github.com/inconshreveable/mousetrap
|
github.com/inconshreveable/mousetrap
|
||||||
# github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053
|
# github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053
|
||||||
github.com/jaytaylor/html2text
|
github.com/jaytaylor/html2text
|
||||||
# github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869
|
|
||||||
github.com/jehiah/go-strftime
|
|
||||||
# github.com/jmoiron/sqlx v1.2.0
|
# github.com/jmoiron/sqlx v1.2.0
|
||||||
github.com/jmoiron/sqlx
|
github.com/jmoiron/sqlx
|
||||||
github.com/jmoiron/sqlx/reflectx
|
github.com/jmoiron/sqlx/reflectx
|
||||||
@@ -195,7 +193,7 @@ github.com/rwcarlsen/goexif/exif
|
|||||||
github.com/rwcarlsen/goexif/tiff
|
github.com/rwcarlsen/goexif/tiff
|
||||||
# github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529
|
# github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529
|
||||||
github.com/sean-/seed
|
github.com/sean-/seed
|
||||||
# github.com/segmentio/analytics-go v2.0.1-0.20160426181448-2d840d861c32+incompatible
|
# github.com/segmentio/analytics-go v3.0.1+incompatible
|
||||||
github.com/segmentio/analytics-go
|
github.com/segmentio/analytics-go
|
||||||
# github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c
|
# github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c
|
||||||
github.com/segmentio/backo-go
|
github.com/segmentio/backo-go
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user