github.com/ssgreg/logf@v1.4.1/channel.go (about)

     1  package logf
     2  
     3  import (
     4  	"os"
     5  	"runtime"
     6  	"sync"
     7  	"time"
     8  )
     9  
    10  // ChannelWriterConfig allows to configure ChannelWriter.
    11  type ChannelWriterConfig struct {
    12  	// Capacity specifies the underlying channel capacity.
    13  	Capacity int
    14  
    15  	// Appender specified the basic Appender for all Entries.
    16  	//
    17  	// Default Appender is WriterAppender with JSON Encoder.
    18  	Appender Appender
    19  
    20  	// ErrorAppender specifies the Appender for errors returning by basic
    21  	// Appender.
    22  	//
    23  	// Default ErrorAppender does nothing.
    24  	ErrorAppender Appender
    25  
    26  	// EnableSyncOnError specifies whether Appender.Sync should be called
    27  	// for messages with LevelError or not.
    28  	//
    29  	// Default value is false.
    30  	EnableSyncOnError bool
    31  }
    32  
    33  // WithDefaults returns the new config in which all uninitialized fields are
    34  // filled with their default values.
    35  func (c ChannelWriterConfig) WithDefaults() ChannelWriterConfig {
    36  	// Chan efficiency depends on the number of CPU installed in the system.
    37  	// Tests shows that min chan capacity should be twice as big as CPU count.
    38  	minCap := runtime.NumCPU() * 2
    39  	if c.Capacity < minCap {
    40  		c.Capacity = minCap
    41  	}
    42  	// No ErrorAppender by default.
    43  	if c.ErrorAppender == nil {
    44  		c.ErrorAppender = NewDiscardAppender()
    45  	}
    46  	// Default appender writes JSON-formatter messages to stdout.
    47  	if c.Appender == nil {
    48  		c.Appender = NewWriteAppender(os.Stdout, NewJSONEncoder.Default())
    49  	}
    50  
    51  	return c
    52  }
    53  
    54  // NewChannelWriter returns a new ChannelWriter with the given config.
    55  var NewChannelWriter = channelWriterGetter(
    56  	func(cfg ChannelWriterConfig) (EntryWriter, ChannelWriterCloseFunc) {
    57  		l := &channelWriter{}
    58  		l.init(cfg.WithDefaults())
    59  
    60  		return l, ChannelWriterCloseFunc(
    61  			func() {
    62  				l.close()
    63  			})
    64  	},
    65  )
    66  
    67  // ChannelWriterCloseFunc allows to close channel writer.
    68  type ChannelWriterCloseFunc func()
    69  
    70  type channelWriter struct {
    71  	ChannelWriterConfig
    72  	sync.Mutex
    73  	sync.WaitGroup
    74  
    75  	ch     chan Entry
    76  	closed bool
    77  }
    78  
    79  func (l *channelWriter) WriteEntry(e Entry) {
    80  	l.ch <- e
    81  }
    82  
    83  func (l *channelWriter) init(cfg ChannelWriterConfig) {
    84  	l.ChannelWriterConfig = cfg
    85  	l.ch = make(chan Entry, l.Capacity)
    86  
    87  	l.Add(1)
    88  	go l.worker()
    89  }
    90  
    91  func (l *channelWriter) close() {
    92  	l.Lock()
    93  	defer l.Unlock()
    94  
    95  	// Double close is allowed.
    96  	if !l.closed {
    97  		close(l.ch)
    98  		l.Wait()
    99  
   100  		// Mark channel as closed and drained. Channel is not reset to nil,
   101  		// that allows build-it panic in case of calling WriterEntry after
   102  		// Close.
   103  		l.closed = true
   104  	}
   105  }
   106  
   107  func (l *channelWriter) worker() {
   108  	defer l.Done()
   109  
   110  	var e Entry
   111  	var ok bool
   112  	for {
   113  		select {
   114  		case e, ok = <-l.ch:
   115  		default:
   116  			// Channel is empty. Force appender to flush.
   117  			l.flush()
   118  			e, ok = <-l.ch
   119  		}
   120  		if !ok {
   121  			break
   122  		}
   123  
   124  		l.append(e)
   125  	}
   126  
   127  	// Force appender to flush & sync at exit.
   128  	l.flush()
   129  	l.sync()
   130  }
   131  
   132  func (l *channelWriter) flush() {
   133  	err := l.Appender.Flush()
   134  	if err != nil {
   135  		l.reportError("logf: failed to flush appender", err)
   136  	}
   137  }
   138  
   139  func (l *channelWriter) sync() {
   140  	err := l.Appender.Sync()
   141  	if err != nil {
   142  		l.reportError("logf: failed to sync appender", err)
   143  	}
   144  }
   145  
   146  func (l *channelWriter) append(e Entry) {
   147  	err := l.Appender.Append(e)
   148  	if err != nil {
   149  		l.reportError("logf: failed to append entry", err)
   150  	}
   151  
   152  	// Force appender to Sync if entry contains an error message.
   153  	// This allows to commit buffered messages in case of further unexpected
   154  	// panic or crash.
   155  	if e.Level <= LevelError {
   156  		l.flush()
   157  		if l.EnableSyncOnError {
   158  			l.sync()
   159  		}
   160  	}
   161  }
   162  
   163  func (l *channelWriter) reportError(text string, err error) {
   164  	skipError(l.ErrorAppender.Append(newErrorEntry(text, Error(err))))
   165  	skipError(l.ErrorAppender.Flush())
   166  	skipError(l.ErrorAppender.Sync())
   167  }
   168  
   169  func skipError(_ error) {
   170  }
   171  
   172  func newErrorEntry(text string, fs ...Field) Entry {
   173  	return Entry{
   174  		LoggerID: -1,
   175  		Level:    LevelError,
   176  		Time:     time.Now(),
   177  		Text:     text,
   178  		Fields:   fs,
   179  	}
   180  }
   181  
   182  type channelWriterGetter func(cfg ChannelWriterConfig) (EntryWriter, ChannelWriterCloseFunc)
   183  
   184  func (c channelWriterGetter) Default() (EntryWriter, ChannelWriterCloseFunc) {
   185  	return c(ChannelWriterConfig{})
   186  }