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