@ -62,8 +62,11 @@ type Config struct {
TimeNow func ( ) time . Time // if set, subsitutes uses of time.Now
TimeNow func ( ) time . Time // if set, subsitutes uses of time.Now
Stderr io . Writer // if set, logs are sent here instead of os.Stderr
Stderr io . Writer // if set, logs are sent here instead of os.Stderr
Buffer Buffer // temp storage, if nil a MemoryBuffer
Buffer Buffer // temp storage, if nil a MemoryBuffer
CheckLogs <- chan struct { } // signals Logger to check for filched logs to upload
NewZstdEncoder func ( ) Encoder // if set, used to compress logs for transmission
NewZstdEncoder func ( ) Encoder // if set, used to compress logs for transmission
// DrainLogs, if non-nil, disables autmatic uploading of new logs,
// so that logs are only uploaded when a token is sent to DrainLogs.
DrainLogs <- chan struct { }
}
}
func Log ( cfg Config ) Logger {
func Log ( cfg Config ) Logger {
@ -86,9 +89,6 @@ func Log(cfg Config) Logger {
}
}
cfg . Buffer = NewMemoryBuffer ( pendingSize )
cfg . Buffer = NewMemoryBuffer ( pendingSize )
}
}
if cfg . CheckLogs == nil {
cfg . CheckLogs = make ( chan struct { } )
}
l := & logger {
l := & logger {
stderr : cfg . Stderr ,
stderr : cfg . Stderr ,
httpc : cfg . HTTPC ,
httpc : cfg . HTTPC ,
@ -98,7 +98,7 @@ func Log(cfg Config) Logger {
skipClientTime : cfg . SkipClientTime ,
skipClientTime : cfg . SkipClientTime ,
sent : make ( chan struct { } , 1 ) ,
sent : make ( chan struct { } , 1 ) ,
sentinel : make ( chan int32 , 16 ) ,
sentinel : make ( chan int32 , 16 ) ,
checkLogs: cfg . Check Logs,
drainLogs: cfg . Drain Logs,
timeNow : cfg . TimeNow ,
timeNow : cfg . TimeNow ,
bo : backoff . Backoff {
bo : backoff . Backoff {
Name : "logtail" ,
Name : "logtail" ,
@ -127,7 +127,7 @@ type logger struct {
skipClientTime bool
skipClientTime bool
buffer Buffer
buffer Buffer
sent chan struct { } // signal to speed up drain
sent chan struct { } // signal to speed up drain
check Logs <- chan struct { } // external signal to attempt a drain
drain Logs <- chan struct { } // if non-nil, external signal to attempt a drain
sentinel chan int32
sentinel chan int32
timeNow func ( ) time . Time
timeNow func ( ) time . Time
bo backoff . Backoff
bo backoff . Backoff
@ -164,6 +164,32 @@ func (l *logger) Close() {
l . Shutdown ( context . Background ( ) )
l . Shutdown ( context . Background ( ) )
}
}
// drainBlock is called by drainPending when there are no logs to drain.
//
// In typical operation, every call to the Write method unblocks and triggers
// a buffer.TryReadline, so logs are written with very low latency.
//
// If the caller provides a DrainLogs channel, then unblock-drain-on-Write
// is disabled, and it is up to the caller to trigger unblock the drain.
func ( l * logger ) drainBlock ( ) ( shuttingDown bool ) {
if l . drainLogs == nil {
select {
case <- l . shutdownStart :
return true
case <- l . sent :
}
} else {
select {
case <- l . shutdownStart :
return true
case <- l . drainLogs :
}
}
return false
}
// drainPending drains and encodes a batch of logs from the buffer for upload.
// If no logs are available, drainPending blocks until logs are available.
func ( l * logger ) drainPending ( ) ( res [ ] byte ) {
func ( l * logger ) drainPending ( ) ( res [ ] byte ) {
buf := new ( bytes . Buffer )
buf := new ( bytes . Buffer )
entries := 0
entries := 0
@ -182,12 +208,7 @@ func (l *logger) drainPending() (res []byte) {
break
break
}
}
select {
batchDone = l . drainBlock ( )
case <- l . shutdownStart :
batchDone = true
case <- l . checkLogs :
case <- l . sent :
}
continue
continue
}
}
@ -304,10 +325,12 @@ func (l *logger) Flush() error {
func ( l * logger ) send ( jsonBlob [ ] byte ) ( int , error ) {
func ( l * logger ) send ( jsonBlob [ ] byte ) ( int , error ) {
n , err := l . buffer . Write ( jsonBlob )
n , err := l . buffer . Write ( jsonBlob )
if l . drainLogs == nil {
select {
select {
case l . sent <- struct { } { } :
case l . sent <- struct { } { } :
default :
default :
}
}
}
return n , err
return n , err
}
}