| // +build linux |
| |
| // Package journald provides the log driver for forwarding server logs |
| // to endpoints that receive the systemd format. |
| package journald // import "github.com/docker/docker/daemon/logger/journald" |
| |
| import ( |
| "fmt" |
| "sync" |
| "unicode" |
| |
| "github.com/coreos/go-systemd/journal" |
| "github.com/docker/docker/daemon/logger" |
| "github.com/docker/docker/daemon/logger/loggerutils" |
| "github.com/sirupsen/logrus" |
| ) |
| |
| const name = "journald" |
| |
| type journald struct { |
| mu sync.Mutex |
| vars map[string]string // additional variables and values to send to the journal along with the log message |
| readers map[*logger.LogWatcher]struct{} |
| } |
| |
| func init() { |
| if err := logger.RegisterLogDriver(name, New); err != nil { |
| logrus.Fatal(err) |
| } |
| if err := logger.RegisterLogOptValidator(name, validateLogOpt); err != nil { |
| logrus.Fatal(err) |
| } |
| } |
| |
| // sanitizeKeyMode returns the sanitized string so that it could be used in journald. |
| // In journald log, there are special requirements for fields. |
| // Fields must be composed of uppercase letters, numbers, and underscores, but must |
| // not start with an underscore. |
| func sanitizeKeyMod(s string) string { |
| n := "" |
| for _, v := range s { |
| if 'a' <= v && v <= 'z' { |
| v = unicode.ToUpper(v) |
| } else if ('Z' < v || v < 'A') && ('9' < v || v < '0') { |
| v = '_' |
| } |
| // If (n == "" && v == '_'), then we will skip as this is the beginning with '_' |
| if !(n == "" && v == '_') { |
| n += string(v) |
| } |
| } |
| return n |
| } |
| |
| // New creates a journald logger using the configuration passed in on |
| // the context. |
| func New(info logger.Info) (logger.Logger, error) { |
| if !journal.Enabled() { |
| return nil, fmt.Errorf("journald is not enabled on this host") |
| } |
| |
| // parse log tag |
| tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate) |
| if err != nil { |
| return nil, err |
| } |
| |
| vars := map[string]string{ |
| "CONTAINER_ID": info.ContainerID[:12], |
| "CONTAINER_ID_FULL": info.ContainerID, |
| "CONTAINER_NAME": info.Name(), |
| "CONTAINER_TAG": tag, |
| "IMAGE_NAME": info.ImageName(), |
| "SYSLOG_IDENTIFIER": tag, |
| } |
| extraAttrs, err := info.ExtraAttributes(sanitizeKeyMod) |
| if err != nil { |
| return nil, err |
| } |
| for k, v := range extraAttrs { |
| vars[k] = v |
| } |
| return &journald{vars: vars, readers: make(map[*logger.LogWatcher]struct{})}, nil |
| } |
| |
| // We don't actually accept any options, but we have to supply a callback for |
| // the factory to pass the (probably empty) configuration map to. |
| func validateLogOpt(cfg map[string]string) error { |
| for key := range cfg { |
| switch key { |
| case "labels": |
| case "env": |
| case "env-regex": |
| case "tag": |
| default: |
| return fmt.Errorf("unknown log opt '%s' for journald log driver", key) |
| } |
| } |
| return nil |
| } |
| |
| func (s *journald) Log(msg *logger.Message) error { |
| vars := map[string]string{} |
| for k, v := range s.vars { |
| vars[k] = v |
| } |
| if msg.PLogMetaData != nil && !msg.PLogMetaData.Last { |
| vars["CONTAINER_PARTIAL_MESSAGE"] = "true" |
| } |
| |
| line := string(msg.Line) |
| source := msg.Source |
| logger.PutMessage(msg) |
| |
| if source == "stderr" { |
| return journal.Send(line, journal.PriErr, vars) |
| } |
| return journal.Send(line, journal.PriInfo, vars) |
| } |
| |
| func (s *journald) Name() string { |
| return name |
| } |