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