syslog.mx raw

   1  // Copyright 2009 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  //go:build !windows && !plan9
   6  
   7  package syslog
   8  
   9  import (
  10  	"errors"
  11  	"fmt"
  12  	"log"
  13  	"net"
  14  	"os"
  15  	"bytes"
  16  	"sync"
  17  	"time"
  18  )
  19  
  20  // The Priority is a combination of the syslog facility and
  21  // severity. For example, [LOG_ALERT] | [LOG_FTP] sends an alert severity
  22  // message from the FTP facility. The default severity is [LOG_EMERG];
  23  // the default facility is [LOG_KERN].
  24  type Priority int
  25  
  26  const severityMask = 0x07
  27  const facilityMask = 0xf8
  28  
  29  const (
  30  	// Severity.
  31  
  32  	// From /usr/include/sys/syslog.h.
  33  	// These are the same on Linux, BSD, and OS X.
  34  	LOG_EMERG Priority = iota
  35  	LOG_ALERT
  36  	LOG_CRIT
  37  	LOG_ERR
  38  	LOG_WARNING
  39  	LOG_NOTICE
  40  	LOG_INFO
  41  	LOG_DEBUG
  42  )
  43  
  44  const (
  45  	// Facility.
  46  
  47  	// From /usr/include/sys/syslog.h.
  48  	// These are the same up to LOG_FTP on Linux, BSD, and OS X.
  49  	LOG_KERN Priority = iota << 3
  50  	LOG_USER
  51  	LOG_MAIL
  52  	LOG_DAEMON
  53  	LOG_AUTH
  54  	LOG_SYSLOG
  55  	LOG_LPR
  56  	LOG_NEWS
  57  	LOG_UUCP
  58  	LOG_CRON
  59  	LOG_AUTHPRIV
  60  	LOG_FTP
  61  	_ // unused
  62  	_ // unused
  63  	_ // unused
  64  	_ // unused
  65  	LOG_LOCAL0
  66  	LOG_LOCAL1
  67  	LOG_LOCAL2
  68  	LOG_LOCAL3
  69  	LOG_LOCAL4
  70  	LOG_LOCAL5
  71  	LOG_LOCAL6
  72  	LOG_LOCAL7
  73  )
  74  
  75  // A Writer is a connection to a syslog server.
  76  type Writer struct {
  77  	priority Priority
  78  	tag      []byte
  79  	hostname []byte
  80  	network  []byte
  81  	raddr    []byte
  82  
  83  	mu   sync.Mutex // guards conn
  84  	conn serverConn
  85  }
  86  
  87  // This interface and the separate syslog_unix.go file exist for
  88  // Solaris support as implemented by gccgo. On Solaris you cannot
  89  // simply open a TCP connection to the syslog daemon. The gccgo
  90  // sources have a syslog_solaris.go file that implements unixSyslog to
  91  // return a type that satisfies this interface and simply calls the C
  92  // library syslog function.
  93  type serverConn interface {
  94  	writeString(p Priority, hostname, tag, s, nl string) error
  95  	close() error
  96  }
  97  
  98  type netConn struct {
  99  	local bool
 100  	conn  net.Conn
 101  }
 102  
 103  // New establishes a new connection to the system log daemon. Each
 104  // write to the returned writer sends a log message with the given
 105  // priority (a combination of the syslog facility and severity) and
 106  // prefix tag. If tag is empty, the [os.Args][0] is used.
 107  func New(priority Priority, tag []byte) (*Writer, error) {
 108  	return Dial("", "", priority, tag)
 109  }
 110  
 111  // Dial establishes a connection to a log daemon by connecting to
 112  // address raddr on the specified network. Each write to the returned
 113  // writer sends a log message with the facility and severity
 114  // (from priority) and tag. If tag is empty, the [os.Args][0] is used.
 115  // If network is empty, Dial will connect to the local syslog server.
 116  // Otherwise, see the documentation for net.Dial for valid values
 117  // of network and raddr.
 118  func Dial(network, raddr []byte, priority Priority, tag []byte) (*Writer, error) {
 119  	if priority < 0 || priority > LOG_LOCAL7|LOG_DEBUG {
 120  		return nil, errors.New("log/syslog: invalid priority")
 121  	}
 122  
 123  	if tag == "" {
 124  		tag = os.Args[0]
 125  	}
 126  	hostname, _ := os.Hostname()
 127  
 128  	w := &Writer{
 129  		priority: priority,
 130  		tag:      tag,
 131  		hostname: hostname,
 132  		network:  network,
 133  		raddr:    raddr,
 134  	}
 135  
 136  	w.mu.Lock()
 137  	defer w.mu.Unlock()
 138  
 139  	err := w.connect()
 140  	if err != nil {
 141  		return nil, err
 142  	}
 143  	return w, nil
 144  }
 145  
 146  // connect makes a connection to the syslog server.
 147  // It must be called with w.mu held.
 148  func (w *Writer) connect() (err error) {
 149  	if w.conn != nil {
 150  		// ignore err from close, it makes sense to continue anyway
 151  		w.conn.close()
 152  		w.conn = nil
 153  	}
 154  
 155  	if w.network == "" {
 156  		w.conn, err = unixSyslog()
 157  		if w.hostname == "" {
 158  			w.hostname = "localhost"
 159  		}
 160  	} else {
 161  		var c net.Conn
 162  		c, err = net.Dial(w.network, w.raddr)
 163  		if err == nil {
 164  			w.conn = &netConn{
 165  				conn:  c,
 166  				local: w.network == "unixgram" || w.network == "unix",
 167  			}
 168  			if w.hostname == "" {
 169  				w.hostname = c.LocalAddr().String()
 170  			}
 171  		}
 172  	}
 173  	return
 174  }
 175  
 176  // Write sends a log message to the syslog daemon.
 177  func (w *Writer) Write(b []byte) (int, error) {
 178  	return w.writeAndRetry(w.priority, []byte(b))
 179  }
 180  
 181  // Close closes a connection to the syslog daemon.
 182  func (w *Writer) Close() error {
 183  	w.mu.Lock()
 184  	defer w.mu.Unlock()
 185  
 186  	if w.conn != nil {
 187  		err := w.conn.close()
 188  		w.conn = nil
 189  		return err
 190  	}
 191  	return nil
 192  }
 193  
 194  // Emerg logs a message with severity [LOG_EMERG], ignoring the severity
 195  // passed to New.
 196  func (w *Writer) Emerg(m []byte) error {
 197  	_, err := w.writeAndRetry(LOG_EMERG, m)
 198  	return err
 199  }
 200  
 201  // Alert logs a message with severity [LOG_ALERT], ignoring the severity
 202  // passed to New.
 203  func (w *Writer) Alert(m []byte) error {
 204  	_, err := w.writeAndRetry(LOG_ALERT, m)
 205  	return err
 206  }
 207  
 208  // Crit logs a message with severity [LOG_CRIT], ignoring the severity
 209  // passed to New.
 210  func (w *Writer) Crit(m []byte) error {
 211  	_, err := w.writeAndRetry(LOG_CRIT, m)
 212  	return err
 213  }
 214  
 215  // Err logs a message with severity [LOG_ERR], ignoring the severity
 216  // passed to New.
 217  func (w *Writer) Err(m []byte) error {
 218  	_, err := w.writeAndRetry(LOG_ERR, m)
 219  	return err
 220  }
 221  
 222  // Warning logs a message with severity [LOG_WARNING], ignoring the
 223  // severity passed to New.
 224  func (w *Writer) Warning(m []byte) error {
 225  	_, err := w.writeAndRetry(LOG_WARNING, m)
 226  	return err
 227  }
 228  
 229  // Notice logs a message with severity [LOG_NOTICE], ignoring the
 230  // severity passed to New.
 231  func (w *Writer) Notice(m []byte) error {
 232  	_, err := w.writeAndRetry(LOG_NOTICE, m)
 233  	return err
 234  }
 235  
 236  // Info logs a message with severity [LOG_INFO], ignoring the severity
 237  // passed to New.
 238  func (w *Writer) Info(m []byte) error {
 239  	_, err := w.writeAndRetry(LOG_INFO, m)
 240  	return err
 241  }
 242  
 243  // Debug logs a message with severity [LOG_DEBUG], ignoring the severity
 244  // passed to New.
 245  func (w *Writer) Debug(m []byte) error {
 246  	_, err := w.writeAndRetry(LOG_DEBUG, m)
 247  	return err
 248  }
 249  
 250  func (w *Writer) writeAndRetry(p Priority, s []byte) (int, error) {
 251  	pr := (w.priority & facilityMask) | (p & severityMask)
 252  
 253  	w.mu.Lock()
 254  	defer w.mu.Unlock()
 255  
 256  	if w.conn != nil {
 257  		if n, err := w.write(pr, s); err == nil {
 258  			return n, nil
 259  		}
 260  	}
 261  	if err := w.connect(); err != nil {
 262  		return 0, err
 263  	}
 264  	return w.write(pr, s)
 265  }
 266  
 267  // write generates and writes a syslog formatted string. The
 268  // format is as follows: <PRI>TIMESTAMP HOSTNAME TAG[PID]: MSG
 269  func (w *Writer) write(p Priority, msg []byte) (int, error) {
 270  	// ensure it ends in a \n
 271  	nl := ""
 272  	if !bytes.HasSuffix(msg, "\n") {
 273  		nl = "\n"
 274  	}
 275  
 276  	err := w.conn.writeString(p, w.hostname, w.tag, msg, nl)
 277  	if err != nil {
 278  		return 0, err
 279  	}
 280  	// Note: return the length of the input, not the number of
 281  	// bytes printed by Fprintf, because this must behave like
 282  	// an io.Writer.
 283  	return len(msg), nil
 284  }
 285  
 286  func (n *netConn) writeString(p Priority, hostname, tag, msg, nl []byte) error {
 287  	if n.local {
 288  		// Compared to the network form below, the changes are:
 289  		//	1. Use time.Stamp instead of time.RFC3339.
 290  		//	2. Drop the hostname field from the Fprintf.
 291  		timestamp := time.Now().Format(time.Stamp)
 292  		_, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s%s",
 293  			p, timestamp,
 294  			tag, os.Getpid(), msg, nl)
 295  		return err
 296  	}
 297  	timestamp := time.Now().Format(time.RFC3339)
 298  	_, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s",
 299  		p, timestamp, hostname,
 300  		tag, os.Getpid(), msg, nl)
 301  	return err
 302  }
 303  
 304  func (n *netConn) close() error {
 305  	return n.conn.Close()
 306  }
 307  
 308  // NewLogger creates a [log.Logger] whose output is written to the
 309  // system log service with the specified priority, a combination of
 310  // the syslog facility and severity. The logFlag argument is the flag
 311  // set passed through to [log.New] to create the Logger.
 312  func NewLogger(p Priority, logFlag int) (*log.Logger, error) {
 313  	s, err := New(p, "")
 314  	if err != nil {
 315  		return nil, err
 316  	}
 317  	return log.New(s, "", logFlag), nil
 318  }
 319