btcd logモジュールソースコード分析

5699 ワード

btcdで使用されるlogモジュールは、自分のプロジェクトウェアハウスのbtclogパッケージを参照します.
パッケージ全体で、実装するインタフェースと簡単なパッケージを定義します.

type Logger interface {

  Tracef(format string, params ...interface{})

  Debugf(format string, params ...interface{})

  Infof(format string, params ...interface{})

  Warnf(format string, params ...interface{})

  Errorf(format string, params ...interface{})

  Criticalf(format string, params ...interface{})

  Trace(v ...interface{})
  Debug(v ...interface{})
  Info(v ...interface{})
  Warn(v ...interface{})
  Error(v ...interface{})
  Critical(v ...interface{})
  Level() Level
  SetLevel(level Level
}

//                

type Backend struct {
  w io.Writer
  mu  sync.Mutex // ensures atomic writes
  flag uint32
}

//        log   ,tag       subsystem

// slog          

type  struct {
  lvl Level // atomic
  tag string // tag       log   
  b  *Backend
}

logレベルの判断は、実装インタフェースに含まれていない方法で、対応するレベルと比較して、その日を出力するかどうかを判断します.例えば、次のようにします.

func (l *slog) Warn(args ...interface{}) {
  lvl := l.Level()
  if lvl <= LevelWarn {
  l.b.print("WRN", l.tag, args...)
}

func (l *slog) Warnf(format string, args ...interface{}) {
  lvl := l.Level()
  if lvl <= LevelWarn {
    l.b.printf("WRN", l.tag, format, args...)
  }
}


各実装インタフェースの方法では、書き込み動作は、printおよびprintfによって実現される.
func (b *Backend) print(lvl, tag string, args ...interface{}) {

  t := time.Now() // get as early as possible
  bytebuf := buffer()
  var file string
  var line int

  if b.flag&(Lshortfile|Llongfile) != 0 {
    file, line = callsite(b.flag)
  }

  formatHeader(bytebuf, t, lvl, tag, file, line)
  buf := bytes.NewBuffer(*bytebuf)
  fmt.Fprintln(buf, args...)
  *bytebuf = buf.Bytes()
  b.mu.Lock()
  b.w.Write(*bytebuf)
  b.mu.Unlock()
  recycleBuffer(bytebuf)
}

//      

func (b *Backend) printf(lvl, tag string, format string, args ...interface{}) {
  t := time.Now() // get as early as possible
  bytebuf := buffer()
  var file string
  var line int

  if b.flag&(Lshortfile|Llongfile) != 0 {
    file, line = callsite(b.flag)
  }

  formatHeader(bytebuf, t, lvl, tag, file, line)
  buf := bytes.NewBuffer(*bytebuf)
  fmt.Fprintf(buf, format, args...)
  *bytebuf = append(buf.Bytes(), '
') b.mu.Lock() b.w.Write(*bytebuf) b.mu.Unlock() recycleBuffer(bytebuf) }

btclogパッケージには、グローバル変数Disabledがあり、init関数で初期化されていますが、このlogインスタンスはlogに書き込まれるのではなく、すべてのlogを破棄します.
// Disabled is a Logger that will never output anything.
var Disabled Logger

func init() {
  Disabled = &slog{lvl: LevelOff, b: NewBackend(ioutil.Discard)}
}


使用
btcdプロジェクトmainパッケージでは、グローバルlog変数backendLogを初期化します.次に、各サブモジュールに対して複数のグローバル変数を定義し、各モジュールlogレコードを担当する.これらの変数は、slog構造体のBackendフィールドがポインタタイプであるため、backedLogを共有します.

  backendLog = btclog.NewBackend(logWriter{})

  logRotator *rotator.Rotator

  adxrLog = backendLog.Logger("ADXR")
  amgrLog = backendLog.Logger("AMGR")
  cmgrLog = backendLog.Logger("CMGR")
  bcdbLog = backendLog.Logger("BCDB")
  btcdLog = backendLog.Logger("BTCD")
  chanLog = backendLog.Logger("CHAN")
  discLog = backendLog.Logger("DISC")
  indxLog = backendLog.Logger("INDX")
  minrLog = backendLog.Logger("MINR")
  peerLog = backendLog.Logger("PEER")
  rpcsLog = backendLog.Logger("RPCS")
  scrpLog = backendLog.Logger("SCRP")
  srvrLog = backendLog.Logger("SRVR")
  syncLog = backendLog.Logger("SYNC")
  txmpLog = backendLog.Logger("TXMP")

Logger()メソッドは、tagフィールドを変換して、Backedを共有するだけです
func (b *Backend) Logger(subsystemTag string) Logger {
  return &slog{LevelInfo, subsystemTag, b}
}

mainパケット初期化関数では、各パケットのグローバル変数を割り当てます.mainパッケージのグローバル変数を各モジュールにコピーすることに相当します.各モジュールは,このパケットに対するlogコピーを用いてlogの記録を行う.

func init() {
  addrmgr.UseLogger(amgrLog) //      
  connmgr.UseLogger(cmgrLog)
  database.UseLogger(bcdbLog)
  blockchain.UseLogger(chanLog)
  indexers.UseLogger(indxLog)
  mining.UseLogger(minrLog)
  cpuminer.UseLogger(minrLog)
  peer.UseLogger(peerLog)
  txscript.UseLogger(scrpLog)
  netsync.UseLogger(syncLog)
  mempool.UseLogger(txmpLog)
}

各モジュールでは、golang init関数の初期化順序の問題に関連するログはデフォルトで出力されません.簡単に説明すると、プログラム起動時にmainパケットからmainパケットimportの各モジュールが導入されるため、パッケージが先に初期化され、このとき各モジュールにおけるinit関数が実行する、DisableLog()メソッドは本モジュールにおけるグローバル変数logを初期化し、btclogとする.Disabled、つまり前述したすべてのログを破棄します.現在のモジュールのlogにはログ情報は記録されません.モジュールのinit実行が完了したら、指定したモジュールのグローバル変数を指定したtagを持つlog、すなわちmainの各モジュールlogを表すコピーに再割り当てするmainパケットのinit関数を実行します.これにより,各モジュールのlog定義が完了する.
ここで疑問に思うのは、なぜモジュールのlogが両方を初期化するのかということです.これは一定の柔軟性を実現するためです.たとえば、プログラム設計者がminingモジュールのログ記録を停止した場合、mainパケットからグローバル変数minrLog = backendLog.Logger("MINR")と関数呼び出しmining.UseLogger(minrLog)を除去するだけで、miningがDisabledとしてデフォルト設定され、ログ記録は行われなくなる.
mempoolモジュールを例に挙げると、

var log btclog.Logger

func init() {
  DisableLog() //    
}

// DisableLog disables all library log output. Logging output is disabled
// by default until either UseLogger or SetLogWriter are called.
func DisableLog() {
  log = btclog.Disabled //        
}

// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
  log = logger //   log
}


その他
btclogパケットにはバッファプールも含まれており、一部のモジュールではlogに対してカスタマイズされた処理が行われており、btcdのlog関連ソースコードを見ることができます.