// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !windows && !plan9 package syslog import ( "errors" "fmt" "log" "net" "os" "strings" "sync" "time" ) // The Priority is a combination of the syslog facility and // severity. For example, [LOG_ALERT] | [LOG_FTP] sends an alert severity // message from the FTP facility. The default severity is [LOG_EMERG]; // the default facility is [LOG_KERN]. type Priority int const severityMask = 0x07 const facilityMask = 0xf8 const ( // Severity. // From /usr/include/sys/syslog.h. // These are the same on Linux, BSD, and OS X. LOG_EMERG Priority = iota LOG_ALERT LOG_CRIT LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_DEBUG ) const ( // Facility. // From /usr/include/sys/syslog.h. // These are the same up to LOG_FTP on Linux, BSD, and OS X. LOG_KERN Priority = iota << 3 LOG_USER LOG_MAIL LOG_DAEMON LOG_AUTH LOG_SYSLOG LOG_LPR LOG_NEWS LOG_UUCP LOG_CRON LOG_AUTHPRIV LOG_FTP _ // unused _ // unused _ // unused _ // unused LOG_LOCAL0 LOG_LOCAL1 LOG_LOCAL2 LOG_LOCAL3 LOG_LOCAL4 LOG_LOCAL5 LOG_LOCAL6 LOG_LOCAL7 ) // A Writer is a connection to a syslog server. type Writer struct { priority Priority tag string hostname string network string raddr string mu sync.Mutex // guards conn conn serverConn } // This interface and the separate syslog_unix.go file exist for // Solaris support as implemented by gccgo. On Solaris you cannot // simply open a TCP connection to the syslog daemon. The gccgo // sources have a syslog_solaris.go file that implements unixSyslog to // return a type that satisfies this interface and simply calls the C // library syslog function. type serverConn interface { writeString(p Priority, hostname, tag, s, nl string) error close() error } type netConn struct { local bool conn net.Conn } // New establishes a new connection to the system log daemon. Each // write to the returned writer sends a log message with the given // priority (a combination of the syslog facility and severity) and // prefix tag. If tag is empty, the [os.Args][0] is used. func New(priority Priority, tag string) (*Writer, error) { return Dial("", "", priority, tag) } // Dial establishes a connection to a log daemon by connecting to // address raddr on the specified network. Each write to the returned // writer sends a log message with the facility and severity // (from priority) and tag. If tag is empty, the [os.Args][0] is used. // If network is empty, Dial will connect to the local syslog server. // Otherwise, see the documentation for net.Dial for valid values // of network and raddr. func Dial(network, raddr string, priority Priority, tag string) (*Writer, error) { if priority < 0 || priority > LOG_LOCAL7|LOG_DEBUG { return nil, errors.New("log/syslog: invalid priority") } if tag == "" { tag = os.Args[0] } hostname, _ := os.Hostname() w := &Writer{ priority: priority, tag: tag, hostname: hostname, network: network, raddr: raddr, } w.mu.Lock() defer w.mu.Unlock() err := w.connect() if err != nil { return nil, err } return w, err } // connect makes a connection to the syslog server. // It must be called with w.mu held. func (w *Writer) connect() (err error) { if w.conn != nil { // ignore err from close, it makes sense to continue anyway w.conn.close() w.conn = nil } if w.network == "" { w.conn, err = unixSyslog() if w.hostname == "" { w.hostname = "localhost" } } else { var c net.Conn c, err = net.Dial(w.network, w.raddr) if err == nil { w.conn = &netConn{ conn: c, local: w.network == "unixgram" || w.network == "unix", } if w.hostname == "" { w.hostname = c.LocalAddr().String() } } } return } // Write sends a log message to the syslog daemon. func (w *Writer) Write(b []byte) (int, error) { return w.writeAndRetry(w.priority, string(b)) } // Close closes a connection to the syslog daemon. func (w *Writer) Close() error { w.mu.Lock() defer w.mu.Unlock() if w.conn != nil { err := w.conn.close() w.conn = nil return err } return nil } // Emerg logs a message with severity [LOG_EMERG], ignoring the severity // passed to New. func (w *Writer) Emerg(m string) error { _, err := w.writeAndRetry(LOG_EMERG, m) return err } // Alert logs a message with severity [LOG_ALERT], ignoring the severity // passed to New. func (w *Writer) Alert(m string) error { _, err := w.writeAndRetry(LOG_ALERT, m) return err } // Crit logs a message with severity [LOG_CRIT], ignoring the severity // passed to New. func (w *Writer) Crit(m string) error { _, err := w.writeAndRetry(LOG_CRIT, m) return err } // Err logs a message with severity [LOG_ERR], ignoring the severity // passed to New. func (w *Writer) Err(m string) error { _, err := w.writeAndRetry(LOG_ERR, m) return err } // Warning logs a message with severity [LOG_WARNING], ignoring the // severity passed to New. func (w *Writer) Warning(m string) error { _, err := w.writeAndRetry(LOG_WARNING, m) return err } // Notice logs a message with severity [LOG_NOTICE], ignoring the // severity passed to New. func (w *Writer) Notice(m string) error { _, err := w.writeAndRetry(LOG_NOTICE, m) return err } // Info logs a message with severity [LOG_INFO], ignoring the severity // passed to New. func (w *Writer) Info(m string) error { _, err := w.writeAndRetry(LOG_INFO, m) return err } // Debug logs a message with severity [LOG_DEBUG], ignoring the severity // passed to New. func (w *Writer) Debug(m string) error { _, err := w.writeAndRetry(LOG_DEBUG, m) return err } func (w *Writer) writeAndRetry(p Priority, s string) (int, error) { pr := (w.priority & facilityMask) | (p & severityMask) w.mu.Lock() defer w.mu.Unlock() if w.conn != nil { if n, err := w.write(pr, s); err == nil { return n, nil } } if err := w.connect(); err != nil { return 0, err } return w.write(pr, s) } // write generates and writes a syslog formatted string. The // format is as follows: TIMESTAMP HOSTNAME TAG[PID]: MSG func (w *Writer) write(p Priority, msg string) (int, error) { // ensure it ends in a \n nl := "" if !strings.HasSuffix(msg, "\n") { nl = "\n" } err := w.conn.writeString(p, w.hostname, w.tag, msg, nl) if err != nil { return 0, err } // Note: return the length of the input, not the number of // bytes printed by Fprintf, because this must behave like // an io.Writer. return len(msg), nil } func (n *netConn) writeString(p Priority, hostname, tag, msg, nl string) error { if n.local { // Compared to the network form below, the changes are: // 1. Use time.Stamp instead of time.RFC3339. // 2. Drop the hostname field from the Fprintf. timestamp := time.Now().Format(time.Stamp) _, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s%s", p, timestamp, tag, os.Getpid(), msg, nl) return err } timestamp := time.Now().Format(time.RFC3339) _, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s", p, timestamp, hostname, tag, os.Getpid(), msg, nl) return err } func (n *netConn) close() error { return n.conn.Close() } // NewLogger creates a [log.Logger] whose output is written to the // system log service with the specified priority, a combination of // the syslog facility and severity. The logFlag argument is the flag // set passed through to [log.New] to create the Logger. func NewLogger(p Priority, logFlag int) (*log.Logger, error) { s, err := New(p, "") if err != nil { return nil, err } return log.New(s, "", logFlag), nil }