package log
import (
"errors"
"fmt"
"os"
"regexp"
"strings"
"sync"
"github.com/mattn/go-isatty"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var config Config
func init() {
SetupLogging (configFromEnv ())
}
const (
envIPFSLogging = "IPFS_LOGGING"
envIPFSLoggingFmt = "IPFS_LOGGING_FMT"
envLogging = "GOLOG_LOG_LEVEL"
envLoggingFmt = "GOLOG_LOG_FMT"
envLoggingFile = "GOLOG_FILE"
envLoggingURL = "GOLOG_URL"
envLoggingOutput = "GOLOG_OUTPUT"
envLoggingLabels = "GOLOG_LOG_LABELS"
)
type LogFormat int
const (
ColorizedOutput LogFormat = iota
PlaintextOutput
JSONOutput
)
type Config struct {
Format LogFormat
Level LogLevel
SubsystemLevels map [string ]LogLevel
Stderr bool
Stdout bool
File string
URL string
Labels map [string ]string
}
var ErrNoSuchLogger = errors .New ("error: No such logger" )
var loggerMutex sync .RWMutex
var loggers = make (map [string ]*zap .SugaredLogger )
var levels = make (map [string ]zap .AtomicLevel )
var primaryFormat LogFormat = ColorizedOutput
var defaultLevel LogLevel = LevelError
var primaryCore zapcore .Core
var loggerCore = &lockedMultiCore {}
func GetConfig () Config {
return config
}
func SetupLogging (cfg Config ) {
loggerMutex .Lock ()
defer loggerMutex .Unlock ()
config = cfg
primaryFormat = cfg .Format
defaultLevel = cfg .Level
outputPaths := []string {}
if cfg .Stderr {
outputPaths = append (outputPaths , "stderr" )
}
if cfg .Stdout {
outputPaths = append (outputPaths , "stdout" )
}
if len (cfg .File ) > 0 {
if path , err := normalizePath (cfg .File ); err != nil {
fmt .Fprintf (os .Stderr , "failed to resolve log path '%q', logging to %s\n" , cfg .File , outputPaths )
} else {
outputPaths = append (outputPaths , path )
}
}
if len (cfg .URL ) > 0 {
outputPaths = append (outputPaths , cfg .URL )
}
ws , _ , err := zap .Open (outputPaths ...)
if err != nil {
panic (fmt .Sprintf ("unable to open logging output: %v" , err ))
}
newPrimaryCore := newCore (primaryFormat , ws , LevelDebug )
for k , v := range cfg .Labels {
newPrimaryCore = newPrimaryCore .With ([]zap .Field {zap .String (k , v )})
}
setPrimaryCore (newPrimaryCore )
setAllLoggers (defaultLevel )
for name , level := range cfg .SubsystemLevels {
if leveler , ok := levels [name ]; ok {
leveler .SetLevel (zapcore .Level (level ))
} else {
levels [name ] = zap .NewAtomicLevelAt (zapcore .Level (level ))
}
}
}
func SetPrimaryCore (core zapcore .Core ) {
loggerMutex .Lock ()
defer loggerMutex .Unlock ()
setPrimaryCore (core )
}
func setPrimaryCore(core zapcore .Core ) {
if primaryCore != nil {
loggerCore .ReplaceCore (primaryCore , core )
} else {
loggerCore .AddCore (core )
}
primaryCore = core
}
func SetDebugLogging () {
SetAllLoggers (LevelDebug )
}
func SetAllLoggers (lvl LogLevel ) {
loggerMutex .RLock ()
defer loggerMutex .RUnlock ()
setAllLoggers (lvl )
}
func setAllLoggers(lvl LogLevel ) {
for _ , l := range levels {
l .SetLevel (zapcore .Level (lvl ))
}
}
func SetLogLevel (name , level string ) error {
lvl , err := LevelFromString (level )
if err != nil {
return err
}
if name == "*" {
SetAllLoggers (lvl )
return nil
}
loggerMutex .RLock ()
defer loggerMutex .RUnlock ()
if _ , ok := levels [name ]; !ok {
return ErrNoSuchLogger
}
levels [name ].SetLevel (zapcore .Level (lvl ))
return nil
}
func SetLogLevelRegex (e , l string ) error {
lvl , err := LevelFromString (l )
if err != nil {
return err
}
rem , err := regexp .Compile (e )
if err != nil {
return err
}
loggerMutex .Lock ()
defer loggerMutex .Unlock ()
for name := range loggers {
if rem .MatchString (name ) {
levels [name ].SetLevel (zapcore .Level (lvl ))
}
}
return nil
}
func GetSubsystems () []string {
loggerMutex .RLock ()
defer loggerMutex .RUnlock ()
subs := make ([]string , 0 , len (loggers ))
for k := range loggers {
subs = append (subs , k )
}
return subs
}
func getLogger(name string ) *zap .SugaredLogger {
loggerMutex .Lock ()
defer loggerMutex .Unlock ()
log , ok := loggers [name ]
if !ok {
level , ok := levels [name ]
if !ok {
level = zap .NewAtomicLevelAt (zapcore .Level (defaultLevel ))
levels [name ] = level
}
log = zap .New (loggerCore ).
WithOptions (
zap .IncreaseLevel (level ),
zap .AddCaller (),
).
Named (name ).
Sugar ()
loggers [name ] = log
}
return log
}
func configFromEnv() Config {
cfg := Config {
Format : ColorizedOutput ,
Stderr : true ,
Level : LevelError ,
SubsystemLevels : map [string ]LogLevel {},
Labels : map [string ]string {},
}
format := os .Getenv (envLoggingFmt )
if format == "" {
format = os .Getenv (envIPFSLoggingFmt )
}
var noExplicitFormat bool
switch format {
case "color" :
cfg .Format = ColorizedOutput
case "nocolor" :
cfg .Format = PlaintextOutput
case "json" :
cfg .Format = JSONOutput
default :
if format != "" {
fmt .Fprintf (os .Stderr , "ignoring unrecognized log format '%s'\n" , format )
}
noExplicitFormat = true
}
lvl := os .Getenv (envLogging )
if lvl == "" {
lvl = os .Getenv (envIPFSLogging )
}
if lvl != "" {
for _ , kvs := range strings .Split (lvl , "," ) {
kv := strings .SplitN (kvs , "=" , 2 )
lvl , err := LevelFromString (kv [len (kv )-1 ])
if err != nil {
fmt .Fprintf (os .Stderr , "error setting log level %q: %s\n" , kvs , err )
continue
}
switch len (kv ) {
case 1 :
cfg .Level = lvl
case 2 :
cfg .SubsystemLevels [kv [0 ]] = lvl
}
}
}
cfg .File = os .Getenv (envLoggingFile )
if cfg .File != "" {
cfg .Stderr = false
}
var stderrOpt , stdoutOpt bool
cfg .URL = os .Getenv (envLoggingURL )
output := os .Getenv (envLoggingOutput )
outputOptions := strings .Split (output , "+" )
for _ , opt := range outputOptions {
switch opt {
case "stdout" :
stdoutOpt = true
case "stderr" :
stderrOpt = true
case "file" :
if cfg .File == "" {
fmt .Fprint (os .Stderr , "please specify a GOLOG_FILE value to write to" )
}
case "url" :
if cfg .URL == "" {
fmt .Fprint (os .Stderr , "please specify a GOLOG_URL value to write to" )
}
}
}
if stdoutOpt || stderrOpt {
cfg .Stdout = stdoutOpt
cfg .Stderr = stderrOpt
}
if noExplicitFormat &&
!(cfg .Stdout && isTerm (os .Stdout )) &&
!(cfg .Stderr && isTerm (os .Stderr )) &&
!(cfg .File != "" && pathIsTerm (cfg .File )) {
cfg .Format = PlaintextOutput
}
labels := os .Getenv (envLoggingLabels )
if labels != "" {
labelKVs := strings .Split (labels , "," )
for _ , label := range labelKVs {
kv := strings .Split (label , "=" )
if len (kv ) != 2 {
fmt .Fprint (os .Stderr , "invalid label k=v: " , label )
continue
}
cfg .Labels [kv [0 ]] = kv [1 ]
}
}
return cfg
}
func isTerm(f *os .File ) bool {
return isatty .IsTerminal (f .Fd ()) || isatty .IsCygwinTerminal (f .Fd ())
}
func pathIsTerm(p string ) bool {
f , err := os .OpenFile (p , os .O_WRONLY , 0 )
if f != nil {
defer f .Close ()
}
return err == nil && isTerm (f )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .