gopkg.in/dotcloud/docker.v1@v1.13.1/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 "github.com/Sirupsen/logrus" 12 "golang.org/x/net/context" 13 "google.golang.org/cloud/compute/metadata" 14 "google.golang.org/cloud/logging" 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 client *logging.Client 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(ctx logger.Context) (logger.Logger, error) { 104 initGCP() 105 106 var project string 107 if projectID != "" { 108 project = projectID 109 } 110 if projectID, found := ctx.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 c, err := logging.NewClient(context.Background(), project, "gcplogs-docker-driver") 118 if err != nil { 119 return nil, err 120 } 121 122 if err := c.Ping(); err != nil { 123 return nil, fmt.Errorf("unable to connect or authenticate with Google Cloud Logging: %v", err) 124 } 125 126 l := &gcplogs{ 127 client: c, 128 container: &containerInfo{ 129 Name: ctx.ContainerName, 130 ID: ctx.ContainerID, 131 ImageName: ctx.ContainerImageName, 132 ImageID: ctx.ContainerImageID, 133 Created: ctx.ContainerCreated, 134 Metadata: ctx.ExtraAttributes(nil), 135 }, 136 } 137 138 if ctx.Config[logCmdKey] == "true" { 139 l.container.Command = ctx.Command() 140 } 141 142 if onGCE { 143 l.instance = &instanceInfo{ 144 Zone: zone, 145 Name: instanceName, 146 ID: instanceID, 147 } 148 } else if ctx.Config[logZoneKey] != "" || ctx.Config[logNameKey] != "" || ctx.Config[logIDKey] != "" { 149 l.instance = &instanceInfo{ 150 Zone: ctx.Config[logZoneKey], 151 Name: ctx.Config[logNameKey], 152 ID: ctx.Config[logIDKey], 153 } 154 } 155 156 // The logger "overflows" at a rate of 10,000 logs per second and this 157 // overflow func is called. We want to surface the error to the user 158 // without overly spamming /var/log/docker.log so we log the first time 159 // we overflow and every 1000th time after. 160 c.Overflow = func(_ *logging.Client, _ logging.Entry) error { 161 if i := atomic.AddUint64(&droppedLogs, 1); i%1000 == 1 { 162 logrus.Errorf("gcplogs driver has dropped %v logs", i) 163 } 164 return nil 165 } 166 167 return l, nil 168 } 169 170 // ValidateLogOpts validates the opts passed to the gcplogs driver. Currently, the gcplogs 171 // driver doesn't take any arguments. 172 func ValidateLogOpts(cfg map[string]string) error { 173 for k := range cfg { 174 switch k { 175 case projectOptKey, logLabelsKey, logEnvKey, logCmdKey, logZoneKey, logNameKey, logIDKey: 176 default: 177 return fmt.Errorf("%q is not a valid option for the gcplogs driver", k) 178 } 179 } 180 return nil 181 } 182 183 func (l *gcplogs) Log(m *logger.Message) error { 184 return l.client.Log(logging.Entry{ 185 Time: m.Timestamp, 186 Payload: &dockerLogEntry{ 187 Instance: l.instance, 188 Container: l.container, 189 Data: string(m.Line), 190 }, 191 }) 192 } 193 194 func (l *gcplogs) Close() error { 195 return l.client.Flush() 196 } 197 198 func (l *gcplogs) Name() string { 199 return name 200 }