github.com/sykesm/fabric@v1.1.0-preview.0.20200129034918-2aa12b1a0181/common/flogging/logging.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package flogging
     8  
     9  import (
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"sync"
    14  
    15  	"github.com/hyperledger/fabric/common/flogging/fabenc"
    16  	zaplogfmt "github.com/sykesm/zap-logfmt"
    17  	"go.uber.org/zap"
    18  	"go.uber.org/zap/zapcore"
    19  )
    20  
    21  // Config is used to provide dependencies to a Logging instance.
    22  type Config struct {
    23  	// Format is the log record format specifier for the Logging instance. If the
    24  	// spec is the string "json", log records will be formatted as JSON. Any
    25  	// other string will be provided to the FormatEncoder. Please see
    26  	// fabenc.ParseFormat for details on the supported verbs.
    27  	//
    28  	// If Format is not provided, a default format that provides basic information will
    29  	// be used.
    30  	Format string
    31  
    32  	// LogSpec determines the log levels that are enabled for the logging system. The
    33  	// spec must be in a format that can be processed by ActivateSpec.
    34  	//
    35  	// If LogSpec is not provided, loggers will be enabled at the INFO level.
    36  	LogSpec string
    37  
    38  	// Writer is the sink for encoded and formatted log records.
    39  	//
    40  	// If a Writer is not provided, os.Stderr will be used as the log sink.
    41  	Writer io.Writer
    42  }
    43  
    44  // Logging maintains the state associated with the fabric logging system. It is
    45  // intended to bridge between the legacy logging infrastructure built around
    46  // go-logging and the structured, level logging provided by zap.
    47  type Logging struct {
    48  	*LoggerLevels
    49  
    50  	mutex          sync.RWMutex
    51  	encoding       Encoding
    52  	encoderConfig  zapcore.EncoderConfig
    53  	multiFormatter *fabenc.MultiFormatter
    54  	writer         zapcore.WriteSyncer
    55  	observer       Observer
    56  }
    57  
    58  // New creates a new logging system and initializes it with the provided
    59  // configuration.
    60  func New(c Config) (*Logging, error) {
    61  	encoderConfig := zap.NewProductionEncoderConfig()
    62  	encoderConfig.NameKey = "name"
    63  
    64  	l := &Logging{
    65  		LoggerLevels: &LoggerLevels{
    66  			defaultLevel: defaultLevel,
    67  		},
    68  		encoderConfig:  encoderConfig,
    69  		multiFormatter: fabenc.NewMultiFormatter(),
    70  	}
    71  
    72  	err := l.Apply(c)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	return l, nil
    77  }
    78  
    79  // Apply applies the provided configuration to the logging system.
    80  func (l *Logging) Apply(c Config) error {
    81  	err := l.SetFormat(c.Format)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	if c.LogSpec == "" {
    87  		c.LogSpec = os.Getenv("FABRIC_LOGGING_SPEC")
    88  	}
    89  	if c.LogSpec == "" {
    90  		c.LogSpec = defaultLevel.String()
    91  	}
    92  
    93  	err = l.LoggerLevels.ActivateSpec(c.LogSpec)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	if c.Writer == nil {
    99  		c.Writer = os.Stderr
   100  	}
   101  	l.SetWriter(c.Writer)
   102  
   103  	return nil
   104  }
   105  
   106  // SetFormat updates how log records are formatted and encoded. Log entries
   107  // created after this method has completed will use the new format.
   108  //
   109  // An error is returned if the log format specification cannot be parsed.
   110  func (l *Logging) SetFormat(format string) error {
   111  	l.mutex.Lock()
   112  	defer l.mutex.Unlock()
   113  	if format == "" {
   114  		format = defaultFormat
   115  	}
   116  
   117  	if format == "json" {
   118  		l.encoding = JSON
   119  		return nil
   120  	}
   121  
   122  	if format == "logfmt" {
   123  		l.encoding = LOGFMT
   124  		return nil
   125  	}
   126  
   127  	formatters, err := fabenc.ParseFormat(format)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	l.multiFormatter.SetFormatters(formatters)
   132  	l.encoding = CONSOLE
   133  
   134  	return nil
   135  }
   136  
   137  // SetWriter controls which writer formatted log records are written to.
   138  // Writers, with the exception of an *os.File, need to be safe for concurrent
   139  // use by multiple go routines.
   140  func (l *Logging) SetWriter(w io.Writer) io.Writer {
   141  	var sw zapcore.WriteSyncer
   142  	switch t := w.(type) {
   143  	case *os.File:
   144  		sw = zapcore.Lock(t)
   145  	case zapcore.WriteSyncer:
   146  		sw = t
   147  	default:
   148  		sw = zapcore.AddSync(w)
   149  	}
   150  
   151  	l.mutex.Lock()
   152  	ow := l.writer
   153  	l.writer = sw
   154  	l.mutex.Unlock()
   155  
   156  	return ow
   157  }
   158  
   159  // SetObserver is used to provide a log observer that will be called as log
   160  // levels are checked or written.. Only a single observer is supported.
   161  func (l *Logging) SetObserver(observer Observer) Observer {
   162  	l.mutex.Lock()
   163  	so := l.observer
   164  	l.observer = observer
   165  	l.mutex.Unlock()
   166  
   167  	return so
   168  }
   169  
   170  // Write satisfies the io.Write contract. It delegates to the writer argument
   171  // of SetWriter or the Writer field of Config. The Core uses this when encoding
   172  // log records.
   173  func (l *Logging) Write(b []byte) (int, error) {
   174  	l.mutex.RLock()
   175  	w := l.writer
   176  	l.mutex.RUnlock()
   177  
   178  	return w.Write(b)
   179  }
   180  
   181  // Sync satisfies the zapcore.WriteSyncer interface. It is used by the Core to
   182  // flush log records before terminating the process.
   183  func (l *Logging) Sync() error {
   184  	l.mutex.RLock()
   185  	w := l.writer
   186  	l.mutex.RUnlock()
   187  
   188  	return w.Sync()
   189  }
   190  
   191  // Encoding satisfies the Encoding interface. It determines whether the JSON or
   192  // CONSOLE encoder should be used by the Core when log records are written.
   193  func (l *Logging) Encoding() Encoding {
   194  	l.mutex.RLock()
   195  	e := l.encoding
   196  	l.mutex.RUnlock()
   197  	return e
   198  }
   199  
   200  // ZapLogger instantiates a new zap.Logger with the specified name. The name is
   201  // used to determine which log levels are enabled.
   202  func (l *Logging) ZapLogger(name string) *zap.Logger {
   203  	if !isValidLoggerName(name) {
   204  		panic(fmt.Sprintf("invalid logger name: %s", name))
   205  	}
   206  
   207  	l.mutex.RLock()
   208  	core := &Core{
   209  		LevelEnabler: l.LoggerLevels,
   210  		Levels:       l.LoggerLevels,
   211  		Encoders: map[Encoding]zapcore.Encoder{
   212  			JSON:    zapcore.NewJSONEncoder(l.encoderConfig),
   213  			CONSOLE: fabenc.NewFormatEncoder(l.multiFormatter),
   214  			LOGFMT:  zaplogfmt.NewEncoder(l.encoderConfig),
   215  		},
   216  		Selector: l,
   217  		Output:   l,
   218  		Observer: l,
   219  	}
   220  	l.mutex.RUnlock()
   221  
   222  	return NewZapLogger(core).Named(name)
   223  }
   224  
   225  func (l *Logging) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) {
   226  	l.mutex.RLock()
   227  	observer := l.observer
   228  	l.mutex.RUnlock()
   229  
   230  	if observer != nil {
   231  		observer.Check(e, ce)
   232  	}
   233  }
   234  
   235  func (l *Logging) WriteEntry(e zapcore.Entry, fields []zapcore.Field) {
   236  	l.mutex.RLock()
   237  	observer := l.observer
   238  	l.mutex.RUnlock()
   239  
   240  	if observer != nil {
   241  		observer.WriteEntry(e, fields)
   242  	}
   243  }
   244  
   245  // Logger instantiates a new FabricLogger with the specified name. The name is
   246  // used to determine which log levels are enabled.
   247  func (l *Logging) Logger(name string) *FabricLogger {
   248  	zl := l.ZapLogger(name)
   249  	return NewFabricLogger(zl)
   250  }