1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
//go:build linux
// +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"
"strconv"
"sync"
"unicode"
"github.com/coreos/go-systemd/v22/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 //nolint:structcheck,unused
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 "labels-regex":
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 {
vars["CONTAINER_PARTIAL_ID"] = msg.PLogMetaData.ID
vars["CONTAINER_PARTIAL_ORDINAL"] = strconv.Itoa(msg.PLogMetaData.Ordinal)
vars["CONTAINER_PARTIAL_LAST"] = strconv.FormatBool(msg.PLogMetaData.Last)
if !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
}
|