send.go 4.1 KB

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