github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/daemon/logger/gcplogs/gcplogging.go (about)

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