journal.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. _, err = io.Copy(file, data)
  84. if err != nil {
  85. return journalError(err.Error())
  86. }
  87. rights := syscall.UnixRights(int(file.Fd()))
  88. /* this connection should always be a UnixConn, but better safe than sorry */
  89. unixConn, ok := conn.(*net.UnixConn)
  90. if !ok {
  91. return journalError("can't send file through non-Unix connection")
  92. }
  93. unixConn.WriteMsgUnix([]byte{}, rights, nil)
  94. } else if err != nil {
  95. return journalError(err.Error())
  96. }
  97. return nil
  98. }
  99. // Print prints a message to the local systemd journal using Send().
  100. func Print(priority Priority, format string, a ...interface{}) error {
  101. return Send(fmt.Sprintf(format, a...), priority, nil)
  102. }
  103. func appendVariable(w io.Writer, name, value string) {
  104. if !validVarName(name) {
  105. journalError("variable name contains invalid character, ignoring")
  106. }
  107. if strings.ContainsRune(value, '\n') {
  108. /* When the value contains a newline, we write:
  109. * - the variable name, followed by a newline
  110. * - the size (in 64bit little endian format)
  111. * - the data, followed by a newline
  112. */
  113. fmt.Fprintln(w, name)
  114. binary.Write(w, binary.LittleEndian, uint64(len(value)))
  115. fmt.Fprintln(w, value)
  116. } else {
  117. /* just write the variable and value all on one line */
  118. fmt.Fprintf(w, "%s=%s\n", name, value)
  119. }
  120. }
  121. func validVarName(name string) bool {
  122. /* The variable name must be in uppercase and consist only of characters,
  123. * numbers and underscores, and may not begin with an underscore. (from the docs)
  124. */
  125. valid := name[0] != '_'
  126. for _, c := range name {
  127. valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_'
  128. }
  129. return valid
  130. }
  131. func isSocketSpaceError(err error) bool {
  132. opErr, ok := err.(*net.OpError)
  133. if !ok {
  134. return false
  135. }
  136. sysErr, ok := opErr.Err.(syscall.Errno)
  137. if !ok {
  138. return false
  139. }
  140. return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
  141. }
  142. func tempFd() (*os.File, error) {
  143. file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
  144. if err != nil {
  145. return nil, err
  146. }
  147. syscall.Unlink(file.Name())
  148. if err != nil {
  149. return nil, err
  150. }
  151. return file, nil
  152. }
  153. func journalError(s string) error {
  154. s = "journal error: " + s
  155. fmt.Fprintln(os.Stderr, s)
  156. return errors.New(s)
  157. }