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 }