diff --git a/api/common/logging.go b/api/common/logging.go index c8eab5e5f..b752ed2c4 100644 --- a/api/common/logging.go +++ b/api/common/logging.go @@ -4,6 +4,7 @@ import ( "net/url" "os" + "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) @@ -18,6 +19,12 @@ func SetLogLevel(ll string) { logLevel = logrus.InfoLevel } logrus.SetLevel(logLevel) + + // this effectively just adds more gin log goodies + gin.SetMode(gin.ReleaseMode) + if logLevel == logrus.DebugLevel { + gin.SetMode(gin.DebugMode) + } } func SetLogDest(to, prefix string) { diff --git a/api/server/init.go b/api/server/init.go index b13efe0b7..e02403d40 100644 --- a/api/server/init.go +++ b/api/server/init.go @@ -6,26 +6,13 @@ import ( "os/signal" "strconv" + "github.com/fnproject/fn/api/common" "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" ) func init() { - logLevel, err := logrus.ParseLevel(getEnv(EnvLogLevel, DefaultLogLevel)) - if err != nil { - logrus.WithError(err).Fatalln("Invalid log level.") - } - logrus.SetLevel(logLevel) - + // gin is not nice by default, this can get set in logging initialization gin.SetMode(gin.ReleaseMode) - if logLevel == logrus.DebugLevel { - gin.SetMode(gin.DebugMode) - } - - // do this in init so that it's only run once & before server.New() which may - // start things that use spans, which are global. - // TODO there's not a great reason that our fn spans don't work w/ noop spans, should fix this really. - setupTracer(getEnv(EnvZipkinURL, "")) } func getEnv(key, fallback string) string { @@ -56,11 +43,11 @@ func contextWithSignal(ctx context.Context, signals ...os.Signal) (context.Conte for { select { case <-c: - logrus.Info("Halting...") + common.Logger(ctx).Info("Halting...") halt() return case <-ctx.Done(): - logrus.Info("Halting... Original server context canceled.") + common.Logger(ctx).Info("Halting... Original server context canceled.") halt() return } diff --git a/api/server/server.go b/api/server/server.go index 134d2f2ca..1081a8681 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -31,6 +31,8 @@ import ( const ( EnvLogLevel = "FN_LOG_LEVEL" + EnvLogDest = "FN_LOG_DEST" + EnvLogPrefix = "FN_LOG_PREFIX" EnvMQURL = "FN_MQ_URL" EnvDBURL = "FN_DB_URL" EnvLOGDBURL = "FN_LOGSTORE_URL" @@ -42,6 +44,7 @@ const ( // Defaults DefaultLogLevel = "info" + DefaultLogDest = "stderr" DefaultPort = 8080 ) @@ -99,6 +102,9 @@ func NewFromEnv(ctx context.Context, opts ...ServerOption) *Server { defaultDB = fmt.Sprintf("sqlite3://%s/data/fn.db", curDir) defaultMQ = fmt.Sprintf("bolt://%s/data/fn.mq", curDir) } + opts = append(opts, WithLogLevel(getEnv(EnvLogLevel, DefaultLogLevel))) + opts = append(opts, WithLogDest(getEnv(EnvLogDest, DefaultLogDest), getEnv(EnvLogPrefix, ""))) + opts = append(opts, WithTracer(getEnv(EnvZipkinURL, ""))) // do this early on, so below can use these opts = append(opts, WithDBURL(getEnv(EnvDBURL, defaultDB))) opts = append(opts, WithMQURL(getEnv(EnvMQURL, defaultMQ))) opts = append(opts, WithLogURL(getEnv(EnvLOGDBURL, ""))) @@ -116,6 +122,20 @@ func pwd() string { return strings.Replace(cwd, "\\", "/", -1) } +func WithLogLevel(ll string) ServerOption { + return func(ctx context.Context, s *Server) error { + common.SetLogLevel(ll) + return nil + } +} + +func WithLogDest(dst, prefix string) ServerOption { + return func(ctx context.Context, s *Server) error { + common.SetLogDest(dst, prefix) + return nil + } +} + func WithDBURL(dbURL string) ServerOption { return func(ctx context.Context, s *Server) error { if dbURL != "" { @@ -206,6 +226,9 @@ func WithAgent(agent agent.Agent) ServerOption { // New creates a new Functions server with the opts given. For convenience, users may // prefer to use NewFromEnv but New is more flexible if needed. func New(ctx context.Context, opts ...ServerOption) *Server { + span, ctx := opentracing.StartSpanFromContext(ctx, "server_init") + defer span.Finish() + log := common.Logger(ctx) s := &Server{ Router: gin.New(), @@ -260,50 +283,53 @@ func New(ctx context.Context, opts ...ServerOption) *Server { } // TODO need to fix this to handle the nil case better -func setupTracer(zipkinURL string) { - var ( - debugMode = false - serviceName = "fnserver" - serviceHostPort = "localhost:8080" // meh - zipkinHTTPEndpoint = zipkinURL - // ex: "http://zipkin:9411/api/v1/spans" - ) +func WithTracer(zipkinURL string) ServerOption { + return func(ctx context.Context, s *Server) error { + var ( + debugMode = false + serviceName = "fnserver" + serviceHostPort = "localhost:8080" // meh + zipkinHTTPEndpoint = zipkinURL + // ex: "http://zipkin:9411/api/v1/spans" + ) - var collector zipkintracer.Collector + var collector zipkintracer.Collector - // custom Zipkin collector to send tracing spans to Prometheus - promCollector, promErr := NewPrometheusCollector() - if promErr != nil { - logrus.WithError(promErr).Fatalln("couldn't start Prometheus trace collector") - } - - logger := zipkintracer.LoggerFunc(func(i ...interface{}) error { logrus.Error(i...); return nil }) - - if zipkinHTTPEndpoint != "" { - // Custom PrometheusCollector and Zipkin HTTPCollector - httpCollector, zipErr := zipkintracer.NewHTTPCollector(zipkinHTTPEndpoint, zipkintracer.HTTPLogger(logger)) - if zipErr != nil { - logrus.WithError(zipErr).Fatalln("couldn't start Zipkin trace collector") + // custom Zipkin collector to send tracing spans to Prometheus + promCollector, promErr := NewPrometheusCollector() + if promErr != nil { + logrus.WithError(promErr).Fatalln("couldn't start Prometheus trace collector") } - collector = zipkintracer.MultiCollector{httpCollector, promCollector} - } else { - // Custom PrometheusCollector only - collector = promCollector + + logger := zipkintracer.LoggerFunc(func(i ...interface{}) error { logrus.Error(i...); return nil }) + + if zipkinHTTPEndpoint != "" { + // Custom PrometheusCollector and Zipkin HTTPCollector + httpCollector, zipErr := zipkintracer.NewHTTPCollector(zipkinHTTPEndpoint, zipkintracer.HTTPLogger(logger)) + if zipErr != nil { + logrus.WithError(zipErr).Fatalln("couldn't start Zipkin trace collector") + } + collector = zipkintracer.MultiCollector{httpCollector, promCollector} + } else { + // Custom PrometheusCollector only + collector = promCollector + } + + ziptracer, err := zipkintracer.NewTracer(zipkintracer.NewRecorder(collector, debugMode, serviceHostPort, serviceName), + zipkintracer.ClientServerSameSpan(true), + zipkintracer.TraceID128Bit(true), + ) + if err != nil { + logrus.WithError(err).Fatalln("couldn't start tracer") + } + + // wrap the Zipkin tracer in a FnTracer which will also send spans to Prometheus + fntracer := NewFnTracer(ziptracer) + + opentracing.SetGlobalTracer(fntracer) + logrus.WithFields(logrus.Fields{"url": zipkinHTTPEndpoint}).Info("started tracer") + return nil } - - ziptracer, err := zipkintracer.NewTracer(zipkintracer.NewRecorder(collector, debugMode, serviceHostPort, serviceName), - zipkintracer.ClientServerSameSpan(true), - zipkintracer.TraceID128Bit(true), - ) - if err != nil { - logrus.WithError(err).Fatalln("couldn't start tracer") - } - - // wrap the Zipkin tracer in a FnTracer which will also send spans to Prometheus - fntracer := NewFnTracer(ziptracer) - - opentracing.SetGlobalTracer(fntracer) - logrus.WithFields(logrus.Fields{"url": zipkinHTTPEndpoint}).Info("started tracer") } func setMachineID() { diff --git a/docs/operating/options.md b/docs/operating/options.md index 174529e64..6caef3cf5 100644 --- a/docs/operating/options.md +++ b/docs/operating/options.md @@ -26,6 +26,8 @@ docker run -e VAR_NAME=VALUE ... | `FN_API_URL` | The primary Fn API URL to that this instance will talk to. In a production environment, this would be your load balancer URL. | N/A | | `FN_PORT `| Sets the port to run on | 8080 | | `FN_LOG_LEVEL` | Set to DEBUG to enable debugging | INFO | +| `FN_LOG_DEST` | Set a url to send logs to, instead of stderr. [scheme://][host][:port][/path]; default scheme to udp:// if none given, possible schemes: { udp, tcp, file } +| `FN_LOG_PREFIX` | If supplying a syslog url in `FN_LOG_DEST`, a prefix to add to each log line | `FN_API_CORS` | A comma separated list of URLs to enable [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for (or `*` for all domains). This corresponds to the allowed origins in the `Acccess-Control-Allow-Origin` header. | None | | `DOCKER_HOST` | Docker remote API URL. | /var/run/docker.sock | | `DOCKER_API_VERSION` | Docker remote API version. | 1.24 |