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