journal.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // 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. // Package journal provides write bindings to the local systemd journal.
  15. // It is implemented in pure Go and connects to the journal directly over its
  16. // unix socket.
  17. //
  18. // To read from the journal, see the "sdjournal" package, which wraps the
  19. // sd-journal a C API.
  20. //
  21. // http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html
  22. package journal
  23. import (
  24. "bytes"
  25. "encoding/binary"
  26. "errors"
  27. "fmt"
  28. "io"
  29. "io/ioutil"
  30. "net"
  31. "os"
  32. "strconv"
  33. "strings"
  34. "syscall"
  35. )
  36. // Priority of a journal message
  37. type Priority int
  38. const (
  39. PriEmerg Priority = iota
  40. PriAlert
  41. PriCrit
  42. PriErr
  43. PriWarning
  44. PriNotice
  45. PriInfo
  46. PriDebug
  47. )
  48. var conn net.Conn
  49. func init() {
  50. var err error
  51. conn, err = net.Dial("unixgram", "/run/systemd/journal/socket")
  52. if err != nil {
  53. conn = nil
  54. }
  55. }
  56. // Enabled returns true if the local systemd journal is available for logging
  57. func Enabled() bool {
  58. return conn != nil
  59. }
  60. // Send a message to the local systemd journal. vars is a map of journald
  61. // fields to values. Fields must be composed of uppercase letters, numbers,
  62. // and underscores, but must not start with an underscore. Within these
  63. // restrictions, any arbitrary field name may be used. Some names have special
  64. // significance: see the journalctl documentation
  65. // (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
  66. // for more details. vars may be nil.
  67. func Send(message string, priority Priority, vars map[string]string) error {
  68. if conn == nil {
  69. return journalError("could not connect to journald socket")
  70. }
  71. data := new(bytes.Buffer)
  72. appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
  73. appendVariable(data, "MESSAGE", message)
  74. for k, v := range vars {
  75. appendVariable(data, k, v)
  76. }
  77. _, err := io.Copy(conn, data)
  78. if err != nil && isSocketSpaceError(err) {
  79. file, err := tempFd()
  80. if err != nil {
  81. return journalError(err.Error())
  82. }
  83. defer file.Close()
  84. _, err = io.Copy(file, data)
  85. if err != nil {
  86. return journalError(err.Error())
  87. }
  88. rights := syscall.UnixRights(int(file.Fd()))
  89. /* this connection should always be a UnixConn, but better safe than sorry */
  90. unixConn, ok := conn.(*net.UnixConn)
  91. if !ok {
  92. return journalError("can't send file through non-Unix connection")
  93. }
  94. unixConn.WriteMsgUnix([]byte{}, rights, nil)
  95. } else if err != nil {
  96. return journalError(err.Error())
  97. }
  98. return nil
  99. }
  100. // Print prints a message to the local systemd journal using Send().
  101. func Print(priority Priority, format string, a ...interface{}) error {
  102. return Send(fmt.Sprintf(format, a...), priority, nil)
  103. }
  104. func appendVariable(w io.Writer, name, value string) {
  105. if !validVarName(name) {
  106. journalError("variable name contains invalid character, ignoring")
  107. }
  108. if strings.ContainsRune(value, '\n') {
  109. /* When the value contains a newline, we write:
  110. * - the variable name, followed by a newline
  111. * - the size (in 64bit little endian format)
  112. * - the data, followed by a newline
  113. */
  114. fmt.Fprintln(w, name)
  115. binary.Write(w, binary.LittleEndian, uint64(len(value)))
  116. fmt.Fprintln(w, value)
  117. } else {
  118. /* just write the variable and value all on one line */
  119. fmt.Fprintf(w, "%s=%s\n", name, value)
  120. }
  121. }
  122. func validVarName(name string) bool {
  123. /* The variable name must be in uppercase and consist only of characters,
  124. * numbers and underscores, and may not begin with an underscore. (from the docs)
  125. */
  126. valid := name[0] != '_'
  127. for _, c := range name {
  128. valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_'
  129. }
  130. return valid
  131. }
  132. func isSocketSpaceError(err error) bool {
  133. opErr, ok := err.(*net.OpError)
  134. if !ok {
  135. return false
  136. }
  137. sysErr, ok := opErr.Err.(syscall.Errno)
  138. if !ok {
  139. return false
  140. }
  141. return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
  142. }
  143. func tempFd() (*os.File, error) {
  144. file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
  145. if err != nil {
  146. return nil, err
  147. }
  148. syscall.Unlink(file.Name())
  149. if err != nil {
  150. return nil, err
  151. }
  152. return file, nil
  153. }
  154. func journalError(s string) error {
  155. s = "journal error: " + s
  156. fmt.Fprintln(os.Stderr, s)
  157. return errors.New(s)
  158. }