diff --git a/Godeps b/Godeps index f194ce9d7f..001f26c705 100644 --- a/Godeps +++ b/Godeps @@ -6,6 +6,7 @@ github.com/elazarl/go-bindata-assetfs 9a6736ed45b44bf3835afeebb3034b57ed329f3e github.com/gogo/protobuf 6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b github.com/google/go-github 1bc362c7737e51014af7299e016444b654095ad9 github.com/google/go-querystring 9235644dd9e52eeae6fa48efd539fdc351a0af53 +github.com/influxdata/usage-client 6d3895376368aa52a3a81d2a16e90f0f52371967 github.com/jessevdk/go-flags 4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa github.com/satori/go.uuid b061729afc07e77a8aa4fad0a2fd840958f1942a github.com/tylerb/graceful 50a48b6e73fcc75b45e22c05b79629a67c79e938 diff --git a/Makefile b/Makefile index cc580e6373..7a0074b7cb 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,9 @@ VERSION ?= $$(git describe --always --tags) COMMIT ?= $$(git rev-parse --short=8 HEAD) -BRANCH ?= $$(git rev-parse --abbrev-ref HEAD | tr / _) -BUILD_TIME ?= $$(date +%FT%T%z) SOURCES := $(shell find . -name '*.go') -LDFLAGS=-ldflags "-s -X main.Version=${VERSION} -X main.Commit=${COMMIT} -X main.BuildTime=${BUILD_TIME} -X main.Branch=${BRANCH}" +LDFLAGS=-ldflags "-s -X main.Version=${VERSION} -X main.Commit=${COMMIT}" BINARY=chronograf default: dep build diff --git a/canned/apps.go b/canned/apps.go index a3ff982642..aaa3d69534 100644 --- a/canned/apps.go +++ b/canned/apps.go @@ -17,7 +17,7 @@ import ( const AppExt = ".json" -var logger = clog.New() +var logger = clog.New(clog.DebugLevel) // Apps are canned JSON layouts. Implements LayoutStore. type Apps struct { diff --git a/chronograf.go b/chronograf.go index dfec8b8fc1..ea09d778b2 100644 --- a/chronograf.go +++ b/chronograf.go @@ -27,11 +27,12 @@ func (e Error) Error() string { // provides methods to trigger log messages at various alert levels and a // WithField method to set keys for a structured log message. type Logger interface { + Debug(...interface{}) Info(...interface{}) Warn(...interface{}) - Debug(...interface{}) - Panic(...interface{}) Error(...interface{}) + Fatal(...interface{}) + Panic(...interface{}) WithField(string, interface{}) Logger } diff --git a/cmd/chronograf-server/main.go b/cmd/chronograf-server/main.go index bf667e68c8..e875990da5 100644 --- a/cmd/chronograf-server/main.go +++ b/cmd/chronograf-server/main.go @@ -10,14 +10,17 @@ import ( // Build flags var ( - Version = "" - Commit = "" - BuildTime = "" - Branch = "" + Version = "" + Commit = "" ) func main() { - srv := server.Server{} + srv := server.Server{ + BuildInfo: server.BuildInfo{ + Version: Version, + Commit: Commit, + }, + } parser := flags.NewParser(&srv, flags.Default) parser.ShortDescription = `Chronograf` diff --git a/influx/influx_test.go b/influx/influx_test.go index 7d7b48c8fb..1a89e219e4 100644 --- a/influx/influx_test.go +++ b/influx/influx_test.go @@ -26,7 +26,7 @@ func Test_Influx_MakesRequestsToQueryEndpoint(t *testing.T) { defer ts.Close() var series chronograf.TimeSeries - series, err := influx.NewClient(ts.URL, log.New()) + series, err := influx.NewClient(ts.URL, log.New(log.DebugLevel)) if err != nil { t.Fatal("Unexpected error initializing client: err:", err) } @@ -59,7 +59,7 @@ func Test_Influx_CancelsInFlightRequests(t *testing.T) { ts.Close() }() - series, _ := influx.NewClient(ts.URL, log.New()) + series, _ := influx.NewClient(ts.URL, log.New(log.DebugLevel)) ctx, cancel := context.WithCancel(context.Background()) errs := make(chan (error)) @@ -102,7 +102,7 @@ func Test_Influx_CancelsInFlightRequests(t *testing.T) { } func Test_Influx_RejectsInvalidHosts(t *testing.T) { - _, err := influx.NewClient(":", log.New()) + _, err := influx.NewClient(":", log.New(log.DebugLevel)) if err == nil { t.Fatal("Expected err but was nil") } @@ -114,7 +114,7 @@ func Test_Influx_ReportsInfluxErrs(t *testing.T) { })) defer ts.Close() - cl, err := influx.NewClient(ts.URL, log.New()) + cl, err := influx.NewClient(ts.URL, log.New(log.DebugLevel)) if err != nil { t.Fatal("Encountered unexpected error while initializing influx client: err:", err) } diff --git a/log/log.go b/log/log.go index 786d65107c..2cfbb25133 100644 --- a/log/log.go +++ b/log/log.go @@ -7,11 +7,56 @@ import ( "github.com/influxdata/chronograf" ) +// Level type +type Level uint8 + +// These are the different logging levels. +const ( + // PanicLevel level, highest level of severity. Logs and then calls panic with the + // message passed to Debug, Info, ... + PanicLevel Level = iota + // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the + // logging level is set to Panic. + FatalLevel + // ErrorLevel level. Logs. Used for errors that should definitely be noted. + // Commonly used for hooks to send errors to an error tracking service. + ErrorLevel + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel + // InfoLevel level. General operational entries about what's going on inside the + // application. + InfoLevel + // DebugLevel level. Usually only enabled when debugging. Very verbose logging. + DebugLevel +) + +// ParseLevel takes a string level and returns the Logrus log level constant. +func ParseLevel(lvl string) Level { + switch lvl { + case "panic": + return PanicLevel + case "fatal": + return FatalLevel + case "error": + return ErrorLevel + case "warn": + return WarnLevel + case "info": + return InfoLevel + default: + return DebugLevel + } +} + // LogrusLogger is a chronograf.Logger that uses logrus to process logs type logrusLogger struct { l *logrus.Entry } +func (ll *logrusLogger) Debug(items ...interface{}) { + ll.l.Debug(items...) +} + func (ll *logrusLogger) Info(items ...interface{}) { ll.l.Info(items...) } @@ -20,28 +65,28 @@ func (ll *logrusLogger) Warn(items ...interface{}) { ll.l.Warn(items...) } -func (ll *logrusLogger) Debug(items ...interface{}) { - ll.l.Debug(items...) +func (ll *logrusLogger) Error(items ...interface{}) { + ll.l.Error(items...) +} + +func (ll *logrusLogger) Fatal(items ...interface{}) { + ll.l.Fatal(items...) } func (ll *logrusLogger) Panic(items ...interface{}) { ll.l.Panic(items...) } -func (ll *logrusLogger) Error(items ...interface{}) { - ll.l.Error(items...) -} - func (ll *logrusLogger) WithField(key string, value interface{}) chronograf.Logger { return &logrusLogger{ll.l.WithField(key, value)} } -func New() chronograf.Logger { +func New(l Level) chronograf.Logger { logger := &logrus.Logger{ Out: os.Stderr, Formatter: new(logrus.TextFormatter), Hooks: make(logrus.LevelHooks), - Level: logrus.DebugLevel, + Level: logrus.Level(l), } return &logrusLogger{ diff --git a/server/auth_test.go b/server/auth_test.go index 93165e5352..cb231aebbb 100644 --- a/server/auth_test.go +++ b/server/auth_test.go @@ -9,8 +9,8 @@ import ( "time" "github.com/influxdata/chronograf" - "github.com/influxdata/chronograf/server" clog "github.com/influxdata/chronograf/log" + "github.com/influxdata/chronograf/server" ) func TestCookieExtractor(t *testing.T) { @@ -180,7 +180,7 @@ func TestAuthorizedToken(t *testing.T) { Principal: test.Principal, } - logger := clog.New() + logger := clog.New(clog.DebugLevel) handler := server.AuthorizedToken(a, e, logger, next) handler.ServeHTTP(w, req) if w.Code != test.Code { diff --git a/server/server.go b/server/server.go index 164566780c..e27f43f8e4 100644 --- a/server/server.go +++ b/server/server.go @@ -1,8 +1,10 @@ package server import ( + "math/rand" "net" "net/http" + "runtime" "strconv" "time" @@ -13,12 +15,11 @@ import ( "github.com/influxdata/chronograf/layouts" clog "github.com/influxdata/chronograf/log" "github.com/influxdata/chronograf/uuid" + client "github.com/influxdata/usage-client/v1" flags "github.com/jessevdk/go-flags" "github.com/tylerb/graceful" ) -var logger = clog.New() - // Server for the chronograf API type Server struct { Host string `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"` @@ -35,18 +36,28 @@ type Server struct { TokenSecret string `short:"t" long:"token-secret" description:"Secret to sign tokens" env:"TOKEN_SECRET"` GithubClientID string `short:"i" long:"github-client-id" description:"Github Client ID for OAuth 2 support" env:"GH_CLIENT_ID"` GithubClientSecret string `short:"s" long:"github-client-secret" description:"Github Client Secret for OAuth 2 support" env:"GH_CLIENT_SECRET"` + ReportingDisabled bool `short:"r" long:"reporting-disabled" description:"Disable reporting of usage stats (os,arch,version,cluster_id) once every 24hr" env:"REPORTING_DISABLED"` + LogLevel string `short:"l" long:"log-level" value-name:"choice" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"fatal" choice:"panic" default:"info" description:"Set the logging level" env:"LOG_LEVEL"` + + BuildInfo BuildInfo Listener net.Listener handler http.Handler } +type BuildInfo struct { + Version string + Commit string +} + func (s *Server) useAuth() bool { return s.TokenSecret != "" && s.GithubClientID != "" && s.GithubClientSecret != "" } // Serve starts and runs the chronograf server func (s *Server) Serve() error { - service := openService(s.BoltPath, s.CannedPath) + logger := clog.New(clog.ParseLevel(s.LogLevel)) + service := openService(s.BoltPath, s.CannedPath, logger) s.handler = NewMux(MuxOpts{ Develop: s.Develop, TokenSecret: s.TokenSecret, @@ -70,9 +81,13 @@ func (s *Server) Serve() error { httpServer.TCPKeepAlive = 1 * time.Minute httpServer.Handler = s.handler + if !s.ReportingDisabled { + go reportUsageStats(s.BuildInfo, logger) + } + logger. WithField("component", "server"). - Info("Serving chronograf at http://%s", s.Listener.Addr()) + Info("Serving chronograf at http://", s.Listener.Addr()) if err := httpServer.Serve(s.Listener); err != nil { logger. @@ -83,12 +98,12 @@ func (s *Server) Serve() error { logger. WithField("component", "server"). - Info("Stopped serving chronograf at http://%s", s.Listener.Addr()) + Info("Stopped serving chronograf at http://", s.Listener.Addr()) return nil } -func openService(boltPath, cannedPath string) Service { +func openService(boltPath, cannedPath string, logger chronograf.Logger) Service { db := bolt.NewClient() db.Path = boltPath if err := db.Open(); err != nil { @@ -115,3 +130,39 @@ func openService(boltPath, cannedPath string) Service { LayoutStore: layouts, } } + +// reportUsageStats starts periodic server reporting. +func reportUsageStats(bi BuildInfo, logger chronograf.Logger) { + rand.Seed(time.Now().UTC().UnixNano()) + serverID := strconv.FormatUint(uint64(rand.Int63()), 10) + reporter := client.New("") + u := &client.Usage{ + Product: "chronograf", + Data: []client.UsageData{ + { + Values: client.Values{ + "os": runtime.GOOS, + "arch": runtime.GOARCH, + "version": bi.Version, + "cluster_id": serverID, + }, + }, + }, + } + l := logger.WithField("component", "usage"). + WithField("reporting_addr", reporter.URL). + WithField("freq", "24h"). + WithField("stats", "os,arch,version,cluster_id") + l.Info("Reporting usage stats") + reporter.Save(u) + + ticker := time.NewTicker(24 * time.Hour) + defer ticker.Stop() + for { + select { + case <-ticker.C: + l.Debug("Reporting usage stats") + go reporter.Save(u) + } + } +}