github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/daemon/logger/journald/journald.go (about)

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