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:21:39 +01:00
"sync"
2019-05-22 23:16:55 +02:00
"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-09-21 18:25:33 +02:00
loggerLevel = flag . String ( "loggerLevel" , "INFO" , "Minimum level of errors to log. Possible values: INFO, WARN, ERROR, FATAL, PANIC" )
loggerFormat = flag . String ( "loggerFormat" , "default" , "Format for logs. Possible values: default, json" )
loggerOutput = flag . String ( "loggerOutput" , "stderr" , "Output for the logs. Supported values: stderr, stdout" )
disableTimestamps = flag . Bool ( "loggerDisableTimestamps" , false , "Whether to disable writing timestamps in logs" )
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" )
2020-11-18 02:40:12 +01:00
warnsPerSecondLimit = flag . Int ( "loggerWarnsPerSecondLimit" , 10 , "Per-second limit on the number of WARN messages. If more than the given number of warns " +
"are emitted per second, then the remaining warns 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 ( )
2020-11-19 11:36:50 +01:00
go logLimiterCleaner ( )
2019-05-22 23:16:55 +02:00
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
}
2020-11-19 11:36:50 +01:00
func logLimiterCleaner ( ) {
2019-05-22 23:16:55 +02:00
for {
2020-06-23 11:41:30 +02:00
time . Sleep ( time . Second )
2020-11-19 11:17:23 +01:00
logLimiter . reset ( )
2019-05-22 23:16:55 +02:00
}
}
2020-11-19 11:36:50 +01:00
var logLimiter = newLogLimit ( )
2019-05-22 23:16:55 +02:00
2020-11-19 11:17:23 +01:00
func newLogLimit ( ) * logLimit {
return & logLimit {
2020-11-19 11:36:50 +01:00
m : make ( map [ string ] uint64 ) ,
2020-11-19 11:17:23 +01:00
}
}
type logLimit struct {
mu sync . Mutex
2020-11-19 11:36:50 +01:00
m map [ string ] uint64
2020-11-19 11:17:23 +01:00
}
func ( ll * logLimit ) reset ( ) {
ll . mu . Lock ( )
2020-11-19 11:36:50 +01:00
ll . m = make ( map [ string ] uint64 , len ( ll . m ) )
2020-11-19 11:17:23 +01:00
ll . mu . Unlock ( )
}
2020-11-19 11:36:50 +01:00
// needSuppress checks if the number of calls for the given location exceeds the given limit.
//
// When the number of calls equals limit, log message prefix returned.
func ( ll * logLimit ) needSuppress ( location string , limit uint64 ) ( bool , string ) {
2020-11-19 11:17:23 +01:00
// fast path
var msg string
if limit == 0 {
return false , msg
}
ll . mu . Lock ( )
defer ll . mu . Unlock ( )
2020-11-19 11:36:50 +01:00
if n , ok := ll . m [ location ] ; ok {
2020-11-19 11:17:23 +01:00
if n >= limit {
switch n {
// report only once
case limit :
msg = fmt . Sprintf ( "suppressing log message with rate limit=%d: " , limit )
default :
return true , msg
}
}
2020-11-19 11:36:50 +01:00
ll . m [ location ] = n + 1
2020-11-19 11:17:23 +01:00
} else {
2020-11-19 11:36:50 +01:00
ll . m [ location ] = 1
2020-11-19 11:17:23 +01:00
}
return false , msg
}
2019-05-22 23:16:55 +02:00
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-09-21 18:25:33 +02:00
timestamp := ""
if ! * disableTimestamps {
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/" ) : ]
}
2020-11-19 11:17:23 +01:00
location := fmt . Sprintf ( "%s:%d" , file , line )
// rate limit ERROR and WARN log messages with given limit.
if level == "ERROR" || level == "WARN" {
limit := uint64 ( * errorsPerSecondLimit )
if level == "WARN" {
limit = uint64 ( * warnsPerSecondLimit )
}
ok , suppressMessage := logLimiter . needSuppress ( location , limit )
if ok {
return
}
if len ( suppressMessage ) > 0 {
msg = suppressMessage + msg
}
}
2019-05-22 23:16:55 +02:00
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" :
2020-09-21 18:25:33 +02:00
if * disableTimestamps {
2020-11-19 11:17:23 +01:00
logMsg = fmt . Sprintf ( ` { "level":%q,"caller":%q,"msg":%q} ` + "\n" , levelLowercase , location , msg )
2020-09-21 18:25:33 +02:00
} else {
2020-11-19 11:17:23 +01:00
logMsg = fmt . Sprintf ( ` { "ts":%q,"level":%q,"caller":%q,"msg":%q} ` + "\n" , timestamp , levelLowercase , location , msg )
2020-09-21 18:25:33 +02:00
}
2019-12-13 14:06:17 +01:00
default :
2020-09-21 18:25:33 +02:00
if * disableTimestamps {
2020-11-19 11:17:23 +01:00
logMsg = fmt . Sprintf ( "%s\t%s\t%s\n" , levelLowercase , location , msg )
2020-09-21 18:25:33 +02:00
} else {
2020-11-19 11:17:23 +01:00
logMsg = fmt . Sprintf ( "%s\t%s\t%s\t%s\n" , timestamp , levelLowercase , location , msg )
2020-09-21 18:25:33 +02:00
}
2019-12-13 14:06:17 +01:00
}
2019-05-22 23:16:55 +02:00
2019-12-24 11:21:39 +01:00
// Serialize writes to log.
mu . Lock ( )
2020-02-04 20:47:15 +01:00
fmt . Fprint ( output , logMsg )
2019-12-24 11:21:39 +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
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:21:39 +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
}
}