github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/logger/journald/journald.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package journald // import "github.com/docker/docker/daemon/logger/journald"
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"sync/atomic"
    10  	"time"
    11  	"unicode"
    12  
    13  	"github.com/coreos/go-systemd/v22/journal"
    14  
    15  	"github.com/docker/docker/daemon/logger"
    16  	"github.com/docker/docker/daemon/logger/loggerutils"
    17  	"github.com/docker/docker/pkg/stringid"
    18  )
    19  
    20  const name = "journald"
    21  
    22  // Well-known user journal fields.
    23  // https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
    24  const (
    25  	fieldSyslogIdentifier = "SYSLOG_IDENTIFIER"
    26  	fieldSyslogTimestamp  = "SYSLOG_TIMESTAMP"
    27  )
    28  
    29  // User journal fields used by the log driver.
    30  const (
    31  	fieldContainerID     = "CONTAINER_ID"
    32  	fieldContainerIDFull = "CONTAINER_ID_FULL"
    33  	fieldContainerName   = "CONTAINER_NAME"
    34  	fieldContainerTag    = "CONTAINER_TAG"
    35  	fieldImageName       = "IMAGE_NAME"
    36  
    37  	// Fields used to serialize PLogMetaData.
    38  
    39  	fieldPLogID         = "CONTAINER_PARTIAL_ID"
    40  	fieldPLogOrdinal    = "CONTAINER_PARTIAL_ORDINAL"
    41  	fieldPLogLast       = "CONTAINER_PARTIAL_LAST"
    42  	fieldPartialMessage = "CONTAINER_PARTIAL_MESSAGE"
    43  
    44  	fieldLogEpoch   = "CONTAINER_LOG_EPOCH"
    45  	fieldLogOrdinal = "CONTAINER_LOG_ORDINAL"
    46  )
    47  
    48  var waitUntilFlushed func(*journald) error
    49  
    50  type journald struct {
    51  	// Sequence number of the most recent message sent by this instance of
    52  	// the log driver, starting from 1. Corollary: ordinal == 0 implies no
    53  	// messages have been sent by this instance.
    54  	ordinal uint64 // Placed first in struct to ensure 8-byte alignment for atomic ops.
    55  	// Epoch identifier to distinguish sequence numbers from this instance
    56  	// vs. other instances.
    57  	epoch string
    58  
    59  	vars map[string]string // additional variables and values to send to the journal along with the log message
    60  
    61  	closed chan struct{}
    62  
    63  	// Overrides for unit tests.
    64  
    65  	sendToJournal   func(message string, priority journal.Priority, vars map[string]string) error
    66  	journalReadDir  string //nolint:unused // Referenced in read.go, which has more restrictive build constraints.
    67  	readSyncTimeout time.Duration
    68  }
    69  
    70  func init() {
    71  	if err := logger.RegisterLogDriver(name, New); err != nil {
    72  		panic(err)
    73  	}
    74  	if err := logger.RegisterLogOptValidator(name, validateLogOpt); err != nil {
    75  		panic(err)
    76  	}
    77  }
    78  
    79  // sanitizeKeyMode returns the sanitized string so that it could be used in journald.
    80  // In journald log, there are special requirements for fields.
    81  // Fields must be composed of uppercase letters, numbers, and underscores, but must
    82  // not start with an underscore.
    83  func sanitizeKeyMod(s string) string {
    84  	n := ""
    85  	for _, v := range s {
    86  		if 'a' <= v && v <= 'z' {
    87  			v = unicode.ToUpper(v)
    88  		} else if ('Z' < v || v < 'A') && ('9' < v || v < '0') {
    89  			v = '_'
    90  		}
    91  		// If (n == "" && v == '_'), then we will skip as this is the beginning with '_'
    92  		if !(n == "" && v == '_') {
    93  			n += string(v)
    94  		}
    95  	}
    96  	return n
    97  }
    98  
    99  // New creates a journald logger using the configuration passed in on
   100  // the context.
   101  func New(info logger.Info) (logger.Logger, error) {
   102  	if !journal.Enabled() {
   103  		return nil, fmt.Errorf("journald is not enabled on this host")
   104  	}
   105  
   106  	return new(info)
   107  }
   108  
   109  func new(info logger.Info) (*journald, error) {
   110  	// parse log tag
   111  	tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	epoch := stringid.GenerateRandomID()
   117  
   118  	vars := map[string]string{
   119  		fieldContainerID:      info.ContainerID[:12],
   120  		fieldContainerIDFull:  info.ContainerID,
   121  		fieldContainerName:    info.Name(),
   122  		fieldContainerTag:     tag,
   123  		fieldImageName:        info.ImageName(),
   124  		fieldSyslogIdentifier: tag,
   125  		fieldLogEpoch:         epoch,
   126  	}
   127  	extraAttrs, err := info.ExtraAttributes(sanitizeKeyMod)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	for k, v := range extraAttrs {
   132  		vars[k] = v
   133  	}
   134  	return &journald{
   135  		epoch:         epoch,
   136  		vars:          vars,
   137  		closed:        make(chan struct{}),
   138  		sendToJournal: journal.Send,
   139  	}, nil
   140  }
   141  
   142  // We don't actually accept any options, but we have to supply a callback for
   143  // the factory to pass the (probably empty) configuration map to.
   144  func validateLogOpt(cfg map[string]string) error {
   145  	for key := range cfg {
   146  		switch key {
   147  		case "labels":
   148  		case "labels-regex":
   149  		case "env":
   150  		case "env-regex":
   151  		case "tag":
   152  		default:
   153  			return fmt.Errorf("unknown log opt '%s' for journald log driver", key)
   154  		}
   155  	}
   156  	return nil
   157  }
   158  
   159  func (s *journald) Log(msg *logger.Message) error {
   160  	vars := map[string]string{}
   161  	for k, v := range s.vars {
   162  		vars[k] = v
   163  	}
   164  	if !msg.Timestamp.IsZero() {
   165  		vars[fieldSyslogTimestamp] = msg.Timestamp.Format(time.RFC3339Nano)
   166  	}
   167  	if msg.PLogMetaData != nil {
   168  		vars[fieldPLogID] = msg.PLogMetaData.ID
   169  		vars[fieldPLogOrdinal] = strconv.Itoa(msg.PLogMetaData.Ordinal)
   170  		vars[fieldPLogLast] = strconv.FormatBool(msg.PLogMetaData.Last)
   171  		if !msg.PLogMetaData.Last {
   172  			vars[fieldPartialMessage] = "true"
   173  		}
   174  	}
   175  
   176  	line := string(msg.Line)
   177  	source := msg.Source
   178  	logger.PutMessage(msg)
   179  
   180  	seq := atomic.AddUint64(&s.ordinal, 1)
   181  	vars[fieldLogOrdinal] = strconv.FormatUint(seq, 10)
   182  
   183  	if source == "stderr" {
   184  		return s.sendToJournal(line, journal.PriErr, vars)
   185  	}
   186  	return s.sendToJournal(line, journal.PriInfo, vars)
   187  }
   188  
   189  func (s *journald) Name() string {
   190  	return name
   191  }
   192  
   193  func (s *journald) Close() error {
   194  	close(s.closed)
   195  	if waitUntilFlushed != nil {
   196  		return waitUntilFlushed(s)
   197  	}
   198  	return nil
   199  }