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