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  }