func (tcp *Tcp) String() string {
	return fmt.Sprintf("TcpTarget[%s:%d]", tcp.options.IP, tcp.options.Port)
}
String returns a string representation of this target.
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  
)
type Tcp struct {
	options *TcpOptions
	addy    string
	mutex    sync.Mutex
	conn     net.Conn
	monitor  chan struct{}
	shutdown chan struct{}
}
type TcpOptions struct {
	IP       string `json:"ip,omitempty"`  
	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
}
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
}
func (tcp *Tcp) Init() error {
	return nil
}
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
	}
}
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
}
func (tcp *Tcp) Shutdown() error {
	err := tcp.close()
	close(tcp.shutdown)
	return err
}
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++
	}
}
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() {
			continue
		}
		conn.Close()
		return
	}
}