github.com/letsencrypt/boulder@v0.20251208.0/log/log.go (about)

     1  package log
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/binary"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"hash/crc32"
    10  	"io"
    11  	"log/syslog"
    12  	"os"
    13  	"strings"
    14  	"sync"
    15  
    16  	"github.com/jmhodges/clock"
    17  	"golang.org/x/term"
    18  
    19  	"github.com/letsencrypt/boulder/core"
    20  )
    21  
    22  // A Logger logs messages with explicit priority levels. It is
    23  // implemented by a logging back-end as provided by New() or
    24  // NewMock(). Any additions to this interface with format strings should be
    25  // added to the govet configuration in .golangci.yml
    26  type Logger interface {
    27  	Err(msg string)
    28  	Errf(format string, a ...any)
    29  	Warning(msg string)
    30  	Warningf(format string, a ...any)
    31  	Info(msg string)
    32  	Infof(format string, a ...any)
    33  	InfoObject(string, any)
    34  	Debug(msg string)
    35  	Debugf(format string, a ...any)
    36  	AuditInfo(msg string)
    37  	AuditInfof(format string, a ...any)
    38  	AuditObject(string, any)
    39  	AuditErr(string)
    40  	AuditErrf(format string, a ...any)
    41  }
    42  
    43  // impl implements Logger.
    44  type impl struct {
    45  	w writer
    46  }
    47  
    48  // singleton defines the object of a Singleton pattern
    49  type singleton struct {
    50  	once sync.Once
    51  	log  Logger
    52  }
    53  
    54  // _Singleton is the single impl entity in memory
    55  var _Singleton singleton
    56  
    57  // The constant used to identify audit-specific messages
    58  const auditTag = "[AUDIT]"
    59  
    60  // New returns a new Logger that uses the given syslog.Writer as a backend
    61  // and also writes to stdout/stderr. It is safe for concurrent use.
    62  func New(log *syslog.Writer, stdoutLogLevel int, syslogLogLevel int) (Logger, error) {
    63  	if log == nil {
    64  		return nil, errors.New("Attempted to use a nil System Logger")
    65  	}
    66  	return &impl{
    67  		&bothWriter{
    68  			sync.Mutex{},
    69  			log,
    70  			newStdoutWriter(stdoutLogLevel),
    71  			syslogLogLevel,
    72  		},
    73  	}, nil
    74  }
    75  
    76  // StdoutLogger returns a Logger that writes solely to stdout and stderr.
    77  // It is safe for concurrent use.
    78  func StdoutLogger(level int) Logger {
    79  	return &impl{newStdoutWriter(level)}
    80  }
    81  
    82  func newStdoutWriter(level int) *stdoutWriter {
    83  	prefix, clkFormat := getPrefix()
    84  	return &stdoutWriter{
    85  		prefix:    prefix,
    86  		level:     level,
    87  		clkFormat: clkFormat,
    88  		clk:       clock.New(),
    89  		stdout:    os.Stdout,
    90  		stderr:    os.Stderr,
    91  		isatty:    term.IsTerminal(int(os.Stdout.Fd())),
    92  	}
    93  }
    94  
    95  // initialize is used in unit tests and called by `Get` before the logger
    96  // is fully set up.
    97  func initialize() {
    98  	const defaultPriority = syslog.LOG_INFO | syslog.LOG_LOCAL0
    99  	syslogger, err := syslog.Dial("", "", defaultPriority, "test")
   100  	if err != nil {
   101  		panic(err)
   102  	}
   103  	logger, err := New(syslogger, int(syslog.LOG_DEBUG), int(syslog.LOG_DEBUG))
   104  	if err != nil {
   105  		panic(err)
   106  	}
   107  
   108  	_ = Set(logger)
   109  }
   110  
   111  // Set configures the singleton Logger. This method
   112  // must only be called once, and before calling Get the
   113  // first time.
   114  func Set(logger Logger) (err error) {
   115  	if _Singleton.log != nil {
   116  		err = errors.New("You may not call Set after it has already been implicitly or explicitly set")
   117  		_Singleton.log.Warning(err.Error())
   118  	} else {
   119  		_Singleton.log = logger
   120  	}
   121  	return
   122  }
   123  
   124  // Get obtains the singleton Logger. If Set has not been called first, this
   125  // method initializes with basic defaults.  The basic defaults cannot error, and
   126  // subsequent access to an already-set Logger also cannot error, so this method is
   127  // error-safe.
   128  func Get() Logger {
   129  	_Singleton.once.Do(func() {
   130  		if _Singleton.log == nil {
   131  			initialize()
   132  		}
   133  	})
   134  
   135  	return _Singleton.log
   136  }
   137  
   138  type writer interface {
   139  	logAtLevel(syslog.Priority, string, ...any)
   140  }
   141  
   142  // bothWriter implements writer and writes to both syslog and stdout.
   143  type bothWriter struct {
   144  	sync.Mutex
   145  	*syslog.Writer
   146  	*stdoutWriter
   147  	syslogLevel int
   148  }
   149  
   150  // stdoutWriter implements writer and writes just to stdout.
   151  type stdoutWriter struct {
   152  	// prefix is a set of information that is the same for every log line,
   153  	// imitating what syslog emits for us when we use the syslog writer.
   154  	prefix    string
   155  	level     int
   156  	clkFormat string
   157  	clk       clock.Clock
   158  	stdout    io.Writer
   159  	stderr    io.Writer
   160  	isatty    bool
   161  }
   162  
   163  // LogLineChecksum computes a CRC32 over the log line, which can be checked by
   164  // log-validator to ensure no unexpected log corruption has occurred.
   165  func LogLineChecksum(line string) string {
   166  	crc := crc32.ChecksumIEEE([]byte(line))
   167  	buf := make([]byte, crc32.Size)
   168  	// Error is unreachable because we provide a supported type and buffer size
   169  	_, _ = binary.Encode(buf, binary.LittleEndian, crc)
   170  	return base64.RawURLEncoding.EncodeToString(buf)
   171  }
   172  
   173  func checkSummed(msg string) string {
   174  	return fmt.Sprintf("%s %s", LogLineChecksum(msg), msg)
   175  }
   176  
   177  // logAtLevel logs the provided message at the appropriate level, writing to
   178  // both stdout and the Logger
   179  func (w *bothWriter) logAtLevel(level syslog.Priority, msg string, a ...any) {
   180  	var err error
   181  
   182  	// Apply conditional formatting for f functions
   183  	if a != nil {
   184  		msg = fmt.Sprintf(msg, a...)
   185  	}
   186  
   187  	// Since messages are delimited by newlines, we have to escape any internal or
   188  	// trailing newlines before generating the checksum or outputting the message.
   189  	msg = strings.Replace(msg, "\n", "\\n", -1)
   190  
   191  	w.Lock()
   192  	defer w.Unlock()
   193  
   194  	switch syslogAllowed := int(level) <= w.syslogLevel; level {
   195  	case syslog.LOG_ERR:
   196  		if syslogAllowed {
   197  			err = w.Err(checkSummed(msg))
   198  		}
   199  	case syslog.LOG_WARNING:
   200  		if syslogAllowed {
   201  			err = w.Warning(checkSummed(msg))
   202  		}
   203  	case syslog.LOG_INFO:
   204  		if syslogAllowed {
   205  			err = w.Info(checkSummed(msg))
   206  		}
   207  	case syslog.LOG_DEBUG:
   208  		if syslogAllowed {
   209  			err = w.Debug(checkSummed(msg))
   210  		}
   211  	default:
   212  		err = w.Err(fmt.Sprintf("%s (unknown logging level: %d)", checkSummed(msg), int(level)))
   213  	}
   214  
   215  	if err != nil {
   216  		fmt.Fprintf(os.Stderr, "Failed to write to syslog: %d %s (%s)\n", int(level), checkSummed(msg), err)
   217  	}
   218  
   219  	w.stdoutWriter.logAtLevel(level, msg)
   220  }
   221  
   222  // logAtLevel logs the provided message to stdout, or stderr if it is at Warning or Error level.
   223  func (w *stdoutWriter) logAtLevel(level syslog.Priority, msg string, a ...any) {
   224  	if int(level) <= w.level {
   225  		output := w.stdout
   226  		if int(level) <= int(syslog.LOG_WARNING) {
   227  			output = w.stderr
   228  		}
   229  
   230  		// Apply conditional formatting for f functions
   231  		if a != nil {
   232  			msg = fmt.Sprintf(msg, a...)
   233  		}
   234  
   235  		msg = strings.Replace(msg, "\n", "\\n", -1)
   236  
   237  		var color string
   238  		var reset string
   239  
   240  		const red = "\033[31m\033[1m"
   241  		const yellow = "\033[33m"
   242  		const gray = "\033[37m\033[2m"
   243  
   244  		if w.isatty {
   245  			if int(level) == int(syslog.LOG_DEBUG) {
   246  				color = gray
   247  				reset = "\033[0m"
   248  			} else if int(level) == int(syslog.LOG_WARNING) {
   249  				color = yellow
   250  				reset = "\033[0m"
   251  			} else if int(level) <= int(syslog.LOG_ERR) {
   252  				color = red
   253  				reset = "\033[0m"
   254  			}
   255  		}
   256  
   257  		if _, err := fmt.Fprintf(output, "%s%s %s%d %s %s%s\n",
   258  			color,
   259  			w.clk.Now().UTC().Format(w.clkFormat),
   260  			w.prefix,
   261  			int(level),
   262  			core.Command(),
   263  			checkSummed(msg),
   264  			reset); err != nil {
   265  			panic(fmt.Sprintf("failed to write to stdout: %v\n", err))
   266  		}
   267  	}
   268  }
   269  
   270  func (log *impl) auditAtLevel(level syslog.Priority, msg string, a ...any) {
   271  	msg = fmt.Sprintf("%s %s", auditTag, msg)
   272  	log.w.logAtLevel(level, msg, a...)
   273  }
   274  
   275  // Err level messages are always marked with the audit tag, for special handling
   276  // at the upstream system logger.
   277  func (log *impl) Err(msg string) {
   278  	log.Errf(msg)
   279  }
   280  
   281  // Errf level messages are always marked with the audit tag, for special handling
   282  // at the upstream system logger.
   283  func (log *impl) Errf(format string, a ...any) {
   284  	log.auditAtLevel(syslog.LOG_ERR, format, a...)
   285  }
   286  
   287  // Warning level messages pass through normally.
   288  func (log *impl) Warning(msg string) {
   289  	log.Warningf(msg)
   290  }
   291  
   292  // Warningf level messages pass through normally.
   293  func (log *impl) Warningf(format string, a ...any) {
   294  	log.w.logAtLevel(syslog.LOG_WARNING, format, a...)
   295  }
   296  
   297  // Info level messages pass through normally.
   298  func (log *impl) Info(msg string) {
   299  	log.Infof(msg)
   300  }
   301  
   302  // Infof level messages pass through normally.
   303  func (log *impl) Infof(format string, a ...any) {
   304  	log.w.logAtLevel(syslog.LOG_INFO, format, a...)
   305  }
   306  
   307  // InfoObject logs an INFO level JSON-serialized object message.
   308  func (log *impl) InfoObject(msg string, obj any) {
   309  	jsonObj, err := json.Marshal(obj)
   310  	if err != nil {
   311  		log.auditAtLevel(syslog.LOG_ERR, fmt.Sprintf("Object for msg %q could not be serialized to JSON. Raw: %+v", msg, obj))
   312  		return
   313  	}
   314  
   315  	log.Infof("%s JSON=%s", msg, jsonObj)
   316  }
   317  
   318  // Debug level messages pass through normally.
   319  func (log *impl) Debug(msg string) {
   320  	log.Debugf(msg)
   321  
   322  }
   323  
   324  // Debugf level messages pass through normally.
   325  func (log *impl) Debugf(format string, a ...any) {
   326  	log.w.logAtLevel(syslog.LOG_DEBUG, format, a...)
   327  }
   328  
   329  // AuditInfo sends an INFO-severity message that is prefixed with the
   330  // audit tag, for special handling at the upstream system logger.
   331  func (log *impl) AuditInfo(msg string) {
   332  	log.AuditInfof(msg)
   333  }
   334  
   335  // AuditInfof sends an INFO-severity message that is prefixed with the
   336  // audit tag, for special handling at the upstream system logger.
   337  func (log *impl) AuditInfof(format string, a ...any) {
   338  	log.auditAtLevel(syslog.LOG_INFO, format, a...)
   339  }
   340  
   341  // AuditObject sends an INFO-severity JSON-serialized object message that is prefixed
   342  // with the audit tag, for special handling at the upstream system logger.
   343  func (log *impl) AuditObject(msg string, obj any) {
   344  	jsonObj, err := json.Marshal(obj)
   345  	if err != nil {
   346  		log.auditAtLevel(syslog.LOG_ERR, fmt.Sprintf("Object for msg %q could not be serialized to JSON. Raw: %+v", msg, obj))
   347  		return
   348  	}
   349  
   350  	log.auditAtLevel(syslog.LOG_INFO, fmt.Sprintf("%s JSON=%s", msg, jsonObj))
   351  }
   352  
   353  // AuditErr can format an error for auditing; it does so at ERR level.
   354  func (log *impl) AuditErr(msg string) {
   355  	log.AuditErrf(msg)
   356  }
   357  
   358  // AuditErrf can format an error for auditing; it does so at ERR level.
   359  func (log *impl) AuditErrf(format string, a ...any) {
   360  	log.auditAtLevel(syslog.LOG_ERR, format, a...)
   361  }