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 }