215 lines
5.4 KiB
Go
215 lines
5.4 KiB
Go
|
// 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: <PRI>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")
|
||
|
}
|