Home | History | Annotate | Download | only in syslog
      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 // +build !windows,!nacl,!plan9
      6 
      7 package syslog
      8 
      9 import (
     10 	"errors"
     11 	"fmt"
     12 	"log"
     13 	"net"
     14 	"os"
     15 	"strings"
     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      string
     79 	hostname string
     80 	network  string
     81 	raddr    string
     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 string) (*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 string, priority Priority, tag string) (*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, err
    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{conn: c}
    165 			if w.hostname == "" {
    166 				w.hostname = c.LocalAddr().String()
    167 			}
    168 		}
    169 	}
    170 	return
    171 }
    172 
    173 // Write sends a log message to the syslog daemon.
    174 func (w *Writer) Write(b []byte) (int, error) {
    175 	return w.writeAndRetry(w.priority, string(b))
    176 }
    177 
    178 // Close closes a connection to the syslog daemon.
    179 func (w *Writer) Close() error {
    180 	w.mu.Lock()
    181 	defer w.mu.Unlock()
    182 
    183 	if w.conn != nil {
    184 		err := w.conn.close()
    185 		w.conn = nil
    186 		return err
    187 	}
    188 	return nil
    189 }
    190 
    191 // Emerg logs a message with severity LOG_EMERG, ignoring the severity
    192 // passed to New.
    193 func (w *Writer) Emerg(m string) error {
    194 	_, err := w.writeAndRetry(LOG_EMERG, m)
    195 	return err
    196 }
    197 
    198 // Alert logs a message with severity LOG_ALERT, ignoring the severity
    199 // passed to New.
    200 func (w *Writer) Alert(m string) error {
    201 	_, err := w.writeAndRetry(LOG_ALERT, m)
    202 	return err
    203 }
    204 
    205 // Crit logs a message with severity LOG_CRIT, ignoring the severity
    206 // passed to New.
    207 func (w *Writer) Crit(m string) error {
    208 	_, err := w.writeAndRetry(LOG_CRIT, m)
    209 	return err
    210 }
    211 
    212 // Err logs a message with severity LOG_ERR, ignoring the severity
    213 // passed to New.
    214 func (w *Writer) Err(m string) error {
    215 	_, err := w.writeAndRetry(LOG_ERR, m)
    216 	return err
    217 }
    218 
    219 // Warning logs a message with severity LOG_WARNING, ignoring the
    220 // severity passed to New.
    221 func (w *Writer) Warning(m string) error {
    222 	_, err := w.writeAndRetry(LOG_WARNING, m)
    223 	return err
    224 }
    225 
    226 // Notice logs a message with severity LOG_NOTICE, ignoring the
    227 // severity passed to New.
    228 func (w *Writer) Notice(m string) error {
    229 	_, err := w.writeAndRetry(LOG_NOTICE, m)
    230 	return err
    231 }
    232 
    233 // Info logs a message with severity LOG_INFO, ignoring the severity
    234 // passed to New.
    235 func (w *Writer) Info(m string) error {
    236 	_, err := w.writeAndRetry(LOG_INFO, m)
    237 	return err
    238 }
    239 
    240 // Debug logs a message with severity LOG_DEBUG, ignoring the severity
    241 // passed to New.
    242 func (w *Writer) Debug(m string) error {
    243 	_, err := w.writeAndRetry(LOG_DEBUG, m)
    244 	return err
    245 }
    246 
    247 func (w *Writer) writeAndRetry(p Priority, s string) (int, error) {
    248 	pr := (w.priority & facilityMask) | (p & severityMask)
    249 
    250 	w.mu.Lock()
    251 	defer w.mu.Unlock()
    252 
    253 	if w.conn != nil {
    254 		if n, err := w.write(pr, s); err == nil {
    255 			return n, err
    256 		}
    257 	}
    258 	if err := w.connect(); err != nil {
    259 		return 0, err
    260 	}
    261 	return w.write(pr, s)
    262 }
    263 
    264 // write generates and writes a syslog formatted string. The
    265 // format is as follows: <PRI>TIMESTAMP HOSTNAME TAG[PID]: MSG
    266 func (w *Writer) write(p Priority, msg string) (int, error) {
    267 	// ensure it ends in a \n
    268 	nl := ""
    269 	if !strings.HasSuffix(msg, "\n") {
    270 		nl = "\n"
    271 	}
    272 
    273 	err := w.conn.writeString(p, w.hostname, w.tag, msg, nl)
    274 	if err != nil {
    275 		return 0, err
    276 	}
    277 	// Note: return the length of the input, not the number of
    278 	// bytes printed by Fprintf, because this must behave like
    279 	// an io.Writer.
    280 	return len(msg), nil
    281 }
    282 
    283 func (n *netConn) writeString(p Priority, hostname, tag, msg, nl string) error {
    284 	if n.local {
    285 		// Compared to the network form below, the changes are:
    286 		//	1. Use time.Stamp instead of time.RFC3339.
    287 		//	2. Drop the hostname field from the Fprintf.
    288 		timestamp := time.Now().Format(time.Stamp)
    289 		_, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s%s",
    290 			p, timestamp,
    291 			tag, os.Getpid(), msg, nl)
    292 		return err
    293 	}
    294 	timestamp := time.Now().Format(time.RFC3339)
    295 	_, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s",
    296 		p, timestamp, hostname,
    297 		tag, os.Getpid(), msg, nl)
    298 	return err
    299 }
    300 
    301 func (n *netConn) close() error {
    302 	return n.conn.Close()
    303 }
    304 
    305 // NewLogger creates a log.Logger whose output is written to the
    306 // system log service with the specified priority, a combination of
    307 // the syslog facility and severity. The logFlag argument is the flag
    308 // set passed through to log.New to create the Logger.
    309 func NewLogger(p Priority, logFlag int) (*log.Logger, error) {
    310 	s, err := New(p, "")
    311 	if err != nil {
    312 		return nil, err
    313 	}
    314 	return log.New(s, "", logFlag), nil
    315 }
    316