// This file is taken from the log/syslog in the standard lib. // However, there is a bug with overwhelming syslog that causes writes // to block indefinitely. This is fixed by adding a write deadline. // // +build !windows,!nacl,!plan9 package gsyslog import ( "errors" "fmt" "log/syslog" "net" "os" "strings" "sync" "time" ) const severityMask = 0x07 const facilityMask = 0xf8 const localDeadline = 20 * time.Millisecond const remoteDeadline = 50 * time.Millisecond // A builtinWriter is a connection to a syslog server. type builtinWriter struct { priority syslog.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 can not // 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 syslog.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 and prefix. func newBuiltin(priority syslog.Priority, tag string) (w *builtinWriter, err error) { return dialBuiltin("", "", 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 given facility, severity and // tag. // If network is empty, Dial will connect to the local syslog server. func dialBuiltin(network, raddr string, priority syslog.Priority, tag string) (*builtinWriter, error) { if priority < 0 || priority > syslog.LOG_LOCAL7|syslog.LOG_DEBUG { return nil, errors.New("log/syslog: invalid priority") } if tag == "" { tag = os.Args[0] } hostname, _ := os.Hostname() w := &builtinWriter{ 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 *builtinWriter) 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.DialTimeout(w.network, w.raddr, remoteDeadline) if err == nil { w.conn = &netConn{conn: c} if w.hostname == "" { w.hostname = c.LocalAddr().String() } } } return } // Write sends a log message to the syslog daemon. func (w *builtinWriter) Write(b []byte) (int, error) { return w.writeAndRetry(w.priority, string(b)) } // Close closes a connection to the syslog daemon. func (w *builtinWriter) Close() error { w.mu.Lock() defer w.mu.Unlock() if w.conn != nil { err := w.conn.close() w.conn = nil return err } return nil } func (w *builtinWriter) writeAndRetry(p syslog.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, err } } 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 *builtinWriter) write(p syslog.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 syslog.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) n.conn.SetWriteDeadline(time.Now().Add(localDeadline)) _, 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) n.conn.SetWriteDeadline(time.Now().Add(remoteDeadline)) _, 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() } // unixSyslog opens a connection to the syslog daemon running on the // local machine using a Unix domain socket. func unixSyslog() (conn serverConn, err error) { logTypes := []string{"unixgram", "unix"} logPaths := []string{"/dev/log", "/var/run/syslog"} for _, network := range logTypes { for _, path := range logPaths { conn, err := net.DialTimeout(network, path, localDeadline) if err != nil { continue } else { return &netConn{conn: conn, local: true}, nil } } } return nil, errors.New("Unix syslog delivery error") }