github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/daemon/logger/gcplogs/gcplogging.go (about)

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