| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- // Copyright 2023 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package logsink
- import (
- "bytes"
- "context"
- "fmt"
- "strconv"
- "strings"
- "sync"
- "time"
- "github.com/golang/glog/internal/stackdump"
- )
- // MaxLogMessageLen is the limit on length of a formatted log message, including
- // the standard line prefix and trailing newline.
- //
- // Chosen to match C++ glog.
- const MaxLogMessageLen = 15000
- // A Severity is a severity at which a message can be logged.
- type Severity int8
- // These constants identify the log levels in order of increasing severity.
- // A message written to a high-severity log file is also written to each
- // lower-severity log file.
- const (
- Info Severity = iota
- Warning
- Error
- // Fatal contains logs written immediately before the process terminates.
- //
- // Sink implementations should not terminate the process themselves: the log
- // package will perform any necessary cleanup and terminate the process as
- // appropriate.
- Fatal
- )
- func (s Severity) String() string {
- switch s {
- case Info:
- return "INFO"
- case Warning:
- return "WARNING"
- case Error:
- return "ERROR"
- case Fatal:
- return "FATAL"
- }
- return fmt.Sprintf("%T(%d)", s, s)
- }
- // ParseSeverity returns the case-insensitive Severity value for the given string.
- func ParseSeverity(name string) (Severity, error) {
- name = strings.ToUpper(name)
- for s := Info; s <= Fatal; s++ {
- if s.String() == name {
- return s, nil
- }
- }
- return -1, fmt.Errorf("logsink: invalid severity %q", name)
- }
- // Meta is metadata about a logging call.
- type Meta struct {
- // The context with which the log call was made (or nil). If set, the context
- // is only valid during the logsink.Structured.Printf call, it should not be
- // retained.
- Context context.Context
- // Time is the time at which the log call was made.
- Time time.Time
- // File is the source file from which the log entry originates.
- File string
- // Line is the line offset within the source file.
- Line int
- // Depth is the number of stack frames between the logsink and the log call.
- Depth int
- Severity Severity
- // Verbose indicates whether the call was made via "log.V". Log entries below
- // the current verbosity threshold are not sent to the sink.
- Verbose bool
- // Thread ID. This can be populated with a thread ID from another source,
- // such as a system we are importing logs from. In the normal case, this
- // will be set to the process ID (PID), since Go doesn't have threads.
- Thread int64
- // Stack trace starting in the logging function. May be nil.
- // A logsink should implement the StackWanter interface to request this.
- //
- // Even if WantStack returns false, this field may be set (e.g. if another
- // sink wants a stack trace).
- Stack *stackdump.Stack
- }
- // Structured is a logging destination that accepts structured data as input.
- type Structured interface {
- // Printf formats according to a fmt.Printf format specifier and writes a log
- // entry. The precise result of formatting depends on the sink, but should
- // aim for consistency with fmt.Printf.
- //
- // Printf returns the number of bytes occupied by the log entry, which
- // may not be equal to the total number of bytes written.
- //
- // Printf returns any error encountered *if* it is severe enough that the log
- // package should terminate the process.
- //
- // The sink must not modify the *Meta parameter, nor reference it after
- // Printf has returned: it may be reused in subsequent calls.
- Printf(meta *Meta, format string, a ...any) (n int, err error)
- }
- // StackWanter can be implemented by a logsink.Structured to indicate that it
- // wants a stack trace to accompany at least some of the log messages it receives.
- type StackWanter interface {
- // WantStack returns true if the sink requires a stack trace for a log message
- // with this metadata.
- //
- // NOTE: Returning true implies that meta.Stack will be non-nil. Returning
- // false does NOT imply that meta.Stack will be nil.
- WantStack(meta *Meta) bool
- }
- // Text is a logging destination that accepts pre-formatted log lines (instead of
- // structured data).
- type Text interface {
- // Enabled returns whether this sink should output messages for the given
- // Meta. If the sink returns false for a given Meta, the Printf function will
- // not call Emit on it for the corresponding log message.
- Enabled(*Meta) bool
- // Emit writes a pre-formatted text log entry (including any applicable
- // header) to the log. It returns the number of bytes occupied by the entry
- // (which may differ from the length of the passed-in slice).
- //
- // Emit returns any error encountered *if* it is severe enough that the log
- // package should terminate the process.
- //
- // The sink must not modify the *Meta parameter, nor reference it after
- // Printf has returned: it may be reused in subsequent calls.
- //
- // NOTE: When developing a text sink, keep in mind the surface in which the
- // logs will be displayed, and whether it's important that the sink be
- // resistent to tampering in the style of b/211428300. Standard text sinks
- // (like `stderrSink`) do not protect against this (e.g. by escaping
- // characters) because the cases where they would show user-influenced bytes
- // are vanishingly small.
- Emit(*Meta, []byte) (n int, err error)
- }
- // bufs is a pool of *bytes.Buffer used in formatting log entries.
- var bufs sync.Pool // Pool of *bytes.Buffer.
- // textPrintf formats a text log entry and emits it to all specified Text sinks.
- //
- // The returned n is the maximum across all Emit calls.
- // The returned err is the first non-nil error encountered.
- // Sinks that are disabled by configuration should return (0, nil).
- func textPrintf(m *Meta, textSinks []Text, format string, args ...any) (n int, err error) {
- // We expect at most file, stderr, and perhaps syslog. If there are more,
- // we'll end up allocating - no big deal.
- const maxExpectedTextSinks = 3
- var noAllocSinks [maxExpectedTextSinks]Text
- sinks := noAllocSinks[:0]
- for _, s := range textSinks {
- if s.Enabled(m) {
- sinks = append(sinks, s)
- }
- }
- if len(sinks) == 0 && m.Severity != Fatal {
- return 0, nil // No TextSinks specified; don't bother formatting.
- }
- bufi := bufs.Get()
- var buf *bytes.Buffer
- if bufi == nil {
- buf = bytes.NewBuffer(nil)
- bufi = buf
- } else {
- buf = bufi.(*bytes.Buffer)
- buf.Reset()
- }
- // Lmmdd hh:mm:ss.uuuuuu PID/GID file:line]
- //
- // The "PID" entry arguably ought to be TID for consistency with other
- // environments, but TID is not meaningful in a Go program due to the
- // multiplexing of goroutines across threads.
- //
- // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand.
- // It's worth about 3X. Fprintf is hard.
- const severityChar = "IWEF"
- buf.WriteByte(severityChar[m.Severity])
- _, month, day := m.Time.Date()
- hour, minute, second := m.Time.Clock()
- twoDigits(buf, int(month))
- twoDigits(buf, day)
- buf.WriteByte(' ')
- twoDigits(buf, hour)
- buf.WriteByte(':')
- twoDigits(buf, minute)
- buf.WriteByte(':')
- twoDigits(buf, second)
- buf.WriteByte('.')
- nDigits(buf, 6, uint64(m.Time.Nanosecond()/1000), '0')
- buf.WriteByte(' ')
- nDigits(buf, 7, uint64(m.Thread), ' ')
- buf.WriteByte(' ')
- {
- file := m.File
- if i := strings.LastIndex(file, "/"); i >= 0 {
- file = file[i+1:]
- }
- buf.WriteString(file)
- }
- buf.WriteByte(':')
- {
- var tmp [19]byte
- buf.Write(strconv.AppendInt(tmp[:0], int64(m.Line), 10))
- }
- buf.WriteString("] ")
- msgStart := buf.Len()
- fmt.Fprintf(buf, format, args...)
- if buf.Len() > MaxLogMessageLen-1 {
- buf.Truncate(MaxLogMessageLen - 1)
- }
- msgEnd := buf.Len()
- if b := buf.Bytes(); b[len(b)-1] != '\n' {
- buf.WriteByte('\n')
- }
- for _, s := range sinks {
- sn, sErr := s.Emit(m, buf.Bytes())
- if sn > n {
- n = sn
- }
- if sErr != nil && err == nil {
- err = sErr
- }
- }
- if m.Severity == Fatal {
- savedM := *m
- fatalMessageStore(savedEntry{
- meta: &savedM,
- msg: buf.Bytes()[msgStart:msgEnd],
- })
- } else {
- bufs.Put(bufi)
- }
- return n, err
- }
- const digits = "0123456789"
- // twoDigits formats a zero-prefixed two-digit integer to buf.
- func twoDigits(buf *bytes.Buffer, d int) {
- buf.WriteByte(digits[(d/10)%10])
- buf.WriteByte(digits[d%10])
- }
- // nDigits formats an n-digit integer to buf, padding with pad on the left. It
- // assumes d != 0.
- func nDigits(buf *bytes.Buffer, n int, d uint64, pad byte) {
- var tmp [20]byte
- cutoff := len(tmp) - n
- j := len(tmp) - 1
- for ; d > 0; j-- {
- tmp[j] = digits[d%10]
- d /= 10
- }
- for ; j >= cutoff; j-- {
- tmp[j] = pad
- }
- j++
- buf.Write(tmp[j:])
- }
- // Printf writes a log entry to all registered TextSinks in this package, then
- // to all registered StructuredSinks.
- //
- // The returned n is the maximum across all Emit and Printf calls.
- // The returned err is the first non-nil error encountered.
- // Sinks that are disabled by configuration should return (0, nil).
- func Printf(m *Meta, format string, args ...any) (n int, err error) {
- m.Depth++
- n, err = textPrintf(m, TextSinks, format, args...)
- for _, sink := range StructuredSinks {
- // TODO: Support TextSinks that implement StackWanter?
- if sw, ok := sink.(StackWanter); ok && sw.WantStack(m) {
- if m.Stack == nil {
- // First, try to find a stacktrace in args, otherwise generate one.
- for _, arg := range args {
- if stack, ok := arg.(stackdump.Stack); ok {
- m.Stack = &stack
- break
- }
- }
- if m.Stack == nil {
- stack := stackdump.Caller( /* skipDepth = */ m.Depth)
- m.Stack = &stack
- }
- }
- }
- sn, sErr := sink.Printf(m, format, args...)
- if sn > n {
- n = sn
- }
- if sErr != nil && err == nil {
- err = sErr
- }
- }
- return n, err
- }
- // The sets of sinks to which logs should be written.
- //
- // These must only be modified during package init, and are read-only thereafter.
- var (
- // StructuredSinks is the set of Structured sink instances to which logs
- // should be written.
- StructuredSinks []Structured
- // TextSinks is the set of Text sink instances to which logs should be
- // written.
- //
- // These are registered separately from Structured sink implementations to
- // avoid the need to repeat the work of formatting a message for each Text
- // sink that writes it. The package-level Printf function writes to both sets
- // independenty, so a given log destination should only register a Structured
- // *or* a Text sink (not both).
- TextSinks []Text
- )
- type savedEntry struct {
- meta *Meta
- msg []byte
- }
- // StructuredTextWrapper is a Structured sink which forwards logs to a set of Text sinks.
- //
- // The purpose of this sink is to allow applications to intercept logging calls before they are
- // serialized and sent to Text sinks. For example, if one needs to redact PII from logging
- // arguments before they reach STDERR, one solution would be to do the redacting in a Structured
- // sink that forwards logs to a StructuredTextWrapper instance, and make STDERR a child of that
- // StructuredTextWrapper instance. This is how one could set this up in their application:
- //
- // func init() {
- //
- // wrapper := logsink.StructuredTextWrapper{TextSinks: logsink.TextSinks}
- // // sanitizersink will intercept logs and remove PII
- // sanitizer := sanitizersink{Sink: &wrapper}
- // logsink.StructuredSinks = append(logsink.StructuredSinks, &sanitizer)
- // logsink.TextSinks = nil
- //
- // }
- type StructuredTextWrapper struct {
- // TextSinks is the set of Text sinks that should receive logs from this
- // StructuredTextWrapper instance.
- TextSinks []Text
- }
- // Printf forwards logs to all Text sinks registered in the StructuredTextWrapper.
- func (w *StructuredTextWrapper) Printf(meta *Meta, format string, args ...any) (n int, err error) {
- return textPrintf(meta, w.TextSinks, format, args...)
- }
|