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 }