github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/syslogger/worker.go (about)

     1  // Copyright 2022 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package syslogger
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names/v5"
    14  	"github.com/juju/worker/v3"
    15  	"github.com/juju/worker/v3/catacomb"
    16  
    17  	corelogger "github.com/juju/juju/core/logger"
    18  )
    19  
    20  // NewLogger is a factory function to create a new syslog logger.
    21  type NewLogger func(Priority, string) (io.WriteCloser, error)
    22  
    23  // WorkerConfig encapsulates the configuration options for the
    24  // dbaccessor worker.
    25  type WorkerConfig struct {
    26  	NewLogger NewLogger
    27  }
    28  
    29  // Validate ensures that the config values are valid.
    30  func (c *WorkerConfig) Validate() error {
    31  	if c.NewLogger == nil {
    32  		return errors.NotValidf("nil NewLogger")
    33  	}
    34  	return nil
    35  }
    36  
    37  type Priority int
    38  
    39  const (
    40  	// Severity.
    41  
    42  	// From /usr/include/sys/syslog.h.
    43  	// These are the same on Linux, BSD, and OS X.
    44  	LOG_EMERG Priority = iota
    45  	LOG_ALERT
    46  	LOG_CRIT
    47  	LOG_ERR
    48  	LOG_WARNING
    49  	LOG_NOTICE
    50  	LOG_INFO
    51  	LOG_DEBUG
    52  )
    53  
    54  // SysLogger defines an interface for logging log records.
    55  type SysLogger interface {
    56  	Log([]corelogger.LogRecord) error
    57  }
    58  
    59  type syslogWorker struct {
    60  	cfg      WorkerConfig
    61  	catacomb catacomb.Catacomb
    62  
    63  	writers map[loggo.Level]io.WriteCloser
    64  }
    65  
    66  var syslogLoggoLevels = map[loggo.Level]Priority{
    67  	loggo.CRITICAL:    LOG_CRIT,
    68  	loggo.ERROR:       LOG_ERR,
    69  	loggo.WARNING:     LOG_WARNING,
    70  	loggo.INFO:        LOG_INFO,
    71  	loggo.DEBUG:       LOG_DEBUG,
    72  	loggo.TRACE:       LOG_DEBUG, // syslog has not trace level.
    73  	loggo.UNSPECIFIED: LOG_DEBUG,
    74  }
    75  
    76  func NewWorker(cfg WorkerConfig) (worker.Worker, error) {
    77  	var err error
    78  	if err = cfg.Validate(); err != nil {
    79  		return nil, errors.Trace(err)
    80  	}
    81  
    82  	// Create a writer for every log level, so we can stream line the logging
    83  	// process.
    84  	writers := make(map[loggo.Level]io.WriteCloser)
    85  	for level, priority := range syslogLoggoLevels {
    86  		writer, err := cfg.NewLogger(priority, "juju.daemon")
    87  		if err != nil {
    88  			return nil, errors.Trace(err)
    89  		}
    90  		writers[level] = writer
    91  	}
    92  
    93  	w := &syslogWorker{
    94  		cfg:     cfg,
    95  		writers: writers,
    96  	}
    97  
    98  	if err = catacomb.Invoke(catacomb.Plan{
    99  		Site: &w.catacomb,
   100  		Work: func() error {
   101  			<-w.catacomb.Dying()
   102  			w.close()
   103  			return nil
   104  		},
   105  	}); err != nil {
   106  		return nil, errors.Trace(err)
   107  	}
   108  
   109  	return w, nil
   110  }
   111  
   112  // Kill is part of the worker.Worker interface.
   113  func (w *syslogWorker) Kill() {
   114  	w.catacomb.Kill(nil)
   115  }
   116  
   117  // Wait is part of the worker.Worker interface.
   118  func (w *syslogWorker) Wait() error {
   119  	return w.catacomb.Wait()
   120  }
   121  
   122  func (w *syslogWorker) Log(logs []corelogger.LogRecord) error {
   123  	// Prevent logging out if the worker has already been killed.
   124  	select {
   125  	case <-w.catacomb.Dead():
   126  		return w.catacomb.Err()
   127  	default:
   128  	}
   129  
   130  	for _, log := range logs {
   131  		writer, ok := w.writers[log.Level]
   132  		if !ok {
   133  			continue
   134  		}
   135  		module := log.Module
   136  		if names.IsValidModel(log.ModelUUID) {
   137  			module = fmt.Sprintf("%s.%s", log.Module, names.NewModelTag(log.ModelUUID).ShortId())
   138  		}
   139  		dateTime := log.Time.In(time.UTC).Format("2006-01-02 15:04:05")
   140  		_, _ = fmt.Fprintf(writer, "%s %s %s %s\n", dateTime, log.Entity, module, log.Message)
   141  	}
   142  	return nil
   143  }
   144  
   145  func (w *syslogWorker) close() {
   146  	for _, writer := range w.writers {
   147  		_ = writer.Close()
   148  	}
   149  }