github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/daemon/logger/gcplogs/gcplogging.go (about)

     1  package gcplogs // import "github.com/Prakhar-Agarwal-byte/moby/daemon/logger/gcplogs"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"github.com/Prakhar-Agarwal-byte/moby/daemon/logger"
    11  
    12  	"cloud.google.com/go/compute/metadata"
    13  	"cloud.google.com/go/logging"
    14  	"github.com/containerd/log"
    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  	if err := logger.RegisterLogDriver(name, New); err != nil {
    47  		panic(err)
    48  	}
    49  
    50  	if err := logger.RegisterLogOptValidator(name, ValidateLogOpts); err != nil {
    51  		panic(err)
    52  	}
    53  }
    54  
    55  type gcplogs struct {
    56  	client    *logging.Client
    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  	c, err := logging.NewClient(context.Background(), project)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	var instanceResource *instanceInfo
   125  	if onGCE {
   126  		instanceResource = &instanceInfo{
   127  			Zone: zone,
   128  			Name: instanceName,
   129  			ID:   instanceID,
   130  		}
   131  	} else if info.Config[logZoneKey] != "" || info.Config[logNameKey] != "" || info.Config[logIDKey] != "" {
   132  		instanceResource = &instanceInfo{
   133  			Zone: info.Config[logZoneKey],
   134  			Name: info.Config[logNameKey],
   135  			ID:   info.Config[logIDKey],
   136  		}
   137  	}
   138  
   139  	options := []logging.LoggerOption{}
   140  	if instanceResource != nil {
   141  		vmMrpb := logging.CommonResource(
   142  			&mrpb.MonitoredResource{
   143  				Type: "gce_instance",
   144  				Labels: map[string]string{
   145  					"instance_id": instanceResource.ID,
   146  					"zone":        instanceResource.Zone,
   147  				},
   148  			},
   149  		)
   150  		options = []logging.LoggerOption{vmMrpb}
   151  	}
   152  	lg := c.Logger("gcplogs-docker-driver", options...)
   153  
   154  	if err := c.Ping(context.Background()); err != nil {
   155  		return nil, fmt.Errorf("unable to connect or authenticate with Google Cloud Logging: %v", err)
   156  	}
   157  
   158  	extraAttributes, err := info.ExtraAttributes(nil)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	l := &gcplogs{
   164  		client: c,
   165  		logger: lg,
   166  		container: &containerInfo{
   167  			Name:      info.ContainerName,
   168  			ID:        info.ContainerID,
   169  			ImageName: info.ContainerImageName,
   170  			ImageID:   info.ContainerImageID,
   171  			Created:   info.ContainerCreated,
   172  			Metadata:  extraAttributes,
   173  		},
   174  	}
   175  
   176  	if info.Config[logCmdKey] == "true" {
   177  		l.container.Command = info.Command()
   178  	}
   179  
   180  	if instanceResource != nil {
   181  		l.instance = instanceResource
   182  	}
   183  
   184  	// The logger "overflows" at a rate of 10,000 logs per second and this
   185  	// overflow func is called. We want to surface the error to the user
   186  	// without overly spamming /var/log/docker.log so we log the first time
   187  	// we overflow and every 1000th time after.
   188  	c.OnError = func(err error) {
   189  		if err == logging.ErrOverflow {
   190  			if i := atomic.AddUint64(&droppedLogs, 1); i%1000 == 1 {
   191  				log.G(context.TODO()).Errorf("gcplogs driver has dropped %v logs", i)
   192  			}
   193  		} else {
   194  			log.G(context.TODO()).Error(err)
   195  		}
   196  	}
   197  
   198  	return l, nil
   199  }
   200  
   201  // ValidateLogOpts validates the opts passed to the gcplogs driver. Currently, the gcplogs
   202  // driver doesn't take any arguments.
   203  func ValidateLogOpts(cfg map[string]string) error {
   204  	for k := range cfg {
   205  		switch k {
   206  		case projectOptKey, logLabelsKey, logLabelsRegexKey, logEnvKey, logEnvRegexKey, logCmdKey, logZoneKey, logNameKey, logIDKey:
   207  		default:
   208  			return fmt.Errorf("%q is not a valid option for the gcplogs driver", k)
   209  		}
   210  	}
   211  	return nil
   212  }
   213  
   214  func (l *gcplogs) Log(m *logger.Message) error {
   215  	message := string(m.Line)
   216  	ts := m.Timestamp
   217  	logger.PutMessage(m)
   218  
   219  	l.logger.Log(logging.Entry{
   220  		Timestamp: ts,
   221  		Payload: &dockerLogEntry{
   222  			Instance:  l.instance,
   223  			Container: l.container,
   224  			Message:   message,
   225  		},
   226  	})
   227  	return nil
   228  }
   229  
   230  func (l *gcplogs) Close() error {
   231  	l.logger.Flush()
   232  	return l.client.Close()
   233  }
   234  
   235  func (l *gcplogs) Name() string {
   236  	return name
   237  }