diff --git a/ctl/server.go b/ctl/server.go index 83edb5456..5379ce0d3 100644 --- a/ctl/server.go +++ b/ctl/server.go @@ -21,6 +21,7 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) { flags.StringVar(&srv.Config.AdvertiseGRPC, "advertise-grpc", srv.Config.AdvertiseGRPC, "Address to advertise externally for gRPC.") flags.IntVar(&srv.Config.MaxWritesPerRequest, "max-writes-per-request", srv.Config.MaxWritesPerRequest, "Number of write commands per request.") flags.StringVar(&srv.Config.LogPath, "log-path", srv.Config.LogPath, "Log path") + flags.StringVar(&srv.Config.QueryLogPath , "query-log-path", srv.Config.QueryLogPath, "Path to save user queries") flags.BoolVar(&srv.Config.Verbose, "verbose", srv.Config.Verbose, "Enable verbose logging") flags.Uint64Var(&srv.Config.MaxMapCount, "max-map-count", srv.Config.MaxMapCount, "Limits the maximum number of active mmaps. FeatureBase will fall back to reading files once this is exhausted. Set below your system's vm.max_map_count.") flags.Uint64Var(&srv.Config.MaxFileCount, "max-file-count", srv.Config.MaxFileCount, "Soft limit on the maximum number of fragment files FeatureBase keeps open simultaneously.") diff --git a/http/handler.go b/http/handler.go index 9e626babf..39c99e5d2 100644 --- a/http/handler.go +++ b/http/handler.go @@ -51,6 +51,8 @@ type Handler struct { logger logger.Logger + querylogger logger.Logger + // Keeps the query argument validators for each handler validators map[string]*queryValidationSpec @@ -127,6 +129,13 @@ func OptHandlerLogger(logger logger.Logger) handlerOption { } } +func OptHandlerQueryLogger(logger logger.Logger) handlerOption { + return func(h *Handler) error { + h.querylogger = logger + return nil + } +} + // OptHandlerListener set the listener that will be used by the HTTP server. // Url must be the advertised URL. It will be used to show a log to the user // about where the Web UI is. This option is mandatory. diff --git a/server.go b/server.go index 8858ab122..ba5891863 100644 --- a/server.go +++ b/server.go @@ -67,6 +67,7 @@ type Server struct { // nolint: maligned systemInfo SystemInfo gcNotifier GCNotifier logger logger.Logger + querylogger logger.Logger snapshotQueue SnapshotQueue nodeID string @@ -112,6 +113,13 @@ func OptServerLogger(l logger.Logger) ServerOption { } } +func OptServerQueryLogger(l logger.Logger) ServerOption { + return func(s *Server) error { + s.querylogger = l + return nil + } +} + // OptServerReplicaN is a functional option on Server // used to set the number of replicas. func OptServerReplicaN(n int) ServerOption { diff --git a/server/config.go b/server/config.go index c215d1596..7801b8ce0 100644 --- a/server/config.go +++ b/server/config.go @@ -81,6 +81,9 @@ type Config struct { // LogPath configures where Pilosa will write logs. LogPath string `toml:"log-path"` + // QueryLogPath, security logs + QueryLogPath string `toml:"query-log-path"` + // Verbose toggles verbose logging which can be useful for debugging. Verbose bool `toml:"verbose"` diff --git a/server/server.go b/server/server.go index a6d0049ae..d06b52ba8 100644 --- a/server/server.go +++ b/server/server.go @@ -68,7 +68,9 @@ type Command struct { done chan struct{} logOutput io.Writer + querylogOutput io.Writer logger loggerLogger + querylogger loggerLogger Handler pilosa.Handler grpcServer *grpcServer @@ -334,6 +336,10 @@ func (m *Command) SetupServer() error { if err != nil { return errors.Wrap(err, "setting up logger") } + err = m.setupQueryLogger() + if err != nil { + return errors.Wrap(err, "setting up querylogger") + } m.logger.Infof("%s", pilosa.VersionInfo(m.Config.Future.Rename)) @@ -473,6 +479,7 @@ func (m *Command) SetupServer() error { pilosa.OptServerOpenTranslateReader(http.GetOpenTranslateReaderWithLockerFunc(c, &sync.Mutex{})), pilosa.OptServerOpenIDAllocator(pilosa.OpenIDAllocator), pilosa.OptServerLogger(m.logger), + pilosa.OptServerQueryLogger(m.querylogger), pilosa.OptServerSystemInfo(gopsutil.NewSystemInfo()), pilosa.OptServerGCNotifier(gcnotify.NewActiveGCNotifier()), pilosa.OptServerStatsClient(statsClient), @@ -527,6 +534,7 @@ func (m *Command) SetupServer() error { http.OptHandlerAllowedOrigins(m.Config.Handler.AllowedOrigins), http.OptHandlerAPI(m.API), http.OptHandlerLogger(m.logger), + http.OptHandlerQueryLogger(m.querylogger), http.OptHandlerFileSystem(&statik.FileSystem{}), http.OptHandlerListener(m.ln, m.Config.Advertise), http.OptHandlerCloseTimeout(m.closeTimeout), @@ -576,6 +584,40 @@ func (m *Command) setupLogger() error { return nil } +func (m *Command) setupQueryLogger() error { + var f *logger.FileWriter + var err error + + if m.Config.QueryLogPath == "" { + f, err = logger.NewFileWriterMode( "queries/query.log", 600) + if err != nil { + return errors.Wrap(err, "opening file") + } + } else { + f, err = logger.NewFileWriterMode(m.Config.QueryLogPath , 600) + if err != nil { + return errors.Wrap(err, "opening file") + } + } + m.querylogOutput = f + + m.querylogger = logger.NewStandardLogger(m.querylogOutput) + + sighup := make(chan os.Signal, 1) + signal.Notify(sighup, syscall.SIGHUP) + go func() { + for { + // reopen log file on SIGHUP + <-sighup + err = f.Reopen() + if err != nil { + m.querylogger.Infof("reopen: %s\n", err.Error()) + } + } + }() + return nil +} + // Close shuts down the server. func (m *Command) Close() error { select {