internal/log: add package

Package log implements event handlers for logging.

Change-Id: I271c4381f2bf159b4bea22c37b61e765f54327f1
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/463622
Reviewed-by: Julie Qiu <julieqiu@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Julie Qiu <julieqiu@google.com>
diff --git a/go.mod b/go.mod
index 2183769..f97b124 100644
--- a/go.mod
+++ b/go.mod
@@ -4,9 +4,11 @@
 
 require (
 	cloud.google.com/go/errorreporting v0.3.0
+	cloud.google.com/go/logging v1.6.1
 	github.com/client9/misspell v0.3.4
 	github.com/google/go-cmp v0.5.9
-	go.opencensus.io v0.23.0
+	go.opencensus.io v0.24.0
+	golang.org/x/exp/event v0.0.0-20230125214544-b3c2aaf6208d
 	golang.org/x/mod v0.7.0
 	golang.org/x/net v0.5.0
 	golang.org/x/tools v0.5.0
@@ -15,8 +17,10 @@
 )
 
 require (
+	cloud.google.com/go v0.105.0 // indirect
 	cloud.google.com/go/compute v1.12.1 // indirect
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
+	cloud.google.com/go/longrunning v0.3.0 // indirect
 	github.com/BurntSushi/toml v0.4.1 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
@@ -27,9 +31,9 @@
 	golang.org/x/sync v0.1.0 // indirect
 	golang.org/x/sys v0.4.0 // indirect
 	golang.org/x/text v0.6.0 // indirect
-	google.golang.org/api v0.102.0 // indirect
+	google.golang.org/api v0.103.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect
+	google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c // indirect
 	google.golang.org/grpc v1.50.1 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 01fb6ec..fab6761 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,16 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
+cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
 cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
 cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
 cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
 cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
 cloud.google.com/go/errorreporting v0.3.0 h1:kj1XEWMu8P0qlLhm3FwcaFsUvXChV/OraZwA70trRR0=
 cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=
+cloud.google.com/go/logging v1.6.1 h1:ZBsZK+JG+oCDT+vaxwqF2egKNRjz8soXiS6Xv79benI=
+cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=
+cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
+cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
 github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
@@ -14,6 +19,7 @@
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -54,12 +60,18 @@
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
-go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp/event v0.0.0-20230125214544-b3c2aaf6208d h1:uD2g//YGsKF7KHtDmYBg3pjgvFOIzUVfBZmKRMtgdd4=
+golang.org/x/exp/event v0.0.0-20230125214544-b3c2aaf6208d/go.mod h1:AVlZHjhWbW/3yOcmKMtJiObwBPJajBlUpQXRijFNrNc=
 golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM=
 golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -103,8 +115,8 @@
 golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
 golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I=
-google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=
+google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ=
+google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@@ -112,8 +124,8 @@
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y=
-google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
+google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c h1:S34D59DS2GWOEwWNt4fYmTcFrtlOgukG2k9WsomZ7tg=
+google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@@ -136,6 +148,7 @@
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA=
diff --git a/internal/log/gcpjson.go b/internal/log/gcpjson.go
new file mode 100644
index 0000000..1deaba6
--- /dev/null
+++ b/internal/log/gcpjson.go
@@ -0,0 +1,86 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package log
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"sync"
+	"time"
+
+	"golang.org/x/exp/event"
+)
+
+// NewGCPJSONLogger returns a handler which logs events in a format that is
+// understood by Google Cloud Platform logging.
+func NewGCPJSONHandler(w io.Writer, traceID string) event.Handler {
+	return &gcpJSONHandler{w: w, traceID: traceID}
+}
+
+type gcpJSONHandler struct {
+	traceID string
+	mu      sync.Mutex // ensure a log line is not interrupted
+	w       io.Writer
+}
+
+// Event implements event.Handler.Event.
+// It handles Log events and ignores all others.
+// See https://cloud.google.com/logging/docs/agent/logging/configuration#special-fields
+// for treatment of special fields.
+func (h *gcpJSONHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+	if ev.Kind != event.LogKind {
+		return ctx
+	}
+	h.mu.Lock()
+	defer h.mu.Unlock()
+
+	fmt.Fprintf(h.w, `{"time": %q`, ev.At.Format(time.RFC3339))
+	if h.traceID != "" {
+		fmt.Fprintf(h.w, `, "logging.googleapis.com/trace": %q`, h.traceID)
+	}
+	gcpLabels := map[string]string{}
+	for _, l := range ev.Labels {
+		var key string
+		switch l.Name {
+		case "msg":
+			key = "message"
+		case "level":
+			key = "severity"
+		default:
+			gcpLabels[l.Name] = l.String() // already quoted, regardless of label kind
+			continue
+		}
+		fmt.Fprintf(h.w, ", %q: ", key)
+		switch {
+		case !l.HasValue():
+			fmt.Fprint(h.w, "null")
+		case l.IsInt64():
+			fmt.Fprintf(h.w, "%d", l.Int64())
+		case l.IsUint64():
+			fmt.Fprintf(h.w, "%d", l.Uint64())
+		case l.IsFloat64():
+			fmt.Fprintf(h.w, "%g", l.Float64())
+		case l.IsBool():
+			fmt.Fprintf(h.w, "%t", l.Bool())
+		default:
+			fmt.Fprintf(h.w, "%q", l.String())
+		}
+	}
+	if len(gcpLabels) > 0 {
+		fmt.Fprintf(h.w, `, "logging.googleapis.com/labels": {`)
+		first := true
+		for k, v := range gcpLabels {
+			if !first {
+				fmt.Fprint(h.w, ", ")
+			}
+			first = false
+			fmt.Fprintf(h.w, "%q: %q", k, v)
+		}
+		fmt.Fprint(h.w, "}")
+	}
+	fmt.Fprint(h.w, "}\n")
+	return ctx
+}
diff --git a/internal/log/gcpjson_test.go b/internal/log/gcpjson_test.go
new file mode 100644
index 0000000..2dc7b36
--- /dev/null
+++ b/internal/log/gcpjson_test.go
@@ -0,0 +1,45 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package log
+
+import (
+	"bytes"
+	"context"
+	"testing"
+	"time"
+
+	"golang.org/x/exp/event"
+	"golang.org/x/exp/event/severity"
+)
+
+func TestGCPJSON(t *testing.T) {
+	now := time.Date(2002, 3, 4, 5, 6, 7, 0, time.UTC)
+	for _, test := range []struct {
+		ev   event.Event
+		want string
+	}{
+		{
+			ev: event.Event{
+				At:   now,
+				Kind: event.LogKind,
+				Labels: []event.Label{
+					event.String("msg", "hello"),
+					event.Int64("count", 17),
+					severity.Info.Label(),
+				},
+			},
+			want: `{"time": "2002-03-04T05:06:07Z", "logging.googleapis.com/trace": "tid", "message": "hello", "severity": "info", "logging.googleapis.com/labels": {"count": "17"}}
+`,
+		},
+	} {
+		var buf bytes.Buffer
+		h := &gcpJSONHandler{w: &buf, traceID: "tid"}
+		h.Event(context.Background(), &test.ev)
+		got := buf.String()
+		if got != test.want {
+			t.Errorf("%+v:\ngot  %s\nwant %s", test.ev, got, test.want)
+		}
+	}
+}
diff --git a/internal/log/log.go b/internal/log/log.go
new file mode 100644
index 0000000..7976bcf
--- /dev/null
+++ b/internal/log/log.go
@@ -0,0 +1,282 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package log implements event handlers for logging.
+package log
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"reflect"
+	"strings"
+	"sync"
+	"time"
+
+	"cloud.google.com/go/logging"
+	"golang.org/x/exp/event"
+	"golang.org/x/exp/event/severity"
+)
+
+// NewLineHandler returns an event Handler that writes log events one per line
+// in an easy-to-read format:
+//
+//	time level message label1=value1 label2=value2 ...
+func NewLineHandler(w io.Writer) event.Handler {
+	return &lineHandler{w: w}
+}
+
+type lineHandler struct {
+	mu sync.Mutex // ensure a log line is not interrupted
+	w  io.Writer
+}
+
+// Event implements event.Handler.Event for log events.
+func (h *lineHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+	if ev.Kind != event.LogKind {
+		return ctx
+	}
+	h.mu.Lock()
+	defer h.mu.Unlock()
+
+	var msg, level string
+	var others []string
+	for _, lab := range ev.Labels {
+		switch lab.Name {
+		case "msg":
+			msg = lab.String()
+		case "level":
+			level = strings.ToUpper(lab.String())
+		default:
+			others = append(others, fmt.Sprintf("%s=%s", lab.Name, lab.String()))
+		}
+	}
+	var s string
+	if len(others) > 0 {
+		s = " " + strings.Join(others, " ")
+	}
+	if level != "" {
+		level = " " + level
+	}
+	fmt.Fprintf(h.w, "%s%s %s%s\n", ev.At.Format("2006/01/02 15:04:05"), level, msg, s)
+	return ctx
+}
+
+type Labels []event.Label
+
+func With(kvs ...interface{}) Labels {
+	return Labels(nil).With(kvs...)
+}
+
+func (ls Labels) With(kvs ...interface{}) Labels {
+	if len(kvs)%2 != 0 {
+		panic("args must be key-value pairs")
+	}
+	for i := 0; i < len(kvs); i += 2 {
+		ls = append(ls, pairToLabel(kvs[i].(string), kvs[i+1]))
+	}
+	return ls
+}
+
+func pairToLabel(name string, value interface{}) event.Label {
+	if d, ok := value.(time.Duration); ok {
+		return event.Duration(name, d)
+	}
+	v := reflect.ValueOf(value)
+	switch v.Kind() {
+	case reflect.String:
+		return event.String(name, v.String())
+	case reflect.Bool:
+		return event.Bool(name, v.Bool())
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return event.Int64(name, v.Int())
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return event.Uint64(name, v.Uint())
+	case reflect.Float32, reflect.Float64:
+		return event.Float64(name, v.Float())
+	default:
+		return event.Value(name, value)
+	}
+}
+
+func (l Labels) logf(ctx context.Context, s severity.Level, format string, args ...interface{}) {
+	event.Log(ctx, fmt.Sprintf(format, args...), append(l, s.Label())...)
+}
+
+func (l Labels) Debugf(ctx context.Context, format string, args ...interface{}) {
+	l.logf(ctx, severity.Debug, format, args...)
+}
+
+func (l Labels) Infof(ctx context.Context, format string, args ...interface{}) {
+	l.logf(ctx, severity.Info, format, args...)
+}
+
+func (l Labels) Warningf(ctx context.Context, format string, args ...interface{}) {
+	l.logf(ctx, severity.Warning, format, args...)
+}
+
+func (l Labels) Errorf(ctx context.Context, format string, args ...interface{}) {
+	l.logf(ctx, severity.Error, format, args...)
+}
+
+var (
+	mu     sync.Mutex
+	logger interface {
+		log(context.Context, logging.Severity, interface{})
+	} = stdlibLogger{}
+
+	// currentLevel holds current log level.
+	// No logs will be printed below currentLevel.
+	currentLevel = logging.Default
+)
+
+type (
+	// traceIDKey is the type of the context key for trace IDs.
+	traceIDKey struct{}
+
+	// labelsKey is the type of the context key for labels.
+	labelsKey struct{}
+)
+
+// Set the log level
+func SetLevel(v string) {
+	mu.Lock()
+	defer mu.Unlock()
+	currentLevel = toLevel(v)
+}
+
+func getLevel() logging.Severity {
+	mu.Lock()
+	defer mu.Unlock()
+	return currentLevel
+}
+
+// NewContextWithTraceID creates a new context from ctx that adds the trace ID.
+func NewContextWithTraceID(ctx context.Context, traceID string) context.Context {
+	return context.WithValue(ctx, traceIDKey{}, traceID)
+}
+
+// NewContextWithLabel creates a new context from ctx that adds a label that will
+// appear in the log entry.
+func NewContextWithLabel(ctx context.Context, key, value string) context.Context {
+	oldLabels, _ := ctx.Value(labelsKey{}).(map[string]string)
+	// Copy the labels, to preserve immutability of contexts.
+	newLabels := map[string]string{}
+	for k, v := range oldLabels {
+		newLabels[k] = v
+	}
+	newLabels[key] = value
+	return context.WithValue(ctx, labelsKey{}, newLabels)
+}
+
+// stdlibLogger uses the Go standard library logger.
+type stdlibLogger struct{}
+
+func (stdlibLogger) log(ctx context.Context, s logging.Severity, payload interface{}) {
+	var extras []string
+	traceID, _ := ctx.Value(traceIDKey{}).(string) // if not present, traceID is ""
+	if traceID != "" {
+		extras = append(extras, fmt.Sprintf("traceID %s", traceID))
+	}
+	if labels, ok := ctx.Value(labelsKey{}).(map[string]string); ok {
+		extras = append(extras, fmt.Sprint(labels))
+	}
+	var extra string
+	if len(extras) > 0 {
+		extra = " (" + strings.Join(extras, ", ") + ")"
+	}
+	log.Printf("%s%s: %+v", s, extra, payload)
+
+}
+
+// Infof logs a formatted string at the Info level.
+func Infof(ctx context.Context, format string, args ...interface{}) {
+	logf(ctx, logging.Info, format, args)
+}
+
+// Warningf logs a formatted string at the Warning level.
+func Warningf(ctx context.Context, format string, args ...interface{}) {
+	logf(ctx, logging.Warning, format, args)
+}
+
+// Errorf logs a formatted string at the Error level.
+func Errorf(ctx context.Context, format string, args ...interface{}) {
+	logf(ctx, logging.Error, format, args)
+}
+
+// Debugf logs a formatted string at the Debug level.
+func Debugf(ctx context.Context, format string, args ...interface{}) {
+	logf(ctx, logging.Debug, format, args)
+}
+
+// Fatalf logs formatted string at the Critical level followed by exiting the program.
+func Fatalf(ctx context.Context, format string, args ...interface{}) {
+	logf(ctx, logging.Critical, format, args)
+	die()
+}
+
+func logf(ctx context.Context, s logging.Severity, format string, args []interface{}) {
+	doLog(ctx, s, fmt.Sprintf(format, args...))
+}
+
+// Info logs arg, which can be a string or a struct, at the Info level.
+func Info(ctx context.Context, arg interface{}) { doLog(ctx, logging.Info, arg) }
+
+// Warning logs arg, which can be a string or a struct, at the Warning level.
+func Warning(ctx context.Context, arg interface{}) { doLog(ctx, logging.Warning, arg) }
+
+// Error logs arg, which can be a string or a struct, at the Error level.
+func Error(ctx context.Context, arg interface{}) { doLog(ctx, logging.Error, arg) }
+
+// Debug logs arg, which can be a string or a struct, at the Debug level.
+func Debug(ctx context.Context, arg interface{}) { doLog(ctx, logging.Debug, arg) }
+
+// Fatal logs arg, which can be a string or a struct, at the Critical level followed by exiting the program.
+func Fatal(ctx context.Context, arg interface{}) {
+	doLog(ctx, logging.Critical, arg)
+	die()
+}
+
+func doLog(ctx context.Context, s logging.Severity, payload interface{}) {
+	if getLevel() > s {
+		return
+	}
+	mu.Lock()
+	l := logger
+	mu.Unlock()
+	l.log(ctx, s, payload)
+}
+
+func die() {
+	os.Exit(1)
+}
+
+// toLevel returns the logging.Severity for a given string.
+// Possible input values are "", "debug", "info", "warning", "error", "fatal".
+// In case of invalid string input, it maps to DefaultLevel.
+func toLevel(v string) logging.Severity {
+	v = strings.ToLower(v)
+
+	switch v {
+	case "":
+		// default log level will print everything.
+		return logging.Default
+	case "debug":
+		return logging.Debug
+	case "info":
+		return logging.Info
+	case "warning":
+		return logging.Warning
+	case "error":
+		return logging.Error
+	case "fatal":
+		return logging.Critical
+	}
+
+	// Default log level in case of invalid input.
+	log.Printf("Error: %s is invalid LogLevel. Possible values are [debug, info, warning, error, fatal]", v)
+	return logging.Default
+}