// NewClient returns a new log client, logging to the named log in the
// provided project.
//
// The exported fields on the returned client may be modified before
// the client is used for logging. Once log entries are in flight,
// the fields must not be modified.
func NewClient(ctx context.Context, projectID, logName string, opts ...cloud.ClientOption) (*Client, error) {
	httpClient, endpoint, err := transport.NewHTTPClient(ctx, append([]cloud.ClientOption{
		cloud.WithEndpoint(prodAddr),
		cloud.WithScopes(Scope),
		cloud.WithUserAgent(userAgent),
	}, opts...)...)
	if err != nil {
		return nil, err
	}
	svc, err := api.New(httpClient)
	if err != nil {
		return nil, err
	}
	svc.BasePath = endpoint
	c := &Client{
		svc:     svc,
		logs:    api.NewProjectsLogsEntriesService(svc),
		logName: logName,
		projID:  projectID,
	}
	for i := range c.writer {
		level := Level(i)
		c.writer[level] = levelWriter{level, c}
		c.logger[level] = log.New(c.writer[level], "", 0)
	}
	return c, nil
}
Exemple #2
0
// getLogger chooses a base logger instance to use.
//
// If we're running on GCE, we will use a "cloud logging" logger; otherwise,
// we will use a console logger.
//
// The returned function should be called on application termination to flush
// the logger.
func (c *loggerConfig) use(ctx context.Context, client *http.Client) (context.Context, func(), error) {
	if c.projectName == "" {
		return ctx, nil, errors.New("logging: You must supply a project name")
	}
	if c.resourceType == "" {
		return ctx, nil, errors.New("logging: You must supply a resource type")
	}
	if c.logsID == "" {
		return ctx, nil, errors.New("logging: You must supply a logs ID")
	}

	configCopy := *c
	configCopy.labels = make(map[string]string)
	for k, v := range c.labels {
		configCopy.labels[k] = v
	}
	if c.resourceType != "" {
		configCopy.labels["compute.googleapis.com/resource_type"] = c.resourceType
	}
	if c.resourceID != "" {
		configCopy.labels["compute.googleapis.com/resource_id"] = c.resourceID
	}

	if configCopy.sessionID == "" {
		sessionID := make([]byte, loggingSessionIDSize)
		if _, err := rand.Read(sessionID); err != nil {
			return ctx, nil, err
		}

		configCopy.sessionID = base64.URLEncoding.EncodeToString(sessionID)
	}

	// Load GCE credentials for cloud logger.
	service, err := cloudlog.New(client)
	if err != nil {
		return ctx, nil, err
	}

	clog := newCloudLogging(ctx, &configCopy, service)

	ctx = log.SetFactory(ctx, func(c context.Context) log.Logger {
		return clog.bind(c)
	})
	return ctx, clog.finish, nil
}
Exemple #3
0
// NewClient returns new object that knows how to push log entries to a single
// log in Cloud Logging.
func NewClient(opts ClientOptions, client *http.Client) (Client, error) {
	if err := opts.Validate(); err != nil {
		return nil, err
	}

	if opts.ResourceType == "" {
		opts.ResourceType = DefaultResourceType
	}
	if opts.ResourceID == "" {
		var err error
		hostname, err := os.Hostname()
		if err != nil {
			return nil, err
		}
		opts.ResourceID = hostname
	}
	if opts.LogID == "" {
		return nil, errors.New("cloudlogging: no LogID is provided")
	}

	service, err := cloudlog.New(client)
	if err != nil {
		return nil, err
	}
	if opts.UserAgent != "" {
		service.UserAgent = opts.UserAgent
	}

	c := clientImpl{
		ClientOptions: &opts,
		service:       cloudlog.NewProjectsLogsEntriesService(service),
		commonLabels:  make(map[string]string, len(opts.CommonLabels)),
	}
	for k, v := range opts.CommonLabels {
		c.commonLabels[k] = v
	}
	if c.ResourceType != "" {
		c.commonLabels["compute.googleapis.com/resource_type"] = c.ResourceType
	}
	if c.ResourceID != "" {
		c.commonLabels["compute.googleapis.com/resource_id"] = c.ResourceID
	}
	return &c, nil
}
Exemple #4
0
// NewClient returns new object that knows how to push log entries to a single
// log in Cloud Logging.
func NewClient(opts ClientOptions) (Client, error) {
	if opts.Logger == nil {
		opts.Logger = logging.Null()
	}
	if opts.ProjectID == "" {
		return nil, fmt.Errorf("no ProjectID is provided")
	}
	if opts.ResourceType == "" {
		opts.ResourceType = DefaultResourceType
	}
	if opts.ResourceID == "" {
		var err error
		hostname, err := os.Hostname()
		if err != nil {
			return nil, err
		}
		opts.ResourceID = hostname
	}
	if opts.LogID == "" {
		return nil, fmt.Errorf("no LogID is provided")
	}
	service, err := cloudlog.New(opts.Client)
	if err != nil {
		return nil, err
	}
	service.UserAgent = opts.UserAgent
	return &loggingClient{
		opts: opts,
		commonLabels: map[string]string{
			"compute.googleapis.com/resource_id":   opts.ResourceID,
			"compute.googleapis.com/resource_type": opts.ResourceType,
		},
		serviceName: "compute.googleapis.com",
		writeFunc: func(projID, logID string, req *cloudlog.WriteLogEntriesRequest) error {
			_, err := service.Projects.Logs.Entries.Write(projID, logID, req).Do()
			return err
		},
	}, nil
}
Exemple #5
0
// NewClient returns a new log client, logging to the named log.  The
// log must exist in the Google Cloud Platform project ID associated
// with the provided context. Use the google.golang.org/cloud package
// to create a context.
//
// The exported fields on the returned client may be modified before
// the client is used for logging. Once log entries are in flight,
// the fields must not be modified.
func NewClient(ctx context.Context, logName string) (*Client, error) {
	projID := internal.ProjID(ctx)
	httpClient := internal.HTTPClient(ctx)
	if projID == "" || httpClient == nil {
		return nil, errors.New("logging: invalid or non-google.golang.org/cloud Context")
	}
	svc, err := api.New(httpClient)
	if err != nil {
		return nil, err
	}
	c := &Client{
		svc:     svc,
		logs:    api.NewProjectsLogsEntriesService(svc),
		logName: logName,
		projID:  projID,
	}
	for i := range c.writer {
		level := Level(i)
		c.writer[level] = levelWriter{level, c}
		c.logger[level] = log.New(c.writer[level], "", 0)
	}
	return c, nil
}
Exemple #6
0
func TestCloudLogging(t *testing.T) {
	Convey(`A cloud logging instance using a test HTTP client/server`, t, func() {
		ctx, _ := testclock.UseTime(context.Background(), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC))

		// Do not retry.
		ctx = retry.Use(ctx, func(context.Context) retry.Iterator {
			return &retry.Limited{}
		})

		h := &testCloudLoggingHandler{
			logC: make(chan *cloudLogBundle, 1),
		}
		srv := httptest.NewServer(h)
		defer srv.Close()

		tr := &http.Transport{
			DialTLS: func(network, addr string) (net.Conn, error) {
				u, err := url.Parse(srv.URL)
				if err != nil {
					return nil, err
				}
				return net.Dial(network, u.Host)
			},
		}
		client := &http.Client{
			Transport: tr,
		}

		config := loggerConfig{
			projectName:  "test-project",
			resourceType: "test-resource",
			logsID:       "test-logs-id",
		}

		service, err := cloudlog.New(client)
		So(err, ShouldBeNil)

		cl := newCloudLogging(ctx, &config, service)
		defer cl.finish()

		Convey(`A bound cloud logging instance`, func() {
			l := cl.bind(ctx)

			Convey(`Can publish logging data.`, func() {
				l.Infof("Message at %s", "INFO")

				bundle := <-h.logC
				So(len(bundle.Entries), ShouldEqual, 1)
				So(bundle.Entries[0], shouldMatchLog, &cloudlog.LogEntry{
					InsertId: "-0-0",
					Metadata: &cloudlog.LogEntryMetadata{
						ProjectId: "test-project",
						Severity:  "INFO",
						Timestamp: "2015-01-01T00:00:00Z",
					},
					TextPayload: "Message at INFO",
				})
			})

			Convey(`Will batch logging data.`, func() {
				cl.testLogAckC = make(chan []*logEntry, 1)

				// The first message will be read immediately.
				l.Infof("Initial unbatched message.")
				<-cl.testLogAckC

				// The next set of messages will be batched, since we're not release our
				// HTTP server yet.
				for i := 0; i < cloudLoggingBatchSize; i++ {
					l.Infof("Batch message #%d", i)
				}
				<-cl.testLogAckC

				// Read the first bundle.
				bundle := <-h.logC
				So(len(bundle.Entries), ShouldEqual, 1)
				So(bundle.Entries[0], shouldMatchLog, &cloudlog.LogEntry{
					InsertId: "-0-0",
					Metadata: &cloudlog.LogEntryMetadata{
						ProjectId: "test-project",
						Severity:  "INFO",
						Timestamp: "2015-01-01T00:00:00Z",
					},
					TextPayload: "Initial unbatched message.",
				})

				// Read the second bundle.
				bundle = <-h.logC
				So(len(bundle.Entries), ShouldEqual, cloudLoggingBatchSize)
				for i, entry := range bundle.Entries {
					So(entry, shouldMatchLog, &cloudlog.LogEntry{
						InsertId: fmt.Sprintf("-1-%d", i),
						Metadata: &cloudlog.LogEntryMetadata{
							ProjectId: "test-project",
							Severity:  "INFO",
							Timestamp: "2015-01-01T00:00:00Z",
						},
						TextPayload: fmt.Sprintf("Batch message #%d", i),
					})
				}
			})
		})
	})
}