journald.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. // +build linux
  2. // Package journald provides the log driver for forwarding server logs
  3. // to endpoints that receive the systemd format.
  4. package journald
  5. import (
  6. "fmt"
  7. "sync"
  8. "unicode"
  9. "github.com/Sirupsen/logrus"
  10. "github.com/coreos/go-systemd/journal"
  11. "github.com/docker/docker/daemon/logger"
  12. "github.com/docker/docker/daemon/logger/loggerutils"
  13. )
  14. const name = "journald"
  15. type journald struct {
  16. mu sync.Mutex
  17. vars map[string]string // additional variables and values to send to the journal along with the log message
  18. readers readerList
  19. closed bool
  20. }
  21. type readerList struct {
  22. readers map[*logger.LogWatcher]*logger.LogWatcher
  23. }
  24. func init() {
  25. if err := logger.RegisterLogDriver(name, New); err != nil {
  26. logrus.Fatal(err)
  27. }
  28. if err := logger.RegisterLogOptValidator(name, validateLogOpt); err != nil {
  29. logrus.Fatal(err)
  30. }
  31. }
  32. // sanitizeKeyMode returns the sanitized string so that it could be used in journald.
  33. // In journald log, there are special requirements for fields.
  34. // Fields must be composed of uppercase letters, numbers, and underscores, but must
  35. // not start with an underscore.
  36. func sanitizeKeyMod(s string) string {
  37. n := ""
  38. for _, v := range s {
  39. if 'a' <= v && v <= 'z' {
  40. v = unicode.ToUpper(v)
  41. } else if ('Z' < v || v < 'A') && ('9' < v || v < '0') {
  42. v = '_'
  43. }
  44. // If (n == "" && v == '_'), then we will skip as this is the beginning with '_'
  45. if !(n == "" && v == '_') {
  46. n += string(v)
  47. }
  48. }
  49. return n
  50. }
  51. // New creates a journald logger using the configuration passed in on
  52. // the context.
  53. func New(info logger.Info) (logger.Logger, error) {
  54. if !journal.Enabled() {
  55. return nil, fmt.Errorf("journald is not enabled on this host")
  56. }
  57. // parse log tag
  58. tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate)
  59. if err != nil {
  60. return nil, err
  61. }
  62. vars := map[string]string{
  63. "CONTAINER_ID": info.ContainerID[:12],
  64. "CONTAINER_ID_FULL": info.ContainerID,
  65. "CONTAINER_NAME": info.Name(),
  66. "CONTAINER_TAG": tag,
  67. }
  68. extraAttrs, err := info.ExtraAttributes(sanitizeKeyMod)
  69. if err != nil {
  70. return nil, err
  71. }
  72. for k, v := range extraAttrs {
  73. vars[k] = v
  74. }
  75. return &journald{vars: vars, readers: readerList{readers: make(map[*logger.LogWatcher]*logger.LogWatcher)}}, nil
  76. }
  77. // We don't actually accept any options, but we have to supply a callback for
  78. // the factory to pass the (probably empty) configuration map to.
  79. func validateLogOpt(cfg map[string]string) error {
  80. for key := range cfg {
  81. switch key {
  82. case "labels":
  83. case "env":
  84. case "env-regex":
  85. case "tag":
  86. default:
  87. return fmt.Errorf("unknown log opt '%s' for journald log driver", key)
  88. }
  89. }
  90. return nil
  91. }
  92. func (s *journald) Log(msg *logger.Message) error {
  93. vars := map[string]string{}
  94. for k, v := range s.vars {
  95. vars[k] = v
  96. }
  97. if msg.Partial {
  98. vars["CONTAINER_PARTIAL_MESSAGE"] = "true"
  99. }
  100. line := string(msg.Line)
  101. logger.PutMessage(msg)
  102. if msg.Source == "stderr" {
  103. return journal.Send(line, journal.PriErr, vars)
  104. }
  105. return journal.Send(line, journal.PriInfo, vars)
  106. }
  107. func (s *journald) Name() string {
  108. return name
  109. }