gcplogging.go 5.6 KB

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