gcplogging.go 4.6 KB

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