github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/daemon/logger/gcplogs/gcplogging.go (about) 1 package gcplogs // import "github.com/Prakhar-Agarwal-byte/moby/daemon/logger/gcplogs" 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "sync/atomic" 8 "time" 9 10 "github.com/Prakhar-Agarwal-byte/moby/daemon/logger" 11 12 "cloud.google.com/go/compute/metadata" 13 "cloud.google.com/go/logging" 14 "github.com/containerd/log" 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 if err := logger.RegisterLogDriver(name, New); err != nil { 47 panic(err) 48 } 49 50 if err := logger.RegisterLogOptValidator(name, ValidateLogOpts); err != nil { 51 panic(err) 52 } 53 } 54 55 type gcplogs struct { 56 client *logging.Client 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 c, err := logging.NewClient(context.Background(), project) 121 if err != nil { 122 return nil, err 123 } 124 var instanceResource *instanceInfo 125 if onGCE { 126 instanceResource = &instanceInfo{ 127 Zone: zone, 128 Name: instanceName, 129 ID: instanceID, 130 } 131 } else if info.Config[logZoneKey] != "" || info.Config[logNameKey] != "" || info.Config[logIDKey] != "" { 132 instanceResource = &instanceInfo{ 133 Zone: info.Config[logZoneKey], 134 Name: info.Config[logNameKey], 135 ID: info.Config[logIDKey], 136 } 137 } 138 139 options := []logging.LoggerOption{} 140 if instanceResource != nil { 141 vmMrpb := logging.CommonResource( 142 &mrpb.MonitoredResource{ 143 Type: "gce_instance", 144 Labels: map[string]string{ 145 "instance_id": instanceResource.ID, 146 "zone": instanceResource.Zone, 147 }, 148 }, 149 ) 150 options = []logging.LoggerOption{vmMrpb} 151 } 152 lg := c.Logger("gcplogs-docker-driver", options...) 153 154 if err := c.Ping(context.Background()); err != nil { 155 return nil, fmt.Errorf("unable to connect or authenticate with Google Cloud Logging: %v", err) 156 } 157 158 extraAttributes, err := info.ExtraAttributes(nil) 159 if err != nil { 160 return nil, err 161 } 162 163 l := &gcplogs{ 164 client: c, 165 logger: lg, 166 container: &containerInfo{ 167 Name: info.ContainerName, 168 ID: info.ContainerID, 169 ImageName: info.ContainerImageName, 170 ImageID: info.ContainerImageID, 171 Created: info.ContainerCreated, 172 Metadata: extraAttributes, 173 }, 174 } 175 176 if info.Config[logCmdKey] == "true" { 177 l.container.Command = info.Command() 178 } 179 180 if instanceResource != nil { 181 l.instance = instanceResource 182 } 183 184 // The logger "overflows" at a rate of 10,000 logs per second and this 185 // overflow func is called. We want to surface the error to the user 186 // without overly spamming /var/log/docker.log so we log the first time 187 // we overflow and every 1000th time after. 188 c.OnError = func(err error) { 189 if err == logging.ErrOverflow { 190 if i := atomic.AddUint64(&droppedLogs, 1); i%1000 == 1 { 191 log.G(context.TODO()).Errorf("gcplogs driver has dropped %v logs", i) 192 } 193 } else { 194 log.G(context.TODO()).Error(err) 195 } 196 } 197 198 return l, nil 199 } 200 201 // ValidateLogOpts validates the opts passed to the gcplogs driver. Currently, the gcplogs 202 // driver doesn't take any arguments. 203 func ValidateLogOpts(cfg map[string]string) error { 204 for k := range cfg { 205 switch k { 206 case projectOptKey, logLabelsKey, logLabelsRegexKey, logEnvKey, logEnvRegexKey, logCmdKey, logZoneKey, logNameKey, logIDKey: 207 default: 208 return fmt.Errorf("%q is not a valid option for the gcplogs driver", k) 209 } 210 } 211 return nil 212 } 213 214 func (l *gcplogs) Log(m *logger.Message) error { 215 message := string(m.Line) 216 ts := m.Timestamp 217 logger.PutMessage(m) 218 219 l.logger.Log(logging.Entry{ 220 Timestamp: ts, 221 Payload: &dockerLogEntry{ 222 Instance: l.instance, 223 Container: l.container, 224 Message: message, 225 }, 226 }) 227 return nil 228 } 229 230 func (l *gcplogs) Close() error { 231 l.logger.Flush() 232 return l.client.Close() 233 } 234 235 func (l *gcplogs) Name() string { 236 return name 237 }