github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/util/util.go (about)

     1  // Copyright 2021 iLogtail Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package util
    16  
    17  import (
    18  	"bufio"
    19  	"bytes"
    20  	"crypto/rand"
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  	"hash/fnv"
    27  	"log"
    28  	"os"
    29  	"os/exec"
    30  	"path/filepath"
    31  	"reflect"
    32  	"strconv"
    33  	"strings"
    34  	"sync/atomic"
    35  	"time"
    36  	"unicode"
    37  	"unsafe"
    38  
    39  	"github.com/alibaba/ilogtail/pkg/protocol"
    40  )
    41  
    42  const alphanum string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    43  
    44  const (
    45  	ShardHashTagKey = "__shardhash__"
    46  	PackIDTagKey    = "__pack_id__"
    47  )
    48  
    49  var (
    50  	ErrCommandTimeout = errors.New("command time out")
    51  	ErrNotImplemented = errors.New("not implemented yet")
    52  	ErrInvalidEnvType = errors.New("invalid env type")
    53  )
    54  
    55  // ReadLines reads contents from a file and splits them by new lines.
    56  // A convenience wrapper to ReadLinesOffsetN(filename, 0, -1).
    57  func ReadLines(filename string) ([]string, error) {
    58  	return ReadLinesOffsetN(filename, 0, -1)
    59  }
    60  
    61  // ReadFirstBlock read first \S+ from head of line
    62  func ReadFirstBlock(line string) string {
    63  	for i, c := range line {
    64  		// 32 -> [SPACE] 33 -> ! 126 -> ~ 127 -> [DEL]
    65  		if c < 33 || c > 126 {
    66  			return line[0:i]
    67  		}
    68  	}
    69  	return line
    70  }
    71  
    72  // ReadLinesOffsetN reads contents from file and splits them by new line.
    73  // The offset tells at which line number to start.
    74  // The count determines the number of lines to read (starting from offset):
    75  // n >= 0: at most n lines
    76  // n < 0: whole file
    77  func ReadLinesOffsetN(filename string, offset uint, n int) ([]string, error) {
    78  	f, err := os.Open(filepath.Clean(filename))
    79  	if err != nil {
    80  		return []string{""}, err
    81  	}
    82  	defer func(f *os.File) {
    83  		_ = f.Close()
    84  	}(f)
    85  
    86  	var ret []string
    87  
    88  	r := bufio.NewReader(f)
    89  	for i := 0; i < n+int(offset) || n < 0; i++ {
    90  		line, err := r.ReadString('\n')
    91  		if err != nil {
    92  			break
    93  		}
    94  		if i < int(offset) {
    95  			continue
    96  		}
    97  		ret = append(ret, strings.Trim(line, "\n"))
    98  	}
    99  
   100  	return ret, nil
   101  }
   102  
   103  // RandomString returns a random string of alpha-numeric characters
   104  func RandomString(n int) string {
   105  	var slice = make([]byte, n)
   106  	_, _ = rand.Read(slice)
   107  	for i, b := range slice {
   108  		slice[i] = alphanum[b%byte(len(alphanum))]
   109  	}
   110  	return string(slice)
   111  }
   112  
   113  // GetTLSConfig gets a tls.Config object from the given certs, key, and CA files.
   114  // you must give the full path to the files.
   115  // If all files are blank and InsecureSkipVerify=false, returns a nil pointer.
   116  func GetTLSConfig(sslCert, sslKey, sslCA string, insecureSkipVerify bool) (*tls.Config, error) {
   117  	if sslCert == "" && sslKey == "" && sslCA == "" && !insecureSkipVerify {
   118  		return nil, nil
   119  	}
   120  
   121  	t := &tls.Config{InsecureSkipVerify: insecureSkipVerify} //nolint:gosec
   122  
   123  	if sslCA != "" {
   124  		caCert, err := os.ReadFile(filepath.Clean(sslCA))
   125  		if err != nil {
   126  			return nil, fmt.Errorf("Could not load TLS CA: %v", err)
   127  		}
   128  
   129  		caCertPool := x509.NewCertPool()
   130  		caCertPool.AppendCertsFromPEM(caCert)
   131  		t.RootCAs = caCertPool
   132  	}
   133  
   134  	if sslCert != "" && sslKey != "" {
   135  		cert, err := tls.LoadX509KeyPair(sslCert, sslKey)
   136  		if err != nil {
   137  			return nil, fmt.Errorf("could not load TLS client key/certificate from %s:%s: %s", sslKey, sslCert, err)
   138  		}
   139  
   140  		t.Certificates = []tls.Certificate{cert}
   141  		t.BuildNameToCertificate()
   142  	}
   143  
   144  	// will be nil by default if nothing is provided
   145  	return t, nil
   146  }
   147  
   148  // SnakeCase converts the given string to snake case following the Golang format:
   149  // acronyms are converted to lower-case and preceded by an underscore.
   150  func SnakeCase(in string) string {
   151  	runes := []rune(in)
   152  	length := len(runes)
   153  
   154  	var out []rune
   155  	for i := 0; i < length; i++ {
   156  		if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
   157  			out = append(out, '_')
   158  		}
   159  		out = append(out, unicode.ToLower(runes[i]))
   160  	}
   161  
   162  	return string(out)
   163  }
   164  
   165  // CombinedOutputTimeout runs the given command with the given timeout and
   166  // returns the combined output of stdout and stderr.
   167  // If the command times out, it attempts to kill the process.
   168  func CombinedOutputTimeout(c *exec.Cmd, timeout time.Duration) ([]byte, error) {
   169  	var b bytes.Buffer
   170  	c.Stdout = &b
   171  	c.Stderr = &b
   172  	if err := c.Start(); err != nil {
   173  		return nil, err
   174  	}
   175  	err := WaitTimeout(c, timeout)
   176  	return b.Bytes(), err
   177  }
   178  
   179  // RunTimeout runs the given command with the given timeout.
   180  // If the command times out, it attempts to kill the process.
   181  func RunTimeout(c *exec.Cmd, timeout time.Duration) error {
   182  	if err := c.Start(); err != nil {
   183  		return err
   184  	}
   185  	return WaitTimeout(c, timeout)
   186  }
   187  
   188  // WaitTimeout waits for the given command to finish with a timeout.
   189  // It assumes the command has already been started.
   190  // If the command times out, it attempts to kill the process.
   191  func WaitTimeout(c *exec.Cmd, timeout time.Duration) error {
   192  	timer := time.NewTimer(timeout)
   193  	done := make(chan error)
   194  	go func() { done <- c.Wait() }()
   195  	select {
   196  	case err := <-done:
   197  		timer.Stop()
   198  		return err
   199  	case <-timer.C:
   200  		if err := c.Process.Kill(); err != nil {
   201  			log.Printf("E! FATAL error killing process: %s", err)
   202  			return err
   203  		}
   204  		// wait for the command to return after killing it
   205  		<-done
   206  		return ErrCommandTimeout
   207  	}
   208  }
   209  
   210  // return true if shutdown is signaled
   211  func RandomSleep(base time.Duration, precisionLose float64, shutdown <-chan struct{}) bool {
   212  	// TODO: Last implementation costs too much CPU, find a better way to implement it.
   213  	return Sleep(base, shutdown)
   214  }
   215  
   216  // Sleep returns true if shutdown is signaled.
   217  func Sleep(interval time.Duration, shutdown <-chan struct{}) bool {
   218  	select {
   219  	case <-time.After(interval):
   220  		return false
   221  	case <-shutdown:
   222  		return true
   223  	}
   224  }
   225  
   226  func CutString(val string, maxLen int) string {
   227  	if len(val) < maxLen {
   228  		return val
   229  	}
   230  	return val[0:maxLen]
   231  }
   232  
   233  func PathExists(path string) (bool, error) {
   234  	_, err := os.Stat(path)
   235  	if err == nil {
   236  		return true, nil
   237  	}
   238  	if os.IsNotExist(err) {
   239  		return false, nil
   240  	}
   241  	return false, err
   242  }
   243  
   244  func SplitPath(path string) (dir string, filename string) {
   245  	lastIndex := strings.LastIndexByte(path, '/')
   246  	lastIndex2 := strings.LastIndexByte(path, '\\')
   247  	if lastIndex < 0 && lastIndex2 < 0 {
   248  		return "", ""
   249  	}
   250  	index := 0
   251  	if lastIndex > lastIndex2 {
   252  		index = lastIndex
   253  	} else {
   254  		index = lastIndex2
   255  	}
   256  	return path[0:index], path[index+1:]
   257  }
   258  
   259  func InitFromEnvBool(key string, value *bool, defaultValue bool) error {
   260  	if envValue := os.Getenv(key); len(envValue) > 0 {
   261  		lowErVal := strings.ToLower(envValue)
   262  		if strings.HasPrefix(lowErVal, "y") || strings.HasPrefix(lowErVal, "t") || strings.HasPrefix(lowErVal, "on") || strings.HasPrefix(lowErVal, "ok") {
   263  			*value = true
   264  		} else {
   265  			*value = false
   266  		}
   267  		return nil
   268  	}
   269  	*value = defaultValue
   270  	return nil
   271  }
   272  
   273  func InitFromEnvInt(key string, value *int, defaultValue int) error {
   274  	if envValue := os.Getenv(key); len(envValue) > 0 {
   275  		if val, err := strconv.Atoi(envValue); err == nil {
   276  			*value = val
   277  			return nil
   278  		}
   279  		*value = defaultValue
   280  		return ErrInvalidEnvType
   281  	}
   282  	*value = defaultValue
   283  	return nil
   284  }
   285  
   286  func InitFromEnvString(key string, value *string, defaultValue string) error {
   287  	if envValue := os.Getenv(key); len(envValue) > 0 {
   288  		*value = envValue
   289  		return nil
   290  	}
   291  	*value = defaultValue
   292  	return nil
   293  }
   294  
   295  // GuessRegionByEndpoint guess region
   296  // cn-hangzhou.log.aliyuncs.com cn-hangzhou-intranet.log.aliyuncs.com cn-hangzhou-vpc.log.aliyuncs.com cn-hangzhou-share.log.aliyuncs.com
   297  func GuessRegionByEndpoint(endPoint, defaultRegion string) string {
   298  
   299  	if strings.HasPrefix(endPoint, "http://") {
   300  		endPoint = endPoint[len("http://"):]
   301  	} else {
   302  		endPoint = strings.TrimPrefix(endPoint, "https://")
   303  	}
   304  	if dotIndex := strings.IndexByte(endPoint, '.'); dotIndex > 0 {
   305  		region := endPoint[0:dotIndex]
   306  		if strings.HasSuffix(region, "-intranet") || strings.HasSuffix(region, "-vpc") || strings.HasSuffix(region, "-share") {
   307  			region = region[0:strings.LastIndexByte(region, '-')]
   308  		}
   309  		return region
   310  	}
   311  	return defaultRegion
   312  }
   313  
   314  func DeepCopy(src *map[string]interface{}) *map[string]interface{} {
   315  	if src == nil {
   316  		return nil
   317  	}
   318  	var buf []byte
   319  	var err error
   320  	if buf, err = json.Marshal(src); err != nil {
   321  		return nil
   322  	}
   323  	dst := new(map[string]interface{})
   324  	if err := json.Unmarshal(buf, dst); err != nil {
   325  		return nil
   326  	}
   327  	return dst
   328  }
   329  
   330  func InterfaceToString(val interface{}) (string, bool) {
   331  	if val == nil {
   332  		return "", false
   333  	}
   334  	strVal, ok := val.(string)
   335  	return strVal, ok
   336  }
   337  
   338  func InterfaceToJSONString(val interface{}) (string, error) {
   339  	b, err := json.Marshal(val)
   340  	return string(b), err
   341  }
   342  
   343  func NewPackIDPrefix(text string) string {
   344  	h := fnv.New64a()
   345  	_, _ = h.Write([]byte(text + GetIPAddress() + time.Now().String()))
   346  	return fmt.Sprintf("%X-", h.Sum64())
   347  }
   348  
   349  func NewLogTagForPackID(prefix string, seqNum *int64) *protocol.LogTag {
   350  	tag := &protocol.LogTag{
   351  		Key:   PackIDTagKey,
   352  		Value: fmt.Sprintf("%s%X", prefix, atomic.LoadInt64(seqNum)),
   353  	}
   354  	atomic.AddInt64(seqNum, 1)
   355  	return tag
   356  }
   357  
   358  func MinInt(a, b int) int {
   359  	if a < b {
   360  		return a
   361  	}
   362  	return b
   363  }
   364  
   365  // StringDeepCopy returns a deep copy or src.
   366  // Because we can not make sure the life cycle of string passed from C++,
   367  // so we have to make a deep copy of them so that they are always valid in Go.
   368  func StringDeepCopy(src string) string {
   369  	return string([]byte(src))
   370  }
   371  
   372  // StringPointer returns the pointer of the given string.
   373  // nolint:gosec
   374  func StringPointer(s string) unsafe.Pointer {
   375  	p := (*reflect.StringHeader)(unsafe.Pointer(&s))
   376  	return unsafe.Pointer(p.Data)
   377  }
   378  
   379  // UniqueStrings Merge (append) slices and remove duplicate from them!
   380  func UniqueStrings(strSlices ...[]string) []string {
   381  	uniqueMap := map[string]bool{}
   382  	for _, strSlice := range strSlices {
   383  		for _, number := range strSlice {
   384  			uniqueMap[number] = true
   385  		}
   386  	}
   387  	result := make([]string, 0, len(uniqueMap))
   388  	for key := range uniqueMap {
   389  		result = append(result, key)
   390  	}
   391  	return result
   392  }
   393  
   394  func Contains[T comparable](s []T, e T) bool {
   395  	for _, a := range s {
   396  		if a == e {
   397  			return true
   398  		}
   399  	}
   400  	return false
   401  }