1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
package logging
import (
"context"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"sync"
"time"
"go.sudomsg.com/kit/net"
)
type Facility int
const (
FacilityKern Facility = 0 // kernel messages
FacilityUser Facility = 1 // user-level messages
FacilityMail Facility = 2 // mail system
FacilityDaemon Facility = 3 // system daemons
FacilityAuth Facility = 4 // security/authorization messages
FacilitySyslog Facility = 5 // messages generated internally by syslogd
FacilityLPR Facility = 6 // line printer subsystem
FacilityNews Facility = 7 // network news subsystem
FacilityUUCP Facility = 8 // UUCP subsystem
FacilityCron Facility = 9 // clock daemon
FacilityAuthPriv Facility = 10 // security/authorization messages (private)
FacilityFTP Facility = 11 // FTP daemon
FacilityLocal0 Facility = 16
FacilityLocal7 Facility = 23
)
type SyslogSink struct {
Writer io.Writer
Facility Facility
Tag string
Hostname string
Level slog.Level
Encoder Encoder
EmitHostname bool
ReplaceAttr func(groups []string, a slog.Attr) slog.Attr
mu sync.Mutex
}
var _ Sink = &SyslogSink{}
type SyslogOptions struct {
Facility Facility
Tag string
Hostname string
Level slog.Leveler
ReplaceAttr func(groups []string, a slog.Attr) slog.Attr
}
// DialSyslog creates a Sink connected to a local or remote syslog daemon.
// raddr: "/dev/log", "localhost:514", etc.
func DialSyslog(ctx context.Context, network net.NetType, raddr string, enc Encoder, options SyslogOptions) (*SyslogSink, error) {
if raddr == "" {
network, raddr = resolveLocalEndpoint()
}
conn, err := net.Dial(ctx, network, raddr)
if err != nil {
return nil, fmt.Errorf("syslog dial failed: %w", err)
}
isLocal := (network == net.NetUnixDatagram || network == net.NetUnix)
sink := NewSyslogSink(conn, enc, options)
sink.EmitHostname = !isLocal
return sink, nil
}
func NewSyslogSink(w io.Writer, enc Encoder, options SyslogOptions) *SyslogSink {
if options.Hostname == "" {
options.Hostname, _ = os.Hostname()
}
if options.Hostname == "" {
options.Hostname = "localhost"
}
if options.Tag == "" {
options.Tag = filepath.Base(os.Args[0])
}
if options.Tag == "" {
options.Tag = "kit"
}
if len(options.Tag) > 32 {
options.Tag = options.Tag[:32]
}
return &SyslogSink{
Writer: w,
Encoder: enc,
Tag: options.Tag,
Facility: options.Facility,
Hostname: options.Hostname,
ReplaceAttr: options.ReplaceAttr,
}
}
func (s *SyslogSink) Enabled(level slog.Level) bool {
return s.Level <= level
}
func (s *SyslogSink) Append(r slog.Record) error {
level := slog.Leveler(s.Level)
var ts time.Time
it := RecordAll(r, func(groups []string, attr slog.Attr) slog.Attr {
if s.ReplaceAttr != nil {
attr = s.ReplaceAttr(groups, attr)
}
if len(groups) == 0 {
if attr.Key == slog.TimeKey {
ts = attr.Value.Time()
return slog.Attr{}
}
if attr.Key == slog.LevelKey {
level = attr.Value.Any().(slog.Leveler)
return slog.Attr{}
}
}
return attr
})
out, err := s.Encoder(it)
if err != nil {
return fmt.Errorf("failed to encode: %w", err)
}
pri := int(s.Facility)*8 + mapLevelToSyslog(level.Level())
// Note: RFC 3164 uses a space for leading zeros in days (e.g., "Jan 2")
t := ""
if !ts.IsZero() {
t = r.Time.Format("Jan _2 15:04:05")
}
hostname := ""
if s.EmitHostname {
hostname = s.Hostname
}
packet := fmt.Sprintf("<%d>%s %s %s: %s", pri, t, hostname, s.Tag, out)
s.mu.Lock()
defer s.mu.Unlock()
_, err = fmt.Fprint(s.Writer, packet)
return err
}
func mapLevelToSyslog(l slog.Level) int {
switch {
case l >= slog.LevelError:
return 3
case l >= slog.LevelWarn:
return 4
case l >= slog.LevelInfo:
return 6
default:
return 7
}
}
|