2019-05-22 23:16:55 +02:00
package logger
import (
"errors"
"flag"
"fmt"
2020-02-04 20:47:15 +01:00
"io"
2019-05-22 23:16:55 +02:00
"log"
"os"
"runtime"
"strings"
2019-12-24 11:20:45 +01:00
"sync"
2019-05-22 23:16:55 +02:00
"sync/atomic"
"time"
2019-12-01 17:37:49 +01:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
"github.com/VictoriaMetrics/metrics"
2019-05-22 23:16:55 +02:00
)
2019-12-13 14:06:17 +01:00
var (
2020-04-15 23:19:10 +02:00
loggerLevel = flag . String ( "loggerLevel" , "INFO" , "Minimum level of errors to log. Possible values: INFO, WARN, ERROR, FATAL, PANIC" )
2019-12-13 14:06:17 +01:00
loggerFormat = flag . String ( "loggerFormat" , "default" , "Format for logs. Possible values: default, json" )
2020-02-04 20:47:15 +01:00
loggerOutput = flag . String ( "loggerOutput" , "stderr" , "Output for the logs. Supported values: stderr, stdout" )
2020-06-23 11:41:30 +02:00
errorsPerSecondLimit = flag . Int ( "loggerErrorsPerSecondLimit" , 10 , "Per-second limit on the number of ERROR messages. If more than the given number of errors " +
"are emitted per second, then the remaining errors are suppressed. Zero value disables the rate limit" )
2019-12-13 14:06:17 +01:00
)
2019-05-22 23:16:55 +02:00
// Init initializes the logger.
//
// Init must be called after flag.Parse()
//
// There is no need in calling Init from tests.
func Init ( ) {
2020-02-04 20:47:15 +01:00
setLoggerOutput ( )
2019-05-22 23:16:55 +02:00
validateLoggerLevel ( )
2019-12-13 14:06:17 +01:00
validateLoggerFormat ( )
2019-05-22 23:16:55 +02:00
go errorsLoggedCleaner ( )
logAllFlags ( )
}
2020-02-04 20:47:15 +01:00
func setLoggerOutput ( ) {
switch * loggerOutput {
case "stderr" :
output = os . Stderr
case "stdout" :
output = os . Stdout
default :
panic ( fmt . Errorf ( "FATAL: unsupported `loggerOutput` value: %q; supported values are: stderr, stdout" , * loggerOutput ) )
}
}
2020-02-04 21:40:44 +01:00
var output io . Writer = os . Stderr
2020-02-04 20:47:15 +01:00
2019-05-22 23:16:55 +02:00
func validateLoggerLevel ( ) {
switch * loggerLevel {
2020-04-15 19:50:12 +02:00
case "INFO" , "WARN" , "ERROR" , "FATAL" , "PANIC" :
2019-05-22 23:16:55 +02:00
default :
// We cannot use logger.Panicf here, since the logger isn't initialized yet.
2020-04-15 19:50:12 +02:00
panic ( fmt . Errorf ( "FATAL: unsupported `-loggerLevel` value: %q; supported values are: INFO, WARN, ERROR, FATAL, PANIC" , * loggerLevel ) )
2019-05-22 23:16:55 +02:00
}
}
2019-12-13 14:06:17 +01:00
func validateLoggerFormat ( ) {
switch * loggerFormat {
case "default" , "json" :
default :
// We cannot use logger.Pancif here, since the logger isn't initialized yet.
panic ( fmt . Errorf ( "FATAL: unsupported `-loggerFormat` value: %q; supported values are: default, json" , * loggerFormat ) )
}
}
2019-05-22 23:16:55 +02:00
var stdErrorLogger = log . New ( & logWriter { } , "" , 0 )
// StdErrorLogger returns standard error logger.
func StdErrorLogger ( ) * log . Logger {
return stdErrorLogger
}
// Infof logs info message.
func Infof ( format string , args ... interface { } ) {
logLevel ( "INFO" , format , args ... )
}
2020-04-15 19:50:12 +02:00
// Warnf logs warn message.
func Warnf ( format string , args ... interface { } ) {
logLevel ( "WARN" , format , args ... )
}
2019-05-22 23:16:55 +02:00
// Errorf logs error message.
func Errorf ( format string , args ... interface { } ) {
logLevel ( "ERROR" , format , args ... )
}
2020-04-15 19:50:12 +02:00
// WarnfSkipframes logs warn message and skips the given number of frames for the caller.
func WarnfSkipframes ( skipframes int , format string , args ... interface { } ) {
logLevelSkipframes ( skipframes , "WARN" , format , args ... )
}
2020-01-25 19:16:47 +01:00
// ErrorfSkipframes logs error message and skips the given number of frames for the caller.
func ErrorfSkipframes ( skipframes int , format string , args ... interface { } ) {
logLevelSkipframes ( skipframes , "ERROR" , format , args ... )
}
2019-05-22 23:16:55 +02:00
// Fatalf logs fatal message and terminates the app.
func Fatalf ( format string , args ... interface { } ) {
logLevel ( "FATAL" , format , args ... )
}
// Panicf logs panic message and panics.
func Panicf ( format string , args ... interface { } ) {
logLevel ( "PANIC" , format , args ... )
}
func logLevel ( level , format string , args ... interface { } ) {
2020-01-26 17:34:02 +01:00
logLevelSkipframes ( 1 , level , format , args ... )
2020-01-25 19:16:47 +01:00
}
func logLevelSkipframes ( skipframes int , level , format string , args ... interface { } ) {
2019-05-22 23:16:55 +02:00
if shouldSkipLog ( level ) {
return
}
msg := fmt . Sprintf ( format , args ... )
2020-01-25 19:16:47 +01:00
logMessage ( level , msg , 3 + skipframes )
2019-05-22 23:16:55 +02:00
}
func errorsLoggedCleaner ( ) {
for {
2020-06-23 11:41:30 +02:00
time . Sleep ( time . Second )
2019-05-22 23:16:55 +02:00
atomic . StoreUint64 ( & errorsLogged , 0 )
}
}
var errorsLogged uint64
type logWriter struct {
}
func ( lw * logWriter ) Write ( p [ ] byte ) ( int , error ) {
2020-01-25 19:16:47 +01:00
logLevelSkipframes ( 2 , "ERROR" , "%s" , p )
2019-05-22 23:16:55 +02:00
return len ( p ) , nil
}
func logMessage ( level , msg string , skipframes int ) {
2020-01-25 19:16:47 +01:00
// rate limit ERROR log messages
if level == "ERROR" {
2020-06-23 11:41:30 +02:00
if n := atomic . AddUint64 ( & errorsLogged , 1 ) ; * errorsPerSecondLimit > 0 && n > uint64 ( * errorsPerSecondLimit ) {
2020-01-25 19:16:47 +01:00
return
}
}
2019-12-13 14:06:17 +01:00
timestamp := time . Now ( ) . UTC ( ) . Format ( "2006-01-02T15:04:05.000Z" )
2019-05-22 23:16:55 +02:00
levelLowercase := strings . ToLower ( level )
_ , file , line , ok := runtime . Caller ( skipframes )
if ! ok {
file = "???"
line = 0
}
if n := strings . Index ( file , "/VictoriaMetrics/" ) ; n >= 0 {
// Strip /VictoriaMetrics/ prefix
file = file [ n + len ( "/VictoriaMetrics/" ) : ]
}
for len ( msg ) > 0 && msg [ len ( msg ) - 1 ] == '\n' {
msg = msg [ : len ( msg ) - 1 ]
}
2019-12-13 14:06:17 +01:00
var logMsg string
switch * loggerFormat {
case "json" :
caller := fmt . Sprintf ( "%s:%d" , file , line )
logMsg = fmt . Sprintf ( ` { "ts":%q,"level":%q,"caller":%q,"msg":%q} ` + "\n" , timestamp , levelLowercase , caller , msg )
default :
logMsg = fmt . Sprintf ( "%s\t%s\t%s:%d\t%s\n" , timestamp , levelLowercase , file , line , msg )
}
2019-05-22 23:16:55 +02:00
2019-12-24 11:20:45 +01:00
// Serialize writes to log.
mu . Lock ( )
2020-02-04 20:47:15 +01:00
fmt . Fprint ( output , logMsg )
2019-12-24 11:20:45 +01:00
mu . Unlock ( )
2019-05-22 23:16:55 +02:00
2019-12-01 23:47:12 +01:00
// Increment vm_log_messages_total
2019-12-02 13:03:44 +01:00
location := fmt . Sprintf ( "%s:%d" , file , line )
counterName := fmt . Sprintf ( ` vm_log_messages_total { app_version=%q, level=%q, location=%q} ` , buildinfo . Version , levelLowercase , location )
2019-12-01 17:37:49 +01:00
metrics . GetOrCreateCounter ( counterName ) . Inc ( )
2019-05-22 23:16:55 +02:00
switch level {
case "PANIC" :
2020-02-04 20:37:23 +01:00
if * loggerFormat == "json" {
// Do not clutter `json` output with panic stack trace
os . Exit ( - 1 )
}
2019-05-22 23:16:55 +02:00
panic ( errors . New ( msg ) )
case "FATAL" :
os . Exit ( - 1 )
}
}
2019-12-24 11:20:45 +01:00
var mu sync . Mutex
2019-05-22 23:16:55 +02:00
func shouldSkipLog ( level string ) bool {
switch * loggerLevel {
2020-04-15 19:50:12 +02:00
case "WARN" :
switch level {
case "WARN" , "ERROR" , "FATAL" , "PANIC" :
return false
default :
return true
}
2019-05-22 23:16:55 +02:00
case "ERROR" :
switch level {
case "ERROR" , "FATAL" , "PANIC" :
return false
default :
return true
}
case "FATAL" :
switch level {
case "FATAL" , "PANIC" :
return false
default :
return true
}
case "PANIC" :
return level != "PANIC"
default :
return false
}
}