@ -12,6 +12,7 @@ import (
"context"
"crypto/rand"
"encoding/binary"
"expvar"
"fmt"
"io"
"log"
@ -28,6 +29,7 @@ import (
"github.com/creachadair/msync/trigger"
"github.com/go-json-experiment/json/jsontext"
"tailscale.com/envknob"
"tailscale.com/metrics"
"tailscale.com/net/netmon"
"tailscale.com/net/sockstats"
"tailscale.com/tstime"
@ -180,6 +182,12 @@ type Logger struct {
shutdownStartMu sync . Mutex // guards the closing of shutdownStart
shutdownStart chan struct { } // closed when shutdown begins
shutdownDone chan struct { } // closed when shutdown complete
// Metrics (see [Logger.ExpVar] for details).
uploadCalls expvar . Int
failedCalls expvar . Int
uploadedBytes expvar . Int
uploadingTime expvar . Int
}
type atomicSocktatsLabel struct { p atomic . Uint32 }
@ -477,6 +485,9 @@ func (lg *Logger) awaitInternetUp(ctx context.Context) {
// origlen indicates the pre-compression body length.
// origlen of -1 indicates that the body is not compressed.
func ( lg * Logger ) upload ( ctx context . Context , body [ ] byte , origlen int ) ( retryAfter time . Duration , err error ) {
lg . uploadCalls . Add ( 1 )
startUpload := time . Now ( )
const maxUploadTime = 45 * time . Second
ctx = sockstats . WithSockStats ( ctx , lg . sockstatsLabel . Load ( ) , lg . Logf )
ctx , cancel := context . WithTimeout ( ctx , maxUploadTime )
@ -516,15 +527,20 @@ func (lg *Logger) upload(ctx context.Context, body []byte, origlen int) (retryAf
lg . httpDoCalls . Add ( 1 )
resp , err := lg . httpc . Do ( req )
if err != nil {
lg . failedCalls . Add ( 1 )
return 0 , fmt . Errorf ( "log upload of %d bytes %s failed: %v" , len ( body ) , compressedNote , err )
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
lg . failedCalls . Add ( 1 )
n , _ := strconv . Atoi ( resp . Header . Get ( "Retry-After" ) )
b , _ := io . ReadAll ( io . LimitReader ( resp . Body , 1 << 10 ) )
return time . Duration ( n ) * time . Second , fmt . Errorf ( "log upload of %d bytes %s failed %d: %s" , len ( body ) , compressedNote , resp . StatusCode , bytes . TrimSpace ( b ) )
}
lg . uploadedBytes . Add ( int64 ( len ( body ) ) )
lg . uploadingTime . Add ( int64 ( time . Since ( startUpload ) ) )
return 0 , nil
}
@ -546,6 +562,30 @@ func (lg *Logger) StartFlush() {
}
}
// ExpVar report metrics about the logger.
//
// - counter_upload_calls: Total number of upload attempts.
//
// - counter_upload_errors: Total number of upload attempts that failed.
//
// - counter_uploaded_bytes: Total number of bytes successfully uploaded
// (which is calculated after compression is applied).
//
// - counter_uploading_nsecs: Total number of nanoseconds spent uploading.
//
// - buffer: An optional [metrics.Set] with metrics for the [Buffer].
func ( lg * Logger ) ExpVar ( ) expvar . Var {
m := new ( metrics . Set )
m . Set ( "counter_upload_calls" , & lg . uploadCalls )
m . Set ( "counter_upload_errors" , & lg . failedCalls )
m . Set ( "counter_uploaded_bytes" , & lg . uploadedBytes )
m . Set ( "counter_uploading_nsecs" , & lg . uploadingTime )
if v , ok := lg . buffer . ( interface { ExpVar ( ) expvar . Var } ) ; ok {
m . Set ( "buffer" , v . ExpVar ( ) )
}
return m
}
// logtailDisabled is whether logtail uploads to logcatcher are disabled.
var logtailDisabled atomic . Bool