gaelog

package module
v1.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2023 License: Apache-2.0 Imports: 11 Imported by: 4

README

Easy Stackdriver Logging on Google App Engine Standard second generation runtimes and Cloud Run

GoDoc Go Report Card

Using Stackdriver Logging on App Engine Standard and Cloud Run is complicated. It doesn't have to be that way.

package main

import (
  "fmt"
  "log"
  "net/http"
  "os"

  "github.com/mtraver/gaelog"
)

// wrappedHandler must be wrapped using gaelog.Wrap or gaelog.WrapWithID so that the
// request context can be used with the package-level logging functions.
type wrappedHandler struct{}

func (h wrappedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  if r.URL.Path != "/" {
    http.NotFound(w, r)
    return
  }

  ctx := r.Context()

  gaelog.Debugf(ctx, "Debug")
  gaelog.Infof(ctx, "Info")
  gaelog.Noticef(ctx, "Notice")
  gaelog.Warningf(ctx, "Warning")
  gaelog.Errorf(ctx, "Error")
  gaelog.Criticalf(ctx, "Critical")
  gaelog.Alertf(ctx, "Alert")
  gaelog.Emergencyf(ctx, "Emergency")

  message := struct {
    Places []string
  }{
    []string{"Kings Canyon", "Sequoia", "Yosemite", "Death Valley"},
  }

  gaelog.Info(ctx, message)

  fmt.Fprintf(w, "Hello!")
}

// manualHandler creates and closes a logger manually. This usage does not require
// gaelog.Wrap or gaelog.WrapWithID.
func manualHandler(w http.ResponseWriter, r *http.Request) {
  lg, err := gaelog.New(r)
  if err != nil {
    // The returned logger is valid despite the error. It falls back to logging
    // via the standard library's "log" package.
    lg.Errorf("Failed to make logger: %v", err)
  }
  defer lg.Close()

  lg.Warningf("Some important info right here, that's for sure")

  fmt.Fprintf(w, "Hello!")
}

func main() {
  // Wrap the handler.
  http.Handle("/", gaelog.Wrap(wrappedHandler{}))

  http.HandleFunc("/manual", manualHandler)

  port := os.Getenv("PORT")
  if port == "" {
    port = "8080"
  }
  log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}

Screenshot of logs in Stackdriver UI

What's it doing under the hood?

At its core, gaelog is setting up the MonitoredResource and trace ID of the underlying Stackdriver Logging logger. The trace ID is retrieved from the X-Cloud-Trace-Context header and the app info is retrieved from environment variables.

On App Engine the MonitoredResource is set as follows to match that set by App Engine itself on request logs. The type, "gae_app", is the literal value that App Engine uses (and gaelog mimics). The other strings below are placeholders for your app's info.

resource: {
  labels: {
    module_id: "my-service-id"
    project_id: "my-project-id"
    version_id: "my-version-id"
  }
  type: "gae_app"
}

Logging on Cloud Run

Imagine you containerize your App Engine app and deploy it to Cloud Run. It would be nice if logs were still correlated on Cloud Run, right? Well, they will be. If the app info as expected on App Engine is not present then gaelog will look up the info as it's expected on Cloud Run.

On Cloud Run the MonitoredResource is set as follows to match that set by Cloud Run itself on request logs. The type, "cloud_run_revision", is the literal value that Cloud Run uses (and gaelog mimics). The other strings below are placeholders for your revision's info.

resource: {
  labels: {
    configuration_name: "my-config-name"
    project_id: "my-project-id"
    revision_name: "my-revision"
    service_name: "my-service"
  }
  type: "cloud_run_revision"
}

Known issues

  1. Request log (aka parent) severity is not set. A nice property of google.golang.org/appengine/log is that the severity of the request log entry (aka parent entry) is set to the maximum severity of the log entries correlated with it. This makes it easy to see in the Stackdriver UI which requests have logs associated with them, and at which severity. Alas, that is not possible with this package. App Engine itself makes the request log entries and it does not know about any correlated entries created separately (such as with this package). Furthermore, entries cannot be modified after they are created. A possible remedy is for this package to emit request log entries of its own; open an issue if you'd like this and we can discuss.

  2. If a request has any log entries made by google.golang.org/appengine/log or App Engine itself, then entries made by this package will not be correlated (i.e. nested) with the request in the Stackdriver Logging UI. Such logs are "embedded" logs—if you expand the request log entry you'll see the other log lines embedded there in the data structure. Embedded and correlated logs do not play nicely together. An annoying consequence is that requests that start a new instance or that time out will not be correlated with logs emitted via this package because App Engine adds embedded logs on such requests. This issue is not solvable in this package.

Documentation

Overview

Package gaelog provides easy Stackdriver Logging on Google App Engine Standard second generation runtimes.

Index

Examples

Constants

View Source
const (
	// DefaultLogID is the default log ID of the underlying Stackdriver Logging logger. Request
	// logs are logged under the ID "request_log", so use "app_log" for consistency. To use a
	// different ID create your logger with NewWithID.
	DefaultLogID = "app_log"

	// GAEAppResourceType is the type set on the logger's MonitoredResource for App Engine apps.
	// This matches the type that App Engine itself assigns to request logs.
	GAEAppResourceType = "gae_app"

	// CloudRunResourceType is the type set on the logger's MonitoredResource for Cloud Run revisions.
	// This matches the type that Cloud Run itself assigns to request logs.
	CloudRunResourceType = "cloud_run_revision"
)

Variables

This section is empty.

Functions

func Alert added in v0.2.0

func Alert(ctx context.Context, v interface{})

Alert calls Log with alert severity.

func Alertf added in v0.2.0

func Alertf(ctx context.Context, format string, v ...interface{})

Alertf calls Logf with alert severity.

func Critical added in v0.2.0

func Critical(ctx context.Context, v interface{})

Critical calls Log with critical severity.

func Criticalf added in v0.2.0

func Criticalf(ctx context.Context, format string, v ...interface{})

Criticalf calls Logf with critical severity.

func Debug added in v0.2.0

func Debug(ctx context.Context, v interface{})

Debug calls Log with debug severity.

func Debugf added in v0.2.0

func Debugf(ctx context.Context, format string, v ...interface{})

Debugf calls Logf with debug severity.

func Emergency added in v0.2.0

func Emergency(ctx context.Context, v interface{})

Emergency calls Log with emergency severity.

func Emergencyf added in v0.2.0

func Emergencyf(ctx context.Context, format string, v ...interface{})

Emergencyf calls Logf with emergency severity.

func Error added in v0.2.0

func Error(ctx context.Context, v interface{})

Error calls Log with error severity.

func Errorf added in v0.2.0

func Errorf(ctx context.Context, format string, v ...interface{})

Errorf calls Logf with error severity.

func Info added in v0.2.0

func Info(ctx context.Context, v interface{})

Info calls Log with info severity.

func Infof added in v0.2.0

func Infof(ctx context.Context, format string, v ...interface{})

Infof calls Logf with info severity.

func Log added in v0.2.0

func Log(ctx context.Context, severity logging.Severity, v interface{})

Log logs with the given severity. v must be either a string, or something that marshals via the encoding/json package to a JSON object (and not any other type of JSON value). This should be called from a handler that has been wrapped with Wrap or WrapWithID. If it is called from a handler that has not been wrapped then messages are simply logged using the standard library's log package.

func Logf added in v0.2.0

func Logf(ctx context.Context, severity logging.Severity, format string, v ...interface{})

Logf logs with the given severity. Remaining arguments are handled in the manner of fmt.Printf. This should be called from a handler that has been wrapped with Wrap or WrapWithID. If it is called from a handler that has not been wrapped then messages are simply logged using the standard library's log package.

func Notice added in v0.2.0

func Notice(ctx context.Context, v interface{})

Notice calls Log with notice severity.

func Noticef added in v0.2.0

func Noticef(ctx context.Context, format string, v ...interface{})

Noticef calls Logf with notice severity.

func Warning added in v0.2.0

func Warning(ctx context.Context, v interface{})

Warning calls Log with warning severity.

func Warningf added in v0.2.0

func Warningf(ctx context.Context, format string, v ...interface{})

Warningf calls Logf with warning severity.

func Wrap added in v0.2.0

func Wrap(h http.Handler, options ...logging.LoggerOption) http.Handler

Wrap is identical to WrapWithID with the exception that it uses the default log ID.

Example
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/mtraver/gaelog"
)

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		gaelog.Warningf(ctx, "Some important info right here, that's for sure")

		fmt.Fprintf(w, "Hey")
	})

	http.Handle("/", gaelog.Wrap(handler))

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}
Output:

func WrapWithID added in v0.2.0

func WrapWithID(h http.Handler, logID string, options ...logging.LoggerOption) http.Handler

WrapWithID wraps a handler such that the request's context may be used to call the package-level logging functions. See NewWithID for details on this function's arguments and how the logger is created.

Example
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/mtraver/gaelog"
)

func main() {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		gaelog.Warningf(ctx, "Some important info right here, that's for sure")

		fmt.Fprintf(w, "Hey")
	})

	http.Handle("/", gaelog.WrapWithID(handler, "my_log"))

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}
Output:

Types

type Logger

type Logger struct {
	// contains filtered or unexported fields
}

A Logger logs messages to Stackdriver Logging (though in certain cases it may fall back to the standard library's "log" package; see New). Logs will be correlated with requests in Stackdriver.

func New

func New(r *http.Request, options ...logging.LoggerOption) (*Logger, error)

New is identical to NewWithID with the exception that it uses the default log ID.

func NewWithID

func NewWithID(r *http.Request, logID string, options ...logging.LoggerOption) (*Logger, error)

NewWithID creates a new Logger. The Logger is initialized using environment variables that are present on App Engine:

  • GOOGLE_CLOUD_PROJECT
  • GAE_SERVICE
  • GAE_VERSION

If they are not present then it is initialized using environment variables present on Cloud Run:

  • K_SERVICE
  • K_REVISION
  • K_CONFIGURATION
  • Project ID is fetched from the metadata server, not an env var

The given log ID will be passed through to the underlying Stackdriver Logging logger.

Additionally, options (of type LoggerOption, from cloud.google.com/go/logging) will be passed through to the underlying Stackdriver Logging logger. Note that the option CommonResource will have no effect because the MonitoredResource is set when each log entry is made, thus overriding any value set with CommonResource. This is intended: much of the value of this package is in setting up the MonitoredResource so that log entries correlate with requests.

The Logger will be valid in all cases, even when the error is non-nil. In the case of a non-nil error the Logger will fall back to the standard library's "log" package. There are three cases in which the error will be non-nil:

  1. Any of the aforementioned environment variables are not set.
  2. The given http.Request does not have the X-Cloud-Trace-Context header.
  3. Initialization of the underlying Stackdriver Logging client produced an error.

func (*Logger) Alert

func (lg *Logger) Alert(v interface{})

Alert calls Log with alert severity.

func (*Logger) Alertf

func (lg *Logger) Alertf(format string, v ...interface{})

Alertf calls Logf with alert severity.

func (*Logger) Close

func (lg *Logger) Close() error

Close closes the Logger, ensuring all logs are flushed and closing the underlying Stackdriver Logging client.

func (*Logger) Critical

func (lg *Logger) Critical(v interface{})

Critical calls Log with critical severity.

func (*Logger) Criticalf

func (lg *Logger) Criticalf(format string, v ...interface{})

Criticalf calls Logf with critical severity.

func (*Logger) Debug

func (lg *Logger) Debug(v interface{})

Debug calls Log with debug severity.

func (*Logger) Debugf

func (lg *Logger) Debugf(format string, v ...interface{})

Debugf calls Logf with debug severity.

func (*Logger) Emergency

func (lg *Logger) Emergency(v interface{})

Emergency calls Log with emergency severity.

func (*Logger) Emergencyf

func (lg *Logger) Emergencyf(format string, v ...interface{})

Emergencyf calls Logf with emergency severity.

func (*Logger) Error

func (lg *Logger) Error(v interface{})

Error calls Log with error severity.

func (*Logger) Errorf

func (lg *Logger) Errorf(format string, v ...interface{})

Errorf calls Logf with error severity.

func (*Logger) Info

func (lg *Logger) Info(v interface{})

Info calls Log with info severity.

func (*Logger) Infof

func (lg *Logger) Infof(format string, v ...interface{})

Infof calls Logf with info severity.

func (*Logger) Log

func (lg *Logger) Log(severity logging.Severity, v interface{})

Log logs with the given severity. v must be either a string, or something that marshals via the encoding/json package to a JSON object (and not any other type of JSON value).

func (*Logger) Logf

func (lg *Logger) Logf(severity logging.Severity, format string, v ...interface{})

Logf logs with the given severity. Remaining arguments are handled in the manner of fmt.Printf.

func (*Logger) Notice

func (lg *Logger) Notice(v interface{})

Notice calls Log with notice severity.

func (*Logger) Noticef

func (lg *Logger) Noticef(format string, v ...interface{})

Noticef calls Logf with notice severity.

func (*Logger) Warning

func (lg *Logger) Warning(v interface{})

Warning calls Log with warning severity.

func (*Logger) Warningf

func (lg *Logger) Warningf(format string, v ...interface{})

Warningf calls Logf with warning severity.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL