diff --git a/.gitignore b/.gitignore index 4c2ce9d9eba..b6907cd2dad 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ default.pd .DS_Store tags /.retools/ +default* diff --git a/Gopkg.lock b/Gopkg.lock index e2379a0b2ac..a28a64dce7d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -325,6 +325,14 @@ pruneopts = "NUT" revision = "eec48234540b323cdfc3e92c4341d667fa245178" +[[projects]] + branch = "master" + digest = "1:1c69e1f3a2fd78cfd7fb489411cb620c110f7aded247db5c9ca00a073c2971f7" + name = "github.com/pingcap/log" + packages = ["."] + pruneopts = "NUT" + revision = "bd41d9273596826ed2cc8c70b3e8d307f459eca6" + [[projects]] digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121" name = "github.com/pkg/errors" @@ -467,7 +475,7 @@ version = "v1.1.0" [[projects]] - digest = "1:25531f2a1f9e75b832a945c670bf84f8caf5bd0fe8d8ad5bab5f13e162680922" + digest = "1:4af5a022f8c5c97734c3c6fad58fdce078e2322ddc98d3102b225a729f03916e" name = "go.uber.org/zap" packages = [ ".", @@ -478,8 +486,8 @@ "zapcore", ] pruneopts = "NUT" - revision = "eeedf312bc6c57391d84767a4cd413f02a917974" - version = "v1.8.0" + revision = "27376062155ad36be76b0f12cf1572a221d3a48c" + version = "v1.10.0" [[projects]] branch = "master" @@ -643,6 +651,7 @@ "github.com/pingcap/kvproto/pkg/eraftpb", "github.com/pingcap/kvproto/pkg/metapb", "github.com/pingcap/kvproto/pkg/pdpb", + "github.com/pingcap/log", "github.com/pkg/errors", "github.com/prometheus/client_golang/prometheus", "github.com/prometheus/client_golang/prometheus/push", diff --git a/Gopkg.toml b/Gopkg.toml index 9c97b30373c..241c25ba72f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -43,3 +43,11 @@ [[override]] name = "github.com/BurntSushi/toml" revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" + +[[constraint]] + branch = "master" + name = "github.com/pingcap/log" + +[[constraint]] + name = "go.uber.org/zap" + version = "1.9.1" diff --git a/cmd/pd-server/main.go b/cmd/pd-server/main.go index 39c457415ce..8cf1306930c 100644 --- a/cmd/pd-server/main.go +++ b/cmd/pd-server/main.go @@ -16,18 +16,18 @@ package main import ( "context" "flag" - "fmt" "os" "os/signal" "syscall" - "github.com/grpc-ecosystem/go-grpc-prometheus" + grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + log "github.com/pingcap/log" "github.com/pingcap/pd/pkg/logutil" "github.com/pingcap/pd/pkg/metricutil" "github.com/pingcap/pd/server" "github.com/pingcap/pd/server/api" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" + "go.uber.org/zap" // Register schedulers. _ "github.com/pingcap/pd/server/schedulers" @@ -41,7 +41,7 @@ func main() { if cfg.Version { server.PrintPDInfo() - os.Exit(0) + exit(0) } defer logutil.LogPanic() @@ -49,14 +49,24 @@ func main() { switch errors.Cause(err) { case nil: case flag.ErrHelp: - os.Exit(0) + exit(0) default: - log.Fatalf("parse cmd flags error: %s\n", fmt.Sprintf("%+v", err)) + log.Fatal("parse cmd flags error", zap.Error(err)) } + // New zap logger + err = cfg.SetupLogger() + if err == nil { + log.ReplaceGlobals(cfg.GetZapLogger(), cfg.GetZapLogProperties()) + } else { + log.Fatal("initialize logger error", zap.Error(err)) + } + // Flushing any buffered log entries + defer log.Sync() + // The old logger err = logutil.InitLogger(&cfg.Log) if err != nil { - log.Fatalf("initialize logger error: %s\n", fmt.Sprintf("%+v", err)) + log.Fatal("initialize logger error", zap.Error(err)) } server.LogPDInfo() @@ -72,15 +82,15 @@ func main() { err = server.PrepareJoinCluster(cfg) if err != nil { - log.Fatal("join error ", fmt.Sprintf("%+v", err)) + log.Fatal("join meet error", zap.Error(err)) } svr, err := server.CreateServer(cfg, api.NewHandler) if err != nil { - log.Fatalf("create server failed: %v", fmt.Sprintf("%+v", err)) + log.Fatal("create server failed", zap.Error(err)) } if err = server.InitHTTPClient(svr); err != nil { - log.Fatalf("initial http client for api handler failed: %v", fmt.Sprintf("%+v", err)) + log.Fatal("initial http client for api handler failed", zap.Error(err)) } sc := make(chan os.Signal, 1) @@ -98,17 +108,22 @@ func main() { }() if err := svr.Run(ctx); err != nil { - log.Fatalf("run server failed: %v", fmt.Sprintf("%+v", err)) + log.Fatal("run server failed", zap.Error(err)) } <-ctx.Done() - log.Infof("Got signal [%d] to exit.", sig) + log.Info("Got signal to exit", zap.String("signal", sig.String())) svr.Close() switch sig { case syscall.SIGTERM: - os.Exit(0) + exit(0) default: - os.Exit(1) + exit(1) } } + +func exit(code int) { + log.Sync() + os.Exit(code) +} diff --git a/pkg/logutil/log.go b/pkg/logutil/log.go index 5ae1bf9264c..9ad5a439056 100644 --- a/pkg/logutil/log.go +++ b/pkg/logutil/log.go @@ -25,6 +25,7 @@ import ( "github.com/coreos/etcd/raft" "github.com/coreos/pkg/capnslog" + zaplog "github.com/pingcap/log" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "google.golang.org/grpc/grpclog" @@ -196,7 +197,7 @@ func stringToLogFormatter(format string, disableTimestamp bool) log.Formatter { } // InitFileLog initializes file based logging options. -func InitFileLog(cfg *FileLogConfig) error { +func InitFileLog(cfg *zaplog.FileLogConfig) error { if st, err := os.Stat(cfg.Filename); err == nil { if st.IsDir() { return errors.New("can't use directory as log file name") @@ -238,7 +239,7 @@ func (lg *wrapLogrus) V(l int) bool { var once sync.Once // InitLogger initializes PD's logger. -func InitLogger(cfg *LogConfig) error { +func InitLogger(cfg *zaplog.Config) error { var err error once.Do(func() { diff --git a/pkg/logutil/log_test.go b/pkg/logutil/log_test.go index 07b8815997e..4a62c583b3a 100644 --- a/pkg/logutil/log_test.go +++ b/pkg/logutil/log_test.go @@ -20,6 +20,7 @@ import ( "github.com/coreos/pkg/capnslog" . "github.com/pingcap/check" + zaplog "github.com/pingcap/log" log "github.com/sirupsen/logrus" ) @@ -53,7 +54,7 @@ func (s *testLogSuite) TestStringToLogLevel(c *C) { // TestLogging assure log format and log redirection works. func (s *testLogSuite) TestLogging(c *C) { - conf := &LogConfig{Level: "warn", File: FileLogConfig{}} + conf := &zaplog.Config{Level: "warn", File: zaplog.FileLogConfig{}} c.Assert(InitLogger(conf), IsNil) log.SetOutput(s.buf) @@ -76,3 +77,8 @@ func (s *testLogSuite) TestLogging(c *C) { c.Assert(entry, Matches, logPattern) c.Assert(strings.Contains(entry, "log_test.go"), IsTrue) } + +func (s *testLogSuite) TestFileLog(c *C) { + c.Assert(InitFileLog(&zaplog.FileLogConfig{Filename: "/tmp"}), NotNil) + c.Assert(InitFileLog(&zaplog.FileLogConfig{Filename: "/tmp/test_file_log", MaxSize: 0}), IsNil) +} diff --git a/server/api/server_test.go b/server/api/server_test.go index 68d9b6131d3..542342a8f02 100644 --- a/server/api/server_test.go +++ b/server/api/server_test.go @@ -18,12 +18,14 @@ import ( "net/http" "os" "strings" + "sync" "testing" "time" . "github.com/pingcap/check" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" + log "github.com/pingcap/log" "github.com/pingcap/pd/pkg/testutil" "github.com/pingcap/pd/server" "google.golang.org/grpc" @@ -73,6 +75,8 @@ func mustNewServer(c *C) (*server.Server, cleanUpFunc) { return svrs[0], cleanup } +var zapLogOnce sync.Once + func mustNewCluster(c *C, num int) ([]*server.Config, []*server.Server, cleanUpFunc) { svrs := make([]*server.Server, 0, num) cfgs := server.NewTestMultiConfig(num) @@ -80,6 +84,11 @@ func mustNewCluster(c *C, num int) ([]*server.Config, []*server.Server, cleanUpF ch := make(chan *server.Server, num) for _, cfg := range cfgs { go func(cfg *server.Config) { + err := cfg.SetupLogger() + c.Assert(err, IsNil) + zapLogOnce.Do(func() { + log.ReplaceGlobals(cfg.GetZapLogger(), cfg.GetZapLogProperties()) + }) s, err := server.CreateServer(cfg, NewHandler) c.Assert(err, IsNil) err = s.Run(context.TODO()) diff --git a/server/config.go b/server/config.go index a295e7f92fb..95c9259b67d 100644 --- a/server/config.go +++ b/server/config.go @@ -27,11 +27,12 @@ import ( "github.com/coreos/etcd/embed" "github.com/coreos/etcd/pkg/transport" "github.com/coreos/go-semver/semver" - "github.com/pingcap/pd/pkg/logutil" + "github.com/pingcap/log" "github.com/pingcap/pd/pkg/metricutil" "github.com/pingcap/pd/pkg/typeutil" "github.com/pingcap/pd/server/namespace" "github.com/pkg/errors" + "go.uber.org/zap" ) // Config is the pd server configuration. @@ -61,7 +62,7 @@ type Config struct { LeaderLease int64 `toml:"lease" json:"lease"` // Log related config. - Log logutil.LogConfig `toml:"log" json:"log"` + Log log.Config `toml:"log" json:"log"` // Backward compatibility. LogFileDeprecated string `toml:"log-file" json:"log-file"` @@ -123,6 +124,9 @@ type Config struct { heartbeatStreamBindInterval typeutil.Duration leaderPriorityCheckInterval typeutil.Duration + + logger *zap.Logger + logProps *log.ZapProperties } // NewConfig creates a new config. @@ -756,6 +760,27 @@ func ParseUrls(s string) ([]url.URL, error) { return urls, nil } +// SetupLogger setup the logger. +func (c *Config) SetupLogger() error { + lg, p, err := log.InitLogger(&c.Log) + if err != nil { + return err + } + c.logger = lg + c.logProps = p + return nil +} + +// GetZapLogger gets the created zap logger. +func (c *Config) GetZapLogger() *zap.Logger { + return c.logger +} + +// GetZapLogProperties gets properties of the zap logger. +func (c *Config) GetZapLogProperties() *log.ZapProperties { + return c.logProps +} + // generates a configuration for embedded etcd. func (c *Config) genEmbedEtcdConfig() (*embed.Config, error) { cfg := embed.NewConfig() @@ -780,7 +805,9 @@ func (c *Config) genEmbedEtcdConfig() (*embed.Config, error) { cfg.PeerTLSInfo.TrustedCAFile = c.Security.CAPath cfg.PeerTLSInfo.CertFile = c.Security.CertPath cfg.PeerTLSInfo.KeyFile = c.Security.KeyPath - + // TODO: update etcd + // cfg.ZapLoggerBuilder = embed.NewZapCoreLoggerBuilder(c.logger, c.logger.Core(), c.logSyncer) + // cfg.Logger = "zap" var err error cfg.LPUrls, err = ParseUrls(c.PeerUrls) diff --git a/server/server.go b/server/server.go index da74b250e6f..a402679edb9 100644 --- a/server/server.go +++ b/server/server.go @@ -31,12 +31,13 @@ import ( "github.com/coreos/go-semver/semver" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" + log "github.com/pingcap/log" "github.com/pingcap/pd/pkg/etcdutil" "github.com/pingcap/pd/pkg/logutil" "github.com/pingcap/pd/server/core" "github.com/pingcap/pd/server/namespace" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" + "go.uber.org/zap" "google.golang.org/grpc" ) @@ -97,11 +98,14 @@ type Server struct { lastSavedTime time.Time // For async region heartbeat. hbStreams *heartbeatStreams + // Zap logger + lg *zap.Logger + logProps *log.ZapProperties } // CreateServer creates the UNINITIALIZED pd server with given configuration. func CreateServer(cfg *Config, apiRegister func(*Server) http.Handler) (*Server, error) { - log.Infof("PD config - %v", cfg) + log.Info("PD Config", zap.Reflect("config", cfg)) rand.Seed(time.Now().UnixNano()) s := &Server{ @@ -124,11 +128,14 @@ func CreateServer(cfg *Config, apiRegister func(*Server) http.Handler) (*Server, s.etcdCfg = etcdCfg if EnableZap { // The etcd master version has removed embed.Config.SetupLogging. - // Now logger is set up automatically based on embed.Config.Logger, embed.Config.LogOutputs, embed.Config.Debug fields. - // Use zap logger in the test, otherwise will panic. Reference: https://github.com/coreos/etcd/blob/master/embed/config_logging.go#L45 + // Now logger is set up automatically based on embed.Config.Logger, + // Use zap logger in the test, otherwise will panic. + // Reference: https://github.com/coreos/etcd/blob/master/embed/config_logging.go#L45 s.etcdCfg.Logger = "zap" s.etcdCfg.LogOutputs = []string{"stdout"} } + s.lg = cfg.logger + s.logProps = cfg.logProps return s, nil } @@ -163,7 +170,7 @@ func (s *Server) startEtcd(ctx context.Context) error { } endpoints := []string{s.etcdCfg.ACUrls[0].String()} - log.Infof("create etcd v3 client with endpoints %v", endpoints) + log.Info("create etcd v3 client", zap.Strings("endpoints", endpoints)) client, err := clientv3.New(clientv3.Config{ Endpoints: endpoints, @@ -185,7 +192,7 @@ func (s *Server) startEtcd(ctx context.Context) error { if etcdServerID == m.ID { etcdPeerURLs := strings.Join(m.PeerURLs, ",") if s.cfg.AdvertisePeerUrls != etcdPeerURLs { - log.Infof("update advertise peer urls from %s to %s", s.cfg.AdvertisePeerUrls, etcdPeerURLs) + log.Info("update advertise peer urls", zap.String("from", s.cfg.AdvertisePeerUrls), zap.String("to", etcdPeerURLs)) s.cfg.AdvertisePeerUrls = etcdPeerURLs } } @@ -202,7 +209,7 @@ func (s *Server) startServer() error { if err = s.initClusterID(); err != nil { return err } - log.Infof("init cluster id %v", s.clusterID) + log.Info("init cluster id", zap.Uint64("cluster-id", s.clusterID)) // It may lose accuracy if use float64 to store uint64. So we store the // cluster id in label. metadataGauge.WithLabelValues(fmt.Sprintf("cluster%d", s.clusterID)).Set(0) @@ -277,7 +284,7 @@ var timeMonitorOnce sync.Once func (s *Server) Run(ctx context.Context) error { timeMonitorOnce.Do(func() { go StartMonitor(time.Now, func() { - log.Errorf("system time jumps backward") + log.Error("system time jumps backward") timeJumpBackCounter.Inc() }) }) @@ -334,7 +341,9 @@ func (s *Server) collectEtcdStateMetrics() { func (s *Server) bootstrapCluster(req *pdpb.BootstrapRequest) (*pdpb.BootstrapResponse, error) { clusterID := s.clusterID - log.Infof("try to bootstrap raft cluster %d with %v", clusterID, req) + log.Info("try to bootstrap raft cluster", + zap.Uint64("cluster-id", clusterID), + zap.String("request", fmt.Sprintf("%v", req))) if err := checkBootstrapRequest(clusterID, req); err != nil { return nil, err @@ -387,11 +396,11 @@ func (s *Server) bootstrapCluster(req *pdpb.BootstrapRequest) (*pdpb.BootstrapRe return nil, errors.WithStack(err) } if !resp.Succeeded { - log.Warnf("cluster %d already bootstrapped", clusterID) + log.Warn("cluster already bootstrapped", zap.Uint64("cluster-id", clusterID)) return nil, errors.Errorf("cluster %d already bootstrapped", clusterID) } - log.Infof("bootstrap cluster %d ok", clusterID) + log.Info("bootstrap cluster ok", zap.Uint64("cluster-id", clusterID)) if err := s.cluster.start(); err != nil { return nil, err @@ -491,7 +500,7 @@ func (s *Server) SetScheduleConfig(cfg ScheduleConfig) error { if err := s.scheduleOpt.persist(s.kv); err != nil { return err } - log.Infof("schedule config is updated: %+v, old: %+v", cfg, old) + log.Info("schedule config is updated", zap.Reflect("new", cfg), zap.Reflect("old", old)) return nil } @@ -509,11 +518,10 @@ func (s *Server) SetReplicationConfig(cfg ReplicationConfig) error { } old := s.scheduleOpt.rep.load() s.scheduleOpt.rep.store(&cfg) - s.scheduleOpt.persist(s.kv) if err := s.scheduleOpt.persist(s.kv); err != nil { return err } - log.Infof("replication config is updated: %+v, old: %+v", cfg, old) + log.Info("replication config is updated", zap.Reflect("new", cfg), zap.Reflect("old", old)) return nil } @@ -548,11 +556,11 @@ func (s *Server) SetNamespaceConfig(name string, cfg NamespaceConfig) { old := s.scheduleOpt.ns[name].load() n.store(&cfg) s.scheduleOpt.persist(s.kv) - log.Infof("namespace:%v config is updated: %+v, old: %+v", name, cfg, old) + log.Info("namespace config is updated", zap.String("name", name), zap.Reflect("new", cfg), zap.Reflect("old", old)) } else { s.scheduleOpt.ns[name] = newNamespaceOption(&cfg) s.scheduleOpt.persist(s.kv) - log.Infof("namespace:%v config is added: %+v", name, cfg) + log.Info("namespace config is added", zap.String("name", name), zap.Reflect("new", cfg)) } } @@ -562,7 +570,7 @@ func (s *Server) DeleteNamespaceConfig(name string) { cfg := n.load() delete(s.scheduleOpt.ns, name) s.scheduleOpt.persist(s.kv) - log.Infof("namespace:%v config is deleted: %+v", name, *cfg) + log.Info("namespace config is deleted", zap.String("name", name), zap.Reflect("config", *cfg)) } } @@ -573,7 +581,7 @@ func (s *Server) SetLabelProperty(typ, labelKey, labelValue string) error { if err != nil { return err } - log.Infof("label property config is updated: %+v", s.scheduleOpt.loadLabelPropertyConfig()) + log.Info("label property config is updated", zap.Reflect("config", s.scheduleOpt.loadLabelPropertyConfig())) return nil } @@ -584,7 +592,7 @@ func (s *Server) DeleteLabelProperty(typ, labelKey, labelValue string) error { if err != nil { return err } - log.Infof("label property config is updated: %+v", s.scheduleOpt.loadLabelPropertyConfig()) + log.Info("label property config is deleted", zap.Reflect("config", s.scheduleOpt.loadLabelPropertyConfig())) return nil } @@ -604,7 +612,7 @@ func (s *Server) SetClusterVersion(v string) error { if err != nil { return err } - log.Infof("cluster version is updated to %s", v) + log.Info("cluster version is updated", zap.String("new-version", v)) return nil } diff --git a/server/testutil.go b/server/testutil.go index 0596c58c4d3..2ee46830fc7 100644 --- a/server/testutil.go +++ b/server/testutil.go @@ -19,9 +19,11 @@ import ( "io/ioutil" "os" "strings" + "sync" "time" "github.com/coreos/etcd/embed" + "github.com/pingcap/log" "github.com/pingcap/pd/pkg/tempurl" "github.com/pingcap/pd/pkg/typeutil" "github.com/pingcap/pd/server/schedule" @@ -56,6 +58,8 @@ func NewTestServer() (*Config, *Server, CleanupFunc, error) { return cfg, s, cleanup, nil } +var zapLogOnce sync.Once + // NewTestSingleConfig is only for test to create one pd. // Because PD client also needs this, so export here. func NewTestSingleConfig() *Config { @@ -76,8 +80,15 @@ func NewTestSingleConfig() *Config { cfg.InitialCluster = fmt.Sprintf("pd=%s", cfg.PeerUrls) cfg.disableStrictReconfigCheck = true cfg.TickInterval = typeutil.NewDuration(100 * time.Millisecond) - cfg.ElectionInterval = typeutil.NewDuration(3000 * time.Millisecond) + cfg.ElectionInterval = typeutil.NewDuration(3 * time.Second) cfg.leaderPriorityCheckInterval = typeutil.NewDuration(100 * time.Millisecond) + err := cfg.SetupLogger() + if err != nil { + log.Fatal("setup logger failed") + } + zapLogOnce.Do(func() { + log.ReplaceGlobals(cfg.GetZapLogger(), cfg.GetZapLogProperties()) + }) cfg.Adjust(nil) diff --git a/server/util.go b/server/util.go index 416cbb5cdcb..138efd5543b 100644 --- a/server/util.go +++ b/server/util.go @@ -26,9 +26,10 @@ import ( "github.com/golang/protobuf/proto" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" + log "github.com/pingcap/log" "github.com/pingcap/pd/pkg/etcdutil" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" + "go.uber.org/zap" ) const ( @@ -55,11 +56,11 @@ var dialClient = &http.Client{ // LogPDInfo prints the PD version information. func LogPDInfo() { - log.Infof("Welcome to Placement Driver (PD).") - log.Infof("Release Version: %s", PDReleaseVersion) - log.Infof("Git Commit Hash: %s", PDGitHash) - log.Infof("Git Branch: %s", PDGitBranch) - log.Infof("UTC Build Time: %s", PDBuildTS) + log.Info("Welcome to Placement Driver (PD)") + log.Info("PD", zap.String("release-version", PDReleaseVersion)) + log.Info("PD", zap.String("git-hash", PDGitHash)) + log.Info("PD", zap.String("git-branch", PDGitBranch)) + log.Info("PD", zap.String("utc-build-time", PDBuildTS)) } // PrintPDInfo prints the PD version information without log info. @@ -78,7 +79,10 @@ func CheckPDVersion(opt *scheduleOption) { } clusterVersion := opt.loadClusterVersion() if pdVersion.LessThan(clusterVersion) { - log.Warnf("PD version %s less than cluster version: %s, please upgrade PD", pdVersion, clusterVersion) + log.Warn( + "PD version less than cluster version, please upgrade PD", + zap.String("PD-version", pdVersion.String()), + zap.String("cluster-version", clusterVersion.String())) } } @@ -213,7 +217,10 @@ func (t *slowLogTxn) Commit() (*clientv3.TxnResponse, error) { cost := time.Since(start) if cost > slowRequestTime { - log.Warnf("txn runs too slow, resp: %v, err: %v, cost: %s", resp, err, cost) + log.Warn("txn runs too slow", + zap.Error(err), + zap.Reflect("response", resp), + zap.Duration("cost", cost)) } label := "success" if err != nil { diff --git a/vendor/github.com/pingcap/log/LICENSE b/vendor/github.com/pingcap/log/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/pingcap/log/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/pingcap/log/config.go b/vendor/github.com/pingcap/log/config.go new file mode 100644 index 00000000000..b56897340bd --- /dev/null +++ b/vendor/github.com/pingcap/log/config.go @@ -0,0 +1,122 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +const ( + defaultLogMaxSize = 300 // MB +) + +// FileLogConfig serializes file log related config in toml/json. +type FileLogConfig struct { + // Log filename, leave empty to disable file log. + Filename string `toml:"filename" json:"filename"` + // Is log rotate enabled. + LogRotate bool `toml:"log-rotate" json:"log-rotate"` + // Max size for a single file, in MB. + MaxSize int `toml:"max-size" json:"max-size"` + // Max log keep days, default is never deleting. + MaxDays int `toml:"max-days" json:"max-days"` + // Maximum number of old log files to retain. + MaxBackups int `toml:"max-backups" json:"max-backups"` +} + +// Config serializes log related config in toml/json. +type Config struct { + // Log level. + Level string `toml:"level" json:"level"` + // Log format. one of json, text, or console. + Format string `toml:"format" json:"format"` + // Disable automatic timestamps in output. + DisableTimestamp bool `toml:"disable-timestamp" json:"disable-timestamp"` + // File log config. + File FileLogConfig `toml:"file" json:"file"` + // Development puts the logger in development mode, which changes the + // behavior of DPanicLevel and takes stacktraces more liberally. + Development bool `toml:"development" json:"development"` + // DisableCaller stops annotating logs with the calling function's file + // name and line number. By default, all logs are annotated. + DisableCaller bool `toml:"disable-caller" json:"disable-caller"` + // DisableStacktrace completely disables automatic stacktrace capturing. By + // default, stacktraces are captured for WarnLevel and above logs in + // development and ErrorLevel and above in production. + DisableStacktrace bool `toml:"disable-stacktrace" json:"disable-stacktrace"` + // SamplingConfig sets a sampling strategy for the logger. Sampling caps the + // global CPU and I/O load that logging puts on your process while attempting + // to preserve a representative subset of your logs. + // + // Values configured here are per-second. See zapcore.NewSampler for details. + Sampling *zap.SamplingConfig `toml:"sampling" json:"sampling"` +} + +// ZapProperties records some information about zap. +type ZapProperties struct { + Core zapcore.Core + Syncer zapcore.WriteSyncer + Level zap.AtomicLevel +} + +func newZapTextEncoder(cfg *Config) zapcore.Encoder { + cc := zapcore.EncoderConfig{ + // Keys can be anything except the empty string. + TimeKey: "time", + LevelKey: "level", + NameKey: "name", + CallerKey: "caller", + MessageKey: "message", + StacktraceKey: "stack", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: DefaultTimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: ShortCallerEncoder, + } + if cfg.DisableTimestamp { + cc.TimeKey = "" + } + return NewTextEncoder(cc) +} + +func (cfg *Config) buildOptions(errSink zapcore.WriteSyncer) []zap.Option { + opts := []zap.Option{zap.ErrorOutput(errSink)} + + if cfg.Development { + opts = append(opts, zap.Development()) + } + + if !cfg.DisableCaller { + opts = append(opts, zap.AddCaller()) + } + + stackLevel := zap.ErrorLevel + if cfg.Development { + stackLevel = zap.WarnLevel + } + if !cfg.DisableStacktrace { + opts = append(opts, zap.AddStacktrace(stackLevel)) + } + + if cfg.Sampling != nil { + opts = append(opts, zap.WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewSampler(core, time.Second, int(cfg.Sampling.Initial), int(cfg.Sampling.Thereafter)) + })) + } + return opts +} diff --git a/vendor/github.com/pingcap/log/global.go b/vendor/github.com/pingcap/log/global.go new file mode 100644 index 00000000000..5d00254d961 --- /dev/null +++ b/vendor/github.com/pingcap/log/global.go @@ -0,0 +1,70 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// Debug logs a message at DebugLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func Debug(msg string, fields ...zap.Field) { + L().WithOptions(zap.AddCallerSkip(1)).Debug(msg, fields...) +} + +// Info logs a message at InfoLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func Info(msg string, fields ...zap.Field) { + L().WithOptions(zap.AddCallerSkip(1)).Info(msg, fields...) +} + +// Warn logs a message at WarnLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func Warn(msg string, fields ...zap.Field) { + L().WithOptions(zap.AddCallerSkip(1)).Warn(msg, fields...) +} + +// Error logs a message at ErrorLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func Error(msg string, fields ...zap.Field) { + L().WithOptions(zap.AddCallerSkip(1)).Error(msg, fields...) +} + +// Panic logs a message at PanicLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +// +// The logger then panics, even if logging at PanicLevel is disabled. +func Panic(msg string, fields ...zap.Field) { + L().WithOptions(zap.AddCallerSkip(1)).Panic(msg, fields...) +} + +// Fatal logs a message at FatalLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +// +// The logger then calls os.Exit(1), even if logging at FatalLevel is +// disabled. +func Fatal(msg string, fields ...zap.Field) { + L().WithOptions(zap.AddCallerSkip(1)).Fatal(msg, fields...) +} + +// SetLevel alters the logging level. +func SetLevel(l zapcore.Level) { + _globalP.Level.SetLevel(l) +} + +// GetLevel gets the logging level. +func GetLevel() zapcore.Level { + return _globalP.Level.Level() +} diff --git a/vendor/github.com/pingcap/log/log.go b/vendor/github.com/pingcap/log/log.go new file mode 100644 index 00000000000..571fb450867 --- /dev/null +++ b/vendor/github.com/pingcap/log/log.go @@ -0,0 +1,117 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "errors" + "os" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + lumberjack "gopkg.in/natefinch/lumberjack.v2" +) + +// InitLogger initializes a zap logger. +func InitLogger(cfg *Config, opts ...zap.Option) (*zap.Logger, *ZapProperties, error) { + var output zapcore.WriteSyncer + if len(cfg.File.Filename) > 0 { + lg, err := initFileLog(&cfg.File) + if err != nil { + return nil, nil, err + } + output = zapcore.AddSync(lg) + } else { + stdOut, close, err := zap.Open([]string{"stdout"}...) + if err != nil { + close() + return nil, nil, err + } + output = stdOut + } + level := zap.NewAtomicLevel() + err := level.UnmarshalText([]byte(cfg.Level)) + if err != nil { + return nil, nil, err + } + core := NewTextCore(newZapTextEncoder(cfg).(*textEncoder), output, level) + opts = append(opts, cfg.buildOptions(output)...) + lg := zap.New(core, opts...) + r := &ZapProperties{ + Core: core, + Syncer: output, + Level: level, + } + return lg, r, nil +} + +// initFileLog initializes file based logging options. +func initFileLog(cfg *FileLogConfig) (*lumberjack.Logger, error) { + if st, err := os.Stat(cfg.Filename); err == nil { + if st.IsDir() { + return nil, errors.New("can't use directory as log file name") + } + } + if cfg.MaxSize == 0 { + cfg.MaxSize = defaultLogMaxSize + } + + // use lumberjack to logrotate + return &lumberjack.Logger{ + Filename: cfg.Filename, + MaxSize: cfg.MaxSize, + MaxBackups: cfg.MaxBackups, + MaxAge: cfg.MaxDays, + LocalTime: true, + }, nil +} + +func newStdLogger() (*zap.Logger, *ZapProperties) { + conf := &Config{Level: "info", File: FileLogConfig{}} + lg, r, _ := InitLogger(conf) + return lg, r +} + +var ( + _globalL, _globalP = newStdLogger() + _globalS = _globalL.Sugar() +) + +// L returns the global Logger, which can be reconfigured with ReplaceGlobals. +// It's safe for concurrent use. +func L() *zap.Logger { + return _globalL +} + +// S returns the global SugaredLogger, which can be reconfigured with +// ReplaceGlobals. It's safe for concurrent use. +func S() *zap.SugaredLogger { + return _globalS +} + +// ReplaceGlobals replaces the global Logger and SugaredLogger. +// It's unsafe for concurrent use. +func ReplaceGlobals(logger *zap.Logger, props *ZapProperties) { + _globalL = logger + _globalS = logger.Sugar() + _globalP = props +} + +// Sync flushes any buffered log entries. +func Sync() error { + err := L().Sync() + if err != nil { + return err + } + return S().Sync() +} diff --git a/vendor/github.com/pingcap/log/zap_text_core.go b/vendor/github.com/pingcap/log/zap_text_core.go new file mode 100644 index 00000000000..34e7b9aac76 --- /dev/null +++ b/vendor/github.com/pingcap/log/zap_text_core.go @@ -0,0 +1,77 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import "go.uber.org/zap/zapcore" + +// NewTextCore creates a Core that writes logs to a WriteSyncer. +func NewTextCore(enc *textEncoder, ws zapcore.WriteSyncer, enab zapcore.LevelEnabler) zapcore.Core { + return &textIOCore{ + LevelEnabler: enab, + enc: enc, + out: ws, + } +} + +// textIOCore is a copy of zapcore.ioCore that only accept *textEncoder +// it can be removed after https://github.com/uber-go/zap/pull/685 be merged +type textIOCore struct { + zapcore.LevelEnabler + enc *textEncoder + out zapcore.WriteSyncer +} + +func (c *textIOCore) With(fields []zapcore.Field) zapcore.Core { + clone := c.clone() + // it's different to ioCore, here call textEncoder#addFields to fix https://github.com/pingcap/log/issues/3 + clone.enc.addFields(fields) + return clone +} + +func (c *textIOCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if c.Enabled(ent.Level) { + return ce.AddCore(ent, c) + } + return ce +} + +func (c *textIOCore) Write(ent zapcore.Entry, fields []zapcore.Field) error { + buf, err := c.enc.EncodeEntry(ent, fields) + if err != nil { + return err + } + _, err = c.out.Write(buf.Bytes()) + buf.Free() + if err != nil { + return err + } + if ent.Level > zapcore.ErrorLevel { + // Since we may be crashing the program, sync the output. Ignore Sync + // errors, pending a clean solution to issue https://github.com/uber-go/zap/issues/370. + c.Sync() + } + return nil +} + +func (c *textIOCore) Sync() error { + return c.out.Sync() +} + +func (c *textIOCore) clone() *textIOCore { + return &textIOCore{ + LevelEnabler: c.LevelEnabler, + enc: c.enc.Clone().(*textEncoder), + out: c.out, + } +} diff --git a/vendor/github.com/pingcap/log/zap_text_encoder.go b/vendor/github.com/pingcap/log/zap_text_encoder.go new file mode 100644 index 00000000000..a9595981903 --- /dev/null +++ b/vendor/github.com/pingcap/log/zap_text_encoder.go @@ -0,0 +1,633 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package log + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "math" + "strings" + "sync" + "time" + "unicode/utf8" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +// DefaultTimeEncoder serializes time.Time to a human-readable formatted string +func DefaultTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + s := t.Format("2006/01/02 15:04:05.000 -07:00") + if e, ok := enc.(*textEncoder); ok { + for _, c := range []byte(s) { + e.buf.AppendByte(c) + } + return + } + enc.AppendString(s) +} + +// ShortCallerEncoder serializes a caller in file:line format. +func ShortCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(getCallerString(caller)) +} + +func getCallerString(ec zapcore.EntryCaller) string { + if !ec.Defined { + return "" + } + + idx := strings.LastIndexByte(ec.File, '/') + buf := _pool.Get() + for i := idx + 1; i < len(ec.File); i++ { + b := ec.File[i] + switch { + case b >= 'A' && b <= 'Z': + buf.AppendByte(b) + case b >= 'a' && b <= 'z': + buf.AppendByte(b) + case b >= '0' && b <= '9': + buf.AppendByte(b) + case b == '.' || b == '-' || b == '_': + buf.AppendByte(b) + default: + } + } + buf.AppendByte(':') + buf.AppendInt(int64(ec.Line)) + caller := buf.String() + buf.Free() + return caller +} + +// For JSON-escaping; see textEncoder.safeAddString below. +const _hex = "0123456789abcdef" + +var _textPool = sync.Pool{New: func() interface{} { + return &textEncoder{} +}} + +var ( + _pool = buffer.NewPool() + // Get retrieves a buffer from the pool, creating one if necessary. + Get = _pool.Get +) + +func getTextEncoder() *textEncoder { + return _textPool.Get().(*textEncoder) +} + +func putTextEncoder(enc *textEncoder) { + if enc.reflectBuf != nil { + enc.reflectBuf.Free() + } + enc.EncoderConfig = nil + enc.buf = nil + enc.spaced = false + enc.openNamespaces = 0 + enc.reflectBuf = nil + enc.reflectEnc = nil + _textPool.Put(enc) +} + +type textEncoder struct { + *zapcore.EncoderConfig + buf *buffer.Buffer + spaced bool // include spaces after colons and commas + openNamespaces int + + // for encoding generic values by reflection + reflectBuf *buffer.Buffer + reflectEnc *json.Encoder +} + +// NewTextEncoder creates a fast, low-allocation Text encoder. The encoder +// appropriately escapes all field keys and values. +func NewTextEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder { + return &textEncoder{ + EncoderConfig: &cfg, + buf: _pool.Get(), + spaced: false, + } +} + +func (enc *textEncoder) AddArray(key string, arr zapcore.ArrayMarshaler) error { + enc.addKey(key) + return enc.AppendArray(arr) +} + +func (enc *textEncoder) AddObject(key string, obj zapcore.ObjectMarshaler) error { + enc.addKey(key) + return enc.AppendObject(obj) +} + +func (enc *textEncoder) AddBinary(key string, val []byte) { + enc.AddString(key, base64.StdEncoding.EncodeToString(val)) +} + +func (enc *textEncoder) AddByteString(key string, val []byte) { + enc.addKey(key) + enc.AppendByteString(val) +} + +func (enc *textEncoder) AddBool(key string, val bool) { + enc.addKey(key) + enc.AppendBool(val) +} + +func (enc *textEncoder) AddComplex128(key string, val complex128) { + enc.addKey(key) + enc.AppendComplex128(val) +} + +func (enc *textEncoder) AddDuration(key string, val time.Duration) { + enc.addKey(key) + enc.AppendDuration(val) +} + +func (enc *textEncoder) AddFloat64(key string, val float64) { + enc.addKey(key) + enc.AppendFloat64(val) +} + +func (enc *textEncoder) AddInt64(key string, val int64) { + enc.addKey(key) + enc.AppendInt64(val) +} + +func (enc *textEncoder) resetReflectBuf() { + if enc.reflectBuf == nil { + enc.reflectBuf = _pool.Get() + enc.reflectEnc = json.NewEncoder(enc.reflectBuf) + } else { + enc.reflectBuf.Reset() + } +} + +func (enc *textEncoder) AddReflected(key string, obj interface{}) error { + enc.resetReflectBuf() + err := enc.reflectEnc.Encode(obj) + if err != nil { + return err + } + enc.reflectBuf.TrimNewline() + enc.addKey(key) + enc.AppendByteString(enc.reflectBuf.Bytes()) + return nil +} + +func (enc *textEncoder) OpenNamespace(key string) { + enc.addKey(key) + enc.buf.AppendByte('{') + enc.openNamespaces++ +} + +func (enc *textEncoder) AddString(key, val string) { + enc.addKey(key) + enc.AppendString(val) +} + +func (enc *textEncoder) AddTime(key string, val time.Time) { + enc.addKey(key) + enc.AppendTime(val) +} + +func (enc *textEncoder) AddUint64(key string, val uint64) { + enc.addKey(key) + enc.AppendUint64(val) +} + +func (enc *textEncoder) AppendArray(arr zapcore.ArrayMarshaler) error { + enc.addElementSeparator() + ne := enc.cloned() + ne.buf.AppendByte('[') + err := arr.MarshalLogArray(ne) + ne.buf.AppendByte(']') + enc.AppendByteString(ne.buf.Bytes()) + ne.buf.Free() + putTextEncoder(ne) + return err +} + +func (enc *textEncoder) AppendObject(obj zapcore.ObjectMarshaler) error { + enc.addElementSeparator() + ne := enc.cloned() + ne.buf.AppendByte('{') + err := obj.MarshalLogObject(ne) + ne.buf.AppendByte('}') + enc.AppendByteString(ne.buf.Bytes()) + ne.buf.Free() + putTextEncoder(ne) + return err +} + +func (enc *textEncoder) AppendBool(val bool) { + enc.addElementSeparator() + enc.buf.AppendBool(val) +} + +func (enc *textEncoder) AppendByteString(val []byte) { + enc.addElementSeparator() + if !enc.needDoubleQuotes(string(val)) { + enc.safeAddByteString(val) + return + } + enc.buf.AppendByte('"') + enc.safeAddByteString(val) + enc.buf.AppendByte('"') +} + +func (enc *textEncoder) AppendComplex128(val complex128) { + enc.addElementSeparator() + // Cast to a platform-independent, fixed-size type. + r, i := float64(real(val)), float64(imag(val)) + enc.buf.AppendFloat(r, 64) + enc.buf.AppendByte('+') + enc.buf.AppendFloat(i, 64) + enc.buf.AppendByte('i') +} + +func (enc *textEncoder) AppendDuration(val time.Duration) { + cur := enc.buf.Len() + enc.EncodeDuration(val, enc) + if cur == enc.buf.Len() { + // User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep + // JSON valid. + enc.AppendInt64(int64(val)) + } +} + +func (enc *textEncoder) AppendInt64(val int64) { + enc.addElementSeparator() + enc.buf.AppendInt(val) +} + +func (enc *textEncoder) AppendReflected(val interface{}) error { + enc.resetReflectBuf() + err := enc.reflectEnc.Encode(val) + if err != nil { + return err + } + enc.reflectBuf.TrimNewline() + enc.AppendByteString(enc.reflectBuf.Bytes()) + return nil +} + +func (enc *textEncoder) AppendString(val string) { + enc.addElementSeparator() + enc.safeAddStringWithQuote(val) +} + +func (enc *textEncoder) AppendTime(val time.Time) { + cur := enc.buf.Len() + enc.EncodeTime(val, enc) + if cur == enc.buf.Len() { + // User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep + // output JSON valid. + enc.AppendInt64(val.UnixNano()) + } +} + +func (enc *textEncoder) beginQuoteFiled() { + if enc.buf.Len() > 0 { + enc.buf.AppendByte(' ') + } + enc.buf.AppendByte('[') +} + +func (enc *textEncoder) endQuoteFiled() { + enc.buf.AppendByte(']') +} + +func (enc *textEncoder) AppendUint64(val uint64) { + enc.addElementSeparator() + enc.buf.AppendUint(val) +} + +func (enc *textEncoder) AddComplex64(k string, v complex64) { enc.AddComplex128(k, complex128(v)) } +func (enc *textEncoder) AddFloat32(k string, v float32) { enc.AddFloat64(k, float64(v)) } +func (enc *textEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) } +func (enc *textEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) } +func (enc *textEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) } +func (enc *textEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) } +func (enc *textEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) } +func (enc *textEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) } +func (enc *textEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) } +func (enc *textEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) } +func (enc *textEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) } +func (enc *textEncoder) AppendComplex64(v complex64) { enc.AppendComplex128(complex128(v)) } +func (enc *textEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) } +func (enc *textEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) } +func (enc *textEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) } +func (enc *textEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) } +func (enc *textEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) } +func (enc *textEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) } +func (enc *textEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) } +func (enc *textEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) } +func (enc *textEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) } +func (enc *textEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) } +func (enc *textEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) } + +func (enc *textEncoder) Clone() zapcore.Encoder { + clone := enc.cloned() + clone.buf.Write(enc.buf.Bytes()) + return clone +} + +func (enc *textEncoder) cloned() *textEncoder { + clone := getTextEncoder() + clone.EncoderConfig = enc.EncoderConfig + clone.spaced = enc.spaced + clone.openNamespaces = enc.openNamespaces + clone.buf = _pool.Get() + return clone +} + +func (enc *textEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + final := enc.cloned() + if final.TimeKey != "" { + final.beginQuoteFiled() + final.AppendTime(ent.Time) + final.endQuoteFiled() + } + + if final.LevelKey != "" { + final.beginQuoteFiled() + cur := final.buf.Len() + final.EncodeLevel(ent.Level, final) + if cur == final.buf.Len() { + // User-supplied EncodeLevel was a no-op. Fall back to strings to keep + // output JSON valid. + final.AppendString(ent.Level.String()) + } + final.endQuoteFiled() + } + + if ent.LoggerName != "" && final.NameKey != "" { + final.beginQuoteFiled() + cur := final.buf.Len() + nameEncoder := final.EncodeName + + // if no name encoder provided, fall back to FullNameEncoder for backwards + // compatibility + if nameEncoder == nil { + nameEncoder = zapcore.FullNameEncoder + } + + nameEncoder(ent.LoggerName, final) + if cur == final.buf.Len() { + // User-supplied EncodeName was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.LoggerName) + } + final.endQuoteFiled() + } + if ent.Caller.Defined && final.CallerKey != "" { + final.beginQuoteFiled() + cur := final.buf.Len() + final.EncodeCaller(ent.Caller, final) + if cur == final.buf.Len() { + // User-supplied EncodeCaller was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.Caller.String()) + } + final.endQuoteFiled() + } + // add Message + if len(ent.Message) > 0 { + final.beginQuoteFiled() + final.AppendString(ent.Message) + final.endQuoteFiled() + } + if enc.buf.Len() > 0 { + final.buf.AppendByte(' ') + final.buf.Write(enc.buf.Bytes()) + } + final.addFields(fields) + final.closeOpenNamespaces() + if ent.Stack != "" && final.StacktraceKey != "" { + final.beginQuoteFiled() + final.AddString(final.StacktraceKey, ent.Stack) + final.endQuoteFiled() + } + + if final.LineEnding != "" { + final.buf.AppendString(final.LineEnding) + } else { + final.buf.AppendString(zapcore.DefaultLineEnding) + } + + ret := final.buf + putTextEncoder(final) + return ret, nil +} + +func (enc *textEncoder) truncate() { + enc.buf.Reset() +} + +func (enc *textEncoder) closeOpenNamespaces() { + for i := 0; i < enc.openNamespaces; i++ { + enc.buf.AppendByte('}') + } +} + +func (enc *textEncoder) addKey(key string) { + enc.addElementSeparator() + enc.safeAddStringWithQuote(key) + enc.buf.AppendByte('=') +} + +func (enc *textEncoder) addElementSeparator() { + last := enc.buf.Len() - 1 + if last < 0 { + return + } + switch enc.buf.Bytes()[last] { + case '{', '[', ':', ',', ' ', '=': + return + default: + enc.buf.AppendByte(',') + } +} + +func (enc *textEncoder) appendFloat(val float64, bitSize int) { + enc.addElementSeparator() + switch { + case math.IsNaN(val): + enc.buf.AppendString("NaN") + case math.IsInf(val, 1): + enc.buf.AppendString("+Inf") + case math.IsInf(val, -1): + enc.buf.AppendString("-Inf") + default: + enc.buf.AppendFloat(val, bitSize) + } +} + +// safeAddString JSON-escapes a string and appends it to the internal buffer. +// Unlike the standard library's encoder, it doesn't attempt to protect the +// user from browser vulnerabilities or JSONP-related problems. +func (enc *textEncoder) safeAddString(s string) { + for i := 0; i < len(s); { + if enc.tryAddRuneSelf(s[i]) { + i++ + continue + } + r, size := utf8.DecodeRuneInString(s[i:]) + if enc.tryAddRuneError(r, size) { + i++ + continue + } + enc.buf.AppendString(s[i : i+size]) + i += size + } +} + +// safeAddStringWithQuote will automatically add quotoes. +func (enc *textEncoder) safeAddStringWithQuote(s string) { + if !enc.needDoubleQuotes(s) { + enc.safeAddString(s) + return + } + enc.buf.AppendByte('"') + enc.safeAddString(s) + enc.buf.AppendByte('"') +} + +// safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte. +func (enc *textEncoder) safeAddByteString(s []byte) { + for i := 0; i < len(s); { + if enc.tryAddRuneSelf(s[i]) { + i++ + continue + } + r, size := utf8.DecodeRune(s[i:]) + if enc.tryAddRuneError(r, size) { + i++ + continue + } + enc.buf.Write(s[i : i+size]) + i += size + } +} + +// See [log-fileds](https://github.com/tikv/rfcs/blob/master/text/2018-12-19-unified-log-format.md#log-fields-section). +func (enc *textEncoder) needDoubleQuotes(s string) bool { + for i := 0; i < len(s); { + b := s[i] + if b <= 0x20 { + return true + } + switch b { + case '\\', '"', '[', ']', '=': + return true + } + i++ + } + return false +} + +// tryAddRuneSelf appends b if it is valid UTF-8 character represented in a single byte. +func (enc *textEncoder) tryAddRuneSelf(b byte) bool { + if b >= utf8.RuneSelf { + return false + } + if 0x20 <= b && b != '\\' && b != '"' { + enc.buf.AppendByte(b) + return true + } + switch b { + case '\\', '"': + enc.buf.AppendByte('\\') + enc.buf.AppendByte(b) + case '\n': + enc.buf.AppendByte('\\') + enc.buf.AppendByte('n') + case '\r': + enc.buf.AppendByte('\\') + enc.buf.AppendByte('r') + case '\t': + enc.buf.AppendByte('\\') + enc.buf.AppendByte('t') + + default: + // Encode bytes < 0x20, except for the escape sequences above. + enc.buf.AppendString(`\u00`) + enc.buf.AppendByte(_hex[b>>4]) + enc.buf.AppendByte(_hex[b&0xF]) + } + return true +} + +func (enc *textEncoder) tryAddRuneError(r rune, size int) bool { + if r == utf8.RuneError && size == 1 { + enc.buf.AppendString(`\ufffd`) + return true + } + return false +} + +func (enc *textEncoder) addFields(fields []zapcore.Field) { + for _, f := range fields { + if f.Type == zapcore.ErrorType { + // handle ErrorType in pingcap/log to fix "[key=?,keyVerbose=?]" problem. + // see more detail at https://github.com/pingcap/log/pull/5 + enc.encodeError(f) + continue + } + enc.beginQuoteFiled() + f.AddTo(enc) + enc.endQuoteFiled() + } +} + +func (enc *textEncoder) encodeError(f zapcore.Field) { + err := f.Interface.(error) + basic := err.Error() + enc.beginQuoteFiled() + enc.AddString(f.Key, basic) + enc.endQuoteFiled() + if e, isFormatter := err.(fmt.Formatter); isFormatter { + verbose := fmt.Sprintf("%+v", e) + if verbose != basic { + // This is a rich error type, like those produced by + // github.com/pkg/errors. + enc.beginQuoteFiled() + enc.AddString(f.Key+"Verbose", verbose) + enc.endQuoteFiled() + } + } +} diff --git a/vendor/go.uber.org/zap/buffer/buffer.go b/vendor/go.uber.org/zap/buffer/buffer.go index d15f7fdb35b..7592e8c63f6 100644 --- a/vendor/go.uber.org/zap/buffer/buffer.go +++ b/vendor/go.uber.org/zap/buffer/buffer.go @@ -98,6 +98,15 @@ func (b *Buffer) Write(bs []byte) (int, error) { return len(bs), nil } +// TrimNewline trims any final "\n" byte from the end of the buffer. +func (b *Buffer) TrimNewline() { + if i := len(b.bs) - 1; i >= 0 { + if b.bs[i] == '\n' { + b.bs = b.bs[:i] + } + } +} + // Free returns the Buffer to its Pool. // // Callers must not retain references to the Buffer after calling Free. diff --git a/vendor/go.uber.org/zap/config.go b/vendor/go.uber.org/zap/config.go index dae130303e7..6fe17d9e0f5 100644 --- a/vendor/go.uber.org/zap/config.go +++ b/vendor/go.uber.org/zap/config.go @@ -74,10 +74,10 @@ type Config struct { // EncoderConfig sets options for the chosen encoder. See // zapcore.EncoderConfig for details. EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` - // OutputPaths is a list of paths to write logging output to. See Open for - // details. + // OutputPaths is a list of URLs or file paths to write logging output to. + // See Open for details. OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` - // ErrorOutputPaths is a list of paths to write internal logger errors to. + // ErrorOutputPaths is a list of URLs to write internal logger errors to. // The default is standard error. // // Note that this setting only affects internal errors; for sample code that diff --git a/vendor/go.uber.org/zap/doc.go b/vendor/go.uber.org/zap/doc.go index 3f16a8d4576..8638dd1b965 100644 --- a/vendor/go.uber.org/zap/doc.go +++ b/vendor/go.uber.org/zap/doc.go @@ -48,7 +48,7 @@ // "attempt", 3, // "backoff", time.Second, // ) -// sugar.Printf("failed to fetch URL: %s", "http://example.com") +// sugar.Infof("failed to fetch URL: %s", "http://example.com") // // By default, loggers are unbuffered. However, since zap's low-level APIs // allow buffering, calling Sync before letting your process exit is a good diff --git a/vendor/go.uber.org/zap/global.go b/vendor/go.uber.org/zap/global.go index d02232e39fa..c1ac0507cd9 100644 --- a/vendor/go.uber.org/zap/global.go +++ b/vendor/go.uber.org/zap/global.go @@ -31,7 +31,6 @@ import ( ) const ( - _stdLogDefaultDepth = 2 _loggerWriterDepth = 2 _programmerErrorTemplate = "You've found a bug in zap! Please file a bug at " + "https://github.com/uber-go/zap/issues/new and reference this error: %v" diff --git a/vendor/go.uber.org/zap/global_go112.go b/vendor/go.uber.org/zap/global_go112.go new file mode 100644 index 00000000000..6b5dbda8076 --- /dev/null +++ b/vendor/go.uber.org/zap/global_go112.go @@ -0,0 +1,26 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// See #682 for more information. +// +build go1.12 + +package zap + +const _stdLogDefaultDepth = 1 diff --git a/vendor/go.uber.org/zap/global_prego112.go b/vendor/go.uber.org/zap/global_prego112.go new file mode 100644 index 00000000000..d3ab9af933e --- /dev/null +++ b/vendor/go.uber.org/zap/global_prego112.go @@ -0,0 +1,26 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// See #682 for more information. +// +build !go1.12 + +package zap + +const _stdLogDefaultDepth = 2 diff --git a/vendor/go.uber.org/zap/http_handler.go b/vendor/go.uber.org/zap/http_handler.go index f171c3847f4..1b0ecaca9c1 100644 --- a/vendor/go.uber.org/zap/http_handler.go +++ b/vendor/go.uber.org/zap/http_handler.go @@ -48,11 +48,11 @@ func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.Method { - case "GET": + case http.MethodGet: current := lvl.Level() enc.Encode(payload{Level: ¤t}) - case "PUT": + case http.MethodPut: var req payload if errmess := func() string { diff --git a/vendor/go.uber.org/zap/sink.go b/vendor/go.uber.org/zap/sink.go new file mode 100644 index 00000000000..ff0becfe5d0 --- /dev/null +++ b/vendor/go.uber.org/zap/sink.go @@ -0,0 +1,161 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "io" + "net/url" + "os" + "strings" + "sync" + + "go.uber.org/zap/zapcore" +) + +const schemeFile = "file" + +var ( + _sinkMutex sync.RWMutex + _sinkFactories map[string]func(*url.URL) (Sink, error) // keyed by scheme +) + +func init() { + resetSinkRegistry() +} + +func resetSinkRegistry() { + _sinkMutex.Lock() + defer _sinkMutex.Unlock() + + _sinkFactories = map[string]func(*url.URL) (Sink, error){ + schemeFile: newFileSink, + } +} + +// Sink defines the interface to write to and close logger destinations. +type Sink interface { + zapcore.WriteSyncer + io.Closer +} + +type nopCloserSink struct{ zapcore.WriteSyncer } + +func (nopCloserSink) Close() error { return nil } + +type errSinkNotFound struct { + scheme string +} + +func (e *errSinkNotFound) Error() string { + return fmt.Sprintf("no sink found for scheme %q", e.scheme) +} + +// RegisterSink registers a user-supplied factory for all sinks with a +// particular scheme. +// +// All schemes must be ASCII, valid under section 3.1 of RFC 3986 +// (https://tools.ietf.org/html/rfc3986#section-3.1), and must not already +// have a factory registered. Zap automatically registers a factory for the +// "file" scheme. +func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { + _sinkMutex.Lock() + defer _sinkMutex.Unlock() + + if scheme == "" { + return errors.New("can't register a sink factory for empty string") + } + normalized, err := normalizeScheme(scheme) + if err != nil { + return fmt.Errorf("%q is not a valid scheme: %v", scheme, err) + } + if _, ok := _sinkFactories[normalized]; ok { + return fmt.Errorf("sink factory already registered for scheme %q", normalized) + } + _sinkFactories[normalized] = factory + return nil +} + +func newSink(rawURL string) (Sink, error) { + u, err := url.Parse(rawURL) + if err != nil { + return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err) + } + if u.Scheme == "" { + u.Scheme = schemeFile + } + + _sinkMutex.RLock() + factory, ok := _sinkFactories[u.Scheme] + _sinkMutex.RUnlock() + if !ok { + return nil, &errSinkNotFound{u.Scheme} + } + return factory(u) +} + +func newFileSink(u *url.URL) (Sink, error) { + if u.User != nil { + return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u) + } + if u.Fragment != "" { + return nil, fmt.Errorf("fragments not allowed with file URLs: got %v", u) + } + if u.RawQuery != "" { + return nil, fmt.Errorf("query parameters not allowed with file URLs: got %v", u) + } + // Error messages are better if we check hostname and port separately. + if u.Port() != "" { + return nil, fmt.Errorf("ports not allowed with file URLs: got %v", u) + } + if hn := u.Hostname(); hn != "" && hn != "localhost" { + return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u) + } + switch u.Path { + case "stdout": + return nopCloserSink{os.Stdout}, nil + case "stderr": + return nopCloserSink{os.Stderr}, nil + } + return os.OpenFile(u.Path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) +} + +func normalizeScheme(s string) (string, error) { + // https://tools.ietf.org/html/rfc3986#section-3.1 + s = strings.ToLower(s) + if first := s[0]; 'a' > first || 'z' < first { + return "", errors.New("must start with a letter") + } + for i := 1; i < len(s); i++ { // iterate over bytes, not runes + c := s[i] + switch { + case 'a' <= c && c <= 'z': + continue + case '0' <= c && c <= '9': + continue + case c == '.' || c == '+' || c == '-': + continue + } + return "", fmt.Errorf("may not contain %q", c) + } + return s, nil +} diff --git a/vendor/go.uber.org/zap/writer.go b/vendor/go.uber.org/zap/writer.go index 16f55ce4875..86a709ab0be 100644 --- a/vendor/go.uber.org/zap/writer.go +++ b/vendor/go.uber.org/zap/writer.go @@ -21,21 +21,33 @@ package zap import ( + "fmt" + "io" "io/ioutil" - "os" "go.uber.org/zap/zapcore" "go.uber.org/multierr" ) -// Open is a high-level wrapper that takes a variadic number of paths, opens or -// creates each of the specified files, and combines them into a locked +// Open is a high-level wrapper that takes a variadic number of URLs, opens or +// creates each of the specified resources, and combines them into a locked // WriteSyncer. It also returns any error encountered and a function to close // any opened files. // -// Passing no paths returns a no-op WriteSyncer. The special paths "stdout" and -// "stderr" are interpreted as os.Stdout and os.Stderr, respectively. +// Passing no URLs returns a no-op WriteSyncer. Zap handles URLs without a +// scheme and URLs with the "file" scheme. Third-party code may register +// factories for other schemes using RegisterSink. +// +// URLs with the "file" scheme must use absolute paths on the local +// filesystem. No user, password, port, fragments, or query parameters are +// allowed, and the hostname must be empty or "localhost". +// +// Since it's common to write logs to the local filesystem, URLs without a +// scheme (e.g., "/var/log/foo.log") are treated as local file paths. Without +// a scheme, the special paths "stdout" and "stderr" are interpreted as +// os.Stdout and os.Stderr. When specified without a scheme, relative file +// paths also work. func Open(paths ...string) (zapcore.WriteSyncer, func(), error) { writers, close, err := open(paths) if err != nil { @@ -47,33 +59,24 @@ func Open(paths ...string) (zapcore.WriteSyncer, func(), error) { } func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { - var openErr error writers := make([]zapcore.WriteSyncer, 0, len(paths)) - files := make([]*os.File, 0, len(paths)) + closers := make([]io.Closer, 0, len(paths)) close := func() { - for _, f := range files { - f.Close() + for _, c := range closers { + c.Close() } } + + var openErr error for _, path := range paths { - switch path { - case "stdout": - writers = append(writers, os.Stdout) - // Don't close standard out. - continue - case "stderr": - writers = append(writers, os.Stderr) - // Don't close standard error. + sink, err := newSink(path) + if err != nil { + openErr = multierr.Append(openErr, fmt.Errorf("couldn't open sink %q: %v", path, err)) continue } - f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) - openErr = multierr.Append(openErr, err) - if err == nil { - writers = append(writers, f) - files = append(files, f) - } + writers = append(writers, sink) + closers = append(closers, sink) } - if openErr != nil { close() return writers, nil, openErr diff --git a/vendor/go.uber.org/zap/zapcore/field.go b/vendor/go.uber.org/zap/zapcore/field.go index 6a5e33e2f79..ae772e4a170 100644 --- a/vendor/go.uber.org/zap/zapcore/field.go +++ b/vendor/go.uber.org/zap/zapcore/field.go @@ -160,7 +160,7 @@ func (f Field) AddTo(enc ObjectEncoder) { case NamespaceType: enc.OpenNamespace(f.Key) case StringerType: - enc.AddString(f.Key, f.Interface.(fmt.Stringer).String()) + err = encodeStringer(f.Key, f.Interface, enc) case ErrorType: encodeError(f.Key, f.Interface.(error), enc) case SkipType: @@ -199,3 +199,14 @@ func addFields(enc ObjectEncoder, fields []Field) { fields[i].AddTo(enc) } } + +func encodeStringer(key string, stringer interface{}, enc ObjectEncoder) (err error) { + defer func() { + if v := recover(); v != nil { + err = fmt.Errorf("PANIC=%v", v) + } + }() + + enc.AddString(key, stringer.(fmt.Stringer).String()) + return +} diff --git a/vendor/go.uber.org/zap/zapcore/json_encoder.go b/vendor/go.uber.org/zap/zapcore/json_encoder.go index 1006ba2b119..9aec4eada31 100644 --- a/vendor/go.uber.org/zap/zapcore/json_encoder.go +++ b/vendor/go.uber.org/zap/zapcore/json_encoder.go @@ -44,10 +44,15 @@ func getJSONEncoder() *jsonEncoder { } func putJSONEncoder(enc *jsonEncoder) { + if enc.reflectBuf != nil { + enc.reflectBuf.Free() + } enc.EncoderConfig = nil enc.buf = nil enc.spaced = false enc.openNamespaces = 0 + enc.reflectBuf = nil + enc.reflectEnc = nil _jsonPool.Put(enc) } @@ -56,6 +61,10 @@ type jsonEncoder struct { buf *buffer.Buffer spaced bool // include spaces after colons and commas openNamespaces int + + // for encoding generic values by reflection + reflectBuf *buffer.Buffer + reflectEnc *json.Encoder } // NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder @@ -124,13 +133,27 @@ func (enc *jsonEncoder) AddInt64(key string, val int64) { enc.AppendInt64(val) } +func (enc *jsonEncoder) resetReflectBuf() { + if enc.reflectBuf == nil { + enc.reflectBuf = bufferpool.Get() + enc.reflectEnc = json.NewEncoder(enc.reflectBuf) + + // For consistency with our custom JSON encoder. + enc.reflectEnc.SetEscapeHTML(false) + } else { + enc.reflectBuf.Reset() + } +} + func (enc *jsonEncoder) AddReflected(key string, obj interface{}) error { - marshaled, err := json.Marshal(obj) + enc.resetReflectBuf() + err := enc.reflectEnc.Encode(obj) if err != nil { return err } + enc.reflectBuf.TrimNewline() enc.addKey(key) - _, err = enc.buf.Write(marshaled) + _, err = enc.buf.Write(enc.reflectBuf.Bytes()) return err } @@ -213,12 +236,14 @@ func (enc *jsonEncoder) AppendInt64(val int64) { } func (enc *jsonEncoder) AppendReflected(val interface{}) error { - marshaled, err := json.Marshal(val) + enc.resetReflectBuf() + err := enc.reflectEnc.Encode(val) if err != nil { return err } + enc.reflectBuf.TrimNewline() enc.addElementSeparator() - _, err = enc.buf.Write(marshaled) + _, err = enc.buf.Write(enc.reflectBuf.Bytes()) return err } diff --git a/vendor/go.uber.org/zap/zapcore/memory_encoder.go b/vendor/go.uber.org/zap/zapcore/memory_encoder.go index 5c46bc13d6f..dfead0829d6 100644 --- a/vendor/go.uber.org/zap/zapcore/memory_encoder.go +++ b/vendor/go.uber.org/zap/zapcore/memory_encoder.go @@ -43,7 +43,7 @@ func NewMapObjectEncoder() *MapObjectEncoder { // AddArray implements ObjectEncoder. func (m *MapObjectEncoder) AddArray(key string, v ArrayMarshaler) error { - arr := &sliceArrayEncoder{} + arr := &sliceArrayEncoder{elems: make([]interface{}, 0)} err := v.MarshalLogArray(arr) m.cur[key] = arr.elems return err @@ -158,7 +158,7 @@ func (s *sliceArrayEncoder) AppendReflected(v interface{}) error { } func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) } -func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, string(v)) } func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) } func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) } func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) }