Repository: google/exposure-notifications-server
Pull request #850 (2020-08-08 21:42:56)

Always configure a logger on the context
Always create and include a logger on the context, make logging package public

**Release Note**

```release-note
Always include a logger object and make logging package public
```

google-oss-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/850#" title="Author self-approved">sethvargo</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/main/OWNERS)~~ [sethvargo]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
sethvargo: /assign @mikehelmick 
google-oss-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/850#pullrequestreview-463831239" title="Approved">mikehelmick</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/850#" title="Author self-approved">sethvargo</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/main/OWNERS)~~ [mikehelmick,sethvargo]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/cmd/cleanup-export/main.go b/cmd/cleanup-export/main.go
index 711cf62..bdcf9e3 100644
--- a/cmd/cleanup-export/main.go
+++ b/cmd/cleanup-export/main.go
@@ -21,8 +21,8 @@ import (
 	"net/http"
 
 	"github.com/google/exposure-notifications-server/internal/cleanup"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/setup"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	_ "github.com/google/exposure-notifications-server/pkg/observability"
 	"github.com/google/exposure-notifications-server/pkg/server"
 	"github.com/sethvargo/go-signalcontext"
@@ -30,7 +30,9 @@ import (
 
 func main() {
 	ctx, done := signalcontext.OnInterrupt()
-	logger := logging.FromContext(ctx)
+
+	logger := logging.NewLogger(true)
+	ctx = logging.WithLogger(ctx, logger)
 
 	err := realMain(ctx)
 	done()
diff --git a/cmd/cleanup-exposure/main.go b/cmd/cleanup-exposure/main.go
index 0ef72f7..d39b19d 100644
--- a/cmd/cleanup-exposure/main.go
+++ b/cmd/cleanup-exposure/main.go
@@ -21,8 +21,8 @@ import (
 	"net/http"
 
 	"github.com/google/exposure-notifications-server/internal/cleanup"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/setup"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	_ "github.com/google/exposure-notifications-server/pkg/observability"
 	"github.com/google/exposure-notifications-server/pkg/server"
 	"github.com/sethvargo/go-signalcontext"
@@ -30,7 +30,9 @@ import (
 
 func main() {
 	ctx, done := signalcontext.OnInterrupt()
-	logger := logging.FromContext(ctx)
+
+	logger := logging.NewLogger(true)
+	ctx = logging.WithLogger(ctx, logger)
 
 	err := realMain(ctx)
 	done()
diff --git a/cmd/debugger/main.go b/cmd/debugger/main.go
index ba46869..5c0091f 100644
--- a/cmd/debugger/main.go
+++ b/cmd/debugger/main.go
@@ -20,15 +20,17 @@ import (
 	"fmt"
 
 	"github.com/google/exposure-notifications-server/internal/debugger"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/setup"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"github.com/google/exposure-notifications-server/pkg/server"
 	"github.com/sethvargo/go-signalcontext"
 )
 
 func main() {
 	ctx, done := signalcontext.OnInterrupt()
-	logger := logging.FromContext(ctx)
+
+	logger := logging.NewLogger(true)
+	ctx = logging.WithLogger(ctx, logger)
 
 	err := realMain(ctx)
 	done()
diff --git a/cmd/export/main.go b/cmd/export/main.go
index 5edbdae..f0f0518 100644
--- a/cmd/export/main.go
+++ b/cmd/export/main.go
@@ -20,8 +20,8 @@ import (
 	"fmt"
 
 	"github.com/google/exposure-notifications-server/internal/export"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/setup"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	_ "github.com/google/exposure-notifications-server/pkg/observability"
 	"github.com/google/exposure-notifications-server/pkg/server"
 	"github.com/sethvargo/go-signalcontext"
@@ -29,7 +29,9 @@ import (
 
 func main() {
 	ctx, done := signalcontext.OnInterrupt()
-	logger := logging.FromContext(ctx)
+
+	logger := logging.NewLogger(true)
+	ctx = logging.WithLogger(ctx, logger)
 
 	err := realMain(ctx)
 	done()
diff --git a/cmd/exposure/main.go b/cmd/exposure/main.go
index 3d7e1fe..5843bcb 100644
--- a/cmd/exposure/main.go
+++ b/cmd/exposure/main.go
@@ -20,9 +20,9 @@ import (
 	"fmt"
 	"net/http"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/publish"
 	"github.com/google/exposure-notifications-server/internal/setup"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	_ "github.com/google/exposure-notifications-server/pkg/observability"
 	"github.com/google/exposure-notifications-server/pkg/server"
 	"github.com/sethvargo/go-signalcontext"
@@ -30,7 +30,9 @@ import (
 
 func main() {
 	ctx, done := signalcontext.OnInterrupt()
-	logger := logging.FromContext(ctx)
+
+	logger := logging.NewLogger(true)
+	ctx = logging.WithLogger(ctx, logger)
 
 	err := realMain(ctx)
 	done()
diff --git a/cmd/federationin/main.go b/cmd/federationin/main.go
index 4c3edc9..44a3f91 100644
--- a/cmd/federationin/main.go
+++ b/cmd/federationin/main.go
@@ -22,8 +22,8 @@ import (
 	"net/http"
 
 	"github.com/google/exposure-notifications-server/internal/federationin"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/setup"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	_ "github.com/google/exposure-notifications-server/pkg/observability"
 	"github.com/google/exposure-notifications-server/pkg/server"
 	"github.com/sethvargo/go-signalcontext"
@@ -31,7 +31,9 @@ import (
 
 func main() {
 	ctx, done := signalcontext.OnInterrupt()
-	logger := logging.FromContext(ctx)
+
+	logger := logging.NewLogger(true)
+	ctx = logging.WithLogger(ctx, logger)
 
 	err := realMain(ctx)
 	done()
diff --git a/cmd/federationout/main.go b/cmd/federationout/main.go
index f10bbc1..9c7d82d 100644
--- a/cmd/federationout/main.go
+++ b/cmd/federationout/main.go
@@ -25,9 +25,9 @@ import (
 	"go.opencensus.io/plugin/ocgrpc"
 
 	"github.com/google/exposure-notifications-server/internal/federationout"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/pb"
 	"github.com/google/exposure-notifications-server/internal/setup"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	_ "github.com/google/exposure-notifications-server/pkg/observability"
 	"github.com/google/exposure-notifications-server/pkg/server"
 	"github.com/sethvargo/go-signalcontext"
@@ -35,7 +35,9 @@ import (
 
 func main() {
 	ctx, done := signalcontext.OnInterrupt()
-	logger := logging.FromContext(ctx)
+
+	logger := logging.NewLogger(true)
+	ctx = logging.WithLogger(ctx, logger)
 
 	err := realMain(ctx)
 	done()
diff --git a/cmd/generate/main.go b/cmd/generate/main.go
index 309e09b..71e9c07 100644
--- a/cmd/generate/main.go
+++ b/cmd/generate/main.go
@@ -22,8 +22,8 @@ import (
 	"net/http"
 
 	"github.com/google/exposure-notifications-server/internal/generate"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/setup"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	_ "github.com/google/exposure-notifications-server/pkg/observability"
 	"github.com/google/exposure-notifications-server/pkg/server"
 	"github.com/sethvargo/go-signalcontext"
@@ -31,7 +31,9 @@ import (
 
 func main() {
 	ctx, done := signalcontext.OnInterrupt()
-	logger := logging.FromContext(ctx)
+
+	logger := logging.NewLogger(true)
+	ctx = logging.WithLogger(ctx, logger)
 
 	err := realMain(ctx)
 	done()
diff --git a/cmd/key-rotation/main.go b/cmd/key-rotation/main.go
index a8fe294..46ee998 100644
--- a/cmd/key-rotation/main.go
+++ b/cmd/key-rotation/main.go
@@ -20,8 +20,8 @@ import (
 	"fmt"
 
 	"github.com/google/exposure-notifications-server/internal/keyrotation"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/setup"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	_ "github.com/google/exposure-notifications-server/pkg/observability"
 	"github.com/google/exposure-notifications-server/pkg/server"
 	"github.com/sethvargo/go-signalcontext"
@@ -29,7 +29,9 @@ import (
 
 func main() {
 	ctx, done := signalcontext.OnInterrupt()
-	logger := logging.FromContext(ctx)
+
+	logger := logging.NewLogger(true)
+	ctx = logging.WithLogger(ctx, logger)
 
 	err := realMain(ctx)
 	done()
diff --git a/internal/authorizedapp/database_provider.go b/internal/authorizedapp/database_provider.go
index 4cf1266..cec74b4 100644
--- a/internal/authorizedapp/database_provider.go
+++ b/internal/authorizedapp/database_provider.go
@@ -25,8 +25,7 @@ import (
 	"github.com/google/exposure-notifications-server/internal/authorizedapp/model"
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/pkg/cache"
-
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 )
 
 // Compile-time check to assert implementation.
diff --git a/internal/cleanup/cleanup.go b/internal/cleanup/cleanup.go
index 8cb32f3..6e7dc6e 100644
--- a/internal/cleanup/cleanup.go
+++ b/internal/cleanup/cleanup.go
@@ -26,9 +26,9 @@ import (
 	"github.com/google/exposure-notifications-server/internal/export/database"
 	publishdb "github.com/google/exposure-notifications-server/internal/publish/database"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	"github.com/google/exposure-notifications-server/internal/storage"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 )
 
 const (
diff --git a/internal/database/connection.go b/internal/database/connection.go
index 2da6ff8..9d6e06e 100644
--- a/internal/database/connection.go
+++ b/internal/database/connection.go
@@ -21,7 +21,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 
 	"github.com/jackc/pgx/v4/pgxpool"
 )
diff --git a/internal/database/lock.go b/internal/database/lock.go
index b37dd49..856d189 100644
--- a/internal/database/lock.go
+++ b/internal/database/lock.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	pgx "github.com/jackc/pgx/v4"
 )
 
diff --git a/internal/debugger/handler_debug.go b/internal/debugger/handler_debug.go
index 010903b..80c0ca5 100644
--- a/internal/debugger/handler_debug.go
+++ b/internal/debugger/handler_debug.go
@@ -27,7 +27,7 @@ import (
 	authorizedappmodel "github.com/google/exposure-notifications-server/internal/authorizedapp/model"
 	exportdatabase "github.com/google/exposure-notifications-server/internal/export/database"
 	exportmodel "github.com/google/exposure-notifications-server/internal/export/model"
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"github.com/kelseyhightower/run"
 	"golang.org/x/oauth2/google"
 	"google.golang.org/api/iam/v1"
diff --git a/internal/export/batcher.go b/internal/export/batcher.go
index 6af64e7..e4f2681 100644
--- a/internal/export/batcher.go
+++ b/internal/export/batcher.go
@@ -26,7 +26,7 @@ import (
 	"github.com/google/exposure-notifications-server/internal/export/model"
 	publishmodel "github.com/google/exposure-notifications-server/internal/publish/model"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 )
 
 // handleCreateBatches is a handler to iterate the rows of ExportConfig and
diff --git a/internal/export/database/export.go b/internal/export/database/export.go
index 9336d16..865629b 100644
--- a/internal/export/database/export.go
+++ b/internal/export/database/export.go
@@ -25,8 +25,8 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/export/model"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/storage"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 
 	pgx "github.com/jackc/pgx/v4"
 )
diff --git a/internal/export/worker.go b/internal/export/worker.go
index 0693c68..3a2141d 100644
--- a/internal/export/worker.go
+++ b/internal/export/worker.go
@@ -32,7 +32,7 @@ import (
 	"github.com/google/exposure-notifications-server/internal/export/model"
 	publishmodel "github.com/google/exposure-notifications-server/internal/publish/model"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 
 	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
diff --git a/internal/federationin/federationin.go b/internal/federationin/federationin.go
index 8fb835a..712eba4 100644
--- a/internal/federationin/federationin.go
+++ b/internal/federationin/federationin.go
@@ -30,13 +30,13 @@ import (
 	coredb "github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/federationin/database"
 	"github.com/google/exposure-notifications-server/internal/federationin/model"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/metrics"
 	"github.com/google/exposure-notifications-server/internal/pb"
 	publishdb "github.com/google/exposure-notifications-server/internal/publish/database"
 	publishmodel "github.com/google/exposure-notifications-server/internal/publish/model"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 
 	"google.golang.org/api/idtoken"
 	"google.golang.org/grpc"
diff --git a/internal/federationout/federationout.go b/internal/federationout/federationout.go
index d22749d..c8e6e14 100644
--- a/internal/federationout/federationout.go
+++ b/internal/federationout/federationout.go
@@ -24,8 +24,8 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/federationin/model"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/pb"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 
 	coredb "github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/federationout/database"
diff --git a/internal/generate/handler.go b/internal/generate/handler.go
index e392082..f55bb48 100644
--- a/internal/generate/handler.go
+++ b/internal/generate/handler.go
@@ -24,12 +24,12 @@ import (
 
 	"go.opencensus.io/trace"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/publish/database"
 	"github.com/google/exposure-notifications-server/internal/publish/model"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	"github.com/google/exposure-notifications-server/internal/verification"
 	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"github.com/google/exposure-notifications-server/pkg/util"
 )
 
diff --git a/internal/keyrotation/rotate.go b/internal/keyrotation/rotate.go
index 1f54a01..098ad3a 100644
--- a/internal/keyrotation/rotate.go
+++ b/internal/keyrotation/rotate.go
@@ -21,8 +21,8 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/revision/database"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"github.com/hashicorp/go-multierror"
 	"go.opencensus.io/trace"
 )
diff --git a/internal/logging/logger.go b/internal/logging/logger.go
deleted file mode 100644
index becf2a3..0000000
--- a/internal/logging/logger.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package logging sets up and configures logging.
-package logging
-
-import (
-	"context"
-
-	"go.uber.org/zap"
-)
-
-type loggerKey struct{}
-
-var fallbackLogger *zap.SugaredLogger
-
-func init() {
-	// Set up Stackdriver keys (see: https://cloud.google.com/run/docs/logging#special-fields)
-	config := zap.NewProductionConfig()
-	config.EncoderConfig.MessageKey = "message"
-	config.EncoderConfig.LevelKey = "severity"
-	config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
-
-	if logger, err := config.Build(); err != nil {
-		fallbackLogger = zap.NewNop().Sugar()
-	} else {
-		fallbackLogger = logger.Named("default").Sugar()
-	}
-}
-
-func WithLogger(ctx context.Context, logger *zap.SugaredLogger) context.Context {
-	return context.WithValue(ctx, loggerKey{}, logger)
-}
-
-func FromContext(ctx context.Context) *zap.SugaredLogger {
-	if logger, ok := ctx.Value(loggerKey{}).(*zap.SugaredLogger); ok {
-		return logger
-	}
-	return fallbackLogger
-}
diff --git a/internal/metrics/exporter.go b/internal/metrics/exporter.go
index e99604d..cc7becf 100644
--- a/internal/metrics/exporter.go
+++ b/internal/metrics/exporter.go
@@ -18,7 +18,7 @@ package metrics
 import (
 	"context"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"go.uber.org/zap"
 )
 
diff --git a/internal/publish/database/exposure.go b/internal/publish/database/exposure.go
index 30257bd..1737b3d 100644
--- a/internal/publish/database/exposure.go
+++ b/internal/publish/database/exposure.go
@@ -26,11 +26,11 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/database"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/pb"
 	"github.com/google/exposure-notifications-server/internal/publish/model"
 	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 	"github.com/google/exposure-notifications-server/pkg/base64util"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 
 	pgx "github.com/jackc/pgx/v4"
 )
diff --git a/internal/publish/model/exposure_model.go b/internal/publish/model/exposure_model.go
index db34e5e..0aab4d6 100644
--- a/internal/publish/model/exposure_model.go
+++ b/internal/publish/model/exposure_model.go
@@ -24,9 +24,9 @@ import (
 	"strings"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/verification"
 	"github.com/google/exposure-notifications-server/pkg/base64util"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 
 	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1"
 )
diff --git a/internal/publish/publish.go b/internal/publish/publish.go
index 7dec68c..fbea2ef 100644
--- a/internal/publish/publish.go
+++ b/internal/publish/publish.go
@@ -26,7 +26,6 @@ import (
 	"go.opencensus.io/trace"
 
 	"github.com/google/exposure-notifications-server/internal/authorizedapp"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/pb"
 	"github.com/google/exposure-notifications-server/internal/publish/database"
 	"github.com/google/exposure-notifications-server/internal/publish/model"
@@ -37,6 +36,7 @@ import (
 	verifydb "github.com/google/exposure-notifications-server/internal/verification/database"
 	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1"
 	"github.com/google/exposure-notifications-server/pkg/base64util"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"github.com/mikehelmick/go-chaff"
 )
 
@@ -72,7 +72,7 @@ func NewHandler(ctx context.Context, config *Config, env *serverenv.ServerEnv) (
 
 	aadBytes := config.RevisionToken.AAD
 	if len(aadBytes) == 0 {
-		return nil, fmt.Errorf("must provide ADD for revision token encryption in REVISION_TOKEN_AAD env variable: %w", err)
+		return nil, fmt.Errorf("must provide ADD for revision token encryption in REVISION_TOKEN_AAD env variable")
 	}
 	revisionKeyConfig := revisiondb.KMSConfig{
 		WrapperKeyID: config.RevisionToken.KeyID,
diff --git a/internal/revision/database/revision.go b/internal/revision/database/revision.go
index a499d17..deac671 100644
--- a/internal/revision/database/revision.go
+++ b/internal/revision/database/revision.go
@@ -34,8 +34,8 @@ import (
 	"time"
 
 	"github.com/google/exposure-notifications-server/internal/database"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/pkg/keys"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"github.com/jackc/pgx/v4"
 )
 
diff --git a/internal/revision/revision.go b/internal/revision/revision.go
index 0399352..f7fcb44 100644
--- a/internal/revision/revision.go
+++ b/internal/revision/revision.go
@@ -28,10 +28,10 @@ import (
 	"sync"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/pb"
 	"github.com/google/exposure-notifications-server/internal/publish/model"
 	"github.com/google/exposure-notifications-server/internal/revision/database"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"google.golang.org/protobuf/proto"
 )
 
diff --git a/internal/setup/setup.go b/internal/setup/setup.go
index 28574f8..219199a 100644
--- a/internal/setup/setup.go
+++ b/internal/setup/setup.go
@@ -21,11 +21,11 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/authorizedapp"
 	"github.com/google/exposure-notifications-server/internal/database"
-	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/metrics"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	"github.com/google/exposure-notifications-server/internal/storage"
 	"github.com/google/exposure-notifications-server/pkg/keys"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"github.com/google/exposure-notifications-server/pkg/observability"
 	"github.com/google/exposure-notifications-server/pkg/secrets"
 	"github.com/sethvargo/go-envconfig"
diff --git a/pkg/logging/logger.go b/pkg/logging/logger.go
new file mode 100644
index 0000000..fbc9877
--- /dev/null
+++ b/pkg/logging/logger.go
@@ -0,0 +1,156 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package logging sets up and configures logging.
+package logging
+
+import (
+	"context"
+	"sync"
+	"time"
+
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+// contextKey is a private string type to prevent collisions in the context map.
+type contextKey string
+
+// loggerKey points to the value in the context where the logger is stored.
+const loggerKey = contextKey("logger")
+
+var (
+	// defaultLogger is the default logger. It is initialized once per package
+	// include upon calling DefaultLogger.
+	defaultLogger     *zap.SugaredLogger
+	defaultLoggerOnce sync.Once
+)
+
+// NewLogger creates a new logger with the given configuration.
+func NewLogger(debug bool) *zap.SugaredLogger {
+	config := &zap.Config{
+		Level:            zap.NewAtomicLevelAt(zap.InfoLevel),
+		Development:      false,
+		Sampling:         samplingConfig,
+		Encoding:         encodingJSON,
+		EncoderConfig:    encoderConfig,
+		OutputPaths:      outputStderr,
+		ErrorOutputPaths: outputStderr,
+	}
+
+	// Add more details if logging is in debug mode.
+	if debug {
+		config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
+		config.Development = true
+		config.Sampling = nil
+	}
+
+	logger, err := config.Build()
+	if err != nil {
+		logger = zap.NewNop()
+	}
+
+	return logger.Sugar()
+}
+
+// DefaultLogger returns the default logger for the package.
+func DefaultLogger() *zap.SugaredLogger {
+	defaultLoggerOnce.Do(func() {
+		defaultLogger = NewLogger(false)
+	})
+	return defaultLogger
+}
+
+// WithLogger creates a new context with the provided logger attached.
+func WithLogger(ctx context.Context, logger *zap.SugaredLogger) context.Context {
+	return context.WithValue(ctx, loggerKey, logger)
+}
+
+// FromContext returns the logger stored in the context. If no such logger
+// exists, a default logger is returned.
+func FromContext(ctx context.Context) *zap.SugaredLogger {
+	if logger, ok := ctx.Value(loggerKey).(*zap.SugaredLogger); ok {
+		return logger
+	}
+	return DefaultLogger()
+}
+
+const (
+	timestamp  = "timestamp"
+	severity   = "severity"
+	logger     = "logger"
+	caller     = "caller"
+	message    = "message"
+	stacktrace = "stacktrace"
+
+	levelDebug     = "DEBUG"
+	levelInfo      = "INFO"
+	levelWarning   = "WARNING"
+	levelError     = "ERROR"
+	levelCritical  = "CRITICAL"
+	levelAlert     = "ALERT"
+	levelEmergency = "EMERGENCY"
+
+	encodingJSON = "json"
+)
+
+var outputStderr = []string{"stderr"}
+
+var encoderConfig = zapcore.EncoderConfig{
+	TimeKey:        timestamp,
+	LevelKey:       severity,
+	NameKey:        logger,
+	CallerKey:      caller,
+	MessageKey:     message,
+	StacktraceKey:  stacktrace,
+	LineEnding:     zapcore.DefaultLineEnding,
+	EncodeLevel:    levelEncoder(),
+	EncodeTime:     timeEncoder(),
+	EncodeDuration: zapcore.SecondsDurationEncoder,
+	EncodeCaller:   zapcore.ShortCallerEncoder,
+}
+
+var samplingConfig = &zap.SamplingConfig{
+	Initial:    250,
+	Thereafter: 250,
+}
+
+// levelEncoder transforms a zap level to the associated stackdriver level.
+func levelEncoder() zapcore.LevelEncoder {
+	return func(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
+		switch l {
+		case zapcore.DebugLevel:
+			enc.AppendString(levelDebug)
+		case zapcore.InfoLevel:
+			enc.AppendString(levelInfo)
+		case zapcore.WarnLevel:
+			enc.AppendString(levelWarning)
+		case zapcore.ErrorLevel:
+			enc.AppendString(levelError)
+		case zapcore.DPanicLevel:
+			enc.AppendString(levelCritical)
+		case zapcore.PanicLevel:
+			enc.AppendString(levelAlert)
+		case zapcore.FatalLevel:
+			enc.AppendString(levelEmergency)
+		}
+	}
+}
+
+// timeEncoder encodes the time as RFC3339 nano
+func timeEncoder() zapcore.TimeEncoder {
+	return func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+		enc.AppendString(t.Format(time.RFC3339Nano))
+	}
+}
diff --git a/pkg/observability/stackdriver.go b/pkg/observability/stackdriver.go
index fa9e066..b091a0b 100644
--- a/pkg/observability/stackdriver.go
+++ b/pkg/observability/stackdriver.go
@@ -21,7 +21,7 @@ import (
 	"time"
 
 	"contrib.go.opencensus.io/exporter/stackdriver"
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"go.opencensus.io/stats/view"
 	"go.opencensus.io/trace"
 )
diff --git a/pkg/secrets/resolver.go b/pkg/secrets/resolver.go
index 24577a0..4bd5496 100644
--- a/pkg/secrets/resolver.go
+++ b/pkg/secrets/resolver.go
@@ -23,7 +23,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"github.com/sethvargo/go-envconfig"
 )
 
diff --git a/pkg/server/metrics.go b/pkg/server/metrics.go
index 073bd52..27ec976 100644
--- a/pkg/server/metrics.go
+++ b/pkg/server/metrics.go
@@ -23,7 +23,7 @@ import (
 	"strings"
 
 	"contrib.go.opencensus.io/exporter/prometheus"
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 )
 
 // ServeMetricsIfPrometheus serves the opencensus metrics at /metrics when OBSERVABILITY_EXPORTER set to "prometheus"
diff --git a/pkg/server/server.go b/pkg/server/server.go
index 9ae7d2f..0505a77 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -28,7 +28,7 @@ import (
 	"strconv"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/logging"
+	"github.com/google/exposure-notifications-server/pkg/logging"
 	"go.opencensus.io/plugin/ochttp"
 	"google.golang.org/grpc"
 )


Test patch:
diff --git a/pkg/logging/logger_test.go b/pkg/logging/logger_test.go
new file mode 100644
index 0000000..7a13fd6
--- /dev/null
+++ b/pkg/logging/logger_test.go
@@ -0,0 +1,67 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logging_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/exposure-notifications-server/pkg/logging"
+)
+
+func TestNewLogger(t *testing.T) {
+	t.Parallel()
+
+	logger := logging.NewLogger(true)
+	if logger == nil {
+		t.Fatal("expected logger to never be nil")
+	}
+}
+
+func TestDefaultLogger(t *testing.T) {
+	t.Parallel()
+
+	logger1 := logging.DefaultLogger()
+	if logger1 == nil {
+		t.Fatal("expected logger to never be nil")
+	}
+
+	logger2 := logging.DefaultLogger()
+	if logger2 == nil {
+		t.Fatal("expected logger to never be nil")
+	}
+
+	// Intentionally comparing identities here
+	if logger1 != logger2 {
+		t.Errorf("expected %#v to be %#v", logger1, logger2)
+	}
+}
+
+func TestContext(t *testing.T) {
+	t.Parallel()
+
+	ctx := context.Background()
+	logger1 := logging.FromContext(ctx)
+	if logger1 == nil {
+		t.Fatal("expected logger to never be nil")
+	}
+
+	ctx = logging.WithLogger(ctx, logger1)
+
+	logger2 := logging.FromContext(ctx)
+	if logger1 != logger2 {
+		t.Errorf("expected %#v to be %#v", logger1, logger2)
+	}
+}