// 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 }
// 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 }
// 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 }
// 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 }
// 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 }
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), }) } }) }) }) }