gopkg.in/dotcloud/docker.v1@v1.13.1/daemon/logger/journald/journald.go (about) 1 // +build linux 2 3 // Package journald provides the log driver for forwarding server logs 4 // to endpoints that receive the systemd format. 5 package journald 6 7 import ( 8 "fmt" 9 "sync" 10 "unicode" 11 12 "github.com/Sirupsen/logrus" 13 "github.com/coreos/go-systemd/journal" 14 "github.com/docker/docker/daemon/logger" 15 "github.com/docker/docker/daemon/logger/loggerutils" 16 ) 17 18 const name = "journald" 19 20 type journald struct { 21 vars map[string]string // additional variables and values to send to the journal along with the log message 22 readers readerList 23 } 24 25 type readerList struct { 26 mu sync.Mutex 27 readers map[*logger.LogWatcher]*logger.LogWatcher 28 } 29 30 func init() { 31 if err := logger.RegisterLogDriver(name, New); err != nil { 32 logrus.Fatal(err) 33 } 34 if err := logger.RegisterLogOptValidator(name, validateLogOpt); err != nil { 35 logrus.Fatal(err) 36 } 37 } 38 39 // sanitizeKeyMode returns the sanitized string so that it could be used in journald. 40 // In journald log, there are special requirements for fields. 41 // Fields must be composed of uppercase letters, numbers, and underscores, but must 42 // not start with an underscore. 43 func sanitizeKeyMod(s string) string { 44 n := "" 45 for _, v := range s { 46 if 'a' <= v && v <= 'z' { 47 v = unicode.ToUpper(v) 48 } else if ('Z' < v || v < 'A') && ('9' < v || v < '0') { 49 v = '_' 50 } 51 // If (n == "" && v == '_'), then we will skip as this is the beginning with '_' 52 if !(n == "" && v == '_') { 53 n += string(v) 54 } 55 } 56 return n 57 } 58 59 // New creates a journald logger using the configuration passed in on 60 // the context. 61 func New(ctx logger.Context) (logger.Logger, error) { 62 if !journal.Enabled() { 63 return nil, fmt.Errorf("journald is not enabled on this host") 64 } 65 // Strip a leading slash so that people can search for 66 // CONTAINER_NAME=foo rather than CONTAINER_NAME=/foo. 67 name := ctx.ContainerName 68 if name[0] == '/' { 69 name = name[1:] 70 } 71 72 // parse log tag 73 tag, err := loggerutils.ParseLogTag(ctx, loggerutils.DefaultTemplate) 74 if err != nil { 75 return nil, err 76 } 77 78 vars := map[string]string{ 79 "CONTAINER_ID": ctx.ContainerID[:12], 80 "CONTAINER_ID_FULL": ctx.ContainerID, 81 "CONTAINER_NAME": name, 82 "CONTAINER_TAG": tag, 83 } 84 extraAttrs := ctx.ExtraAttributes(sanitizeKeyMod) 85 for k, v := range extraAttrs { 86 vars[k] = v 87 } 88 return &journald{vars: vars, readers: readerList{readers: make(map[*logger.LogWatcher]*logger.LogWatcher)}}, nil 89 } 90 91 // We don't actually accept any options, but we have to supply a callback for 92 // the factory to pass the (probably empty) configuration map to. 93 func validateLogOpt(cfg map[string]string) error { 94 for key := range cfg { 95 switch key { 96 case "labels": 97 case "env": 98 case "tag": 99 default: 100 return fmt.Errorf("unknown log opt '%s' for journald log driver", key) 101 } 102 } 103 return nil 104 } 105 106 func (s *journald) Log(msg *logger.Message) error { 107 vars := map[string]string{} 108 for k, v := range s.vars { 109 vars[k] = v 110 } 111 if msg.Partial { 112 vars["CONTAINER_PARTIAL_MESSAGE"] = "true" 113 } 114 if msg.Source == "stderr" { 115 return journal.Send(string(msg.Line), journal.PriErr, vars) 116 } 117 return journal.Send(string(msg.Line), journal.PriInfo, vars) 118 } 119 120 func (s *journald) Name() string { 121 return name 122 }