github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/daemon/logger/gcplogs/gcplogging.go (about) 1 package gcplogs 2 3 import ( 4 "fmt" 5 "sync" 6 "sync/atomic" 7 "time" 8 9 "github.com/docker/docker/daemon/logger" 10 11 "cloud.google.com/go/compute/metadata" 12 "cloud.google.com/go/logging" 13 "github.com/Sirupsen/logrus" 14 "golang.org/x/net/context" 15 ) 16 17 const ( 18 name = "gcplogs" 19 20 projectOptKey = "gcp-project" 21 logLabelsKey = "labels" 22 logEnvKey = "env" 23 logCmdKey = "gcp-log-cmd" 24 logZoneKey = "gcp-meta-zone" 25 logNameKey = "gcp-meta-name" 26 logIDKey = "gcp-meta-id" 27 ) 28 29 var ( 30 // The number of logs the gcplogs driver has dropped. 31 droppedLogs uint64 32 33 onGCE bool 34 35 // instance metadata populated from the metadata server if available 36 projectID string 37 zone string 38 instanceName string 39 instanceID string 40 ) 41 42 func init() { 43 44 if err := logger.RegisterLogDriver(name, New); err != nil { 45 logrus.Fatal(err) 46 } 47 48 if err := logger.RegisterLogOptValidator(name, ValidateLogOpts); err != nil { 49 logrus.Fatal(err) 50 } 51 } 52 53 type gcplogs struct { 54 logger *logging.Logger 55 instance *instanceInfo 56 container *containerInfo 57 } 58 59 type dockerLogEntry struct { 60 Instance *instanceInfo `json:"instance,omitempty"` 61 Container *containerInfo `json:"container,omitempty"` 62 Data string `json:"data,omitempty"` 63 } 64 65 type instanceInfo struct { 66 Zone string `json:"zone,omitempty"` 67 Name string `json:"name,omitempty"` 68 ID string `json:"id,omitempty"` 69 } 70 71 type containerInfo struct { 72 Name string `json:"name,omitempty"` 73 ID string `json:"id,omitempty"` 74 ImageName string `json:"imageName,omitempty"` 75 ImageID string `json:"imageId,omitempty"` 76 Created time.Time `json:"created,omitempty"` 77 Command string `json:"command,omitempty"` 78 Metadata map[string]string `json:"metadata,omitempty"` 79 } 80 81 var initGCPOnce sync.Once 82 83 func initGCP() { 84 initGCPOnce.Do(func() { 85 onGCE = metadata.OnGCE() 86 if onGCE { 87 // These will fail on instances if the metadata service is 88 // down or the client is compiled with an API version that 89 // has been removed. Since these are not vital, let's ignore 90 // them and make their fields in the dockeLogEntry ,omitempty 91 projectID, _ = metadata.ProjectID() 92 zone, _ = metadata.Zone() 93 instanceName, _ = metadata.InstanceName() 94 instanceID, _ = metadata.InstanceID() 95 } 96 }) 97 } 98 99 // New creates a new logger that logs to Google Cloud Logging using the application 100 // default credentials. 101 // 102 // See https://developers.google.com/identity/protocols/application-default-credentials 103 func New(info logger.Info) (logger.Logger, error) { 104 initGCP() 105 106 var project string 107 if projectID != "" { 108 project = projectID 109 } 110 if projectID, found := info.Config[projectOptKey]; found { 111 project = projectID 112 } 113 if project == "" { 114 return nil, fmt.Errorf("No project was specified and couldn't read project from the meatadata server. Please specify a project") 115 } 116 117 // Issue #29344: gcplogs segfaults (static binary) 118 // If HOME is not set, logging.NewClient() will call os/user.Current() via oauth2/google. 119 // However, in static binary, os/user.Current() leads to segfault due to a glibc issue that won't be fixed 120 // in a short term. (golang/go#13470, https://sourceware.org/bugzilla/show_bug.cgi?id=19341) 121 // So we forcibly set HOME so as to avoid call to os/user/Current() 122 if err := ensureHomeIfIAmStatic(); err != nil { 123 return nil, err 124 } 125 126 c, err := logging.NewClient(context.Background(), project) 127 if err != nil { 128 return nil, err 129 } 130 lg := c.Logger("gcplogs-docker-driver") 131 132 if err := c.Ping(context.Background()); err != nil { 133 return nil, fmt.Errorf("unable to connect or authenticate with Google Cloud Logging: %v", err) 134 } 135 136 l := &gcplogs{ 137 logger: lg, 138 container: &containerInfo{ 139 Name: info.ContainerName, 140 ID: info.ContainerID, 141 ImageName: info.ContainerImageName, 142 ImageID: info.ContainerImageID, 143 Created: info.ContainerCreated, 144 Metadata: info.ExtraAttributes(nil), 145 }, 146 } 147 148 if info.Config[logCmdKey] == "true" { 149 l.container.Command = info.Command() 150 } 151 152 if onGCE { 153 l.instance = &instanceInfo{ 154 Zone: zone, 155 Name: instanceName, 156 ID: instanceID, 157 } 158 } else if info.Config[logZoneKey] != "" || info.Config[logNameKey] != "" || info.Config[logIDKey] != "" { 159 l.instance = &instanceInfo{ 160 Zone: info.Config[logZoneKey], 161 Name: info.Config[logNameKey], 162 ID: info.Config[logIDKey], 163 } 164 } 165 166 // The logger "overflows" at a rate of 10,000 logs per second and this 167 // overflow func is called. We want to surface the error to the user 168 // without overly spamming /var/log/docker.log so we log the first time 169 // we overflow and every 1000th time after. 170 c.OnError = func(err error) { 171 if err == logging.ErrOverflow { 172 if i := atomic.AddUint64(&droppedLogs, 1); i%1000 == 1 { 173 logrus.Errorf("gcplogs driver has dropped %v logs", i) 174 } 175 } else { 176 logrus.Error(err) 177 } 178 } 179 180 return l, nil 181 } 182 183 // ValidateLogOpts validates the opts passed to the gcplogs driver. Currently, the gcplogs 184 // driver doesn't take any arguments. 185 func ValidateLogOpts(cfg map[string]string) error { 186 for k := range cfg { 187 switch k { 188 case projectOptKey, logLabelsKey, logEnvKey, logCmdKey, logZoneKey, logNameKey, logIDKey: 189 default: 190 return fmt.Errorf("%q is not a valid option for the gcplogs driver", k) 191 } 192 } 193 return nil 194 } 195 196 func (l *gcplogs) Log(m *logger.Message) error { 197 l.logger.Log(logging.Entry{ 198 Timestamp: m.Timestamp, 199 Payload: &dockerLogEntry{ 200 Instance: l.instance, 201 Container: l.container, 202 Data: string(m.Line), 203 }, 204 }) 205 return nil 206 } 207 208 func (l *gcplogs) Close() error { 209 l.logger.Flush() 210 return nil 211 } 212 213 func (l *gcplogs) Name() string { 214 return name 215 }