package old_tcp_tailscale // Copyright (c) Tailscale Inc & AUTHORS import ( "bufio" "bytes" "encoding/json" "fmt" "io" "log" "sync" "time" "context" ) // Logf is the basic Tailscale logger type: a printf-like func. // Like log.Printf, the format need not end in a newline. // Logf functions must be safe for concurrent use. type Logf func(format string, args ...any) // A Context is a context.Context that should contain a custom log function, obtainable from FromContext. // If no log function is present, FromContext will return log.Printf. // To construct a Context, use Add // // Deprecated: Do not use. type Context context.Context // jenc is a json.Encode + bytes.Buffer pair wired up to be reused in a pool. type jenc struct { buf bytes.Buffer enc *json.Encoder } var jencPool = &sync.Pool{New: func() any { je := new(jenc) je.enc = json.NewEncoder(&je.buf) return je }} // JSON marshals v as JSON and writes it to logf formatted with the annotation to make logtail // treat it as a structured log. // // The recType is the record type and becomes the key of the wrapper // JSON object that is logged. That is, if recType is "foo" and v is // 123, the value logged is {"foo":123}. // // Do not use recType "logtail", "v", "text", or "metrics", with any case. // Those are reserved for the logging system. // // The level can be from 0 to 9. Levels from 1 to 9 are included in // the logged JSON object, like {"foo":123,"v":2}. func (logf Logf) JSON(level int, recType string, v any) { je := jencPool.Get().(*jenc) defer jencPool.Put(je) je.buf.Reset() je.buf.WriteByte('{') je.enc.Encode(recType) je.buf.Truncate(je.buf.Len() - 1) // remove newline from prior Encode je.buf.WriteByte(':') if err := je.enc.Encode(v); err != nil { logf("[unexpected]: failed to encode structured JSON log record of type %q / %T: %v", recType, v, err) return } je.buf.Truncate(je.buf.Len() - 1) // remove newline from prior Encode je.buf.WriteByte('}') // Magic prefix recognized by logtail: logf("[v\x00JSON]%d%s", level%10, je.buf.Bytes()) } // WithPrefix wraps f, prefixing each format with the provided prefix. func WithPrefix(f Logf, prefix string) Logf { return func(format string, args ...any) { f(prefix+format, args...) } } // FuncWriter returns an io.Writer that writes to f. func FuncWriter(f Logf) io.Writer { return funcWriter{f} } // StdLogger returns a standard library logger from a Logf. func StdLogger(f Logf) *log.Logger { return log.New(FuncWriter(f), "", 0) } type funcWriter struct{ f Logf } func (w funcWriter) Write(p []byte) (int, error) { w.f("%s", p) return len(p), nil } // Discard is a Logf that throws away the logs given to it. func Discard(string, ...any) {} // LogOnChange logs a given line only if line != lastLine, or if maxInterval has passed // since the last time this identical line was logged. func LogOnChange(logf Logf, maxInterval time.Duration, timeNow func() time.Time) Logf { var ( mu sync.Mutex sLastLogged string tLastLogged = timeNow() ) return func(format string, args ...any) { s := fmt.Sprintf(format, args...) mu.Lock() if s == sLastLogged && timeNow().Sub(tLastLogged) < maxInterval { mu.Unlock() return } sLastLogged = s tLastLogged = timeNow() mu.Unlock() // Re-stringify it (instead of using "%s", s) so something like "%s" // doesn't end up getting rate-limited. (And can't use 's' as the pattern, // as it might contain formatting directives.) logf(format, args...) } } // ArgWriter is a fmt.Formatter that can be passed to any Logf func to // efficiently write to a %v argument without allocations. type ArgWriter func(*bufio.Writer) func (fn ArgWriter) Format(f fmt.State, _ rune) { bw := argBufioPool.Get().(*bufio.Writer) bw.Reset(f) fn(bw) bw.Flush() argBufioPool.Put(bw) } var argBufioPool = &sync.Pool{New: func() any { return bufio.NewWriterSize(io.Discard, 1024) }} // Filtered returns a Logf that silently swallows some log lines. // Each inbound format and args is evaluated and printed to a string s. // The original format and args are passed to logf if and only if allow(s) returns true. func Filtered(logf Logf, allow func(s string) bool) Logf { return func(format string, args ...any) { msg := fmt.Sprintf(format, args...) if !allow(msg) { return } logf(format, args...) } } // LogfCloser wraps logf to create a logger that can be closed. // Calling close makes all future calls to newLogf into no-ops. func LogfCloser(logf Logf) (newLogf Logf, close func()) { var ( mu sync.Mutex closed bool ) close = func() { mu.Lock() defer mu.Unlock() closed = true } newLogf = func(msg string, args ...any) { mu.Lock() if closed { mu.Unlock() return } mu.Unlock() logf(msg, args...) } return newLogf, close } // AsJSON returns a formatter that formats v as JSON. The value is suitable to // passing to a regular %v printf argument. (%s is not required) // // If json.Marshal returns an error, the output is "%%!JSON-ERROR:" followed by // the error string. func AsJSON(v any) fmt.Formatter { return asJSONResult{v} } type asJSONResult struct{ v any } func (a asJSONResult) Format(s fmt.State, verb rune) { v, err := json.Marshal(a.v) if err != nil { fmt.Fprintf(s, "%%!JSON-ERROR:%v", err) return } s.Write(v) } // TBLogger is the testing.TB subset needed by TestLogger. type TBLogger interface { Helper() Logf(format string, args ...any) } // TestLogger returns a logger that logs to tb.Logf // with a prefix to make it easier to distinguish spam // from explicit test failures. func TestLogger(tb TBLogger) Logf { return func(format string, args ...any) { tb.Helper() tb.Logf(" ... "+format, args...) } }