[MM-16620] Properly handle analytics client life cycle

Properly handle analytics client life cycle

This change improves the life cycle management of the analytics
client. It does so in two primary ways:
 - Move the client from being a shared pointer to belonging to the
   Server struct.
 - Expose the client.Close() method for proper shutdown of the
   client.
Этот коммит содержится в:
Gabe Jackson
2019-07-03 12:28:51 -04:00
коммит произвёл GitHub
родитель df869e3f86
Коммит f56a8f5a99
3 изменённых файлов: 59 добавлений и 58 удалений

Просмотреть файл

@@ -58,15 +58,13 @@ const (
TRACK_PLUGINS = "plugins" TRACK_PLUGINS = "plugins"
) )
var client *analytics.Client
func (a *App) SendDailyDiagnostics() { func (a *App) SendDailyDiagnostics() {
a.sendDailyDiagnostics(false) a.sendDailyDiagnostics(false)
} }
func (a *App) sendDailyDiagnostics(override bool) { func (a *App) sendDailyDiagnostics(override bool) {
if *a.Config().LogSettings.EnableDiagnostics && a.IsLeader() && (!strings.Contains(SEGMENT_KEY, "placeholder") || override) { if *a.Config().LogSettings.EnableDiagnostics && a.IsLeader() && (!strings.Contains(SEGMENT_KEY, "placeholder") || override) {
a.initDiagnostics("") a.Srv.initDiagnostics("")
a.trackActivity() a.trackActivity()
a.trackConfig() a.trackConfig()
a.trackLicense() a.trackLicense()
@@ -76,24 +74,8 @@ func (a *App) sendDailyDiagnostics(override bool) {
} }
} }
func (a *App) initDiagnostics(endpoint string) {
if client == nil {
client = analytics.New(SEGMENT_KEY)
client.Logger = a.Log.StdLog(mlog.String("source", "segment"))
// For testing
if endpoint != "" {
client.Endpoint = endpoint
client.Verbose = true
client.Size = 1
}
client.Identify(&analytics.Identify{
UserId: a.DiagnosticId(),
})
}
}
func (a *App) SendDiagnostic(event string, properties map[string]interface{}) { func (a *App) SendDiagnostic(event string, properties map[string]interface{}) {
client.Track(&analytics.Track{ a.Srv.diagnosticClient.Track(&analytics.Track{
Event: event, Event: event,
UserId: a.DiagnosticId(), UserId: a.DiagnosticId(),
Properties: properties, Properties: properties,

Просмотреть файл

@@ -4,32 +4,18 @@
package app package app
import ( import (
"bytes" "io/ioutil"
"io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
) )
func newTestServer() (chan string, *httptest.Server) {
result := make(chan string, 100)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
buf := bytes.NewBuffer(nil)
io.Copy(buf, r.Body)
result <- buf.String()
}))
return result, server
}
func TestPluginSetting(t *testing.T) { func TestPluginSetting(t *testing.T) {
settings := &model.PluginSettings{ settings := &model.PluginSettings{
Plugins: map[string]map[string]interface{}{ Plugins: map[string]map[string]interface{}{
@@ -57,42 +43,42 @@ func TestPluginActivated(t *testing.T) {
} }
func TestDiagnostics(t *testing.T) { func TestDiagnostics(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
if testing.Short() { if testing.Short() {
t.SkipNow() t.SkipNow()
} }
data, server := newTestServer() th := Setup(t).InitBasic()
defer th.TearDown()
data := make(chan string, 100)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)
data <- string(body)
}))
defer server.Close() defer server.Close()
diagnosticId := "i am not real" diagnosticID := "test-diagnostic-id-12345"
th.App.SetDiagnosticId(diagnosticId) th.App.SetDiagnosticId(diagnosticID)
th.App.initDiagnostics(server.URL) th.Server.initDiagnostics(server.URL)
// Should send a client identify message // Should send a client identify message
select { select {
case identifyMessage := <-data: case identifyMessage := <-data:
t.Log("Got idmessage:\n" + identifyMessage) require.Contains(t, identifyMessage, diagnosticID)
if !strings.Contains(identifyMessage, diagnosticId) {
t.Fail()
}
case <-time.After(time.Second * 1): case <-time.After(time.Second * 1):
t.Fatal("Did not receive ID message") t.Fatal("Did not receive ID message")
} }
t.Run("Send", func(t *testing.T) { t.Run("Send", func(t *testing.T) {
const TEST_VALUE = "stuff548959847" testValue := "test-send-value-6789"
th.App.SendDiagnostic("Testing Diagnostic", map[string]interface{}{ th.App.SendDiagnostic("Testing Diagnostic", map[string]interface{}{
"hey": TEST_VALUE, "hey": testValue,
}) })
select { select {
case result := <-data: case result := <-data:
t.Log("Got diagnostic:\n" + result) require.Contains(t, result, testValue)
if !strings.Contains(result, TEST_VALUE) {
t.Fail()
}
case <-time.After(time.Second * 1): case <-time.After(time.Second * 1):
t.Fatal("Did not receive diagnostic") t.Fatal("Did not receive diagnostic")
} }
@@ -101,7 +87,7 @@ func TestDiagnostics(t *testing.T) {
t.Run("SendDailyDiagnostics", func(t *testing.T) { t.Run("SendDailyDiagnostics", func(t *testing.T) {
th.App.sendDailyDiagnostics(true) th.App.sendDailyDiagnostics(true)
info := "" var info string
// Collect the info sent. // Collect the info sent.
Loop: Loop:
for { for {
@@ -142,9 +128,7 @@ func TestDiagnostics(t *testing.T) {
TRACK_CONFIG_MESSAGE_EXPORT, TRACK_CONFIG_MESSAGE_EXPORT,
TRACK_PLUGINS, TRACK_PLUGINS,
} { } {
if !strings.Contains(info, item) { require.Contains(t, info, item)
t.Fatal("Sent diagnostics missing item: " + item)
}
} }
}) })

Просмотреть файл

@@ -20,6 +20,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/cors" "github.com/rs/cors"
analytics "github.com/segmentio/analytics-go"
"github.com/throttled/throttled" "github.com/throttled/throttled"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
@@ -100,7 +101,9 @@ type Server struct {
clientConfig map[string]string clientConfig map[string]string
clientConfigHash string clientConfigHash string
limitedClientConfig map[string]string limitedClientConfig map[string]string
diagnosticId string
diagnosticId string
diagnosticClient *analytics.Client
phase2PermissionsMigrationComplete bool phase2PermissionsMigrationComplete bool
@@ -321,6 +324,11 @@ func (s *Server) Shutdown() error {
s.RunOldAppShutdown() s.RunOldAppShutdown()
err := s.shutdownDiagnostics()
if err != nil {
mlog.Error(fmt.Sprintf("Unable to cleanly shutdown diagnostic client: %s", err))
}
s.StopHTTPServer() s.StopHTTPServer()
s.WaitForGoroutines() s.WaitForGoroutines()
@@ -734,3 +742,30 @@ func (s *Server) StartElasticsearch() {
} }
}) })
} }
func (s *Server) initDiagnostics(endpoint string) {
if s.diagnosticClient == nil {
client := analytics.New(SEGMENT_KEY)
client.Logger = s.Log.StdLog(mlog.String("source", "segment"))
// For testing
if endpoint != "" {
client.Endpoint = endpoint
client.Verbose = true
client.Size = 1
}
client.Identify(&analytics.Identify{
UserId: s.diagnosticId,
})
s.diagnosticClient = client
}
}
// ShutdownDiagnostics closes the diagnostic client.
func (s *Server) shutdownDiagnostics() error {
if s.diagnosticClient != nil {
return s.diagnosticClient.Close()
}
return nil
}