gcplogging.go 6.3 KB

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