github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/logger/logger.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 logger 16 17 import ( 18 "bufio" 19 "context" 20 "flag" 21 "fmt" 22 "io" 23 "path" 24 25 "os" 26 "path/filepath" 27 "regexp" 28 "strings" 29 "sync" 30 "sync/atomic" 31 "time" 32 33 "github.com/alibaba/ilogtail/pkg" 34 "github.com/alibaba/ilogtail/pkg/config" 35 "github.com/alibaba/ilogtail/pkg/util" 36 37 "github.com/cihub/seelog" 38 ) 39 40 // seelog template 41 const ( 42 asyncPattern = ` 43 <seelog type="asynctimer" asyncinterval="500000" minlevel="%s" > 44 <outputs formatid="common"> 45 <rollingfile type="size" filename="%s%s" maxsize="20000000" maxrolls="10"/> 46 %s 47 %s 48 </outputs> 49 <formats> 50 <format id="common" format="%%Date %%Time [%%LEV] [%%File:%%Line] [%%FuncShort] %%Msg%%n" /> 51 </formats> 52 </seelog> 53 ` 54 syncPattern = ` 55 <seelog type="sync" minlevel="%s" > 56 <outputs formatid="common"> 57 <rollingfile type="size" filename="%s%s" maxsize="20000000" maxrolls="10"/> 58 %s 59 %s 60 </outputs> 61 <formats> 62 <format id="common" format="%%Date %%Time [%%LEV] [%%File:%%Line] [%%FuncShort] %%Msg%%n" /> 63 </formats> 64 </seelog> 65 ` 66 ) 67 68 const ( 69 FlagLevelName = "logger-level" 70 FlagConsoleName = "logger-console" 71 FlagRetainName = "logger-retain" 72 ) 73 74 // logtailLogger is a global logger instance, which is shared with the whole plugins of LogtailPlugin. 75 // When having LogtailContextMeta of LogtailPlugin in the context.Context, the meta header would be appended to 76 // the log. The reason why we don't use formatter is we want to use on logger instance to control the memory 77 // cost of the logging operation and avoid adding a lock. Also, when the log level is greater than or equal 78 // to warn, these logs will be sent to the back-end service for self-telemetry. 79 var logtailLogger = seelog.Disabled 80 81 // Flags is only used in testing because LogtailPlugin is trigger by C rather than pure Go. 82 var ( 83 loggerLevel = flag.String(FlagLevelName, "", "debug flag") 84 loggerConsole = flag.String(FlagConsoleName, "", "debug flag") 85 loggerRetain = flag.String(FlagRetainName, "", "debug flag") 86 ) 87 88 var ( 89 memoryReceiverFlag bool 90 consoleFlag bool 91 remoteFlag bool 92 retainFlag bool 93 levelFlag string 94 debugFlag int32 95 96 template string 97 once sync.Once 98 wait sync.WaitGroup 99 closeChan chan struct{} 100 closedCatchStdout bool 101 ) 102 103 func InitLogger() { 104 once.Do(func() { 105 initNormalLogger() 106 catchStandardOutput() 107 }) 108 } 109 110 func InitTestLogger(options ...ConfigOption) { 111 once.Do(func() { 112 config.LoongcollectorGlobalConfig.LoongCollectorLogDir = "./" 113 config.LoongcollectorGlobalConfig.LoongCollectorConfDir = "./" 114 config.LoongcollectorGlobalConfig.LoongCollectorLogConfDir = "./" 115 initTestLogger(options...) 116 catchStandardOutput() 117 }) 118 } 119 120 // initNormalLogger extracted from Init method for unit test. 121 func initNormalLogger() { 122 closeChan = make(chan struct{}) 123 remoteFlag = true 124 for _, option := range defaultProductionOptions { 125 option() 126 } 127 confDir := config.LoongcollectorGlobalConfig.LoongCollectorLogConfDir 128 if _, err := os.Stat(confDir); os.IsNotExist(err) { 129 _ = os.MkdirAll(confDir, os.ModePerm) 130 } 131 setLogConf(path.Join(confDir, "plugin_logger.xml")) 132 } 133 134 // initTestLogger extracted from Init method for unit test. 135 func initTestLogger(options ...ConfigOption) { 136 closeChan = make(chan struct{}) 137 remoteFlag = false 138 for _, option := range defaultTestOptions { 139 option() 140 } 141 for _, option := range options { 142 option() 143 } 144 setLogConf(path.Join(config.LoongcollectorGlobalConfig.LoongCollectorLogConfDir, "plugin_logger.xml")) 145 } 146 147 func Debug(ctx context.Context, kvPairs ...interface{}) { 148 if !DebugFlag() { 149 return 150 } 151 ltCtx, ok := ctx.Value(pkg.LogTailMeta).(*pkg.LogtailContextMeta) 152 if ok { 153 logtailLogger.Debug(ltCtx.LoggerHeader(), generateLog(kvPairs...)) 154 } else { 155 logtailLogger.Debug(generateLog(kvPairs...)) 156 } 157 } 158 159 func Debugf(ctx context.Context, format string, params ...interface{}) { 160 if !DebugFlag() { 161 return 162 } 163 ltCtx, ok := ctx.Value(pkg.LogTailMeta).(*pkg.LogtailContextMeta) 164 if ok { 165 logtailLogger.Debugf(ltCtx.LoggerHeader()+format, params...) 166 } else { 167 logtailLogger.Debugf(format, params...) 168 } 169 } 170 171 func Info(ctx context.Context, kvPairs ...interface{}) { 172 ltCtx, ok := ctx.Value(pkg.LogTailMeta).(*pkg.LogtailContextMeta) 173 if ok { 174 logtailLogger.Info(ltCtx.LoggerHeader(), generateLog(kvPairs...)) 175 } else { 176 logtailLogger.Info(generateLog(kvPairs...)) 177 } 178 } 179 180 func Infof(ctx context.Context, format string, params ...interface{}) { 181 ltCtx, ok := ctx.Value(pkg.LogTailMeta).(*pkg.LogtailContextMeta) 182 if ok { 183 logtailLogger.Infof(ltCtx.LoggerHeader()+format, params...) 184 } else { 185 logtailLogger.Infof(format, params...) 186 } 187 } 188 189 func Warning(ctx context.Context, alarmType string, kvPairs ...interface{}) { 190 ltCtx, ok := ctx.Value(pkg.LogTailMeta).(*pkg.LogtailContextMeta) 191 if ok { 192 kvPairs = append(kvPairs, "logstore", ltCtx.GetLogStore(), "config", ltCtx.GetConfigName()) 193 } 194 msg := generateLog(kvPairs...) 195 if ok { 196 _ = logtailLogger.Warn(ltCtx.LoggerHeader(), "AlarmType:", alarmType, "\t", msg) 197 if remoteFlag { 198 ltCtx.RecordAlarm(alarmType, msg) 199 } 200 } else { 201 _ = logtailLogger.Warn("AlarmType:", alarmType, "\t", msg) 202 if remoteFlag { 203 util.GlobalAlarm.Record(alarmType, msg) 204 } 205 } 206 } 207 208 func Warningf(ctx context.Context, alarmType string, format string, params ...interface{}) { 209 ltCtx, ok := ctx.Value(pkg.LogTailMeta).(*pkg.LogtailContextMeta) 210 if ok { 211 format += "\tlogstore:%v\tconfig:%v" 212 params = append(params, ltCtx.GetLogStore(), ltCtx.GetConfigName()) 213 } 214 msg := fmt.Sprintf(format, params...) 215 if ok { 216 _ = logtailLogger.Warn(ltCtx.LoggerHeader(), "AlarmType:", alarmType, "\t", msg) 217 if remoteFlag { 218 ltCtx.RecordAlarm(alarmType, msg) 219 } 220 } else { 221 _ = logtailLogger.Warn("AlarmType:", alarmType, "\t", msg) 222 if remoteFlag { 223 util.GlobalAlarm.Record(alarmType, msg) 224 } 225 } 226 } 227 228 func Error(ctx context.Context, alarmType string, kvPairs ...interface{}) { 229 ltCtx, ok := ctx.Value(pkg.LogTailMeta).(*pkg.LogtailContextMeta) 230 if ok { 231 kvPairs = append(kvPairs, "logstore", ltCtx.GetLogStore(), "config", ltCtx.GetConfigName()) 232 } 233 msg := generateLog(kvPairs...) 234 if ok { 235 _ = logtailLogger.Error(ltCtx.LoggerHeader(), "AlarmType:", alarmType, "\t", msg) 236 if remoteFlag { 237 ltCtx.RecordAlarm(alarmType, msg) 238 } 239 } else { 240 _ = logtailLogger.Error("AlarmType:", alarmType, "\t", msg) 241 if remoteFlag { 242 util.GlobalAlarm.Record(alarmType, msg) 243 } 244 } 245 } 246 247 func Errorf(ctx context.Context, alarmType string, format string, params ...interface{}) { 248 ltCtx, ok := ctx.Value(pkg.LogTailMeta).(*pkg.LogtailContextMeta) 249 if ok { 250 format += "\tlogstore:%v\tconfig:%v" 251 params = append(params, ltCtx.GetLogStore(), ltCtx.GetConfigName()) 252 } 253 msg := fmt.Sprintf(format, params...) 254 if ok { 255 _ = logtailLogger.Error(ltCtx.LoggerHeader(), "AlarmType:", alarmType, "\t", msg) 256 if remoteFlag { 257 ltCtx.RecordAlarm(alarmType, msg) 258 } 259 } else { 260 _ = logtailLogger.Error("AlarmType:", alarmType, "\t", msg) 261 if remoteFlag { 262 util.GlobalAlarm.Record(alarmType, msg) 263 } 264 } 265 } 266 267 // Flush logs to the output when using async logger. 268 func Flush() { 269 logtailLogger.Flush() 270 } 271 272 func setLogConf(logConfig string) { 273 if !retainFlag { 274 _ = os.Remove(path.Join(config.LoongcollectorGlobalConfig.LoongCollectorLogConfDir, "plugin_logger.xml")) 275 } 276 debugFlag = 0 277 logtailLogger = seelog.Disabled 278 path := filepath.Clean(logConfig) 279 if _, err := os.Stat(path); err != nil { 280 logConfigContent := generateDefaultConfig() 281 _ = os.WriteFile(path, []byte(logConfigContent), os.ModePerm) 282 } 283 fmt.Fprintf(os.Stderr, "load log config %s \n", path) 284 content, err := os.ReadFile(path) 285 if err != nil { 286 fmt.Fprintln(os.Stderr, "init logger error", err) 287 return 288 } 289 dat := string(content) 290 aliyunLogtailLogLevel := strings.ToLower(os.Getenv("LOGTAIL_LOG_LEVEL")) 291 if aliyunLogtailLogLevel != "" { 292 pattern := `(?mi)(minlevel=")([^"]*)(")` 293 regExp := regexp.MustCompile(pattern) 294 dat = regExp.ReplaceAllString(dat, `${1}`+aliyunLogtailLogLevel+`${3}`) 295 } 296 logger, err := seelog.LoggerFromConfigAsString(dat) 297 if err != nil { 298 fmt.Fprintln(os.Stderr, "init logger error", err) 299 return 300 } 301 if err := logger.SetAdditionalStackDepth(1); err != nil { 302 fmt.Fprintf(os.Stderr, "cannot set logger stack depth: %v\n", err) 303 return 304 } 305 logtailLogger = logger 306 307 if aliyunLogtailLogLevel == "debug" || strings.Contains(dat, "minlevel=\"debug\"") { 308 debugFlag = 1 309 } 310 } 311 312 func generateLog(kvPairs ...interface{}) string { 313 logString := "" 314 pairLen := len(kvPairs) / 2 315 for i := 0; i < pairLen; i++ { 316 logString += fmt.Sprintf("%v:%v\t", kvPairs[i<<1], kvPairs[i<<1+1]) 317 } 318 if len(kvPairs)&0x01 != 0 { 319 logString += fmt.Sprintf("%v:\t", kvPairs[len(kvPairs)-1]) 320 } 321 return logString 322 } 323 324 func generateDefaultConfig() string { 325 consoleStr := "" 326 if consoleFlag { 327 consoleStr = "<console/>" 328 } 329 memoryReceiverFlagStr := "" 330 if memoryReceiverFlag { 331 memoryReceiverFlagStr = "<custom name=\"memory\" />" 332 } 333 return fmt.Sprintf(template, levelFlag, config.LoongcollectorGlobalConfig.LoongCollectorLogDir, config.LoongcollectorGlobalConfig.LoongCollectorPluginLogName, consoleStr, memoryReceiverFlagStr) 334 } 335 336 // Close the logger and recover the stdout and stderr 337 func Close() { 338 CloseCatchStdout() 339 logtailLogger.Close() 340 } 341 342 // CloseCatchStdout close the goroutine with the catching stdout task. 343 func CloseCatchStdout() { 344 if consoleFlag || closedCatchStdout { 345 return 346 } 347 close(closeChan) 348 wait.Wait() 349 closedCatchStdout = true 350 } 351 352 // catchStandardOutput catch the stdout and stderr to the ilogtail logger. 353 func catchStandardOutput() { 354 // do not open standard output catcher when console output is opening. 355 if consoleFlag { 356 return 357 } 358 catch := func(catcher func(*os.File) (old *os.File), logger func(text []byte), recover func(old *os.File)) error { 359 r, w, err := os.Pipe() 360 if err != nil { 361 return err 362 } 363 old := catcher(w) 364 wait.Add(1) 365 go func() { 366 defer wait.Done() 367 reader := bufio.NewReader(r) 368 go func() { 369 <-closeChan 370 _ = w.Close() 371 _ = r.Close() 372 recover(old) 373 }() 374 for { 375 line, errRead := reader.ReadBytes('\n') 376 if errRead == io.EOF { 377 time.Sleep(100 * time.Millisecond) 378 continue 379 } else if errRead != nil { 380 Error(context.Background(), "CATCH_STANDARD_OUTPUT_ALARM", "err", errRead) 381 break 382 } 383 logger(line) 384 } 385 }() 386 return nil 387 } 388 err := catch( 389 func(w *os.File) (old *os.File) { 390 old = os.Stdout 391 os.Stdout = w 392 return 393 }, 394 func(text []byte) { 395 Info(context.Background(), "stdout", string(text)) 396 }, 397 func(old *os.File) { 398 os.Stdout = old 399 _, _ = fmt.Fprint(os.Stdout, "recover stdout\n") 400 }) 401 if err != nil { 402 Error(context.Background(), "INIT_CATCH_STDOUT_ALARM", "err", err) 403 return 404 } 405 err = catch( 406 func(w *os.File) (old *os.File) { 407 old = os.Stderr 408 os.Stderr = w 409 return 410 }, 411 func(text []byte) { 412 Error(context.Background(), "STDERR_ALARM", "stderr", string(text)) 413 }, 414 func(old *os.File) { 415 os.Stderr = old 416 _, _ = fmt.Fprint(os.Stderr, "recover stderr\n") 417 }) 418 if err != nil { 419 Error(context.Background(), "INIT_CATCH_STDERR_ALARM", "err", err) 420 } 421 } 422 423 // DebugFlag returns true when debug level is opening. 424 func DebugFlag() bool { 425 return atomic.LoadInt32(&debugFlag) == 1 426 }