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  }