mirror of
https://github.com/documize/community.git
synced 2025-07-20 21:59:42 +02:00
393 lines
12 KiB
Go
393 lines
12 KiB
Go
// 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...)
|
|
}
|