mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
9528ea930c
This fix tries to address the issue raised in #23528 where docker labels caused journald log error because journald has special requirements on field names. This fix addresses this issue by sanitize the labels per requirements of journald. Additional unit tests have been added to cover the changes. This fix fixes #23528. Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
122 lines
3.1 KiB
Go
122 lines
3.1 KiB
Go
// +build linux
|
|
|
|
// Package journald provides the log driver for forwarding server logs
|
|
// to endpoints that receive the systemd format.
|
|
package journald
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"unicode"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/coreos/go-systemd/journal"
|
|
"github.com/docker/docker/daemon/logger"
|
|
"github.com/docker/docker/daemon/logger/loggerutils"
|
|
)
|
|
|
|
const name = "journald"
|
|
|
|
type journald struct {
|
|
vars map[string]string // additional variables and values to send to the journal along with the log message
|
|
readers readerList
|
|
}
|
|
|
|
type readerList struct {
|
|
mu sync.Mutex
|
|
readers map[*logger.LogWatcher]*logger.LogWatcher
|
|
}
|
|
|
|
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(ctx logger.Context) (logger.Logger, error) {
|
|
if !journal.Enabled() {
|
|
return nil, fmt.Errorf("journald is not enabled on this host")
|
|
}
|
|
// Strip a leading slash so that people can search for
|
|
// CONTAINER_NAME=foo rather than CONTAINER_NAME=/foo.
|
|
name := ctx.ContainerName
|
|
if name[0] == '/' {
|
|
name = name[1:]
|
|
}
|
|
|
|
// parse log tag
|
|
tag, err := loggerutils.ParseLogTag(ctx, loggerutils.DefaultTemplate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vars := map[string]string{
|
|
"CONTAINER_ID": ctx.ContainerID[:12],
|
|
"CONTAINER_ID_FULL": ctx.ContainerID,
|
|
"CONTAINER_NAME": name,
|
|
"CONTAINER_TAG": tag,
|
|
}
|
|
extraAttrs := ctx.ExtraAttributes(sanitizeKeyMod)
|
|
for k, v := range extraAttrs {
|
|
vars[k] = v
|
|
}
|
|
return &journald{vars: vars, readers: readerList{readers: make(map[*logger.LogWatcher]*logger.LogWatcher)}}, 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 "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.Partial {
|
|
vars["CONTAINER_PARTIAL_MESSAGE"] = "true"
|
|
}
|
|
if msg.Source == "stderr" {
|
|
return journal.Send(string(msg.Line), journal.PriErr, vars)
|
|
}
|
|
return journal.Send(string(msg.Line), journal.PriInfo, vars)
|
|
}
|
|
|
|
func (s *journald) Name() string {
|
|
return name
|
|
}
|