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
7package syslog
8
9import (
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].
24type Priority int
25
26const severityMask = 0x07
27const facilityMask = 0xf8
28
29const (
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
44const (
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.
76type 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.
93type serverConn interface {
94	writeString(p Priority, hostname, tag, s, nl string) error
95	close() error
96}
97
98type 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.
107func 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.
118func 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.
148func (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.
177func (w *Writer) Write(b []byte) (int, error) {
178	return w.writeAndRetry(w.priority, string(b))
179}
180
181// Close closes a connection to the syslog daemon.
182func (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.
196func (w *Writer) Emerg(m string) 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.
203func (w *Writer) Alert(m string) 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.
210func (w *Writer) Crit(m string) 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.
217func (w *Writer) Err(m string) 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.
224func (w *Writer) Warning(m string) 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.
231func (w *Writer) Notice(m string) 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.
238func (w *Writer) Info(m string) 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.
245func (w *Writer) Debug(m string) error {
246	_, err := w.writeAndRetry(LOG_DEBUG, m)
247	return err
248}
249
250func (w *Writer) writeAndRetry(p Priority, s string) (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
269func (w *Writer) write(p Priority, msg string) (int, error) {
270	// ensure it ends in a \n
271	nl := ""
272	if !strings.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
286func (n *netConn) writeString(p Priority, hostname, tag, msg, nl string) 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
304func (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.
312func 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