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 }