github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/shared/mlog/tcp.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package mlog 5 6 import ( 7 "context" 8 "crypto/tls" 9 "errors" 10 "fmt" 11 "net" 12 _ "net/http/pprof" 13 "sync" 14 "time" 15 16 "github.com/hashicorp/go-multierror" 17 "github.com/mattermost/logr" 18 ) 19 20 const ( 21 DialTimeoutSecs = 30 22 WriteTimeoutSecs = 30 23 RetryBackoffMillis int64 = 100 24 MaxRetryBackoffMillis int64 = 30 * 1000 // 30 seconds 25 ) 26 27 // Tcp outputs log records to raw socket server. 28 type Tcp struct { 29 logr.Basic 30 31 params *TcpParams 32 addy string 33 34 mutex sync.Mutex 35 conn net.Conn 36 monitor chan struct{} 37 shutdown chan struct{} 38 } 39 40 // TcpParams provides parameters for dialing a socket server. 41 type TcpParams struct { 42 IP string `json:"IP"` 43 Port int `json:"Port"` 44 TLS bool `json:"TLS"` 45 Cert string `json:"Cert"` 46 Insecure bool `json:"Insecure"` 47 } 48 49 // NewTcpTarget creates a target capable of outputting log records to a raw socket, with or without TLS. 50 func NewTcpTarget(filter logr.Filter, formatter logr.Formatter, params *TcpParams, maxQueue int) (*Tcp, error) { 51 tcp := &Tcp{ 52 params: params, 53 addy: fmt.Sprintf("%s:%d", params.IP, params.Port), 54 monitor: make(chan struct{}), 55 shutdown: make(chan struct{}), 56 } 57 tcp.Basic.Start(tcp, tcp, filter, formatter, maxQueue) 58 59 return tcp, nil 60 } 61 62 // getConn provides a net.Conn. If a connection already exists, it is returned immediately, 63 // otherwise this method blocks until a new connection is created, timeout or shutdown. 64 func (tcp *Tcp) getConn() (net.Conn, error) { 65 tcp.mutex.Lock() 66 defer tcp.mutex.Unlock() 67 68 Log(LvlTcpLogTarget, "getConn enter", String("addy", tcp.addy)) 69 defer Log(LvlTcpLogTarget, "getConn exit", String("addy", tcp.addy)) 70 71 if tcp.conn != nil { 72 Log(LvlTcpLogTarget, "reusing existing conn", String("addy", tcp.addy)) // use "With" once Zap is removed 73 return tcp.conn, nil 74 } 75 76 type result struct { 77 conn net.Conn 78 err error 79 } 80 81 connChan := make(chan result) 82 ctx, cancel := context.WithTimeout(context.Background(), time.Second*DialTimeoutSecs) 83 defer cancel() 84 85 go func(ctx context.Context, ch chan result) { 86 Log(LvlTcpLogTarget, "dailing", String("addy", tcp.addy)) 87 conn, err := tcp.dial(ctx) 88 if err == nil { 89 tcp.conn = conn 90 tcp.monitor = make(chan struct{}) 91 go monitor(tcp.conn, tcp.monitor, Log) 92 } 93 ch <- result{conn: conn, err: err} 94 }(ctx, connChan) 95 96 select { 97 case <-tcp.shutdown: 98 return nil, errors.New("shutdown") 99 case res := <-connChan: 100 return res.conn, res.err 101 } 102 } 103 104 // dial connects to a TCP socket, and optionally performs a TLS handshake. 105 // A non-nil context must be provided which can cancel the dial. 106 func (tcp *Tcp) dial(ctx context.Context) (net.Conn, error) { 107 var dialer net.Dialer 108 dialer.Timeout = time.Second * DialTimeoutSecs 109 conn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", tcp.params.IP, tcp.params.Port)) 110 if err != nil { 111 return nil, err 112 } 113 114 if !tcp.params.TLS { 115 return conn, nil 116 } 117 118 Log(LvlTcpLogTarget, "TLS handshake", String("addy", tcp.addy)) 119 120 tlsconfig := &tls.Config{ 121 ServerName: tcp.params.IP, 122 InsecureSkipVerify: tcp.params.Insecure, 123 } 124 if tcp.params.Cert != "" { 125 pool, err := getCertPool(tcp.params.Cert) 126 if err != nil { 127 return nil, err 128 } 129 tlsconfig.RootCAs = pool 130 } 131 132 tlsConn := tls.Client(conn, tlsconfig) 133 if err := tlsConn.Handshake(); err != nil { 134 return nil, err 135 } 136 return tlsConn, nil 137 } 138 139 func (tcp *Tcp) close() error { 140 tcp.mutex.Lock() 141 defer tcp.mutex.Unlock() 142 143 var err error 144 if tcp.conn != nil { 145 Log(LvlTcpLogTarget, "closing connection", String("addy", tcp.addy)) 146 close(tcp.monitor) 147 err = tcp.conn.Close() 148 tcp.conn = nil 149 } 150 return err 151 } 152 153 // Shutdown stops processing log records after making best effort to flush queue. 154 func (tcp *Tcp) Shutdown(ctx context.Context) error { 155 errs := &multierror.Error{} 156 157 Log(LvlTcpLogTarget, "shutting down", String("addy", tcp.addy)) 158 159 if err := tcp.Basic.Shutdown(ctx); err != nil { 160 errs = multierror.Append(errs, err) 161 } 162 163 if err := tcp.close(); err != nil { 164 errs = multierror.Append(errs, err) 165 } 166 167 close(tcp.shutdown) 168 return errs.ErrorOrNil() 169 } 170 171 // Write converts the log record to bytes, via the Formatter, and outputs to the socket. 172 // Called by dedicated target goroutine and will block until success or shutdown. 173 func (tcp *Tcp) Write(rec *logr.LogRec) error { 174 _, stacktrace := tcp.IsLevelEnabled(rec.Level()) 175 176 buf := rec.Logger().Logr().BorrowBuffer() 177 defer rec.Logger().Logr().ReleaseBuffer(buf) 178 179 buf, err := tcp.Formatter().Format(rec, stacktrace, buf) 180 if err != nil { 181 return err 182 } 183 184 try := 1 185 backoff := RetryBackoffMillis 186 for { 187 select { 188 case <-tcp.shutdown: 189 return err 190 default: 191 } 192 193 conn, err := tcp.getConn() 194 if err != nil { 195 Log(LvlTcpLogTarget, "failed getting connection", String("addy", tcp.addy), Err(err)) 196 reporter := rec.Logger().Logr().ReportError 197 reporter(fmt.Errorf("log target %s connection error: %w", tcp.String(), err)) 198 backoff = tcp.sleep(backoff) 199 continue 200 } 201 202 conn.SetWriteDeadline(time.Now().Add(time.Second * WriteTimeoutSecs)) 203 _, err = buf.WriteTo(conn) 204 if err == nil { 205 return nil 206 } 207 208 Log(LvlTcpLogTarget, "write error", String("addy", tcp.addy), Err(err)) 209 reporter := rec.Logger().Logr().ReportError 210 reporter(fmt.Errorf("log target %s write error: %w", tcp.String(), err)) 211 212 _ = tcp.close() 213 214 backoff = tcp.sleep(backoff) 215 try++ 216 Log(LvlTcpLogTarget, "retrying write", String("addy", tcp.addy), Int("try", try)) 217 } 218 } 219 220 // monitor continuously tries to read from the connection to detect socket close. 221 // This is needed because TCP target uses a write only socket and Linux systems 222 // take a long time to detect a loss of connectivity on a socket when only writing; 223 // the writes simply fail without an error returned. 224 func monitor(conn net.Conn, done <-chan struct{}, logFunc LogFuncCustom) { 225 addy := conn.RemoteAddr().String() 226 defer logFunc(LvlTcpLogTarget, "monitor exiting", String("addy", addy)) 227 228 buf := make([]byte, 1) 229 for { 230 logFunc(LvlTcpLogTarget, "monitor loop", String("addy", addy)) 231 232 select { 233 case <-done: 234 return 235 case <-time.After(1 * time.Second): 236 } 237 238 err := conn.SetReadDeadline(time.Now().Add(time.Second * 30)) 239 if err != nil { 240 continue 241 } 242 243 _, err = conn.Read(buf) 244 245 if errt, ok := err.(net.Error); ok && errt.Timeout() { 246 // read timeout is expected, keep looping. 247 continue 248 } 249 250 // Any other error closes the connection, forcing a reconnect. 251 logFunc(LvlTcpLogTarget, "monitor closing connection", Err(err)) 252 conn.Close() 253 return 254 } 255 } 256 257 // String returns a string representation of this target. 258 func (tcp *Tcp) String() string { 259 return fmt.Sprintf("TcpTarget[%s:%d]", tcp.params.IP, tcp.params.Port) 260 } 261 262 func (tcp *Tcp) sleep(backoff int64) int64 { 263 select { 264 case <-tcp.shutdown: 265 case <-time.After(time.Millisecond * time.Duration(backoff)): 266 } 267 268 nextBackoff := backoff + (backoff >> 1) 269 if nextBackoff > MaxRetryBackoffMillis { 270 nextBackoff = MaxRetryBackoffMillis 271 } 272 return nextBackoff 273 }