 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			401 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			401 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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())
 | |
| }
 | |
| 
 | |
| // Logging environment variables
 | |
| const (
 | |
| 	// IPFS_* prefixed env vars kept for backwards compatibility
 | |
| 	// for this release. They will not be available in the next
 | |
| 	// release.
 | |
| 	//
 | |
| 	// GOLOG_* env vars take precedences over IPFS_* env vars.
 | |
| 	envIPFSLogging    = "IPFS_LOGGING"
 | |
| 	envIPFSLoggingFmt = "IPFS_LOGGING_FMT"
 | |
| 
 | |
| 	envLogging    = "GOLOG_LOG_LEVEL"
 | |
| 	envLoggingFmt = "GOLOG_LOG_FMT"
 | |
| 
 | |
| 	envLoggingFile = "GOLOG_FILE" // /path/to/file
 | |
| 	envLoggingURL  = "GOLOG_URL"  // url that will be processed by sink in the zap
 | |
| 
 | |
| 	envLoggingOutput = "GOLOG_OUTPUT"     // possible values: stdout|stderr|file combine multiple values with '+'
 | |
| 	envLoggingLabels = "GOLOG_LOG_LABELS" // comma-separated key-value pairs, i.e. "app=example_app,dc=sjc-1"
 | |
| )
 | |
| 
 | |
| type LogFormat int
 | |
| 
 | |
| const (
 | |
| 	ColorizedOutput LogFormat = iota
 | |
| 	PlaintextOutput
 | |
| 	JSONOutput
 | |
| )
 | |
| 
 | |
| type Config struct {
 | |
| 	// Format overrides the format of the log output. Defaults to ColorizedOutput
 | |
| 	Format LogFormat
 | |
| 
 | |
| 	// Level is the default minimum enabled logging level.
 | |
| 	Level LogLevel
 | |
| 
 | |
| 	// SubsystemLevels are the default levels per-subsystem. When unspecified, defaults to Level.
 | |
| 	SubsystemLevels map[string]LogLevel
 | |
| 
 | |
| 	// Stderr indicates whether logs should be written to stderr.
 | |
| 	Stderr bool
 | |
| 
 | |
| 	// Stdout indicates whether logs should be written to stdout.
 | |
| 	Stdout bool
 | |
| 
 | |
| 	// File is a path to a file that logs will be written to.
 | |
| 	File string
 | |
| 
 | |
| 	// URL with schema supported by zap. Use zap.RegisterSink
 | |
| 	URL string
 | |
| 
 | |
| 	// Labels is a set of key-values to apply to all loggers
 | |
| 	Labels map[string]string
 | |
| }
 | |
| 
 | |
| // ErrNoSuchLogger is returned when the util pkg is asked for a non existant logger
 | |
| var ErrNoSuchLogger = errors.New("error: No such logger")
 | |
| 
 | |
| var loggerMutex sync.RWMutex // guards access to global logger state
 | |
| 
 | |
| // loggers is the set of loggers in the system
 | |
| var loggers = make(map[string]*zap.SugaredLogger)
 | |
| var levels = make(map[string]zap.AtomicLevel)
 | |
| 
 | |
| // primaryFormat is the format of the primary core used for logging
 | |
| var primaryFormat LogFormat = ColorizedOutput
 | |
| 
 | |
| // defaultLevel is the default log level
 | |
| var defaultLevel LogLevel = LevelError
 | |
| 
 | |
| // primaryCore is the primary logging core
 | |
| var primaryCore zapcore.Core
 | |
| 
 | |
| // loggerCore is the base for all loggers created by this package
 | |
| var loggerCore = &lockedMultiCore{}
 | |
| 
 | |
| // GetConfig returns a copy of the saved config. It can be inspected, modified,
 | |
| // and re-applied using a subsequent call to SetupLogging().
 | |
| func GetConfig() Config {
 | |
| 	return config
 | |
| }
 | |
| 
 | |
| // SetupLogging will initialize the logger backend and set the flags.
 | |
| // TODO calling this in `init` pushes all configuration to env variables
 | |
| // - move it out of `init`? then we need to change all the code (js-ipfs, go-ipfs) to call this explicitly
 | |
| // - have it look for a config file? need to define what that is
 | |
| 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")
 | |
| 	}
 | |
| 
 | |
| 	// check if we log to a file
 | |
| 	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) // the main core needs to log everything.
 | |
| 
 | |
| 	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))
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SetPrimaryCore changes the primary logging core. If the SetupLogging was
 | |
| // called then the previously configured core will be replaced.
 | |
| 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
 | |
| }
 | |
| 
 | |
| // SetDebugLogging calls SetAllLoggers with logging.DEBUG
 | |
| func SetDebugLogging() {
 | |
| 	SetAllLoggers(LevelDebug)
 | |
| }
 | |
| 
 | |
| // SetAllLoggers changes the logging level of all loggers to lvl
 | |
| func SetAllLoggers(lvl LogLevel) {
 | |
| 	loggerMutex.RLock()
 | |
| 	defer loggerMutex.RUnlock()
 | |
| 
 | |
| 	setAllLoggers(lvl)
 | |
| }
 | |
| 
 | |
| func setAllLoggers(lvl LogLevel) {
 | |
| 	for _, l := range levels {
 | |
| 		l.SetLevel(zapcore.Level(lvl))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SetLogLevel changes the log level of a specific subsystem
 | |
| // name=="*" changes all subsystems
 | |
| func SetLogLevel(name, level string) error {
 | |
| 	lvl, err := LevelFromString(level)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// wildcard, change all
 | |
| 	if name == "*" {
 | |
| 		SetAllLoggers(lvl)
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	loggerMutex.RLock()
 | |
| 	defer loggerMutex.RUnlock()
 | |
| 
 | |
| 	// Check if we have a logger by that name
 | |
| 	if _, ok := levels[name]; !ok {
 | |
| 		return ErrNoSuchLogger
 | |
| 	}
 | |
| 
 | |
| 	levels[name].SetLevel(zapcore.Level(lvl))
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // SetLogLevelRegex sets all loggers to level `l` that match expression `e`.
 | |
| // An error is returned if `e` fails to compile.
 | |
| 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
 | |
| }
 | |
| 
 | |
| // GetSubsystems returns a slice containing the
 | |
| // names of the current loggers
 | |
| 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
 | |
| }
 | |
| 
 | |
| // configFromEnv returns a Config with defaults populated using environment variables.
 | |
| 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)
 | |
| 	// Disable stderr logging when a file is specified
 | |
| 	// https://github.com/ipfs/go-log/issues/83
 | |
| 	if cfg.File != "" {
 | |
| 		cfg.Stderr = false
 | |
| 	}
 | |
| 
 | |
| 	cfg.URL = os.Getenv(envLoggingURL)
 | |
| 	output := os.Getenv(envLoggingOutput)
 | |
| 	outputOptions := strings.Split(output, "+")
 | |
| 	for _, opt := range outputOptions {
 | |
| 		switch opt {
 | |
| 		case "stdout":
 | |
| 			cfg.Stdout = true
 | |
| 		case "stderr":
 | |
| 			cfg.Stderr = 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")
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Check that neither of the requested Std* nor the file are TTYs
 | |
| 	// At this stage (configFromEnv) we do not have a uniform list to examine yet
 | |
| 	if noExplicitFormat &&
 | |
| 		!(cfg.Stdout && isTerm(os.Stdout)) &&
 | |
| 		!(cfg.Stderr && isTerm(os.Stderr)) &&
 | |
| 		// check this last: expensive
 | |
| 		!(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 {
 | |
| 	// !!!no!!! O_CREAT, if we fail - we fail
 | |
| 	f, err := os.OpenFile(p, os.O_WRONLY, 0)
 | |
| 	if f != nil {
 | |
| 		defer f.Close() // nolint:errcheck
 | |
| 	}
 | |
| 	return err == nil && isTerm(f)
 | |
| }
 |