@@ -33,7 +33,10 @@ import (
33
33
"os"
34
34
"strconv"
35
35
"strings"
36
+ "sync"
37
+ "sync/atomic"
36
38
"syscall"
39
+ "unsafe"
37
40
)
38
41
39
42
// Priority of a journal message
@@ -50,19 +53,35 @@ const (
50
53
PriDebug
51
54
)
52
55
53
- var conn net.Conn
56
+ var (
57
+ // This can be overridden at build-time:
58
+ // https://github.com/golang/go/wiki/GcToolchainTricks#including-build-information-in-the-executable
59
+ journalSocket = "/run/systemd/journal/socket"
60
+
61
+ // unixConnPtr atomically holds the local unconnected Unix-domain socket.
62
+ // Concrete safe pointer type: *net.UnixConn
63
+ unixConnPtr unsafe.Pointer
64
+ // onceConn ensures that unixConnPtr is initialized exactly once.
65
+ onceConn sync.Once
66
+ )
54
67
55
68
func init () {
56
- var err error
57
- conn , err = net .Dial ("unixgram" , "/run/systemd/journal/socket" )
58
- if err != nil {
59
- conn = nil
60
- }
69
+ onceConn .Do (initConn )
61
70
}
62
71
63
- // Enabled returns true if the local systemd journal is available for logging
72
+ // Enabled checks whether the local systemd journal is available for logging.
64
73
func Enabled () bool {
65
- return conn != nil
74
+ onceConn .Do (initConn )
75
+
76
+ if (* net .UnixConn )(atomic .LoadPointer (& unixConnPtr )) == nil {
77
+ return false
78
+ }
79
+
80
+ if _ , err := net .Dial ("unixgram" , journalSocket ); err != nil {
81
+ return false
82
+ }
83
+
84
+ return true
66
85
}
67
86
68
87
// Send a message to the local systemd journal. vars is a map of journald
@@ -73,8 +92,14 @@ func Enabled() bool {
73
92
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
74
93
// for more details. vars may be nil.
75
94
func Send (message string , priority Priority , vars map [string ]string ) error {
95
+ conn := (* net .UnixConn )(atomic .LoadPointer (& unixConnPtr ))
76
96
if conn == nil {
77
- return journalError ("could not connect to journald socket" )
97
+ return errors .New ("could not initialize socket to journald" )
98
+ }
99
+
100
+ socketAddr := & net.UnixAddr {
101
+ Name : journalSocket ,
102
+ Net : "unixgram" ,
78
103
}
79
104
80
105
data := new (bytes.Buffer )
@@ -84,32 +109,30 @@ func Send(message string, priority Priority, vars map[string]string) error {
84
109
appendVariable (data , k , v )
85
110
}
86
111
87
- _ , err := io .Copy (conn , data )
88
- if err != nil && isSocketSpaceError (err ) {
89
- file , err := tempFd ()
90
- if err != nil {
91
- return journalError (err .Error ())
92
- }
93
- defer file .Close ()
94
- _ , err = io .Copy (file , data )
95
- if err != nil {
96
- return journalError (err .Error ())
97
- }
98
-
99
- rights := syscall .UnixRights (int (file .Fd ()))
112
+ _ , _ , err := conn .WriteMsgUnix (data .Bytes (), nil , socketAddr )
113
+ if err == nil {
114
+ return nil
115
+ }
116
+ if ! isSocketSpaceError (err ) {
117
+ return err
118
+ }
100
119
101
- /* this connection should always be a UnixConn, but better safe than sorry */
102
- unixConn , ok := conn .(* net.UnixConn )
103
- if ! ok {
104
- return journalError ("can't send file through non-Unix connection" )
105
- }
106
- _ , _ , err = unixConn .WriteMsgUnix ([]byte {}, rights , nil )
107
- if err != nil {
108
- return journalError (err .Error ())
109
- }
110
- } else if err != nil {
111
- return journalError (err .Error ())
120
+ // Large log entry, send it via tempfile and ancillary-fd.
121
+ file , err := tempFd ()
122
+ if err != nil {
123
+ return err
124
+ }
125
+ defer file .Close ()
126
+ _ , err = io .Copy (file , data )
127
+ if err != nil {
128
+ return err
129
+ }
130
+ rights := syscall .UnixRights (int (file .Fd ()))
131
+ _ , _ , err = conn .WriteMsgUnix ([]byte {}, rights , socketAddr )
132
+ if err != nil {
133
+ return err
112
134
}
135
+
113
136
return nil
114
137
}
115
138
@@ -120,7 +143,7 @@ func Print(priority Priority, format string, a ...interface{}) error {
120
143
121
144
func appendVariable (w io.Writer , name , value string ) {
122
145
if err := validVarName (name ); err != nil {
123
- journalError ( err . Error () )
146
+ fmt . Fprintf ( os . Stderr , "variable name %s contains invalid character, ignoring \n " , name )
124
147
}
125
148
if strings .ContainsRune (value , '\n' ) {
126
149
/* When the value contains a newline, we write:
@@ -137,9 +160,9 @@ func appendVariable(w io.Writer, name, value string) {
137
160
}
138
161
}
139
162
140
- // validVarName validates a variable name to make sure it journald will accept it.
163
+ // validVarName validates a variable name to make sure journald will accept it.
141
164
// The variable name must be in uppercase and consist only of characters,
142
- // numbers and underscores, and may not begin with an underscore. (from the docs)
165
+ // numbers and underscores, and may not begin with an underscore:
143
166
// https://www.freedesktop.org/software/systemd/man/sd_journal_print.html
144
167
func validVarName (name string ) error {
145
168
if name == "" {
@@ -156,20 +179,23 @@ func validVarName(name string) error {
156
179
return nil
157
180
}
158
181
182
+ // isSocketSpaceError checks whether the error is signaling
183
+ // an "overlarge message" condition.
159
184
func isSocketSpaceError (err error ) bool {
160
185
opErr , ok := err .(* net.OpError )
161
- if ! ok {
186
+ if ! ok || opErr == nil {
162
187
return false
163
188
}
164
189
165
- sysErr , ok := opErr .Err .(syscall. Errno )
166
- if ! ok {
190
+ sysErr , ok := opErr .Err .(* os. SyscallError )
191
+ if ! ok || sysErr == nil {
167
192
return false
168
193
}
169
194
170
- return sysErr == syscall .EMSGSIZE || sysErr == syscall .ENOBUFS
195
+ return sysErr . Err == syscall .EMSGSIZE || sysErr . Err == syscall .ENOBUFS
171
196
}
172
197
198
+ // tempFd creates a temporary, unlinked file under `/dev/shm`.
173
199
func tempFd () (* os.File , error ) {
174
200
file , err := ioutil .TempFile ("/dev/shm/" , "journal.XXXXX" )
175
201
if err != nil {
@@ -182,8 +208,18 @@ func tempFd() (*os.File, error) {
182
208
return file , nil
183
209
}
184
210
185
- func journalError (s string ) error {
186
- s = "journal error: " + s
187
- fmt .Fprintln (os .Stderr , s )
188
- return errors .New (s )
211
+ // initConn initializes the global `unixConnPtr` socket.
212
+ // It is meant to be called exactly once, at program startup.
213
+ func initConn () {
214
+ autobind , err := net .ResolveUnixAddr ("unixgram" , "" )
215
+ if err != nil {
216
+ return
217
+ }
218
+
219
+ sock , err := net .ListenUnixgram ("unixgram" , autobind )
220
+ if err != nil {
221
+ return
222
+ }
223
+
224
+ atomic .StorePointer (& unixConnPtr , unsafe .Pointer (sock ))
189
225
}
0 commit comments