Documentation ¶
Overview ¶
Package aelog adds support for structured logging in Google App Engine apps.
Basic usage ¶
In the simplest case, create a new Handler and use it for the default logger:
func main() { slog.SetDefault(slog.New(aelog.NewHandler(os.Stderr, nil, nil))) // register handlers and start server }
Then, any logging through the package-level functions of the slog package will be formatted as JSON in a structure that the Google Cloud Logging machinery can parse.
The handler maps logging levels to matching severities. Any unnamed level maps to the next-higher severity; for example, any level in the half-open interval (slog.LevelWarn, slog.LevelError] maps to ERROR. The package also defines a few additional named levels such as LevelNotice.
HTTP middleware ¶
To support additional logging entries for HTTP requests, you can use the Middleware function to install an HTTP middleware. For this to work, you need to wrap your HTTP handlers using Middleware and use the context-aware logging functions such as slog.InfoContext with a context returned by http.Request.Context (or a derived context). See the example for the Middleware function for a worked-out example.
Example ¶
package main import ( "log/slog" "os" "github.com/phst/aelog" ) func main() { // This shows the most basic use case. slog.SetDefault(slog.New(aelog.NewHandler(os.Stderr, nil, nil))) }
Output:
Index ¶
Examples ¶
Constants ¶
const ( SeverityKey = "severity" MessageKey = "message" TimeKey = "time" SourceLocationKey = "logging.googleapis.com/sourceLocation" )
Constants for special keys in the output record.
const ( LevelCritical = LevelError + 4*(iota+1) LevelAlert LevelEmergency LevelNotice = (LevelInfo + LevelWarn) / 2 )
Additional logging levels corresponding to Cloud Logging severities.
const ( LevelDebug = slog.LevelDebug LevelInfo = slog.LevelInfo LevelWarn = slog.LevelWarn LevelError = slog.LevelError )
Aliases for the standard levels, just for symmetry reasons.
Variables ¶
This section is empty.
Functions ¶
func Middleware ¶
Middleware returns a derived version of the given HTTP handler that calls it after ensuring that a Handler can extract HTTP-specific information from HTTP requests.
Example ¶
package main import ( "io" "log/slog" "net/http" "net/http/httptest" "os" "github.com/phst/aelog" ) func main() { // Suppress time and other noise. removeNoise := func(_ []string, a slog.Attr) slog.Attr { switch a.Key { case aelog.TimeKey, "userAgent", "remoteIp", "protocol": return slog.Group("") default: return a } } log := slog.New(aelog.NewHandler( os.Stdout, &slog.HandlerOptions{ReplaceAttr: removeNoise}, &aelog.Options{ProjectID: "test"}, )) handler := func(w http.ResponseWriter, r *http.Request) { log.InfoContext(r.Context(), "hi") io.WriteString(w, "ok") } srv := httptest.NewServer(aelog.Middleware(http.HandlerFunc(handler))) defer srv.Close() resp, err := srv.Client().Get(srv.URL) if err != nil { panic(err) } resp.Body.Close() req, err := http.NewRequest(http.MethodGet, srv.URL, nil) if err != nil { panic(err) } // https://cloud.google.com/trace/docs/setup#force-trace req.Header.Add("X-Cloud-Trace-Context", "abc/123;o=1") resp, err = srv.Client().Do(req) if err != nil { panic(err) } resp.Body.Close() }
Output: {"severity":"INFO","message":"hi","httpRequest":{"requestMethod":"GET","requestUrl":"/"}} {"severity":"INFO","message":"hi","httpRequest":{"requestMethod":"GET","requestUrl":"/"},"logging.googleapis.com/trace":"projects/test/traces/abc","logging.googleapis.com/spanId":"123"}
Types ¶
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is an slog.Handler that sends structured log messages in JSON format. Use NewHandler to create Handler objects; the zero Handler isn’t valid. Handler objects can’t be copied once created.
Example ¶
package main import ( "log/slog" "os" "github.com/phst/aelog" ) func main() { // Suppress timestamp noise. removeTime := func(_ []string, a slog.Attr) slog.Attr { if a.Key == aelog.TimeKey { return slog.Group("") } return a } log := slog.New(aelog.NewHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: removeTime}, nil)) log.Info("info") log.Warn("warning", "foo", "bar") log.Debug("this message won’t appear") log.With("foo", "bar").Info("info", "attr", 123) log.WithGroup("group").Error("error", "foo", "bar") }
Output: {"severity":"INFO","message":"info"} {"severity":"WARNING","message":"warning","foo":"bar"} {"severity":"INFO","message":"info","foo":"bar","attr":123} {"severity":"ERROR","message":"error","group":{"foo":"bar"}}
func NewHandler ¶
NewHandler creates a new Handler. The handler will write to the given io.Writer (typically os.Stderr). It can be configured using generic slog.HandlerOptions and App Engine-specific Options. Passing nil for any of the options has the same effect as passing a pointer to a zero struct.
If Options doesn’t contain a project ID, NewHandler attempts to auto-detect the current project; this typically works when running in production. If no project can be detected, tracing information won’t be filled out.
func (*Handler) Enabled ¶
Enabled implements slog.Handler.Enabled.
func (*Handler) Handle ¶
Handle implements slog.Handler.Handle.
func (*Handler) WithAttrs ¶
WithAttrs implements slog.Handler.WithAttrs.
type Options ¶
type Options struct { // Alphanumeric Google Cloud project ID of the current project. If // empty, NewHandler tries to auto-detect the project ID. ProjectID string }
Options contains additional options for configuring a Handler. It can be passed to NewHandler.