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