-
-
Notifications
You must be signed in to change notification settings - Fork 579
/
instrumentation.go
158 lines (135 loc) · 5.67 KB
/
instrumentation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package ddevapp
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/denisbrodbeck/machineid"
"github.com/drud/ddev/pkg/globalconfig"
"github.com/drud/ddev/pkg/nodeps"
"github.com/drud/ddev/pkg/output"
"github.com/drud/ddev/pkg/util"
"github.com/drud/ddev/pkg/version"
"gopkg.in/segmentio/analytics-go.v3"
"os"
"runtime"
"strconv"
"strings"
"time"
)
var hashedHostID string
// Define a no-op logger to prevent Segment log messages from being emitted
type SegmentNoopLogger struct{}
func (n *SegmentNoopLogger) Logf(format string, args ...interface{}) {}
func (n *SegmentNoopLogger) Errorf(format string, args ...interface{}) {}
// ReportableEvents is the list of events that we choose to report specifically.
// Excludes non-ddev custom commands.
var ReportableEvents = map[string]bool{"auth": true, "composer": true, "config": true, "debug": true, "delete": true, "describe": true, "exec": true, "export-db": true, "import-db": true, "import-files": true, "launch": true, "list": true, "logs": true, "mysql": true, "pause": true, "poweroff": true, "pull": true, "restart": true, "restore-snapshot": true, "sequelace": true, "sequelpro": true, "share": true, "snapshot": true, "ssh": true, "start": true, "stop": true, "xdebug": true}
// GetInstrumentationUser normally gets just the hashed hostID but if
// an explicit user is provided in global_config.yaml that will be prepended.
func GetInstrumentationUser() string {
return hashedHostID
}
// SetInstrumentationBaseTags sets the basic always-used tags for Segment
func SetInstrumentationBaseTags() {
runTime := util.TimeTrack(time.Now(), "SetInstrumentationBaseTags")
defer runTime()
if globalconfig.DdevGlobalConfig.InstrumentationOptIn {
dockerVersion, _ := version.GetDockerVersion()
timezone, _ := time.Now().In(time.Local).Zone()
lang := os.Getenv("LANG")
nodeps.InstrumentationTags["OS"] = runtime.GOOS
nodeps.InstrumentationTags["architecture"] = runtime.GOARCH
wslDistro := nodeps.GetWSLDistro()
if wslDistro != "" {
nodeps.InstrumentationTags["isWSL"] = "true"
nodeps.InstrumentationTags["wslDistro"] = wslDistro
}
nodeps.InstrumentationTags["dockerVersion"] = dockerVersion
nodeps.InstrumentationTags["dockerToolbox"] = strconv.FormatBool(false)
nodeps.InstrumentationTags["version"] = version.VERSION
nodeps.InstrumentationTags["ServerHash"] = GetInstrumentationUser()
nodeps.InstrumentationTags["timezone"] = timezone
nodeps.InstrumentationTags["language"] = lang
}
}
// getProjectHash combines the machine ID and project name and then
// hashes the result, so we can end up with a unique project id
func getProjectHash(projectName string) string {
ph := hmac.New(sha256.New, []byte(GetInstrumentationUser()+projectName))
_, _ = ph.Write([]byte("phash"))
return hex.EncodeToString(ph.Sum(nil))
}
// SetInstrumentationAppTags creates app-specific tags for Segment
func (app *DdevApp) SetInstrumentationAppTags() {
runTime := util.TimeTrack(time.Now(), "SetInstrumentationAppTags")
defer runTime()
ignoredProperties := []string{"approot", "hostname", "hostnames", "name", "router_status_log", "shortroot"}
describeTags, _ := app.Describe(false)
for key, val := range describeTags {
// Make sure none of the "URL" attributes or the ignoredProperties comes through
if strings.Contains(strings.ToLower(key), "url") || nodeps.ArrayContainsString(ignoredProperties, key) {
continue
}
nodeps.InstrumentationTags[key] = fmt.Sprintf("%v", val)
}
nodeps.InstrumentationTags["ProjectID"] = getProjectHash(app.Name)
}
// SegmentUser does the enqueue of the Identify action, identifying the user
// Here we just use the hashed hostid as the user id
func SegmentUser(client analytics.Client, hashedID string) error {
timezone, _ := time.Now().In(time.Local).Zone()
lang := os.Getenv("LANG")
err := client.Enqueue(analytics.Identify{
UserId: hashedID,
Context: &analytics.Context{App: analytics.AppInfo{Name: "ddev", Version: version.VERSION}, OS: analytics.OSInfo{Name: runtime.GOOS}, Locale: lang, Timezone: timezone},
Traits: analytics.Traits{"instrumentation_user": globalconfig.DdevGlobalConfig.InstrumentationUser},
})
if err != nil {
return err
}
return nil
}
// SegmentEvent provides the event and traits that go with it.
func SegmentEvent(client analytics.Client, hashedID string, event string) error {
if _, ok := ReportableEvents[event]; !ok {
// There's no need to waste people's time on custom commands.
return nil
}
properties := analytics.NewProperties()
for key, val := range nodeps.InstrumentationTags {
if val != "" {
properties = properties.Set(key, val)
}
}
timezone, _ := time.Now().In(time.Local).Zone()
lang := os.Getenv("LANG")
err := client.Enqueue(analytics.Track{
UserId: hashedID,
Event: event,
Properties: properties,
Context: &analytics.Context{App: analytics.AppInfo{Name: "ddev", Version: version.VERSION}, OS: analytics.OSInfo{Name: runtime.GOOS}, Locale: lang, Timezone: timezone},
})
return err
}
// SendInstrumentationEvents does the actual send to segment
func SendInstrumentationEvents(event string) {
runTime := util.TimeTrack(time.Now(), "SendInstrumentationEvents")
defer runTime()
if globalconfig.DdevGlobalConfig.InstrumentationOptIn && globalconfig.IsInternetActive() {
client, _ := analytics.NewWithConfig(version.SegmentKey, analytics.Config{
Logger: &SegmentNoopLogger{},
})
err := SegmentEvent(client, GetInstrumentationUser(), event)
if err != nil {
output.UserOut.Debugf("error sending event to segment: %v", err)
}
err = client.Close()
if err != nil {
output.UserOut.Debugf("segment analytics client.close() failed: %v", err)
}
}
}
func init() {
hashedHostID, _ = machineid.ProtectedID("ddev")
}