github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/logger/gcplogs/gcplogging.go (about)

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