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