github.com/waldiirawan/apm-agent-go/v2@v2.2.2/internal/apmlog/logger.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package apmlog
    19  
    20  import (
    21  	"fmt"
    22  	"io"
    23  	"log"
    24  	"os"
    25  	"strings"
    26  	"sync"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	"go.elastic.co/fastjson"
    31  )
    32  
    33  const (
    34  	// EnvLogFile is the environment variable that controls where the default logger writes.
    35  	EnvLogFile = "ELASTIC_APM_LOG_FILE"
    36  
    37  	// EnvLogLevel is the environment variable that controls the default logger's level.
    38  	EnvLogLevel = "ELASTIC_APM_LOG_LEVEL"
    39  
    40  	// DefaultLevel holds the default log level, if EnvLogLevel is not specified.
    41  	DefaultLevel Level = ErrorLevel
    42  )
    43  
    44  var (
    45  	loggerMu      sync.RWMutex
    46  	defaultLogger *LevelLogger
    47  
    48  	fastjsonPool = &sync.Pool{
    49  		New: func() interface{} {
    50  			return &fastjson.Writer{}
    51  		},
    52  	}
    53  )
    54  
    55  // DefaultLogger initialises defaultLogger using the environment variables
    56  // ELASTIC_APM_LOG_FILE and ELASTIC_APM_LOG_LEVEL. If defaultLogger is non-nil,
    57  // it returns the logger.
    58  func DefaultLogger() *LevelLogger {
    59  	loggerMu.RLock()
    60  	if defaultLogger != nil {
    61  		defer loggerMu.RUnlock()
    62  		return defaultLogger
    63  	}
    64  	loggerMu.RUnlock()
    65  
    66  	loggerMu.Lock()
    67  	defer loggerMu.Unlock()
    68  	// Releasing the RLock could allow another goroutine to call
    69  	// SetDefaultLogger() before trying to take the write Lock; double
    70  	// check that DefaultLogger is still nil after acquiring the write
    71  	// lock.
    72  	if defaultLogger != nil {
    73  		return defaultLogger
    74  	}
    75  
    76  	fileStr := strings.TrimSpace(os.Getenv(EnvLogFile))
    77  	if fileStr == "" {
    78  		return defaultLogger
    79  	}
    80  
    81  	var logWriter io.Writer
    82  	switch strings.ToLower(fileStr) {
    83  	case "stdout":
    84  		logWriter = os.Stdout
    85  	case "stderr":
    86  		logWriter = os.Stderr
    87  	default:
    88  		f, err := os.Create(fileStr)
    89  		if err != nil {
    90  			log.Printf("failed to create %q: %s (disabling logging)", fileStr, err)
    91  			return nil
    92  		}
    93  		logWriter = &syncFile{File: f}
    94  	}
    95  
    96  	logLevel := DefaultLevel
    97  	if levelStr := strings.TrimSpace(os.Getenv(EnvLogLevel)); levelStr != "" {
    98  		level, err := ParseLogLevel(levelStr)
    99  		if err != nil {
   100  			log.Printf("invalid %s %q, falling back to %q", EnvLogLevel, levelStr, logLevel)
   101  		} else {
   102  			logLevel = level
   103  		}
   104  	}
   105  	defaultLogger = &LevelLogger{w: logWriter, level: logLevel}
   106  
   107  	return defaultLogger
   108  }
   109  
   110  // SetDefaultLogger sets the package default logger to the logger provided.
   111  func SetDefaultLogger(l *LevelLogger) {
   112  	loggerMu.Lock()
   113  	defer loggerMu.Unlock()
   114  
   115  	defaultLogger = l
   116  }
   117  
   118  // Log levels.
   119  const (
   120  	TraceLevel Level = iota
   121  	DebugLevel
   122  	InfoLevel
   123  	WarningLevel
   124  	ErrorLevel
   125  	CriticalLevel
   126  	OffLevel
   127  )
   128  
   129  // Level represents a log level.
   130  type Level uint32
   131  
   132  func (l Level) String() string {
   133  	switch l {
   134  	case TraceLevel:
   135  		return "trace"
   136  	case DebugLevel:
   137  		return "debug"
   138  	case InfoLevel:
   139  		return "info"
   140  	case WarningLevel:
   141  		return "warning"
   142  	case ErrorLevel:
   143  		return "error"
   144  	case CriticalLevel:
   145  		return "critical"
   146  	case OffLevel:
   147  		return "off"
   148  	}
   149  	return ""
   150  }
   151  
   152  // ParseLogLevel parses s as a log level.
   153  func ParseLogLevel(s string) (Level, error) {
   154  	switch strings.ToLower(s) {
   155  	case "trace":
   156  		return TraceLevel, nil
   157  	case "debug":
   158  		return DebugLevel, nil
   159  	case "info":
   160  		return InfoLevel, nil
   161  	case "warn", "warning":
   162  		// "warn" exists for backwards compatibility;
   163  		// "warning" is the canonical level name.
   164  		return WarningLevel, nil
   165  	case "error":
   166  		return ErrorLevel, nil
   167  	case "critical":
   168  		return CriticalLevel, nil
   169  	case "off":
   170  		return OffLevel, nil
   171  	}
   172  	return OffLevel, fmt.Errorf("invalid log level string %q", s)
   173  }
   174  
   175  // LevelLogger is a level logging implementation that will log to a file,
   176  // stdout, or stderr. The level may be updated dynamically via SetLevel.
   177  type LevelLogger struct {
   178  	level Level // should be accessed with sync/atomic
   179  	w     io.Writer
   180  }
   181  
   182  // Level returns the current logging level.
   183  func (l *LevelLogger) Level() Level {
   184  	return Level(atomic.LoadUint32((*uint32)(&l.level)))
   185  }
   186  
   187  // SetLevel sets level as the minimum logging level.
   188  func (l *LevelLogger) SetLevel(level Level) {
   189  	atomic.StoreUint32((*uint32)(&l.level), uint32(level))
   190  }
   191  
   192  // Debugf logs a message with log.Printf, with a DEBUG prefix.
   193  func (l *LevelLogger) Debugf(format string, args ...interface{}) {
   194  	l.logf(DebugLevel, format, args...)
   195  }
   196  
   197  // Errorf logs a message with log.Printf, with an ERROR prefix.
   198  func (l *LevelLogger) Errorf(format string, args ...interface{}) {
   199  	l.logf(ErrorLevel, format, args...)
   200  }
   201  
   202  // Warningf logs a message with log.Printf, with a WARNING prefix.
   203  func (l *LevelLogger) Warningf(format string, args ...interface{}) {
   204  	l.logf(WarningLevel, format, args...)
   205  }
   206  
   207  func (l *LevelLogger) logf(level Level, format string, args ...interface{}) {
   208  	if level < l.Level() {
   209  		return
   210  	}
   211  	jw := fastjsonPool.Get().(*fastjson.Writer)
   212  	jw.RawString(`{"level":"`)
   213  	jw.RawString(level.String())
   214  	jw.RawString(`","time":"`)
   215  	jw.Time(time.Now(), time.RFC3339)
   216  	jw.RawString(`","message":`)
   217  	jw.String(fmt.Sprintf(format, args...))
   218  	jw.RawString("}\n")
   219  	l.w.Write(jw.Bytes())
   220  	jw.Reset()
   221  	fastjsonPool.Put(jw)
   222  }
   223  
   224  type syncFile struct {
   225  	mu sync.Mutex
   226  	*os.File
   227  }
   228  
   229  // Write calls f.File.Write with f.mu held, to protect multiple Tracers
   230  // in the same process from one another.
   231  func (f *syncFile) Write(data []byte) (int, error) {
   232  	f.mu.Lock()
   233  	defer f.mu.Unlock()
   234  	return f.File.Write(data)
   235  }