diff options
Diffstat (limited to 'vendor/github.com/mattermost/logr/v2/targets')
7 files changed, 611 insertions, 0 deletions
diff --git a/vendor/github.com/mattermost/logr/v2/targets/file.go b/vendor/github.com/mattermost/logr/v2/targets/file.go new file mode 100644 index 00000000..71133fac --- /dev/null +++ b/vendor/github.com/mattermost/logr/v2/targets/file.go @@ -0,0 +1,78 @@ +package targets + +import ( + "errors" + "io" + + "github.com/mattermost/logr/v2" + "gopkg.in/natefinch/lumberjack.v2" +) + +type FileOptions struct { + // Filename is the file to write logs to. Backup log files will be retained + // in the same directory. It uses <processname>-lumberjack.log in + // os.TempDir() if empty. + Filename string `json:"filename"` + + // MaxSize is the maximum size in megabytes of the log file before it gets + // rotated. It defaults to 100 megabytes. + MaxSize int `json:"max_size"` + + // MaxAge is the maximum number of days to retain old log files based on the + // timestamp encoded in their filename. Note that a day is defined as 24 + // hours and may not exactly correspond to calendar days due to daylight + // savings, leap seconds, etc. The default is not to remove old log files + // based on age. + MaxAge int `json:"max_age"` + + // MaxBackups is the maximum number of old log files to retain. The default + // is to retain all old log files (though MaxAge may still cause them to get + // deleted.) + MaxBackups int `json:"max_backups"` + + // Compress determines if the rotated log files should be compressed + // using gzip. The default is not to perform compression. + Compress bool `json:"compress"` +} + +func (fo FileOptions) CheckValid() error { + if fo.Filename == "" { + return errors.New("filename cannot be empty") + } + return nil +} + +// File outputs log records to a file which can be log rotated based on size or age. +// Uses `https://github.com/natefinch/lumberjack` for rotation. +type File struct { + out io.WriteCloser +} + +// NewFileTarget creates a target capable of outputting log records to a rotated file. +func NewFileTarget(opts FileOptions) *File { + lumber := &lumberjack.Logger{ + Filename: opts.Filename, + MaxSize: opts.MaxSize, + MaxBackups: opts.MaxBackups, + MaxAge: opts.MaxAge, + Compress: opts.Compress, + } + f := &File{out: lumber} + return f +} + +// Init is called once to initialize the target. +func (f *File) Init() error { + return nil +} + +// Write outputs bytes to this file target. +func (f *File) Write(p []byte, rec *logr.LogRec) (int, error) { + return f.out.Write(p) +} + +// Shutdown is called once to free/close any resources. +// Target queue is already drained when this is called. +func (f *File) Shutdown() error { + return f.out.Close() +} diff --git a/vendor/github.com/mattermost/logr/v2/targets/syslog.go b/vendor/github.com/mattermost/logr/v2/targets/syslog.go new file mode 100644 index 00000000..fc3fcc5f --- /dev/null +++ b/vendor/github.com/mattermost/logr/v2/targets/syslog.go @@ -0,0 +1,112 @@ +// +build !windows,!nacl,!plan9 + +package targets + +import ( + "crypto/tls" + "errors" + "fmt" + + "github.com/mattermost/logr/v2" + syslog "github.com/wiggin77/srslog" +) + +// Syslog outputs log records to local or remote syslog. +type Syslog struct { + params *SyslogOptions + writer *syslog.Writer +} + +// SyslogOptions provides parameters for dialing a syslog daemon. +type SyslogOptions struct { + IP string `json:"ip,omitempty"` // deprecated + Host string `json:"host"` + Port int `json:"port"` + TLS bool `json:"tls"` + Cert string `json:"cert"` + Insecure bool `json:"insecure"` + Tag string `json:"tag"` +} + +func (so SyslogOptions) CheckValid() error { + if so.Host == "" && so.IP == "" { + return errors.New("missing host") + } + if so.Port == 0 { + return errors.New("missing port") + } + return nil +} + +// NewSyslogTarget creates a target capable of outputting log records to remote or local syslog, with or without TLS. +func NewSyslogTarget(params *SyslogOptions) (*Syslog, error) { + if params == nil { + return nil, errors.New("params cannot be nil") + } + + s := &Syslog{ + params: params, + } + return s, nil +} + +// Init is called once to initialize the target. +func (s *Syslog) Init() error { + network := "tcp" + var config *tls.Config + + if s.params.TLS { + network = "tcp+tls" + config = &tls.Config{InsecureSkipVerify: s.params.Insecure} + if s.params.Cert != "" { + pool, err := GetCertPool(s.params.Cert) + if err != nil { + return err + } + config.RootCAs = pool + } + } + raddr := fmt.Sprintf("%s:%d", s.params.IP, s.params.Port) + if raddr == ":0" { + // If no IP:port provided then connect to local syslog. + raddr = "" + network = "" + } + + var err error + s.writer, err = syslog.DialWithTLSConfig(network, raddr, syslog.LOG_INFO, s.params.Tag, config) + return err +} + +// Write outputs bytes to this file target. +func (s *Syslog) Write(p []byte, rec *logr.LogRec) (int, error) { + txt := string(p) + n := len(txt) + var err error + + switch rec.Level() { + case logr.Panic, logr.Fatal: + err = s.writer.Crit(txt) + case logr.Error: + err = s.writer.Err(txt) + case logr.Warn: + err = s.writer.Warning(txt) + case logr.Debug, logr.Trace: + err = s.writer.Debug(txt) + default: + // logr.Info plus all custom levels. + err = s.writer.Info(txt) + } + + if err != nil { + n = 0 + // syslog writer will try to reconnect. + } + return n, err +} + +// Shutdown is called once to free/close any resources. +// Target queue is already drained when this is called. +func (s *Syslog) Shutdown() error { + return s.writer.Close() +} diff --git a/vendor/github.com/mattermost/logr/v2/targets/syslog_unsupported.go b/vendor/github.com/mattermost/logr/v2/targets/syslog_unsupported.go new file mode 100644 index 00000000..e4086e96 --- /dev/null +++ b/vendor/github.com/mattermost/logr/v2/targets/syslog_unsupported.go @@ -0,0 +1,56 @@ +// +build windows nacl plan9 + +package targets + +import ( + "errors" + + "github.com/mattermost/logr/v2" + syslog "github.com/wiggin77/srslog" +) + +const ( + unsupported = "Syslog target is not supported on this platform." +) + +// Syslog outputs log records to local or remote syslog. +type Syslog struct { + params *SyslogOptions + writer *syslog.Writer +} + +// SyslogOptions provides parameters for dialing a syslog daemon. +type SyslogOptions struct { + IP string `json:"ip,omitempty"` // deprecated + Host string `json:"host"` + Port int `json:"port"` + TLS bool `json:"tls"` + Cert string `json:"cert"` + Insecure bool `json:"insecure"` + Tag string `json:"tag"` +} + +func (so SyslogOptions) CheckValid() error { + return errors.New(unsupported) +} + +// NewSyslogTarget creates a target capable of outputting log records to remote or local syslog, with or without TLS. +func NewSyslogTarget(params *SyslogOptions) (*Syslog, error) { + return nil, errors.New(unsupported) +} + +// Init is called once to initialize the target. +func (s *Syslog) Init() error { + return errors.New(unsupported) +} + +// Write outputs bytes to this file target. +func (s *Syslog) Write(p []byte, rec *logr.LogRec) (int, error) { + return 0, errors.New(unsupported) +} + +// Shutdown is called once to free/close any resources. +// Target queue is already drained when this is called. +func (s *Syslog) Shutdown() error { + return errors.New(unsupported) +} diff --git a/vendor/github.com/mattermost/logr/v2/targets/tcp.go b/vendor/github.com/mattermost/logr/v2/targets/tcp.go new file mode 100644 index 00000000..ce73e034 --- /dev/null +++ b/vendor/github.com/mattermost/logr/v2/targets/tcp.go @@ -0,0 +1,251 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package targets + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "sync" + "time" + + "github.com/mattermost/logr/v2" +) + +const ( + DialTimeoutSecs = 30 + WriteTimeoutSecs = 30 + RetryBackoffMillis int64 = 100 + MaxRetryBackoffMillis int64 = 30 * 1000 // 30 seconds +) + +// Tcp outputs log records to raw socket server. +type Tcp struct { + options *TcpOptions + addy string + + mutex sync.Mutex + conn net.Conn + monitor chan struct{} + shutdown chan struct{} +} + +// TcpOptions provides parameters for dialing a socket server. +type TcpOptions struct { + IP string `json:"ip,omitempty"` // deprecated + Host string `json:"host"` + Port int `json:"port"` + TLS bool `json:"tls"` + Cert string `json:"cert"` + Insecure bool `json:"insecure"` +} + +func (to TcpOptions) CheckValid() error { + if to.Host == "" && to.IP == "" { + return errors.New("missing host") + } + if to.Port == 0 { + return errors.New("missing port") + } + return nil +} + +// NewTcpTarget creates a target capable of outputting log records to a raw socket, with or without TLS. +func NewTcpTarget(options *TcpOptions) *Tcp { + tcp := &Tcp{ + options: options, + addy: fmt.Sprintf("%s:%d", options.IP, options.Port), + monitor: make(chan struct{}), + shutdown: make(chan struct{}), + } + return tcp +} + +// Init is called once to initialize the target. +func (tcp *Tcp) Init() error { + return nil +} + +// getConn provides a net.Conn. If a connection already exists, it is returned immediately, +// otherwise this method blocks until a new connection is created, timeout or shutdown. +func (tcp *Tcp) getConn(reporter func(err interface{})) (net.Conn, error) { + tcp.mutex.Lock() + defer tcp.mutex.Unlock() + + if tcp.conn != nil { + return tcp.conn, nil + } + + type result struct { + conn net.Conn + err error + } + + connChan := make(chan result) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*DialTimeoutSecs) + defer cancel() + + go func(ctx context.Context, ch chan result) { + conn, err := tcp.dial(ctx) + if err != nil { + reporter(fmt.Errorf("log target %s connection error: %w", tcp.String(), err)) + return + } + tcp.conn = conn + tcp.monitor = make(chan struct{}) + go monitor(tcp.conn, tcp.monitor) + ch <- result{conn: conn, err: err} + }(ctx, connChan) + + select { + case <-tcp.shutdown: + return nil, errors.New("shutdown") + case res := <-connChan: + return res.conn, res.err + } +} + +// dial connects to a TCP socket, and optionally performs a TLS handshake. +// A non-nil context must be provided which can cancel the dial. +func (tcp *Tcp) dial(ctx context.Context) (net.Conn, error) { + var dialer net.Dialer + dialer.Timeout = time.Second * DialTimeoutSecs + conn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", tcp.options.IP, tcp.options.Port)) + if err != nil { + return nil, err + } + + if !tcp.options.TLS { + return conn, nil + } + + tlsconfig := &tls.Config{ + ServerName: tcp.options.IP, + InsecureSkipVerify: tcp.options.Insecure, + } + if tcp.options.Cert != "" { + pool, err := GetCertPool(tcp.options.Cert) + if err != nil { + return nil, err + } + tlsconfig.RootCAs = pool + } + + tlsConn := tls.Client(conn, tlsconfig) + if err := tlsConn.Handshake(); err != nil { + return nil, err + } + return tlsConn, nil +} + +func (tcp *Tcp) close() error { + tcp.mutex.Lock() + defer tcp.mutex.Unlock() + + var err error + if tcp.conn != nil { + close(tcp.monitor) + err = tcp.conn.Close() + tcp.conn = nil + } + return err +} + +// Shutdown stops processing log records after making best effort to flush queue. +func (tcp *Tcp) Shutdown() error { + err := tcp.close() + close(tcp.shutdown) + return err +} + +// Write converts the log record to bytes, via the Formatter, and outputs to the socket. +// Called by dedicated target goroutine and will block until success or shutdown. +func (tcp *Tcp) Write(p []byte, rec *logr.LogRec) (int, error) { + try := 1 + backoff := RetryBackoffMillis + for { + select { + case <-tcp.shutdown: + return 0, nil + default: + } + + reporter := rec.Logger().Logr().ReportError + + conn, err := tcp.getConn(reporter) + if err != nil { + reporter(fmt.Errorf("log target %s connection error: %w", tcp.String(), err)) + backoff = tcp.sleep(backoff) + continue + } + + err = conn.SetWriteDeadline(time.Now().Add(time.Second * WriteTimeoutSecs)) + if err != nil { + reporter(fmt.Errorf("log target %s set write deadline error: %w", tcp.String(), err)) + } + + count, err := conn.Write(p) + if err == nil { + return count, nil + } + + reporter(fmt.Errorf("log target %s write error: %w", tcp.String(), err)) + + _ = tcp.close() + + backoff = tcp.sleep(backoff) + try++ + } +} + +// monitor continuously tries to read from the connection to detect socket close. +// This is needed because TCP target uses a write only socket and Linux systems +// take a long time to detect a loss of connectivity on a socket when only writing; +// the writes simply fail without an error returned. +func monitor(conn net.Conn, done <-chan struct{}) { + buf := make([]byte, 1) + for { + select { + case <-done: + return + case <-time.After(1 * time.Second): + } + + err := conn.SetReadDeadline(time.Now().Add(time.Second * 30)) + if err != nil { + continue + } + + _, err = conn.Read(buf) + + if errt, ok := err.(net.Error); ok && errt.Timeout() { + // read timeout is expected, keep looping. + continue + } + + // Any other error closes the connection, forcing a reconnect. + conn.Close() + return + } +} + +// String returns a string representation of this target. +func (tcp *Tcp) String() string { + return fmt.Sprintf("TcpTarget[%s:%d]", tcp.options.IP, tcp.options.Port) +} + +func (tcp *Tcp) sleep(backoff int64) int64 { + select { + case <-tcp.shutdown: + case <-time.After(time.Millisecond * time.Duration(backoff)): + } + + nextBackoff := backoff + (backoff >> 1) + if nextBackoff > MaxRetryBackoffMillis { + nextBackoff = MaxRetryBackoffMillis + } + return nextBackoff +} diff --git a/vendor/github.com/mattermost/logr/v2/targets/test-tls-client-cert.pem b/vendor/github.com/mattermost/logr/v2/targets/test-tls-client-cert.pem new file mode 100644 index 00000000..6ce8d042 --- /dev/null +++ b/vendor/github.com/mattermost/logr/v2/targets/test-tls-client-cert.pem @@ -0,0 +1,43 @@ +-----BEGIN CERTIFICATE----- +MIIDjzCCAnegAwIBAgIRAPYfRSwdzKopBKxYxKqslJUwDQYJKoZIhvcNAQELBQAw +JzElMCMGA1UEAwwcTWF0dGVybW9zdCwgSW5jLiBJbnRlcm5hbCBDQTAeFw0xOTAz +MjIwMDE0MTVaFw0yMjAzMDYwMDE0MTVaMDsxOTA3BgNVBAMTME1hdHRlcm1vc3Qs +IEluYy4gSW50ZXJuYWwgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMjliRdmvnNL4u/Jr/M2dPwQmTJXEBY/Vq9Q +vAU52X3tRMCPxcaFz+x6ftuvdO2NdohXGAmtx9QU5LZcvFeTDpoVEBo9A+4jtLvD +DZYaTNLpJmoSoJHaDbdWX+OAOqyDiWS741LuiMKWHhew9QOisat2ZINPxjmAd9wE +xthTMgzsv7MUqnMer8U5OGQ0Qy7wAmNRc+2K3qPwkxe2RUvcte50DUFNgxEginsh +vrkOXR383vUCZfu72qu8oggjiQpyTllu5je2Ap6JLjYLkEMiMqrYADuWor/ZHwa6 +WrFqVETxWfAV5u9Eh0wZM/KKYwRQuw9y+Nans77FmUl1tVWWNN8CAwEAAaOBoTCB +njAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBQY4Uqswyr2hO/HetZt2RDxJdTIPjBi +BgNVHSMEWzBZgBRFZXVg2Z5tNIsWeWjBLEy2yzKbMKErpCkwJzElMCMGA1UEAwwc +TWF0dGVybW9zdCwgSW5jLiBJbnRlcm5hbCBDQYIUEifGUOM+bIFZo1tkjZB5YGBr +0xEwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQAEdexL30Q0zBHmPAH8 +LhdK7dbzW1CmILbxRZlKAwRN+hKRXiMW3MHIkhNuoV9Aev602Q+ja4lWsRi/ktOL +ni1FWx5gSScgdG8JGj47dOmoT3vXKX7+umiv4rQLPDl9/DKMuv204OYJq6VT+uNU +6C6kL157jGJEO76H4fMZ8oYsD7Sq0zjiNKtuCYii0ngH3j3gB1jACLqRgveU7MdT +pqOV2KfY31+h8VBtkUvljNztQ9xNY8Fjmt0SMf7E3FaUcaar3ZCr70G5aU3dKbe7 +47vGOBa5tCqw4YK0jgDKid3IJQul9a3J1mSsH8Wy3to9cAV4KGZBQLnzCX15a/+v +3yVh +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDfjCCAmagAwIBAgIUEifGUOM+bIFZo1tkjZB5YGBr0xEwDQYJKoZIhvcNAQEL +BQAwJzElMCMGA1UEAwwcTWF0dGVybW9zdCwgSW5jLiBJbnRlcm5hbCBDQTAeFw0x +OTAzMjEyMTI4NDNaFw0yOTAzMTgyMTI4NDNaMCcxJTAjBgNVBAMMHE1hdHRlcm1v +c3QsIEluYy4gSW50ZXJuYWwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDH0Xq5rMBGpKOVWTpb5MnaJIWFP/vOtvEk+7hVrfOfe1/5x0Kk3UgAHj85 +otaEZD1Lhn/JLkEqCiE/UXMJFwJDlNcO4CkdKBSpYX4bKAqy5q/X3QwioMSNpJG1 ++YYrNGBH0sgKcKjyCaLhmqYLD0xZDVOmWIYBU9jUPyXw5U0tnsVrTqGMxVkm1xCY +krCWN1ZoUrLvL0MCZc5qpxoPTopr9UO9cqSBSuy6BVWVuEWBZhpqHt+ul8VxhzzY +q1k4l7r2qw+/wm1iJBedTeBVeWNag8JaVfLgu+/W7oJVlPO32Po7pnvHp8iJ3b4K +zXyVHaTX4S6Em+6LV8855TYrShzlAgMBAAGjgaEwgZ4wHQYDVR0OBBYEFEVldWDZ +nm00ixZ5aMEsTLbLMpswMGIGA1UdIwRbMFmAFEVldWDZnm00ixZ5aMEsTLbLMpsw +oSukKTAnMSUwIwYDVQQDDBxNYXR0ZXJtb3N0LCBJbmMuIEludGVybmFsIENBghQS +J8ZQ4z5sgVmjW2SNkHlgYGvTETAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjAN +BgkqhkiG9w0BAQsFAAOCAQEAPiCWFmopyAkY2T3Zyo4yaRPhX1+VOTMKJtY6EUhq +/GHz6kzEyvCUBf0N892cibGxekrEoItY9NqO6RQRfowg+Gn5kc13z4NyL2W8/eoT +Xy0ZvfaQbU++fQ6pVtWtMblDMU9xiYd7/MDvJpO328l1Vhcdp8kEi+lCvpy0sCRc +PxzPhbgCMAbZEGx+4TMQd4SZKzlRxW/2fflpReh6v1Dv0VDUSYQWwsUnaLpdKHfh +a5k0vuySYcszE4YKlY0zakeFlJfp7fBp1xTwcdW8aTfw15EicPMwTc6xxA4JJUJx +cddu817n1nayK5u6r9Qh1oIVkr0nC9YELMMy4dpPgJ88SA== +-----END CERTIFICATE----- diff --git a/vendor/github.com/mattermost/logr/v2/targets/utils.go b/vendor/github.com/mattermost/logr/v2/targets/utils.go new file mode 100644 index 00000000..6e605af2 --- /dev/null +++ b/vendor/github.com/mattermost/logr/v2/targets/utils.go @@ -0,0 +1,33 @@ +package targets + +import ( + "crypto/x509" + "encoding/base64" + "errors" + "io/ioutil" +) + +// GetCertPool returns a x509.CertPool containing the cert(s) +// from `cert`, which can be a path to a .pem or .crt file, +// or a base64 encoded cert. +func GetCertPool(cert string) (*x509.CertPool, error) { + if cert == "" { + return nil, errors.New("no cert provided") + } + + // first treat as a file and try to read. + serverCert, err := ioutil.ReadFile(cert) + if err != nil { + // maybe it's a base64 encoded cert + serverCert, err = base64.StdEncoding.DecodeString(cert) + if err != nil { + return nil, errors.New("cert cannot be read") + } + } + + pool := x509.NewCertPool() + if ok := pool.AppendCertsFromPEM(serverCert); ok { + return pool, nil + } + return nil, errors.New("cannot parse cert") +} diff --git a/vendor/github.com/mattermost/logr/v2/targets/writer.go b/vendor/github.com/mattermost/logr/v2/targets/writer.go new file mode 100644 index 00000000..d9f64d76 --- /dev/null +++ b/vendor/github.com/mattermost/logr/v2/targets/writer.go @@ -0,0 +1,38 @@ +package targets + +import ( + "io" + "io/ioutil" + + "github.com/mattermost/logr/v2" +) + +// Writer outputs log records to any `io.Writer`. +type Writer struct { + out io.Writer +} + +// NewWriterTarget creates a target capable of outputting log records to an io.Writer. +func NewWriterTarget(out io.Writer) *Writer { + if out == nil { + out = ioutil.Discard + } + w := &Writer{out: out} + return w +} + +// Init is called once to initialize the target. +func (w *Writer) Init() error { + return nil +} + +// Write outputs bytes to this file target. +func (w *Writer) Write(p []byte, rec *logr.LogRec) (int, error) { + return w.out.Write(p) +} + +// Shutdown is called once to free/close any resources. +// Target queue is already drained when this is called. +func (w *Writer) Shutdown() error { + return nil +} |