github.com/dpiddy/docker@v1.12.2-rc1/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 ) 25 26 var ( 27 // The number of logs the gcplogs driver has dropped. 28 droppedLogs uint64 29 30 onGCE bool 31 32 // instance metadata populated from the metadata server if available 33 projectID string 34 zone string 35 instanceName string 36 instanceID string 37 ) 38 39 func init() { 40 41 if err := logger.RegisterLogDriver(name, New); err != nil { 42 logrus.Fatal(err) 43 } 44 45 if err := logger.RegisterLogOptValidator(name, ValidateLogOpts); err != nil { 46 logrus.Fatal(err) 47 } 48 } 49 50 type gcplogs struct { 51 client *logging.Client 52 instance *instanceInfo 53 container *containerInfo 54 } 55 56 type dockerLogEntry struct { 57 Instance *instanceInfo `json:"instance,omitempty"` 58 Container *containerInfo `json:"container,omitempty"` 59 Data string `json:"data,omitempty"` 60 } 61 62 type instanceInfo struct { 63 Zone string `json:"zone,omitempty"` 64 Name string `json:"name,omitempty"` 65 ID string `json:"id,omitempty"` 66 } 67 68 type containerInfo struct { 69 Name string `json:"name,omitempty"` 70 ID string `json:"id,omitempty"` 71 ImageName string `json:"imageName,omitempty"` 72 ImageID string `json:"imageId,omitempty"` 73 Created time.Time `json:"created,omitempty"` 74 Command string `json:"command,omitempty"` 75 Metadata map[string]string `json:"metadata,omitempty"` 76 } 77 78 var initGCPOnce sync.Once 79 80 func initGCP() { 81 initGCPOnce.Do(func() { 82 onGCE = metadata.OnGCE() 83 if onGCE { 84 // These will fail on instances if the metadata service is 85 // down or the client is compiled with an API version that 86 // has been removed. Since these are not vital, let's ignore 87 // them and make their fields in the dockeLogEntry ,omitempty 88 projectID, _ = metadata.ProjectID() 89 zone, _ = metadata.Zone() 90 instanceName, _ = metadata.InstanceName() 91 instanceID, _ = metadata.InstanceID() 92 } 93 }) 94 } 95 96 // New creates a new logger that logs to Google Cloud Logging using the application 97 // default credentials. 98 // 99 // See https://developers.google.com/identity/protocols/application-default-credentials 100 func New(ctx logger.Context) (logger.Logger, error) { 101 initGCP() 102 103 var project string 104 if projectID != "" { 105 project = projectID 106 } 107 if projectID, found := ctx.Config[projectOptKey]; found { 108 project = projectID 109 } 110 if project == "" { 111 return nil, fmt.Errorf("No project was specified and couldn't read project from the meatadata server. Please specify a project") 112 } 113 114 c, err := logging.NewClient(context.Background(), project, "gcplogs-docker-driver") 115 if err != nil { 116 return nil, err 117 } 118 119 if err := c.Ping(); err != nil { 120 return nil, fmt.Errorf("unable to connect or authenticate with Google Cloud Logging: %v", err) 121 } 122 123 l := &gcplogs{ 124 client: c, 125 container: &containerInfo{ 126 Name: ctx.ContainerName, 127 ID: ctx.ContainerID, 128 ImageName: ctx.ContainerImageName, 129 ImageID: ctx.ContainerImageID, 130 Created: ctx.ContainerCreated, 131 Metadata: ctx.ExtraAttributes(nil), 132 }, 133 } 134 135 if ctx.Config[logCmdKey] == "true" { 136 l.container.Command = ctx.Command() 137 } 138 139 if onGCE { 140 l.instance = &instanceInfo{ 141 Zone: zone, 142 Name: instanceName, 143 ID: instanceID, 144 } 145 } 146 147 // The logger "overflows" at a rate of 10,000 logs per second and this 148 // overflow func is called. We want to surface the error to the user 149 // without overly spamming /var/log/docker.log so we log the first time 150 // we overflow and every 1000th time after. 151 c.Overflow = func(_ *logging.Client, _ logging.Entry) error { 152 if i := atomic.AddUint64(&droppedLogs, 1); i%1000 == 1 { 153 logrus.Errorf("gcplogs driver has dropped %v logs", i) 154 } 155 return nil 156 } 157 158 return l, nil 159 } 160 161 // ValidateLogOpts validates the opts passed to the gcplogs driver. Currently, the gcplogs 162 // driver doesn't take any arguments. 163 func ValidateLogOpts(cfg map[string]string) error { 164 for k := range cfg { 165 switch k { 166 case projectOptKey, logLabelsKey, logEnvKey, logCmdKey: 167 default: 168 return fmt.Errorf("%q is not a valid option for the gcplogs driver", k) 169 } 170 } 171 return nil 172 } 173 174 func (l *gcplogs) Log(m *logger.Message) error { 175 return l.client.Log(logging.Entry{ 176 Time: m.Timestamp, 177 Payload: &dockerLogEntry{ 178 Instance: l.instance, 179 Container: l.container, 180 Data: string(m.Line), 181 }, 182 }) 183 } 184 185 func (l *gcplogs) Close() error { 186 return l.client.Flush() 187 } 188 189 func (l *gcplogs) Name() string { 190 return name 191 }