github.com/dpiddy/docker@v1.12.2-rc1/daemon/logger/gcplogs/gcplogging.go (about)

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