github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/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  	"cloud.google.com/go/compute/metadata"
    12  	"cloud.google.com/go/logging"
    13  	"github.com/Sirupsen/logrus"
    14  	"golang.org/x/net/context"
    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  	logger    *logging.Logger
    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(info logger.Info) (logger.Logger, error) {
   104  	initGCP()
   105  
   106  	var project string
   107  	if projectID != "" {
   108  		project = projectID
   109  	}
   110  	if projectID, found := info.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  	// Issue #29344: gcplogs segfaults (static binary)
   118  	// If HOME is not set, logging.NewClient() will call os/user.Current() via oauth2/google.
   119  	// However, in static binary, os/user.Current() leads to segfault due to a glibc issue that won't be fixed
   120  	// in a short term. (golang/go#13470, https://sourceware.org/bugzilla/show_bug.cgi?id=19341)
   121  	// So we forcibly set HOME so as to avoid call to os/user/Current()
   122  	if err := ensureHomeIfIAmStatic(); err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	c, err := logging.NewClient(context.Background(), project)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	lg := c.Logger("gcplogs-docker-driver")
   131  
   132  	if err := c.Ping(context.Background()); err != nil {
   133  		return nil, fmt.Errorf("unable to connect or authenticate with Google Cloud Logging: %v", err)
   134  	}
   135  
   136  	l := &gcplogs{
   137  		logger: lg,
   138  		container: &containerInfo{
   139  			Name:      info.ContainerName,
   140  			ID:        info.ContainerID,
   141  			ImageName: info.ContainerImageName,
   142  			ImageID:   info.ContainerImageID,
   143  			Created:   info.ContainerCreated,
   144  			Metadata:  info.ExtraAttributes(nil),
   145  		},
   146  	}
   147  
   148  	if info.Config[logCmdKey] == "true" {
   149  		l.container.Command = info.Command()
   150  	}
   151  
   152  	if onGCE {
   153  		l.instance = &instanceInfo{
   154  			Zone: zone,
   155  			Name: instanceName,
   156  			ID:   instanceID,
   157  		}
   158  	} else if info.Config[logZoneKey] != "" || info.Config[logNameKey] != "" || info.Config[logIDKey] != "" {
   159  		l.instance = &instanceInfo{
   160  			Zone: info.Config[logZoneKey],
   161  			Name: info.Config[logNameKey],
   162  			ID:   info.Config[logIDKey],
   163  		}
   164  	}
   165  
   166  	// The logger "overflows" at a rate of 10,000 logs per second and this
   167  	// overflow func is called. We want to surface the error to the user
   168  	// without overly spamming /var/log/docker.log so we log the first time
   169  	// we overflow and every 1000th time after.
   170  	c.OnError = func(err error) {
   171  		if err == logging.ErrOverflow {
   172  			if i := atomic.AddUint64(&droppedLogs, 1); i%1000 == 1 {
   173  				logrus.Errorf("gcplogs driver has dropped %v logs", i)
   174  			}
   175  		} else {
   176  			logrus.Error(err)
   177  		}
   178  	}
   179  
   180  	return l, nil
   181  }
   182  
   183  // ValidateLogOpts validates the opts passed to the gcplogs driver. Currently, the gcplogs
   184  // driver doesn't take any arguments.
   185  func ValidateLogOpts(cfg map[string]string) error {
   186  	for k := range cfg {
   187  		switch k {
   188  		case projectOptKey, logLabelsKey, logEnvKey, logCmdKey, logZoneKey, logNameKey, logIDKey:
   189  		default:
   190  			return fmt.Errorf("%q is not a valid option for the gcplogs driver", k)
   191  		}
   192  	}
   193  	return nil
   194  }
   195  
   196  func (l *gcplogs) Log(m *logger.Message) error {
   197  	l.logger.Log(logging.Entry{
   198  		Timestamp: m.Timestamp,
   199  		Payload: &dockerLogEntry{
   200  			Instance:  l.instance,
   201  			Container: l.container,
   202  			Data:      string(m.Line),
   203  		},
   204  	})
   205  	return nil
   206  }
   207  
   208  func (l *gcplogs) Close() error {
   209  	l.logger.Flush()
   210  	return nil
   211  }
   212  
   213  func (l *gcplogs) Name() string {
   214  	return name
   215  }