logger.go raw

   1  //go:build (darwin && (amd64 || arm64)) || (linux && (amd64 || arm64)) || (windows && amd64)
   2  
   3  package bark
   4  
   5  import (
   6  	"os"
   7  	"path/filepath"
   8  	"sync"
   9  
  10  	"github.com/orandin/lumberjackrus"
  11  	"github.com/sirupsen/logrus"
  12  	bark "gitlab.com/ark-bitcoin/bark-ffi-bindings/golang/bark"
  13  
  14  	"github.com/getAlby/hub/logger"
  15  )
  16  
  17  const barkLogFilename = "bark.log"
  18  const logDir = "log"
  19  
  20  // setLoggerOnce guards bark.SetLogger, which can only be installed once per
  21  // process (calling it again returns an error from the underlying bridge).
  22  var setLoggerOnce sync.Once
  23  
  24  // barkLogger forwards bark's internal log records to a dedicated logrus logger
  25  // (separate from the main app logger, with its own level and log file) so bark
  26  // logs can be tuned independently of the rest of the hub.
  27  type barkLogger struct {
  28  	logger *logrus.Logger
  29  }
  30  
  31  var _ bark.BarkLogger = &barkLogger{}
  32  
  33  // Log implements bark.BarkLogger. The record's bark log level is mapped onto the
  34  // equivalent logrus level, and the bark target is attached as a structured field.
  35  //
  36  // IMPORTANT: this must not call back into any bark API that itself emits log
  37  // records, or the foreign runtime may stack-overflow or deadlock.
  38  func (l *barkLogger) Log(level bark.LogLevel, target string, message string) {
  39  	l.logger.WithFields(logrus.Fields{
  40  		"log_type": "bark",
  41  		"target":   target,
  42  	}).Log(barkLevelToLogrus(level), message)
  43  }
  44  
  45  func barkLevelToLogrus(level bark.LogLevel) logrus.Level {
  46  	switch level {
  47  	case bark.LogLevelError:
  48  		return logrus.ErrorLevel
  49  	case bark.LogLevelWarn:
  50  		return logrus.WarnLevel
  51  	case bark.LogLevelInfo:
  52  		return logrus.InfoLevel
  53  	case bark.LogLevelDebug:
  54  		return logrus.DebugLevel
  55  	case bark.LogLevelTrace:
  56  		return logrus.TraceLevel
  57  	}
  58  	logger.Logger.WithField("log_level", level).Error("Unknown Bark log level")
  59  	return logrus.ErrorLevel
  60  }
  61  
  62  // logrusToBarkLevel maps a logrus level onto the bark log level used to cap
  63  // what the FFI bridge emits, so filtered-out records don't cross the boundary.
  64  func logrusToBarkLevel(level logrus.Level) bark.LogLevel {
  65  	switch level {
  66  	case logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel:
  67  		return bark.LogLevelError
  68  	case logrus.WarnLevel:
  69  		return bark.LogLevelWarn
  70  	case logrus.InfoLevel:
  71  		return bark.LogLevelInfo
  72  	case logrus.DebugLevel:
  73  		return bark.LogLevelDebug
  74  	default:
  75  		return bark.LogLevelTrace
  76  	}
  77  }
  78  
  79  // installBarkLogger wires bark's internal logs into a dedicated logrus logger.
  80  // It is safe to call more than once (e.g. when reopening the wallet); only the
  81  // first call installs the logger, as the bridge can only be set once per
  82  // process.
  83  func installBarkLogger(logLevel logrus.Level, logToFile bool, workDir string) {
  84  	setLoggerOnce.Do(func() {
  85  		barkLogrus, err := createBarkLogger(logLevel, logToFile, workDir)
  86  		if err != nil {
  87  			logger.Logger.WithError(err).Error("Failed to create Bark logger")
  88  			return
  89  		}
  90  		// Cap the FFI bridge at the configured level so records below it are
  91  		// never produced; the dedicated logger applies the same level again.
  92  		if err := bark.SetLogger(&barkLogger{logger: barkLogrus}, logrusToBarkLevel(logLevel)); err != nil {
  93  			logger.Logger.WithError(err).Warn("Failed to install Bark logger")
  94  		}
  95  	})
  96  }
  97  
  98  func createBarkLogger(logLevel logrus.Level, logToFile bool, workDir string) (*logrus.Logger, error) {
  99  	barkLogrus := logrus.New()
 100  	barkLogrus.SetFormatter(&logrus.JSONFormatter{})
 101  	barkLogrus.SetOutput(os.Stdout)
 102  	barkLogrus.SetLevel(logLevel)
 103  
 104  	if logToFile {
 105  		parentDir := filepath.Dir(workDir)
 106  		barkLogFilePath := filepath.Join(parentDir, logDir, barkLogFilename)
 107  		barkFileLoggerHook, err := lumberjackrus.NewHook(
 108  			&lumberjackrus.LogFile{
 109  				Filename:   barkLogFilePath,
 110  				MaxAge:     3,
 111  				MaxBackups: 3,
 112  			},
 113  			logLevel,
 114  			&logrus.JSONFormatter{},
 115  			nil,
 116  		)
 117  		if err != nil {
 118  			return nil, err
 119  		}
 120  		barkLogrus.AddHook(barkFileLoggerHook)
 121  	}
 122  	return barkLogrus, nil
 123  }
 124