blob: a0f4837a02c9aaf9126ab696aecf79dc036d02ad [file] [log] [blame]
divyadesai19009132020-03-04 12:58:08 +00001// Copyright 2015 CoreOS, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package journal provides write bindings to the local systemd journal.
16// It is implemented in pure Go and connects to the journal directly over its
17// unix socket.
18//
19// To read from the journal, see the "sdjournal" package, which wraps the
20// sd-journal a C API.
21//
22// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html
23package journal
24
25import (
26 "bytes"
27 "encoding/binary"
28 "errors"
29 "fmt"
30 "io"
31 "io/ioutil"
32 "net"
33 "os"
34 "strconv"
35 "strings"
36 "sync"
37 "sync/atomic"
38 "syscall"
39 "unsafe"
40)
41
42// Priority of a journal message
43type Priority int
44
45const (
46 PriEmerg Priority = iota
47 PriAlert
48 PriCrit
49 PriErr
50 PriWarning
51 PriNotice
52 PriInfo
53 PriDebug
54)
55
56var (
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)
67
68func init() {
69 onceConn.Do(initConn)
70}
71
72// Enabled checks whether the local systemd journal is available for logging.
73func Enabled() bool {
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
85}
86
87// Send a message to the local systemd journal. vars is a map of journald
88// fields to values. Fields must be composed of uppercase letters, numbers,
89// and underscores, but must not start with an underscore. Within these
90// restrictions, any arbitrary field name may be used. Some names have special
91// significance: see the journalctl documentation
92// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
93// for more details. vars may be nil.
94func Send(message string, priority Priority, vars map[string]string) error {
95 conn := (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr))
96 if conn == nil {
97 return errors.New("could not initialize socket to journald")
98 }
99
100 socketAddr := &net.UnixAddr{
101 Name: journalSocket,
102 Net: "unixgram",
103 }
104
105 data := new(bytes.Buffer)
106 appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
107 appendVariable(data, "MESSAGE", message)
108 for k, v := range vars {
109 appendVariable(data, k, v)
110 }
111
112 _, _, err := conn.WriteMsgUnix(data.Bytes(), nil, socketAddr)
113 if err == nil {
114 return nil
115 }
116 if !isSocketSpaceError(err) {
117 return err
118 }
119
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
134 }
135
136 return nil
137}
138
139// Print prints a message to the local systemd journal using Send().
140func Print(priority Priority, format string, a ...interface{}) error {
141 return Send(fmt.Sprintf(format, a...), priority, nil)
142}
143
144func appendVariable(w io.Writer, name, value string) {
145 if err := validVarName(name); err != nil {
146 fmt.Fprintf(os.Stderr, "variable name %s contains invalid character, ignoring\n", name)
147 }
148 if strings.ContainsRune(value, '\n') {
149 /* When the value contains a newline, we write:
150 * - the variable name, followed by a newline
151 * - the size (in 64bit little endian format)
152 * - the data, followed by a newline
153 */
154 fmt.Fprintln(w, name)
155 binary.Write(w, binary.LittleEndian, uint64(len(value)))
156 fmt.Fprintln(w, value)
157 } else {
158 /* just write the variable and value all on one line */
159 fmt.Fprintf(w, "%s=%s\n", name, value)
160 }
161}
162
163// validVarName validates a variable name to make sure journald will accept it.
164// The variable name must be in uppercase and consist only of characters,
165// numbers and underscores, and may not begin with an underscore:
166// https://www.freedesktop.org/software/systemd/man/sd_journal_print.html
167func validVarName(name string) error {
168 if name == "" {
169 return errors.New("Empty variable name")
170 } else if name[0] == '_' {
171 return errors.New("Variable name begins with an underscore")
172 }
173
174 for _, c := range name {
175 if !(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_') {
176 return errors.New("Variable name contains invalid characters")
177 }
178 }
179 return nil
180}
181
182// isSocketSpaceError checks whether the error is signaling
183// an "overlarge message" condition.
184func isSocketSpaceError(err error) bool {
185 opErr, ok := err.(*net.OpError)
186 if !ok || opErr == nil {
187 return false
188 }
189
190 sysErr, ok := opErr.Err.(*os.SyscallError)
191 if !ok || sysErr == nil {
192 return false
193 }
194
195 return sysErr.Err == syscall.EMSGSIZE || sysErr.Err == syscall.ENOBUFS
196}
197
198// tempFd creates a temporary, unlinked file under `/dev/shm`.
199func tempFd() (*os.File, error) {
200 file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
201 if err != nil {
202 return nil, err
203 }
204 err = syscall.Unlink(file.Name())
205 if err != nil {
206 return nil, err
207 }
208 return file, nil
209}
210
211// initConn initializes the global `unixConnPtr` socket.
212// It is meant to be called exactly once, at program startup.
213func 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))
225}