Repository: google/trillian
Pull request #2787 (2022-07-29 19:07:51)

Switch from glog to klog
Please see the following explanation of the reasons for klog vs. glog: https://github.com/kubernetes/klog#why-was-klog-created

jdolitsky: This should pull in latest go-licenses once this is merged: https://github.com/google/go-licenses/pull/138
mhutchinson: /gcbrun

I've just rebased this to fix conflicts in the go.mod file
mhutchinson: I've got #2791 to update to the latest linter, but it doesn't fix this problem. https://github.com/dominikh/go-tools/blob/v0.3.3/go/ir/exits.go#L100 - looks like more work is required upstream in the staticcheck library to detect the `v2` version of `klog`.
AlCutter: /gcbrun
AlCutter: I've rebased/jimmied onto `HEAD` and fixed a few follow-up bits, @hickford would you mind casting an eye over the tweaks so I'm not approving my own code?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8f6b839..4d2448d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,11 +2,30 @@
 
 ## HEAD
 
-* Deprecate `--tree_ids_with_no_ephemeral_nodes` flag by setting it to all trees
-  (`*`) by default.
-* Updated golangci-lint to v1.47.3 (developers should update to this version)
+## v.1.5.0
+
+### Storage
+
+* Ephemeral nodes are no-longer written for any tree by default (and have not been read since the v1.4.0 release), the corresponding `--tree_ids_with_no_ephemeral_nodes` flag is now deprecated (and will be removed in a future release).
+  
+### Cleanup
+* Format code according to go1.19rc2 by @mhutchinson in https://github.com/google/trillian/pull/2785
 * Delete merkle package, use [github.com/transparency-dev/merkle](https://pkg.go.dev/github.com/transparency-dev/merkle) instead.
-* #2792: Fixed order-dependent unit test.
+
+### Misc
+* Fix order-dependent test by @hickford in https://github.com/google/trillian/pull/2792
+
+### Dependency updates
+* Updated golangci-lint to v1.47.3 (developers should update to this version) by @mhutchinson in https://github.com/google/trillian/pull/2791
+* Bump google.golang.org/api from 0.87.0 to 0.88.0 by @dependabot in https://github.com/google/trillian/pull/2783
+* Bump cloud.google.com/go/spanner from 1.35.0 to 1.36.0 by @dependabot in https://github.com/google/trillian/pull/2784
+* Bump google.golang.org/api from 0.88.0 to 0.90.0 by @dependabot in https://github.com/google/trillian/pull/2789
+* Bump golang.org/x/tools from 0.1.11 to 0.1.12 by @dependabot in https://github.com/google/trillian/pull/2790
+* Bump google.golang.org/protobuf from 1.28.0 to 1.28.1 by @dependabot in https://github.com/google/trillian/pull/2788
+* Bump google.golang.org/api from 0.90.0 to 0.91.0 by @dependabot in https://github.com/google/trillian/pull/2796
+* Bump github.com/prometheus/client_golang from 1.12.2 to 1.13.0 by @dependabot in https://github.com/google/trillian/pull/2795
+* Bump github.com/fullstorydev/grpcurl from 1.8.6 to 1.8.7 by @dependabot in https://github.com/google/trillian/pull/2794
+* Bump google.golang.org/api from 0.91.0 to 0.92.0 by @dependabot in https://github.com/google/trillian/pull/2798
 
 ## v1.4.2
 
diff --git a/client/admin.go b/client/admin.go
index 96a82eb8..e48416e5 100644
--- a/client/admin.go
+++ b/client/admin.go
@@ -19,11 +19,11 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/client/backoff"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
+	"k8s.io/klog/v2"
 )
 
 // CreateAndInitTree uses the adminClient and logClient to create the tree
@@ -47,17 +47,17 @@ func CreateAndInitTree(
 
 	var tree *trillian.Tree
 	err := b.Retry(ctx, func() error {
-		glog.Info("CreateTree...")
+		klog.Info("CreateTree...")
 		var err error
 		tree, err = adminClient.CreateTree(ctx, req)
 		switch code := status.Code(err); code {
 		case codes.Unavailable:
-			glog.Errorf("Admin server unavailable: %v", err)
+			klog.Errorf("Admin server unavailable: %v", err)
 			return err
 		case codes.OK:
 			return nil
 		default:
-			glog.Errorf("failed to CreateTree(%+v): %T %v", req, err, err)
+			klog.Errorf("failed to CreateTree(%+v): %T %v", req, err, err)
 			return err
 		}
 	})
@@ -92,22 +92,22 @@ func InitLog(ctx context.Context, tree *trillian.Tree, logClient trillian.Trilli
 	}
 
 	err := b.Retry(ctx, func() error {
-		glog.Infof("Initialising Log %v...", tree.TreeId)
+		klog.Infof("Initialising Log %v...", tree.TreeId)
 		req := &trillian.InitLogRequest{LogId: tree.TreeId}
 		resp, err := logClient.InitLog(ctx, req)
 		switch code := status.Code(err); code {
 		case codes.Unavailable:
-			glog.Errorf("Log server unavailable: %v", err)
+			klog.Errorf("Log server unavailable: %v", err)
 			return err
 		case codes.AlreadyExists:
-			glog.Warningf("Bizarrely, the just-created Log (%v) is already initialised!: %v", tree.TreeId, err)
+			klog.Warningf("Bizarrely, the just-created Log (%v) is already initialised!: %v", tree.TreeId, err)
 			return err
 		case codes.OK:
-			glog.Infof("Initialised Log (%v) with new SignedTreeHead:\n%+v",
+			klog.Infof("Initialised Log (%v) with new SignedTreeHead:\n%+v",
 				tree.TreeId, resp.Created)
 			return nil
 		default:
-			glog.Errorf("failed to InitLog(%+v): %T %v", req, err, err)
+			klog.Errorf("failed to InitLog(%+v): %T %v", req, err, err)
 			return err
 		}
 	})
diff --git a/client/rpcflags/rpcflags.go b/client/rpcflags/rpcflags.go
index 351a3515..48d1eb55 100644
--- a/client/rpcflags/rpcflags.go
+++ b/client/rpcflags/rpcflags.go
@@ -17,10 +17,10 @@ package rpcflags
 import (
 	"flag"
 
-	"github.com/golang/glog"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials/insecure"
+	"k8s.io/klog/v2"
 )
 
 // tlsCertFile is the flag-assigned value for the path to the Trillian server's TLS certificate.
@@ -32,7 +32,7 @@ func NewClientDialOptionsFromFlags() ([]grpc.DialOption, error) {
 	dialOpts := []grpc.DialOption{}
 
 	if *tlsCertFile == "" {
-		glog.Warning("Using an insecure gRPC connection to Trillian")
+		klog.Warning("Using an insecure gRPC connection to Trillian")
 		dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
 	} else {
 		creds, err := credentials.NewClientTLSFromFile(*tlsCertFile, "")
diff --git a/cmd/createtree/main.go b/cmd/createtree/main.go
index 5315dd52..4d1ba442 100644
--- a/cmd/createtree/main.go
+++ b/cmd/createtree/main.go
@@ -33,13 +33,13 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/client"
 	"github.com/google/trillian/client/rpcflags"
 	"github.com/google/trillian/cmd"
 	"google.golang.org/grpc"
 	"google.golang.org/protobuf/types/known/durationpb"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -103,18 +103,19 @@ func newRequest() (*trillian.CreateTreeRequest, error) {
 		Description:     *description,
 		MaxRootDuration: durationpb.New(*maxRootDuration),
 	}}
-	glog.Infof("Creating tree %+v", ctr.Tree)
+	klog.Infof("Creating tree %+v", ctr.Tree)
 
 	return ctr, nil
 }
 
 func main() {
+	klog.InitFlags(nil)
 	flag.Parse()
-	defer glog.Flush()
+	defer klog.Flush()
 
 	if *configFile != "" {
 		if err := cmd.ParseFlagFile(*configFile); err != nil {
-			glog.Exitf("Failed to load flags from config file %q: %s", *configFile, err)
+			klog.Exitf("Failed to load flags from config file %q: %s", *configFile, err)
 		}
 	}
 
@@ -122,7 +123,7 @@ func main() {
 	defer cancel()
 	tree, err := createTree(ctx)
 	if err != nil {
-		glog.Exitf("Failed to create tree: %v", err)
+		klog.Exitf("Failed to create tree: %v", err)
 	}
 
 	// DO NOT change the output format, scripts are meant to depend on it.
diff --git a/cmd/deletetree/main.go b/cmd/deletetree/main.go
index 9f0e0f49..9c59aed6 100644
--- a/cmd/deletetree/main.go
+++ b/cmd/deletetree/main.go
@@ -23,10 +23,10 @@ import (
 	"context"
 	"flag"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/client/rpcflags"
 	"google.golang.org/grpc"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -36,17 +36,18 @@ var (
 )
 
 func main() {
+	klog.InitFlags(nil)
 	flag.Parse()
-	defer glog.Flush()
+	defer klog.Flush()
 
 	dialOpts, err := rpcflags.NewClientDialOptionsFromFlags()
 	if err != nil {
-		glog.Exitf("Failed to determine dial options: %v", err)
+		klog.Exitf("Failed to determine dial options: %v", err)
 	}
 
 	conn, err := grpc.Dial(*adminServerAddr, dialOpts...)
 	if err != nil {
-		glog.Exitf("Failed to dial %v: %v", *adminServerAddr, err)
+		klog.Exitf("Failed to dial %v: %v", *adminServerAddr, err)
 	}
 	defer conn.Close()
 
@@ -54,12 +55,12 @@ func main() {
 	if !*undeleteTree {
 		_, err = a.DeleteTree(context.Background(), &trillian.DeleteTreeRequest{TreeId: *logID})
 		if err != nil {
-			glog.Exitf("Delete failed: %v", err)
+			klog.Exitf("Delete failed: %v", err)
 		}
 	} else {
 		_, err = a.UndeleteTree(context.Background(), &trillian.UndeleteTreeRequest{TreeId: *logID})
 		if err != nil {
-			glog.Exitf("Undelete failed: %v", err)
+			klog.Exitf("Undelete failed: %v", err)
 		}
 	}
 }
diff --git a/cmd/internal/serverutil/main.go b/cmd/internal/serverutil/main.go
index 03abe10d..32c9b9ee 100644
--- a/cmd/internal/serverutil/main.go
+++ b/cmd/internal/serverutil/main.go
@@ -23,7 +23,6 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/extension"
 	"github.com/google/trillian/monitoring"
@@ -36,6 +35,7 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/reflection"
+	"k8s.io/klog/v2"
 
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	clientv3 "go.etcd.io/etcd/client/v3"
@@ -107,7 +107,7 @@ func (m *Main) healthz(rw http.ResponseWriter, req *http.Request) {
 
 // Run starts the configured server. Blocks until the server exits.
 func (m *Main) Run(ctx context.Context) error {
-	glog.CopyStandardLogTo("WARNING")
+	klog.CopyStandardLogTo("WARNING")
 
 	if m.HealthyDeadline == 0 {
 		m.HealthyDeadline = 5 * time.Second
@@ -115,7 +115,7 @@ func (m *Main) Run(ctx context.Context) error {
 
 	srv, err := m.newGRPCServer()
 	if err != nil {
-		glog.Exitf("Error creating gRPC server: %v", err)
+		klog.Exitf("Error creating gRPC server: %v", err)
 	}
 	defer srv.GracefulStop()
 
@@ -138,7 +138,7 @@ func (m *Main) Run(ctx context.Context) error {
 		}
 
 		run := func() error {
-			glog.Infof("HTTP server starting on %v", endpoint)
+			klog.Infof("HTTP server starting on %v", endpoint)
 
 			var err error
 			// Let http.ListenAndServeTLS handle the error case when only one of the flags is set.
@@ -160,15 +160,15 @@ func (m *Main) Run(ctx context.Context) error {
 		}
 
 		shutdown := func() {
-			glog.Infof("Stopping HTTP server...")
-			glog.Flush()
+			klog.Infof("Stopping HTTP server...")
+			klog.Flush()
 
 			// 15 second exit time limit
 			ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 			defer cancel()
 
 			if err := s.Shutdown(ctx); err != nil {
-				glog.Errorf("Failed to http server shutdown: %v", err)
+				klog.Errorf("Failed to http server shutdown: %v", err)
 			}
 		}
 
@@ -177,7 +177,7 @@ func (m *Main) Run(ctx context.Context) error {
 		})
 	}
 
-	glog.Infof("RPC server starting on %v", m.RPCEndpoint)
+	klog.Infof("RPC server starting on %v", m.RPCEndpoint)
 	lis, err := net.Listen("tcp", m.RPCEndpoint)
 	if err != nil {
 		return err
@@ -185,7 +185,7 @@ func (m *Main) Run(ctx context.Context) error {
 
 	if m.TreeGCEnabled {
 		g.Go(func() error {
-			glog.Info("Deleted tree GC started")
+			klog.Info("Deleted tree GC started")
 			gc := admin.NewDeletedTreeGC(
 				m.Registry.AdminStorage,
 				m.TreeDeleteThreshold,
@@ -205,8 +205,8 @@ func (m *Main) Run(ctx context.Context) error {
 	}
 
 	shutdown := func() {
-		glog.Infof("Stopping RPC server...")
-		glog.Flush()
+		klog.Infof("Stopping RPC server...")
+		klog.Flush()
 
 		srv.GracefulStop()
 	}
@@ -264,26 +264,26 @@ func AnnounceSelf(ctx context.Context, client *clientv3.Client, etcdService, end
 	// Get a lease so our entry self-destructs.
 	leaseRsp, err := client.Grant(ctx, 30)
 	if err != nil {
-		glog.Exitf("Failed to get lease from etcd: %v", err)
+		klog.Exitf("Failed to get lease from etcd: %v", err)
 	}
 
 	keepAliveRspCh, err := client.KeepAlive(ctx, leaseRsp.ID)
 	if err != nil {
-		glog.Exitf("Failed to keep lease alive from etcd: %v", err)
+		klog.Exitf("Failed to keep lease alive from etcd: %v", err)
 	}
 	go listenKeepAliveRsp(ctx, keepAliveRspCh, cancel)
 
 	em, err := endpoints.NewManager(client, etcdService)
 	if err != nil {
-		glog.Exitf("Failed to create etcd manager: %v", err)
+		klog.Exitf("Failed to create etcd manager: %v", err)
 	}
 	fullEndpoint := fmt.Sprintf("%s/%s", etcdService, endpoint)
 	em.AddEndpoint(ctx, fullEndpoint, endpoints.Endpoint{Addr: endpoint})
-	glog.Infof("Announcing our presence in %v", etcdService)
+	klog.Infof("Announcing our presence in %v", etcdService)
 
 	return func() {
 		// Use a background context because the original context may have been cancelled.
-		glog.Infof("Removing our presence in %v", etcdService)
+		klog.Infof("Removing our presence in %v", etcdService)
 		ctx := context.Background()
 		em.DeleteEndpoint(ctx, fullEndpoint)
 		client.Revoke(ctx, leaseRsp.ID)
@@ -296,11 +296,11 @@ func listenKeepAliveRsp(ctx context.Context, keepAliveRspCh <-chan *clientv3.Lea
 	for {
 		select {
 		case <-ctx.Done():
-			glog.Infof("listenKeepAliveRsp canceled: %v", ctx.Err())
+			klog.Infof("listenKeepAliveRsp canceled: %v", ctx.Err())
 			return
 		case _, ok := <-keepAliveRspCh:
 			if !ok {
-				glog.Errorf("listenKeepAliveRsp canceled: unexpected lease expired")
+				klog.Errorf("listenKeepAliveRsp canceled: unexpected lease expired")
 				cancel()
 				return
 			}
diff --git a/cmd/trillian_log_server/main.go b/cmd/trillian_log_server/main.go
index c8c79789..e6b7c98b 100644
--- a/cmd/trillian_log_server/main.go
+++ b/cmd/trillian_log_server/main.go
@@ -26,7 +26,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/cmd"
 	"github.com/google/trillian/cmd/internal/serverutil"
@@ -44,6 +43,7 @@ import (
 	"github.com/google/trillian/util/clock"
 	clientv3 "go.etcd.io/etcd/client/v3"
 	"google.golang.org/grpc"
+	"k8s.io/klog/v2"
 
 	// Register supported storage providers.
 	_ "github.com/google/trillian/storage/cloudspanner"
@@ -83,12 +83,13 @@ var (
 )
 
 func main() {
+	klog.InitFlags(nil)
 	flag.Parse()
-	defer glog.Flush()
+	defer klog.Flush()
 
 	if *configFile != "" {
 		if err := cmd.ParseFlagFile(*configFile); err != nil {
-			glog.Exitf("Failed to load flags from config file %q: %s", *configFile, err)
+			klog.Exitf("Failed to load flags from config file %q: %s", *configFile, err)
 		}
 	}
 
@@ -103,7 +104,7 @@ func main() {
 	if *tracing {
 		opts, err := opencensus.EnableRPCServerTracing(*tracingProjectID, *tracingPercent)
 		if err != nil {
-			glog.Exitf("Failed to initialize stackdriver / opencensus tracing: %v", err)
+			klog.Exitf("Failed to initialize stackdriver / opencensus tracing: %v", err)
 		}
 		// Enable the server request counter tracing etc.
 		options = append(options, opts...)
@@ -111,7 +112,7 @@ func main() {
 
 	sp, err := storage.NewProvider(*storageSystem, mf)
 	if err != nil {
-		glog.Exitf("Failed to get storage provider: %v", err)
+		klog.Exitf("Failed to get storage provider: %v", err)
 	}
 	defer sp.Close()
 
@@ -121,7 +122,7 @@ func main() {
 			Endpoints:   strings.Split(servers, ","),
 			DialTimeout: 5 * time.Second,
 		}); err != nil {
-			glog.Exitf("Failed to connect to etcd at %v: %v", servers, err)
+			klog.Exitf("Failed to connect to etcd at %v: %v", servers, err)
 		}
 		defer client.Close()
 	}
@@ -137,7 +138,7 @@ func main() {
 
 	qm, err := quota.NewManager(*quotaSystem)
 	if err != nil {
-		glog.Exitf("Error creating quota manager: %v", err)
+		klog.Exitf("Error creating quota manager: %v", err)
 	}
 
 	registry := extension.Registry{
@@ -187,7 +188,7 @@ func main() {
 	}
 
 	if err := m.Run(ctx); err != nil {
-		glog.Exitf("Server exited with error: %v", err)
+		klog.Exitf("Server exited with error: %v", err)
 	}
 
 	if *memProfile != "" {
@@ -199,7 +200,7 @@ func main() {
 func mustCreate(fileName string) *os.File {
 	f, err := os.Create(fileName)
 	if err != nil {
-		glog.Fatal(err)
+		klog.Fatal(err)
 	}
 	return f
 }
diff --git a/cmd/trillian_log_signer/main.go b/cmd/trillian_log_signer/main.go
index f2f8c541..365505d3 100644
--- a/cmd/trillian_log_signer/main.go
+++ b/cmd/trillian_log_signer/main.go
@@ -25,7 +25,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/cmd"
 	"github.com/google/trillian/cmd/internal/serverutil"
 	"github.com/google/trillian/extension"
@@ -43,6 +42,7 @@ import (
 	etcdelect "github.com/google/trillian/util/election2/etcd"
 	clientv3 "go.etcd.io/etcd/client/v3"
 	"google.golang.org/grpc"
+	"k8s.io/klog/v2"
 
 	// Register supported storage providers.
 	_ "github.com/google/trillian/storage/cloudspanner"
@@ -85,24 +85,25 @@ var (
 )
 
 func main() {
+	klog.InitFlags(nil)
 	flag.Parse()
-	defer glog.Flush()
+	defer klog.Flush()
 
 	if *configFile != "" {
 		if err := cmd.ParseFlagFile(*configFile); err != nil {
-			glog.Exitf("Failed to load flags from config file %q: %s", *configFile, err)
+			klog.Exitf("Failed to load flags from config file %q: %s", *configFile, err)
 		}
 	}
 
-	glog.CopyStandardLogTo("WARNING")
-	glog.Info("**** Log Signer Starting ****")
+	klog.CopyStandardLogTo("WARNING")
+	klog.Info("**** Log Signer Starting ****")
 
 	mf := prometheus.MetricFactory{}
 	monitoring.SetStartSpan(opencensus.StartSpan)
 
 	sp, err := storage.NewProvider(*storageSystem, mf)
 	if err != nil {
-		glog.Exitf("Failed to get storage provider: %v", err)
+		klog.Exitf("Failed to get storage provider: %v", err)
 	}
 	defer sp.Close()
 
@@ -112,7 +113,7 @@ func main() {
 			Endpoints:   strings.Split(servers, ","),
 			DialTimeout: 5 * time.Second,
 		}); err != nil {
-			glog.Exitf("Failed to connect to etcd at %v: %v", servers, err)
+			klog.Exitf("Failed to connect to etcd at %v: %v", servers, err)
 		}
 		defer client.Close()
 	}
@@ -126,17 +127,17 @@ func main() {
 	var electionFactory election2.Factory
 	switch {
 	case *forceMaster:
-		glog.Warning("**** Acting as master for all logs ****")
+		klog.Warning("**** Acting as master for all logs ****")
 		electionFactory = election2.NoopFactory{}
 	case client != nil:
 		electionFactory = etcdelect.NewFactory(instanceID, client, *lockDir)
 	default:
-		glog.Exit("Either --force_master or --etcd_servers must be supplied")
+		klog.Exit("Either --force_master or --etcd_servers must be supplied")
 	}
 
 	qm, err := quota.NewManager(*quotaSystem)
 	if err != nil {
-		glog.Exitf("Error creating quota manager: %v", err)
+		klog.Exitf("Error creating quota manager: %v", err)
 	}
 
 	registry := extension.Registry{
@@ -196,7 +197,7 @@ func main() {
 	}
 
 	if err := m.Run(ctx); err != nil {
-		glog.Exitf("Server exited with error: %v", err)
+		klog.Exitf("Server exited with error: %v", err)
 	}
 
 	if *memProfile != "" {
@@ -205,14 +206,14 @@ func main() {
 	}
 
 	// Give things a few seconds to tidy up
-	glog.Infof("Stopping server, about to exit")
+	klog.Infof("Stopping server, about to exit")
 	time.Sleep(time.Second * 5)
 }
 
 func mustCreate(fileName string) *os.File {
 	f, err := os.Create(fileName)
 	if err != nil {
-		glog.Fatal(err)
+		klog.Fatal(err)
 	}
 	return f
 }
diff --git a/cmd/updatetree/main.go b/cmd/updatetree/main.go
index 75714b62..88e6b8e6 100644
--- a/cmd/updatetree/main.go
+++ b/cmd/updatetree/main.go
@@ -28,7 +28,6 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/client/rpcflags"
 	"google.golang.org/genproto/protobuf/field_mask"
@@ -38,6 +37,7 @@ import (
 	"google.golang.org/protobuf/encoding/prototext"
 	"google.golang.org/protobuf/reflect/protoreflect"
 	"google.golang.org/protobuf/reflect/protoregistry"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -113,7 +113,7 @@ func updateTree(ctx context.Context) (*trillian.Tree, error) {
 			return tree, nil
 		}
 		if s, ok := status.FromError(err); ok && s.Code() == codes.Unavailable {
-			glog.Errorf("Admin server unavailable, trying again: %v", err)
+			klog.Errorf("Admin server unavailable, trying again: %v", err)
 			time.Sleep(100 * time.Millisecond)
 			continue
 		}
@@ -122,14 +122,15 @@ func updateTree(ctx context.Context) (*trillian.Tree, error) {
 }
 
 func main() {
+	klog.InitFlags(nil)
 	flag.Parse()
-	defer glog.Flush()
+	defer klog.Flush()
 
 	ctx, cancel := context.WithTimeout(context.Background(), *rpcDeadline)
 	defer cancel()
 	tree, err := updateTree(ctx)
 	if err != nil {
-		glog.Exitf("Failed to update tree: %v", err)
+		klog.Exitf("Failed to update tree: %v", err)
 	}
 
 	if *printTree {
diff --git a/crypto/keys/handlers.go b/crypto/keys/handlers.go
index 11634d6b..f1e07240 100644
--- a/crypto/keys/handlers.go
+++ b/crypto/keys/handlers.go
@@ -21,9 +21,9 @@ import (
 	"fmt"
 	"sync"
 
-	"github.com/golang/glog"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/reflect/protoreflect"
+	"k8s.io/klog/v2"
 )
 
 // ProtoHandler uses the information in a protobuf message to obtain a crypto.Signer.
@@ -48,7 +48,7 @@ func RegisterHandler(keyProto proto.Message, handler ProtoHandler) {
 	keyProtoType := keyProto.ProtoReflect().Descriptor().FullName()
 
 	if _, alreadyExists := handlers[keyProtoType]; alreadyExists {
-		glog.Warningf("Overridding ProtoHandler for protobuf %q", keyProtoType)
+		klog.Warningf("Overridding ProtoHandler for protobuf %q", keyProtoType)
 	}
 
 	handlers[keyProtoType] = handler
diff --git a/docs/storage/commit_log/commit_log_based_storage_design.md b/docs/storage/commit_log/commit_log_based_storage_design.md
index 9cb0acb8..e9628597 100644
--- a/docs/storage/commit_log/commit_log_based_storage_design.md
+++ b/docs/storage/commit_log/commit_log_based_storage_design.md
@@ -145,15 +145,15 @@ func SignerRun() {
   // Sanity check that the STH table has what we already know.
   ourSTH := kafka.Read("STHs/<treeID>", dbSTH.sthOffset)
   if ourSTH == nil {
-    glog.Errorf("should not happen - local DB has data ahead of STHs topic")
+    klog.Errorf("should not happen - local DB has data ahead of STHs topic")
     return
   }
   if ourSTH.expectedOffset != dbSTH.sthOffset {
-    glog.Errorf("should not happen - local DB committed to invalid STH from topic")
+    klog.Errorf("should not happen - local DB committed to invalid STH from topic")
     return
   }
   if ourSTH.timestamp != dbSTH.timestamp || ourSTH.tree_size != dbSTH.tree_size {
-    glog.Errorf("should not happen - local DB has different data than STHs topic")
+    klog.Errorf("should not happen - local DB has different data than STHs topic")
     return
   }
 
@@ -169,11 +169,11 @@ func SignerRun() {
     if nextSTH.expectedOffset != nextOffset {
       // Someone's been writing STHs when they weren't supposed to be, skip
       // this one until we find another which is in-sync.
-      glog.Warning("skipping unexpected STH")
+      klog.Warning("skipping unexpected STH")
       continue
     }
     if nextSTH.timestamp < ourSTH.timestamp || nextSTH.tree_size < ourSTH.tree_size {
-      glog.Fatal("should not happen - earlier STH with later offset")
+      klog.Fatal("should not happen - earlier STH with later offset")
       return
     }
   }
@@ -197,7 +197,7 @@ func SignerRun() {
     newSTH.expectedOffset = nextOffset
     actualOffset := kafka.Append("STHs/<treeID>", newSTH)
     if actualOffset != nextOffset {
-      glog.Warning("someone else wrote an STH while we were master")
+      klog.Warning("someone else wrote an STH while we were master")
       tx.Abort()
       return
     }
@@ -215,7 +215,7 @@ func SignerRun() {
     }
     newRoot := tx.UpdateMerkleTreeAndBufferNodes(batch, treeRevision+1)
     if newRoot != nextSTH.root {
-      glog.Warning("calculated root hash != expected root hash, corrupt DB?")
+      klog.Warning("calculated root hash != expected root hash, corrupt DB?")
       tx.Abort()
       return
     }
@@ -317,7 +317,7 @@ func (tx *splitTX) DequeueLeaves() (..., error) {
       if nextSTH.expectedOffset != nextOffset {
         // Someone's been writing STHs when they weren't supposed to be, skip
         // this one until we find another which is in-sync.
-        glog.Warning("skipping invalid STH")
+        klog.Warning("skipping invalid STH")
         continue
       }
       if nextSTH.timestamp < ourSTH.timestamp || nextSTH.tree_size < ourSTH.tree_size {
diff --git a/docs/storage/commit_log/main.go b/docs/storage/commit_log/main.go
index 37b5c76b..4d3a310f 100644
--- a/docs/storage/commit_log/main.go
+++ b/docs/storage/commit_log/main.go
@@ -16,8 +16,8 @@
 // based signer, with a simulated Kafka-like interface and a simulated
 // master election package (which can be triggered to incorrectly report
 // multiple masters), and with the core algorithm in the signer code.
-// glog.Warning is used throughout for unexpected-but-recoverable situations,
-// whereas glog.Error is used for any situation that would indicate data
+// klog.Warning is used throughout for unexpected-but-recoverable situations,
+// whereas klog.Error is used for any situation that would indicate data
 // corruption.
 package main
 
@@ -28,10 +28,10 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/docs/storage/commit_log/signer"
 	"github.com/google/trillian/docs/storage/commit_log/simelection"
 	"github.com/google/trillian/docs/storage/commit_log/simkafka"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -89,7 +89,7 @@ func (ab *lockedBool) Set(v bool) {
 
 func main() {
 	flag.Parse()
-	defer glog.Flush()
+	defer klog.Flush()
 
 	epochMillis := time.Now().UnixNano() / int64(time.Millisecond)
 
@@ -134,7 +134,7 @@ func main() {
 		case choice < *masterChangePercent:
 			which := rand.Intn(len(signers))
 			who := signers[which].Name
-			glog.V(1).Infof("EVENT: Move mastership from %v to [%v]", election.Masters(), who)
+			klog.V(1).Infof("EVENT: Move mastership from %v to [%v]", election.Masters(), who)
 			election.SetMaster(who)
 		case choice < (*masterChangePercent + *dualMasterPercent):
 			if len(election.Masters()) > 1 {
@@ -146,11 +146,11 @@ func main() {
 			which2 := rand.Intn(len(signers))
 			who2 := signers[which2].Name
 			masters := []string{who1, who2}
-			glog.V(1).Infof("EVENT: Make multiple mastership, from %v to %v", election.Masters(), masters)
+			klog.V(1).Infof("EVENT: Make multiple mastership, from %v to %v", election.Masters(), masters)
 			election.SetMasters(masters)
 		case choice < (*masterChangePercent + *dualMasterPercent + *leafTogglePercent):
 			val := generateLeaves.Get()
-			glog.V(1).Infof("EVENT: Toggle leaf generation from %v to %v", val, !val)
+			klog.V(1).Infof("EVENT: Toggle leaf generation from %v to %v", val, !val)
 			generateLeaves.Set(!val)
 		}
 
diff --git a/docs/storage/commit_log/signer/signer.go b/docs/storage/commit_log/signer/signer.go
index 6c52e907..c9833cf8 100644
--- a/docs/storage/commit_log/signer/signer.go
+++ b/docs/storage/commit_log/signer/signer.go
@@ -21,9 +21,9 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/docs/storage/commit_log/simelection"
 	"github.com/google/trillian/docs/storage/commit_log/simkafka"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -88,27 +88,27 @@ func (s *Signer) Run() {
 	defer s.mu.Unlock()
 	// Read from local DB to see what STH we know about locally.
 	dbSTHInfo := s.LatestSTHInfo()
-	glog.V(2).Infof("%s: our DB has data upto STH at %d", s.Name, dbSTHInfo.sthOffset)
+	klog.V(2).Infof("%s: our DB has data upto STH at %d", s.Name, dbSTHInfo.sthOffset)
 
 	// Sanity check that the STH table has what we already know.
 	if dbSTHInfo.sth.TreeSize > 0 {
 		ourSTH, err := sthFromString(simkafka.Read("STHs/<treeID>", dbSTHInfo.sthOffset))
 		if err != nil {
-			glog.Errorf("%s: got an error unpacking STH: %v", s.Name, err)
+			klog.Errorf("%s: got an error unpacking STH: %v", s.Name, err)
 		}
 		if ourSTH == nil {
-			glog.Errorf("%s: local DB has data ahead of STHs topic!!", s.Name)
+			klog.Errorf("%s: local DB has data ahead of STHs topic!!", s.Name)
 			return
 		}
 		if ourSTH.Offset != dbSTHInfo.sthOffset {
-			glog.Errorf("%s: local DB recorded offset %d but that has inconsistent STH %s!!", s.Name, dbSTHInfo.sthOffset, ourSTH)
+			klog.Errorf("%s: local DB recorded offset %d but that has inconsistent STH %s!!", s.Name, dbSTHInfo.sthOffset, ourSTH)
 			return
 		}
 		if ourSTH.TimeStamp != dbSTHInfo.sth.TimeStamp || ourSTH.TreeSize != dbSTHInfo.sth.TreeSize {
-			glog.Errorf("%s: local DB has different data than STHs topic!!", s.Name)
+			klog.Errorf("%s: local DB has different data than STHs topic!!", s.Name)
 			return
 		}
-		glog.V(2).Infof("%s: our DB at %v, matches STH at that offset", s.Name, dbSTHInfo.sthOffset)
+		klog.V(2).Infof("%s: our DB at %v, matches STH at that offset", s.Name, dbSTHInfo.sthOffset)
 	}
 
 	// Look to see if anyone else has already stored data just ahead of our STH.  This will
@@ -120,7 +120,7 @@ func (s *Signer) Run() {
 		nextOffset++
 		sth, err := sthFromString(simkafka.Read("STHs/<treeID>", nextOffset))
 		if err != nil {
-			glog.Errorf("%s: got an error unpacking STH: %v", s.Name, err)
+			klog.Errorf("%s: got an error unpacking STH: %v", s.Name, err)
 		}
 		nextSTH = sth
 		if nextSTH == nil {
@@ -131,15 +131,15 @@ func (s *Signer) Run() {
 			// expected it to be stored at, probably due to another master signer nipping in an
 			// entry ahead of it (due to a bug in mastership election).
 			// Kafka adjudicates the clash: whichever entry got the correct offset wins.
-			glog.V(2).Infof("%s: ignoring inconsistent STH %s at offset %d", s.Name, nextSTH.String(), nextOffset)
+			klog.V(2).Infof("%s: ignoring inconsistent STH %s at offset %d", s.Name, nextSTH.String(), nextOffset)
 			continue
 		}
 		if nextSTH.Offset > nextOffset {
-			glog.Errorf("%s: STH %s is stored at offset %d, earlier than its writer expected!!", s.Name, nextSTH.String(), nextOffset)
+			klog.Errorf("%s: STH %s is stored at offset %d, earlier than its writer expected!!", s.Name, nextSTH.String(), nextOffset)
 			return
 		}
 		if nextSTH.TimeStamp < dbSTHInfo.sth.TimeStamp || nextSTH.TreeSize < dbSTHInfo.sth.TreeSize {
-			glog.Errorf("%s: next STH %s has earlier timestamp than in local DB (%s)!!", s.Name, nextSTH.String(), dbSTHInfo.sth.String())
+			klog.Errorf("%s: next STH %s has earlier timestamp than in local DB (%s)!!", s.Name, nextSTH.String(), dbSTHInfo.sth.String())
 			return
 		}
 		break
@@ -149,15 +149,15 @@ func (s *Signer) Run() {
 	if nextSTH == nil {
 		// We're up-to-date with the STHs topic (as of a moment ago) ...
 		if !s.IsMaster() {
-			glog.V(2).Infof("%s: up-to-date with STHs but not master, so exit", s.Name)
+			klog.V(2).Infof("%s: up-to-date with STHs but not master, so exit", s.Name)
 			return
 		}
 		// ... and we're the master. Move the STHs topic along to encompass any unincorporated leaves.
 		offset := dbSTHInfo.sth.TreeSize
 		batch := simkafka.ReadMultiple("Leaves/<treeID>", offset, *batchSize)
-		glog.V(2).Infof("%s: nothing at next offset %d and we are master, so have read %d more leaves", s.Name, nextOffset, len(batch))
+		klog.V(2).Infof("%s: nothing at next offset %d and we are master, so have read %d more leaves", s.Name, nextOffset, len(batch))
 		if len(batch) == 0 {
-			glog.V(2).Infof("%s: nothing to do", s.Name)
+			klog.V(2).Infof("%s: nothing to do", s.Name)
 			return
 		}
 		timestamp := (time.Now().UnixNano() / int64(time.Millisecond)) - s.epoch
@@ -172,14 +172,14 @@ func (s *Signer) Run() {
 		newSTHInfo.sthOffset = simkafka.Append("STHs/<treeID>", newSTHInfo.sth.String())
 		if newSTHInfo.sthOffset > nextOffset {
 			// The STH didn't get stored at the offset we expected, presumably because someone else got there first
-			glog.Warningf("%s: stored new STH %s at offset %d, which is unexpected; give up", s.Name, newSTHInfo.sth.String(), newSTHInfo.sthOffset)
+			klog.Warningf("%s: stored new STH %s at offset %d, which is unexpected; give up", s.Name, newSTHInfo.sth.String(), newSTHInfo.sthOffset)
 			return
 		}
 		if newSTHInfo.sthOffset < nextOffset {
-			glog.Errorf("%s: stored new STH %s at offset %d, which is earlier than expected!!", s.Name, newSTHInfo.sth.String(), newSTHInfo.sthOffset)
+			klog.Errorf("%s: stored new STH %s at offset %d, which is earlier than expected!!", s.Name, newSTHInfo.sth.String(), newSTHInfo.sthOffset)
 			return
 		}
-		glog.V(2).Infof("%s: stored new STH %s at expected offset, including %d new leaves", s.Name, newSTHInfo.sth.String(), len(batch))
+		klog.V(2).Infof("%s: stored new STH %s at expected offset, including %d new leaves", s.Name, newSTHInfo.sth.String(), len(batch))
 
 		// Now the STH topic is updated (correctly), do our local DB
 		s.db.AddLeaves(timestamp, nextOffset, batch)
@@ -188,10 +188,10 @@ func (s *Signer) Run() {
 		// There is an STH one ahead of us that we're not caught up with yet.
 		// Read the leaves between what we have in our DB, and that STH...
 		count := nextSTH.TreeSize - dbSTHInfo.sth.TreeSize
-		glog.V(2).Infof("%s: our DB is %d leaves behind the next STH at %s, so update it", s.Name, count, nextSTH.String())
+		klog.V(2).Infof("%s: our DB is %d leaves behind the next STH at %s, so update it", s.Name, count, nextSTH.String())
 		batch := simkafka.ReadMultiple("Leaves/<treeID>", dbSTHInfo.sth.TreeSize, count)
 		if len(batch) != count {
-			glog.Errorf("%s: expected to read leaves [%d, %d) but only got %d!!", s.Name, dbSTHInfo.sth.TreeSize, dbSTHInfo.sth.TreeSize+count, len(batch))
+			klog.Errorf("%s: expected to read leaves [%d, %d) but only got %d!!", s.Name, dbSTHInfo.sth.TreeSize, dbSTHInfo.sth.TreeSize+count, len(batch))
 			return
 		}
 		// ... and store it in our local DB
@@ -200,7 +200,7 @@ func (s *Signer) Run() {
 			treeRevision: dbSTHInfo.treeRevision + 1,
 			sthOffset:    nextOffset,
 		}
-		glog.V(2).Infof("%s: update our DB to %s", s.Name, newSTHInfo.sth.String())
+		klog.V(2).Infof("%s: update our DB to %s", s.Name, newSTHInfo.sth.String())
 		s.StoreSTHInfo(newSTHInfo)
 		// We may still not be caught up, but that's for the next time around.
 	}
diff --git a/experimental/batchmap/cmd/build/mapdemo.go b/experimental/batchmap/cmd/build/mapdemo.go
index c941d2b3..091b8a91 100644
--- a/experimental/batchmap/cmd/build/mapdemo.go
+++ b/experimental/batchmap/cmd/build/mapdemo.go
@@ -29,8 +29,7 @@ import (
 	"github.com/apache/beam/sdks/v2/go/pkg/beam"
 	"github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem/local"
 	"github.com/apache/beam/sdks/v2/go/pkg/beam/x/beamx"
-
-	"github.com/golang/glog"
+	"k8s.io/klog/v2"
 
 	"github.com/google/trillian/experimental/batchmap"
 	"github.com/google/trillian/merkle/coniks"
@@ -54,18 +53,19 @@ func init() {
 }
 
 func main() {
+	klog.InitFlags(nil)
 	flag.Parse()
 	beam.Init()
 
 	output := filepath.Clean(*output)
 	if output == "" {
-		glog.Exitf("No output provided")
+		klog.Exitf("No output provided")
 	}
 
 	// Create the directory if it doesn't exist
 	if _, err := os.Stat(output); os.IsNotExist(err) {
 		if err = os.Mkdir(output, 0o700); err != nil {
-			glog.Fatalf("couldn't find or create directory %s, %v", output, err)
+			klog.Fatalf("couldn't find or create directory %s, %v", output, err)
 		}
 	}
 
@@ -79,7 +79,7 @@ func main() {
 	// Create the map, which will be returned as a collection of Tiles.
 	allTiles, err := batchmap.Create(s, entries, *treeID, hash, *prefixStrata)
 	if err != nil {
-		glog.Fatalf("Failed to create pipeline: %v", err)
+		klog.Fatalf("Failed to create pipeline: %v", err)
 	}
 
 	// Write this collection of tiles to the output directory.
@@ -87,7 +87,7 @@ func main() {
 
 	// All of the above constructs the pipeline but doesn't run it. Now we run it.
 	if err := beamx.Run(context.Background(), p); err != nil {
-		glog.Fatalf("Failed to execute job: %v", err)
+		klog.Fatalf("Failed to execute job: %v", err)
 	}
 }
 
diff --git a/experimental/batchmap/cmd/verify/verify.go b/experimental/batchmap/cmd/verify/verify.go
index 616ff811..73fed7fb 100644
--- a/experimental/batchmap/cmd/verify/verify.go
+++ b/experimental/batchmap/cmd/verify/verify.go
@@ -25,11 +25,11 @@ import (
 	"io/ioutil"
 	"path/filepath"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/experimental/batchmap"
 	"github.com/google/trillian/merkle/coniks"
 	"github.com/google/trillian/merkle/smt"
 	"github.com/google/trillian/merkle/smt/node"
+	"k8s.io/klog/v2"
 )
 
 const hash = crypto.SHA512_256
@@ -43,11 +43,12 @@ var (
 )
 
 func main() {
+	klog.InitFlags(nil)
 	flag.Parse()
 
 	mapDir := filepath.Clean(*mapDir)
 	if mapDir == "" {
-		glog.Fatal("No output provided")
+		klog.Fatal("No output provided")
 	}
 
 	// Determine the key/value we expect to find.
@@ -65,7 +66,7 @@ func main() {
 	// Read the tiles required for this check from disk.
 	tiles, err := getTilesForKey(mapDir, keyPath)
 	if err != nil {
-		glog.Exitf("couldn't load tiles: %v", err)
+		klog.Exitf("couldn't load tiles: %v", err)
 	}
 
 	// Perform the verification.
@@ -81,7 +82,7 @@ func main() {
 		tile := tiles[i]
 		// Check the prefix of what we are looking for matches the tile's path.
 		if got, want := tile.Path, needPath[:len(tile.Path)]; !bytes.Equal(got, want) {
-			glog.Fatalf("wrong tile found at index %d: got %x, want %x", i, got, want)
+			klog.Fatalf("wrong tile found at index %d: got %x, want %x", i, got, want)
 		}
 		// Leaf paths within a tile are within the scope of the tile, so we can
 		// drop the prefix from the expected path now we have verified it.
@@ -98,11 +99,11 @@ func main() {
 		}
 
 		// Confirm we found the leaf we needed, and that it had the value we expected.
-		if leaf == nil {
-			glog.Fatalf("couldn't find expected leaf %x in tile %x", needLeafPath, tile.Path)
+		if leaf == nil { //nolint:staticcheck // Remove "related info" lint message for suppressed lint check below.
+			klog.Fatalf("couldn't find expected leaf %x in tile %x", needLeafPath, tile.Path)
 		}
-		if !bytes.Equal(leaf.Hash, needValue) {
-			glog.Fatalf("wrong leaf value in tile %x, leaf %x: got %x, want %x", tile.Path, leaf.Path, leaf.Hash, needValue)
+		if !bytes.Equal(leaf.Hash, needValue) { //nolint:staticcheck // Suppress false +ve due to linter not understanding that klog.Fatal() above will exit if leaf == nil
+			klog.Fatalf("wrong leaf value in tile %x, leaf %x: got %x, want %x", tile.Path, leaf.Path, leaf.Hash, needValue)
 		}
 
 		// Hash this tile given its leaf values, and confirm that the value we compute
@@ -110,16 +111,16 @@ func main() {
 		hs, err := smt.NewHStar3(nodes, coniks.Default.HashChildren,
 			uint(len(tile.Path)+len(leaf.Path))*8, uint(len(tile.Path))*8)
 		if err != nil {
-			glog.Fatalf("failed to create HStar3 for tile %x: %v", tile.Path, err)
+			klog.Fatalf("failed to create HStar3 for tile %x: %v", tile.Path, err)
 		}
 		res, err := hs.Update(et)
 		if err != nil {
-			glog.Fatalf("failed to hash tile %x: %v", tile.Path, err)
+			klog.Fatalf("failed to hash tile %x: %v", tile.Path, err)
 		} else if got, want := len(res), 1; got != want {
-			glog.Fatalf("wrong number of roots for tile %x: got %v, want %v", tile.Path, got, want)
+			klog.Fatalf("wrong number of roots for tile %x: got %v, want %v", tile.Path, got, want)
 		}
 		if got, want := res[0].Hash, tile.RootHash; !bytes.Equal(got, want) {
-			glog.Fatalf("wrong root hash for tile %x: got %x, calculated %x", tile.Path, want, got)
+			klog.Fatalf("wrong root hash for tile %x: got %x, calculated %x", tile.Path, want, got)
 		}
 		// Make the next iteration of the loop check that the tile above this has the
 		// root value of this tile stored as the value at the expected leaf index.
@@ -129,7 +130,7 @@ func main() {
 	// If we get here then we have proved that the value was correct and that the map
 	// root commits to this value. Any other user with the same map root must see the
 	// same value under the same key we have checked.
-	glog.Infof("key %d found at path %x, with value '%s' (%x) committed to by map root %x", *key, keyPath, expectedString, expectedValueHash, needValue)
+	klog.Infof("key %d found at path %x, with value '%s' (%x) committed to by map root %x", *key, keyPath, expectedString, expectedValueHash, needValue)
 }
 
 // getTilesForKey loads the tiles on the path from the root to the given leaf.
diff --git a/go.mod b/go.mod
index f751d2f0..fbc54ffa 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,6 @@ require (
 	github.com/fullstorydev/grpcurl v1.8.7
 	github.com/go-redis/redis v6.15.9+incompatible
 	github.com/go-sql-driver/mysql v1.6.0
-	github.com/golang/glog v1.0.0
 	github.com/golang/mock v1.6.0
 	github.com/google/btree v1.1.2
 	github.com/google/go-cmp v0.5.8
@@ -35,6 +34,7 @@ require (
 	google.golang.org/grpc v1.48.0
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0
 	google.golang.org/protobuf v1.28.1
+	k8s.io/klog/v2 v2.70.1
 )
 
 require (
@@ -64,7 +64,9 @@ require (
 	github.com/envoyproxy/protoc-gen-validate v0.3.0-java // indirect
 	github.com/fatih/color v1.9.0 // indirect
 	github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
+	github.com/go-logr/logr v1.2.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/glog v1.0.0 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/licenseclassifier v0.0.0-20210325184830-bb04aff29e72 // indirect
diff --git a/go.sum b/go.sum
index 686c77bb..62063a19 100644
--- a/go.sum
+++ b/go.sum
@@ -222,6 +222,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
+github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
 github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
@@ -1263,6 +1265,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ=
+k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/integration/log.go b/integration/log.go
index c7e528a5..22faeeb5 100644
--- a/integration/log.go
+++ b/integration/log.go
@@ -22,13 +22,13 @@ import (
 	"math/rand"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/client/backoff"
 	"github.com/google/trillian/types"
 	"github.com/transparency-dev/merkle/proof"
 	"github.com/transparency-dev/merkle/rfc6962"
 	inmemory "github.com/transparency-dev/merkle/testonly"
+	"k8s.io/klog/v2"
 )
 
 // TestParameters bundles up all the settings for a test run
@@ -92,7 +92,7 @@ var consistencyProofBadTestParams = []consistencyProofParams{{0, 0}, {-1, 0}, {1
 func RunLogIntegration(client trillian.TrillianLogClient, params TestParameters) error {
 	// Step 1 - Optionally check log starts empty then optionally queue leaves on server
 	if params.CheckLogEmpty {
-		glog.Infof("Checking log is empty before starting test")
+		klog.Infof("Checking log is empty before starting test")
 		resp, err := getLatestSignedLogRoot(client, params)
 		if err != nil {
 			return fmt.Errorf("failed to get latest log root: %v %v", resp, err)
@@ -110,7 +110,7 @@ func RunLogIntegration(client trillian.TrillianLogClient, params TestParameters)
 
 	preEntries := genEntries(params)
 	if params.QueueLeaves {
-		glog.Infof("Queueing %d leaves to log server ...", params.LeafCount)
+		klog.Infof("Queueing %d leaves to log server ...", params.LeafCount)
 		if err := queueLeaves(client, params, preEntries); err != nil {
 			return fmt.Errorf("failed to queue leaves: %v", err)
 		}
@@ -118,14 +118,14 @@ func RunLogIntegration(client trillian.TrillianLogClient, params TestParameters)
 
 	// Step 2 - Wait for queue to drain when server sequences, give up if it doesn't happen (optional)
 	if params.AwaitSequencing {
-		glog.Infof("Waiting for log to sequence ...")
+		klog.Infof("Waiting for log to sequence ...")
 		if err := waitForSequencing(params.TreeID, client, params); err != nil {
 			return fmt.Errorf("leaves were not sequenced: %v", err)
 		}
 	}
 
 	// Step 3 - Use get entries to read back what was written, check leaves are correct
-	glog.Infof("Reading back leaves from log ...")
+	klog.Infof("Reading back leaves from log ...")
 	entries, err := readEntries(params.TreeID, client, params)
 	if err != nil {
 		return fmt.Errorf("could not read back log entries: %v", err)
@@ -135,7 +135,7 @@ func RunLogIntegration(client trillian.TrillianLogClient, params TestParameters)
 	}
 
 	// Step 4 - Cross validation between log and memory tree root hashes
-	glog.Infof("Checking log STH with our constructed in-memory tree ...")
+	klog.Infof("Checking log STH with our constructed in-memory tree ...")
 	tree := buildMerkleTree(entries, params)
 	if err := checkLogRootHashMatches(tree, client, params); err != nil {
 		return fmt.Errorf("log consistency check failed: %v", err)
@@ -144,7 +144,7 @@ func RunLogIntegration(client trillian.TrillianLogClient, params TestParameters)
 	// Now that the basic tree has passed validation we can start testing proofs
 
 	// Step 5 - Test some inclusion proofs
-	glog.Info("Testing inclusion proofs")
+	klog.Info("Testing inclusion proofs")
 
 	// Ensure log doesn't serve a proof for a leaf index outside the tree size
 	if err := checkInclusionProofLeafOutOfRange(params.TreeID, client, params); err != nil {
@@ -166,7 +166,7 @@ func RunLogIntegration(client trillian.TrillianLogClient, params TestParameters)
 	// TODO(al): test some inclusion proofs by Merkle hash too.
 
 	// Step 6 - Test some consistency proofs
-	glog.Info("Testing consistency proofs")
+	klog.Info("Testing consistency proofs")
 
 	// Make some consistency proof requests that we know should not succeed
 	for _, consistParams := range consistencyProofBadTestParams {
@@ -213,7 +213,7 @@ func genEntries(params TestParameters) []*trillian.LogLeaf {
 	seed := time.Now().UnixNano()
 	rand.Seed(seed)
 	perm := rand.Perm(int(params.LeafCount))
-	glog.Infof("Generating %d leaves, %d unique, using permutation seed %d", params.LeafCount, params.UniqueLeaves, seed)
+	klog.Infof("Generating %d leaves, %d unique, using permutation seed %d", params.LeafCount, params.UniqueLeaves, seed)
 
 	leaves := make([]*trillian.LogLeaf, 0, params.LeafCount)
 	for l := int64(0); l < params.LeafCount; l++ {
@@ -223,7 +223,7 @@ func genEntries(params TestParameters) []*trillian.LogLeaf {
 }
 
 func queueLeaves(client trillian.TrillianLogClient, params TestParameters, entries []*trillian.LogLeaf) error {
-	glog.Infof("Queueing %d leaves...", len(entries))
+	klog.Infof("Queueing %d leaves...", len(entries))
 
 	for _, leaf := range entries {
 		ctx, cancel := getRPCDeadlineContext(params)
@@ -251,7 +251,7 @@ func queueLeaves(client trillian.TrillianLogClient, params TestParameters, entri
 func waitForSequencing(treeID int64, client trillian.TrillianLogClient, params TestParameters) error {
 	endTime := time.Now().Add(params.SequencingWaitTotal)
 
-	glog.Infof("Waiting for sequencing until: %v", endTime)
+	klog.Infof("Waiting for sequencing until: %v", endTime)
 
 	for endTime.After(time.Now()) {
 		req := trillian.GetLatestSignedLogRootRequest{LogId: treeID}
@@ -268,13 +268,13 @@ func waitForSequencing(treeID int64, client trillian.TrillianLogClient, params T
 			return err
 		}
 
-		glog.Infof("Leaf count: %d", root.TreeSize)
+		klog.Infof("Leaf count: %d", root.TreeSize)
 
 		if root.TreeSize >= uint64(params.LeafCount+params.StartLeaf) {
 			return nil
 		}
 
-		glog.Infof("Leaves sequenced: %d. Still waiting ...", root.TreeSize)
+		klog.Infof("Leaves sequenced: %d. Still waiting ...", root.TreeSize)
 
 		time.Sleep(params.SequencingPollWait)
 	}
@@ -294,7 +294,7 @@ func readEntries(logID int64, client trillian.TrillianLogClient, params TestPara
 			count = max
 		}
 
-		glog.Infof("Reading %d leaves from %d ...", count, index)
+		klog.Infof("Reading %d leaves from %d ...", count, index)
 		req := &trillian.GetLeavesByRangeRequest{LogId: logID, StartIndex: index, Count: count}
 		ctx, cancel := getRPCDeadlineContext(params)
 		response, err := client.GetLeavesByRange(ctx, req)
diff --git a/log/operation_manager.go b/log/operation_manager.go
index a532d431..e32cf5ed 100644
--- a/log/operation_manager.go
+++ b/log/operation_manager.go
@@ -25,13 +25,13 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/extension"
 	"github.com/google/trillian/monitoring"
 	"github.com/google/trillian/storage"
 	"github.com/google/trillian/util/clock"
 	"github.com/google/trillian/util/election"
 	"golang.org/x/sync/semaphore"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -167,7 +167,7 @@ func (o *OperationManager) logName(ctx context.Context, logID int64) string {
 
 	tree, err := storage.GetTree(ctx, o.info.Registry.AdminStorage, logID)
 	if err != nil {
-		glog.Errorf("%v: failed to get log info: %v", logID, err)
+		klog.Errorf("%v: failed to get log info: %v", logID, err)
 		return "<err>"
 	}
 
@@ -240,12 +240,12 @@ func (o *OperationManager) masterFor(ctx context.Context, allIDs []int64) ([]int
 // TODO(pavelkalinnikov): Restart the whole log operation rather than just the
 // election, and have a metric for restarts.
 func (o *OperationManager) runElectionWithRestarts(ctx context.Context, logID string) context.CancelFunc {
-	glog.Infof("create master election goroutine for %v", logID)
+	klog.Infof("create master election goroutine for %v", logID)
 	cctx, cancel := context.WithCancel(ctx)
 	run := func(ctx context.Context) {
 		e, err := o.info.Registry.ElectionFactory.NewElection(ctx, logID)
 		if err != nil {
-			glog.Errorf("failed to create election for %v: %v", logID, err)
+			klog.Errorf("failed to create election for %v: %v", logID, err)
 			return
 		}
 		// Warning: NewRunner can attempt to modify the config. Make a separate
@@ -282,12 +282,12 @@ func (o *OperationManager) updateHeldIDs(ctx context.Context, logIDs, activeIDs
 	if !reflect.DeepEqual(logIDs, o.lastHeld) {
 		o.lastHeld = make([]int64, len(logIDs))
 		copy(o.lastHeld, logIDs)
-		glog.Info(msg)
+		klog.Info(msg)
 		if o.info.Registry.SetProcessStatus != nil {
 			o.info.Registry.SetProcessStatus(heldInfo)
 		}
 	} else {
-		glog.V(1).Info(msg)
+		klog.V(1).Info(msg)
 	}
 }
 
@@ -318,19 +318,19 @@ func (o *OperationManager) getLogsAndExecutePass(ctx context.Context) error {
 // and is used only for testing.
 func (o *OperationManager) OperationSingle(ctx context.Context) {
 	if err := o.getLogsAndExecutePass(ctx); err != nil {
-		glog.Errorf("failed to perform operation: %v", err)
+		klog.Errorf("failed to perform operation: %v", err)
 	}
 }
 
 // OperationLoop starts the manager working. It continues until told to exit.
 // TODO(Martin2112): No mechanism for error reporting etc., this is OK for v1 but needs work
 func (o *OperationManager) OperationLoop(ctx context.Context) {
-	glog.Infof("Log operation manager starting")
+	klog.Infof("Log operation manager starting")
 
 	// Outer loop, runs until terminated.
 	for {
 		if err := o.operateOnce(ctx); err != nil {
-			glog.Infof("Log operation manager shutting down")
+			klog.Infof("Log operation manager shutting down")
 			break
 		}
 	}
@@ -338,7 +338,7 @@ func (o *OperationManager) OperationLoop(ctx context.Context) {
 	// Terminate all the election Runners.
 	for logID, cancel := range o.runnerCancels {
 		if cancel != nil {
-			glog.V(1).Infof("cancel election runner for %s", logID)
+			klog.V(1).Infof("cancel election runner for %s", logID)
 			cancel()
 		}
 	}
@@ -350,9 +350,9 @@ func (o *OperationManager) OperationLoop(ctx context.Context) {
 		r.Execute(ctx)
 	}
 
-	glog.Infof("wait for termination of election runners...")
+	klog.Infof("wait for termination of election runners...")
 	o.runnerWG.Wait()
-	glog.Infof("wait for termination of election runners...done")
+	klog.Infof("wait for termination of election runners...done")
 }
 
 // operateOnce runs a single round of operation for each of the active logs
@@ -364,10 +364,10 @@ func (o *OperationManager) operateOnce(ctx context.Context) error {
 	if err := o.getLogsAndExecutePass(ctx); err != nil {
 		// Suppress the error if ctx is done (ctx.Err != nil) as we're exiting.
 		if ctx.Err() != nil {
-			glog.Errorf("failed to execute operation on logs: %v", err)
+			klog.Errorf("failed to execute operation on logs: %v", err)
 		}
 	}
-	glog.V(1).Infof("Log operation manager pass complete")
+	klog.V(1).Infof("Log operation manager pass complete")
 
 	// Process any pending resignations while there's no activity.
 	doneResigning := false
@@ -392,12 +392,12 @@ func (o *OperationManager) operateOnce(ctx context.Context) error {
 	duration := o.info.TimeSource.Now().Sub(start)
 	wait := o.info.RunInterval - duration
 	if wait > 0 {
-		glog.V(1).Infof("Processing started at %v for %v; wait %v before next run", start, duration, wait)
+		klog.V(1).Infof("Processing started at %v for %v; wait %v before next run", start, duration, wait)
 		if err := clock.SleepContext(ctx, wait); err != nil {
 			return err
 		}
 	} else {
-		glog.V(1).Infof("Processing started at %v for %v; start next run immediately", start, duration)
+		klog.V(1).Infof("Processing started at %v for %v; start next run immediately", start, duration)
 	}
 	return nil
 }
@@ -409,10 +409,10 @@ func executePassForAll(ctx context.Context, info *OperationInfo, op Operation, l
 
 	numWorkers := info.NumWorkers
 	if numWorkers <= 0 {
-		glog.Warning("Running executor with NumWorkers <= 0, assuming 1")
+		klog.Warning("Running executor with NumWorkers <= 0, assuming 1")
 		numWorkers = 1
 	}
-	glog.V(1).Infof("Running executor with %d worker(s)", numWorkers)
+	klog.V(1).Infof("Running executor with %d worker(s)", numWorkers)
 
 	sem := semaphore.NewWeighted(int64(numWorkers))
 	var wg sync.WaitGroup
@@ -425,7 +425,7 @@ func executePassForAll(ctx context.Context, info *OperationInfo, op Operation, l
 			defer wg.Done()
 			defer sem.Release(1)
 			if err := executePass(ctx, info, op, logID); err != nil {
-				glog.Errorf("ExecutePass(%v) failed: %v", logID, err)
+				klog.Errorf("ExecutePass(%v) failed: %v", logID, err)
 			}
 		}(logID)
 	}
@@ -433,7 +433,7 @@ func executePassForAll(ctx context.Context, info *OperationInfo, op Operation, l
 	// Wait for the workers to consume all of the logIDs.
 	wg.Wait()
 	d := clock.SecondsSince(info.TimeSource, startBatch)
-	glog.V(1).Infof("Group run completed in %.2f seconds", d)
+	klog.V(1).Infof("Group run completed in %.2f seconds", d)
 }
 
 // executePass runs ExecutePass of the given operation for the passed-in log.
@@ -450,11 +450,11 @@ func executePass(ctx context.Context, info *OperationInfo, op Operation, logID i
 	signingRuns.Inc(label)
 	if count > 0 {
 		d := clock.SecondsSince(info.TimeSource, start)
-		glog.Infof("%v: processed %d items in %.2f seconds (%.2f qps)", logID, count, d, float64(count)/d)
+		klog.Infof("%v: processed %d items in %.2f seconds (%.2f qps)", logID, count, d, float64(count)/d)
 		entriesAdded.Add(float64(count), label)
 		batchesAdded.Inc(label)
 	} else {
-		glog.V(1).Infof("%v: no items to process", logID)
+		klog.V(1).Infof("%v: no items to process", logID)
 	}
 	return nil
 }
diff --git a/log/sequencer.go b/log/sequencer.go
index f9809b36..7a09583d 100644
--- a/log/sequencer.go
+++ b/log/sequencer.go
@@ -23,7 +23,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/monitoring"
 	"github.com/google/trillian/quota"
@@ -34,6 +33,7 @@ import (
 	"github.com/transparency-dev/merkle/compact"
 	"github.com/transparency-dev/merkle/rfc6962"
 	"google.golang.org/protobuf/types/known/timestamppb"
+	"k8s.io/klog/v2"
 )
 
 const logIDLabel = "logid"
@@ -301,7 +301,7 @@ func IntegrateBatch(ctx context.Context, tree *trillian.Tree, limit int, guardWi
 		seqTreeSize.Set(float64(currentRoot.TreeSize), label)
 
 		if currentRoot.RootHash == nil {
-			glog.Warningf("%v: Fresh log - no previous TreeHeads exist.", tree.TreeId)
+			klog.Warningf("%v: Fresh log - no previous TreeHeads exist.", tree.TreeId)
 			return storage.ErrTreeNeedsInit
 		}
 
@@ -334,10 +334,10 @@ func IntegrateBatch(ctx context.Context, tree *trillian.Tree, limit int, guardWi
 			interval := time.Duration(nowNanos - int64(currentRoot.TimestampNanos))
 			if maxRootDurationInterval == 0 || interval < maxRootDurationInterval {
 				// We have nothing to integrate into the tree.
-				glog.V(1).Infof("%v: No leaves sequenced in this signing operation", tree.TreeId)
+				klog.V(1).Infof("%v: No leaves sequenced in this signing operation", tree.TreeId)
 				return nil
 			}
-			glog.Infof("%v: Force new root generation as %v since last root", tree.TreeId, interval)
+			klog.Infof("%v: Force new root generation as %v since last root", tree.TreeId, interval)
 		}
 
 		stageStart = ts.Now()
@@ -417,7 +417,7 @@ func IntegrateBatch(ctx context.Context, tree *trillian.Tree, limit int, guardWi
 
 	seqCounter.Add(float64(numLeaves), label)
 	if newSLR != nil {
-		glog.Infof("%v: sequenced %v leaves, size %v", tree.TreeId, numLeaves, newLogRoot.TreeSize)
+		klog.Infof("%v: sequenced %v leaves, size %v", tree.TreeId, numLeaves, newLogRoot.TreeSize)
 	}
 	return numLeaves, nil
 }
@@ -439,10 +439,10 @@ func replenishQuota(ctx context.Context, numLeaves int, treeID int64, qm quota.M
 			{Group: quota.Global, Kind: quota.Read},
 			{Group: quota.Global, Kind: quota.Write},
 		}
-		glog.V(2).Infof("%v: replenishing %d tokens (numLeaves = %d)", treeID, tokens, numLeaves)
+		klog.V(2).Infof("%v: replenishing %d tokens (numLeaves = %d)", treeID, tokens, numLeaves)
 		err := qm.PutTokens(ctx, tokens, specs)
 		if err != nil {
-			glog.Warningf("%v: failed to replenish %d tokens: %v", treeID, tokens, err)
+			klog.Warningf("%v: failed to replenish %d tokens: %v", treeID, tokens, err)
 		}
 		quota.Metrics.IncReplenished(tokens, specs, err == nil)
 	}
diff --git a/log/sequencer_manager.go b/log/sequencer_manager.go
index 695f57b4..3bdaf15a 100644
--- a/log/sequencer_manager.go
+++ b/log/sequencer_manager.go
@@ -19,10 +19,10 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/extension"
 	"github.com/google/trillian/trees"
+	"k8s.io/klog/v2"
 )
 
 // SequencerManager provides sequencing operations for a collection of Logs.
@@ -56,7 +56,7 @@ func (s *SequencerManager) ExecutePass(ctx context.Context, logID int64, info *O
 
 	maxRootDuration := tree.MaxRootDuration.AsDuration()
 	if !tree.MaxRootDuration.IsValid() {
-		glog.Warning("failed to parse tree.MaxRootDuration, using zero")
+		klog.Warning("failed to parse tree.MaxRootDuration, using zero")
 		maxRootDuration = 0
 	}
 	leaves, err := IntegrateBatch(ctx, tree, info.BatchSize, s.guardWindow, maxRootDuration, info.TimeSource, s.registry.LogStorage, s.registry.QuotaManager)
diff --git a/merkle/coniks/coniks.go b/merkle/coniks/coniks.go
index e6b0fa03..8185dc76 100644
--- a/merkle/coniks/coniks.go
+++ b/merkle/coniks/coniks.go
@@ -21,8 +21,8 @@ import (
 	"encoding/binary"
 	"fmt"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/merkle/smt/node"
+	"k8s.io/klog/v2"
 )
 
 // Domain separation prefixes
@@ -62,8 +62,8 @@ func (m *Hasher) HashEmpty(treeID int64, root node.ID) []byte {
 	binary.Write(buf, binary.BigEndian, uint32(depth))
 	h.Write(buf.Bytes())
 	r := h.Sum(nil)
-	if glog.V(5) {
-		glog.Infof("HashEmpty(%v): %x", root, r)
+	if klog.V(5).Enabled() {
+		klog.Infof("HashEmpty(%v): %x", root, r)
 	}
 	return r
 }
@@ -81,8 +81,8 @@ func (m *Hasher) HashLeaf(treeID int64, id node.ID, leaf []byte) []byte {
 	buf.Write(leaf)
 	h.Write(buf.Bytes())
 	p := h.Sum(nil)
-	if glog.V(5) {
-		glog.Infof("HashLeaf(%v, %s): %x", id, leaf, p)
+	if klog.V(5).Enabled() {
+		klog.Infof("HashLeaf(%v, %s): %x", id, leaf, p)
 	}
 	return p
 }
@@ -96,8 +96,8 @@ func (m *Hasher) HashChildren(l, r []byte) []byte {
 	buf.Write(r)
 	h.Write(buf.Bytes())
 	p := h.Sum(nil)
-	if glog.V(5) {
-		glog.Infof("HashChildren(%x, %x): %x", l, r, p)
+	if klog.V(5).Enabled() {
+		klog.Infof("HashChildren(%x, %x): %x", l, r, p)
 	}
 	return p
 }
diff --git a/monitoring/inert.go b/monitoring/inert.go
index 0ad83fa6..8d711be5 100644
--- a/monitoring/inert.go
+++ b/monitoring/inert.go
@@ -19,7 +19,7 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/golang/glog"
+	"k8s.io/klog/v2"
 )
 
 // InertMetricFactory creates inert metrics for testing.
@@ -79,7 +79,7 @@ func (m *InertFloat) Add(val float64, labelVals ...string) {
 	defer m.mu.Unlock()
 	key, err := keyForLabels(labelVals, m.labelCount)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	m.vals[key] += val
@@ -91,7 +91,7 @@ func (m *InertFloat) Set(val float64, labelVals ...string) {
 	defer m.mu.Unlock()
 	key, err := keyForLabels(labelVals, m.labelCount)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	m.vals[key] = val
@@ -103,7 +103,7 @@ func (m *InertFloat) Value(labelVals ...string) float64 {
 	defer m.mu.Unlock()
 	key, err := keyForLabels(labelVals, m.labelCount)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return 0.0
 	}
 	return m.vals[key]
@@ -123,7 +123,7 @@ func (m *InertDistribution) Observe(val float64, labelVals ...string) {
 	defer m.mu.Unlock()
 	key, err := keyForLabels(labelVals, m.labelCount)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	m.counts[key]++
@@ -136,7 +136,7 @@ func (m *InertDistribution) Info(labelVals ...string) (uint64, float64) {
 	defer m.mu.Unlock()
 	key, err := keyForLabels(labelVals, m.labelCount)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return 0, 0.0
 	}
 	return m.counts[key], m.sums[key]
diff --git a/monitoring/prometheus/metrics.go b/monitoring/prometheus/metrics.go
index 09a66d73..3c32c931 100644
--- a/monitoring/prometheus/metrics.go
+++ b/monitoring/prometheus/metrics.go
@@ -19,10 +19,10 @@ package prometheus
 import (
 	"fmt"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/monitoring"
 	"github.com/prometheus/client_golang/prometheus"
 	dto "github.com/prometheus/client_model/go"
+	"k8s.io/klog/v2"
 )
 
 // MetricFactory allows the creation of Prometheus-based metrics.
@@ -123,7 +123,7 @@ type Counter struct {
 func (m *Counter) Inc(labelVals ...string) {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	if m.vec != nil {
@@ -137,7 +137,7 @@ func (m *Counter) Inc(labelVals ...string) {
 func (m *Counter) Add(val float64, labelVals ...string) {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	if m.vec != nil {
@@ -151,7 +151,7 @@ func (m *Counter) Add(val float64, labelVals ...string) {
 func (m *Counter) Value(labelVals ...string) float64 {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return 0.0
 	}
 	var metric prometheus.Metric
@@ -162,11 +162,11 @@ func (m *Counter) Value(labelVals ...string) float64 {
 	}
 	var metricpb dto.Metric
 	if err := metric.Write(&metricpb); err != nil {
-		glog.Errorf("failed to Write metric: %v", err)
+		klog.Errorf("failed to Write metric: %v", err)
 		return 0.0
 	}
 	if metricpb.Counter == nil {
-		glog.Errorf("counter field missing")
+		klog.Errorf("counter field missing")
 		return 0.0
 	}
 	return metricpb.Counter.GetValue()
@@ -183,7 +183,7 @@ type Gauge struct {
 func (m *Gauge) Inc(labelVals ...string) {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	if m.vec != nil {
@@ -197,7 +197,7 @@ func (m *Gauge) Inc(labelVals ...string) {
 func (m *Gauge) Dec(labelVals ...string) {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	if m.vec != nil {
@@ -211,7 +211,7 @@ func (m *Gauge) Dec(labelVals ...string) {
 func (m *Gauge) Add(val float64, labelVals ...string) {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	if m.vec != nil {
@@ -225,7 +225,7 @@ func (m *Gauge) Add(val float64, labelVals ...string) {
 func (m *Gauge) Set(val float64, labelVals ...string) {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	if m.vec != nil {
@@ -239,7 +239,7 @@ func (m *Gauge) Set(val float64, labelVals ...string) {
 func (m *Gauge) Value(labelVals ...string) float64 {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return 0.0
 	}
 	var metric prometheus.Metric
@@ -250,11 +250,11 @@ func (m *Gauge) Value(labelVals ...string) float64 {
 	}
 	var metricpb dto.Metric
 	if err := metric.Write(&metricpb); err != nil {
-		glog.Errorf("failed to Write metric: %v", err)
+		klog.Errorf("failed to Write metric: %v", err)
 		return 0.0
 	}
 	if metricpb.Gauge == nil {
-		glog.Errorf("gauge field missing")
+		klog.Errorf("gauge field missing")
 		return 0.0
 	}
 	return metricpb.Gauge.GetValue()
@@ -271,7 +271,7 @@ type Histogram struct {
 func (m *Histogram) Observe(val float64, labelVals ...string) {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return
 	}
 	if m.vec != nil {
@@ -285,7 +285,7 @@ func (m *Histogram) Observe(val float64, labelVals ...string) {
 func (m *Histogram) Info(labelVals ...string) (uint64, float64) {
 	labels, err := labelsFor(m.labelNames, labelVals)
 	if err != nil {
-		glog.Error(err.Error())
+		klog.Error(err.Error())
 		return 0, 0.0
 	}
 	var metric prometheus.Metric
@@ -296,12 +296,12 @@ func (m *Histogram) Info(labelVals ...string) (uint64, float64) {
 	}
 	var metricpb dto.Metric
 	if err := metric.Write(&metricpb); err != nil {
-		glog.Errorf("failed to Write metric: %v", err)
+		klog.Errorf("failed to Write metric: %v", err)
 		return 0, 0.0
 	}
 	histVal := metricpb.GetHistogram()
 	if histVal == nil {
-		glog.Errorf("histogram field missing")
+		klog.Errorf("histogram field missing")
 		return 0, 0.0
 	}
 	return histVal.GetSampleCount(), histVal.GetSampleSum()
diff --git a/quota/cacheqm/cache.go b/quota/cacheqm/cache.go
index 036037bf..d12dddc8 100644
--- a/quota/cacheqm/cache.go
+++ b/quota/cacheqm/cache.go
@@ -22,8 +22,8 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/quota"
+	"k8s.io/klog/v2"
 )
 
 const (
@@ -125,7 +125,7 @@ func (m *manager) GetTokens(ctx context.Context, numTokens int, specs []quota.Sp
 		bucket, ok := m.cache[spec]
 		// Sanity check
 		if !ok || bucket.tokens < 0 || bucket.tokens < numTokens {
-			glog.Errorf("Bucket invariants failed for spec %+v: ok = %v, bucket = %+v", spec, ok, bucket)
+			klog.Errorf("Bucket invariants failed for spec %+v: ok = %v, bucket = %+v", spec, ok, bucket)
 			return nil // Something is wrong with the implementation, let requests go through.
 		}
 		bucket.tokens -= numTokens
@@ -158,14 +158,14 @@ func (m *manager) evict(ctx context.Context) {
 	evicts := len(m.cache) - m.maxEntries
 	for i := 0; i < evicts; i++ {
 		b := buckets[i]
-		glog.V(1).Infof("Too many tokens cached, returning least recently used (%v tokens for %+v)", b.tokens, b.spec)
+		klog.V(1).Infof("Too many tokens cached, returning least recently used (%v tokens for %+v)", b.tokens, b.spec)
 		delete(m.cache, b.spec)
 
 		// goroutines must not access the cache, the lock is released before they complete.
 		wg.Add(1)
 		go func() {
 			if err := m.Manager.PutTokens(ctx, b.tokens, []quota.Spec{b.spec}); err != nil {
-				glog.Warningf("Error replenishing tokens from evicted bucket (spec = %+v, bucket = %+v): %v", b.spec, b.bucket, err)
+				klog.Warningf("Error replenishing tokens from evicted bucket (spec = %+v, bucket = %+v): %v", b.spec, b.bucket, err)
 			}
 			wg.Done()
 		}()
diff --git a/quota/etcd/quota_provider.go b/quota/etcd/quota_provider.go
index 3eb01a8c..2531a4f9 100644
--- a/quota/etcd/quota_provider.go
+++ b/quota/etcd/quota_provider.go
@@ -20,11 +20,11 @@ import (
 	"strings"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/quota"
 	"github.com/google/trillian/quota/cacheqm"
 	"github.com/google/trillian/quota/etcd/etcdqm"
 	clientv3 "go.etcd.io/etcd/client/v3"
+	"k8s.io/klog/v2"
 )
 
 // QuotaManagerName identifies the etcd quota implementation.
@@ -42,7 +42,7 @@ var (
 
 func init() {
 	if err := quota.RegisterProvider(QuotaManagerName, newEtcdQuotaManager); err != nil {
-		glog.Fatalf("Failed to register quota manager %v: %v", QuotaManagerName, err)
+		klog.Fatalf("Failed to register quota manager %v: %v", QuotaManagerName, err)
 	}
 }
 
@@ -66,6 +66,6 @@ func newEtcdQuotaManager() (quota.Manager, error) {
 		}
 		qm = cachedQM
 	}
-	glog.Info("Using Etcd QuotaManager")
+	klog.Info("Using Etcd QuotaManager")
 	return qm, nil
 }
diff --git a/quota/etcd/quotaapi/quota_server.go b/quota/etcd/quotaapi/quota_server.go
index 87b2d941..240868ed 100644
--- a/quota/etcd/quotaapi/quota_server.go
+++ b/quota/etcd/quotaapi/quota_server.go
@@ -18,7 +18,6 @@ package quotaapi
 import (
 	"context"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/quota/etcd/quotapb"
 	"github.com/google/trillian/quota/etcd/storage"
 	"github.com/google/trillian/quota/etcd/storagepb"
@@ -27,6 +26,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/types/known/emptypb"
+	"k8s.io/klog/v2"
 )
 
 // Server is a quotapb.QuotaServer implementation backed by etcd.
@@ -113,7 +113,7 @@ func (s *Server) getConfig(ctx context.Context, name string, cfgs *storagepb.Con
 	if err == nil {
 		cfg.CurrentTokens = tokens[cfg.Name]
 	} else {
-		glog.Warningf("Unexpected error peeking token count for %q: %v", cfg.Name, err)
+		klog.Warningf("Unexpected error peeking token count for %q: %v", cfg.Name, err)
 	}
 
 	return cfg, nil
@@ -153,7 +153,7 @@ func (s *Server) ListConfigs(ctx context.Context, req *quotapb.ListConfigsReques
 				cfg.CurrentTokens = tokens[cfg.Name]
 			}
 		} else {
-			glog.Infof("Error peeking token counts for %v: %v", names, err)
+			klog.Infof("Error peeking token counts for %v: %v", names, err)
 		}
 	}
 	return resp, nil
diff --git a/quota/etcd/storage/quota_storage.go b/quota/etcd/storage/quota_storage.go
index a78660c7..c655538b 100644
--- a/quota/etcd/storage/quota_storage.go
+++ b/quota/etcd/storage/quota_storage.go
@@ -23,7 +23,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/quota"
 	"github.com/google/trillian/quota/etcd/storagepb"
 	"github.com/google/trillian/util/clock"
@@ -31,6 +30,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/proto"
+	"k8s.io/klog/v2"
 
 	clientv3 "go.etcd.io/etcd/client/v3"
 )
@@ -51,15 +51,15 @@ func init() {
 	var err error
 	globalPattern, err = regexp.Compile("^quotas/global/(read|write)/config$")
 	if err != nil {
-		glog.Fatalf("bad global pattern: %v", err)
+		klog.Fatalf("bad global pattern: %v", err)
 	}
 	treesPattern, err = regexp.Compile(`^quotas/trees/\d+/(read|write)/config$`)
 	if err != nil {
-		glog.Fatalf("bad trees pattern: %v", err)
+		klog.Fatalf("bad trees pattern: %v", err)
 	}
 	usersPattern, err = regexp.Compile("^quotas/users/[^/]+/(read|write)/config$")
 	if err != nil {
-		glog.Fatalf("bad users pattern: %v", err)
+		klog.Fatalf("bad users pattern: %v", err)
 	}
 }
 
diff --git a/quota/mysqlqm/quota_provider.go b/quota/mysqlqm/quota_provider.go
index 1c15376a..008f13b6 100644
--- a/quota/mysqlqm/quota_provider.go
+++ b/quota/mysqlqm/quota_provider.go
@@ -17,9 +17,9 @@ package mysqlqm
 import (
 	"flag"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/quota"
 	"github.com/google/trillian/storage/mysql"
+	"k8s.io/klog/v2"
 )
 
 // QuotaManagerName identifies the MySQL quota implementation.
@@ -30,7 +30,7 @@ var maxUnsequencedRows = flag.Int("max_unsequenced_rows", DefaultMaxUnsequenced,
 
 func init() {
 	if err := quota.RegisterProvider(QuotaManagerName, newMySQLQuotaManager); err != nil {
-		glog.Fatalf("Failed to register quota manager %v: %v", QuotaManagerName, err)
+		klog.Fatalf("Failed to register quota manager %v: %v", QuotaManagerName, err)
 	}
 }
 
@@ -43,6 +43,6 @@ func newMySQLQuotaManager() (quota.Manager, error) {
 		DB:                 db,
 		MaxUnsequencedRows: *maxUnsequencedRows,
 	}
-	glog.Info("Using MySQL QuotaManager")
+	klog.Info("Using MySQL QuotaManager")
 	return qm, nil
 }
diff --git a/quota/noop.go b/quota/noop.go
index dfddacc0..613fc5e6 100644
--- a/quota/noop.go
+++ b/quota/noop.go
@@ -18,7 +18,7 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/golang/glog"
+	"k8s.io/klog/v2"
 )
 
 // noopManagerName represents the noop quota implementation.
@@ -30,7 +30,7 @@ func init() {
 	if err := RegisterProvider(noopManagerName, func() (Manager, error) {
 		return Noop(), nil
 	}); err != nil {
-		glog.Fatalf("Failed to register %q: %v", noopManagerName, err)
+		klog.Fatalf("Failed to register %q: %v", noopManagerName, err)
 	}
 }
 
diff --git a/scripts/presubmit.sh b/scripts/presubmit.sh
index 11ff2bca..fdfe4a1a 100755
--- a/scripts/presubmit.sh
+++ b/scripts/presubmit.sh
@@ -114,7 +114,7 @@ main() {
     fi
 
     echo "running go test ${TEST_FLAGS} ./..."
-    go test ${TEST_FLAGS} ./... -alsologtostderr
+    go test ${TEST_FLAGS} ./...
   fi
 
   if [[ "${run_lint}" -eq 1 ]]; then
diff --git a/server/admin/admin_server.go b/server/admin/admin_server.go
index e891591e..2874e481 100644
--- a/server/admin/admin_server.go
+++ b/server/admin/admin_server.go
@@ -18,13 +18,13 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/extension"
 	"github.com/google/trillian/storage"
 	"google.golang.org/genproto/protobuf/field_mask"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
+	"k8s.io/klog/v2"
 )
 
 // Server is an implementation of trillian.TrillianAdminServer.
@@ -123,7 +123,7 @@ func (s *Server) UpdateTree(ctx context.Context, req *trillian.UpdateTreeRequest
 	updatedTree, err := storage.UpdateTree(ctx, s.registry.AdminStorage, tree.TreeId, func(other *trillian.Tree) {
 		if err := applyUpdateMask(tree, other, mask); err != nil {
 			// Should never happen (famous last words).
-			glog.Errorf("Error applying mask on tree update: %v", err)
+			klog.Errorf("Error applying mask on tree update: %v", err)
 		}
 	})
 	if err != nil {
diff --git a/server/admin/tree_gc.go b/server/admin/tree_gc.go
index 75e0311a..9b1eacf0 100644
--- a/server/admin/tree_gc.go
+++ b/server/admin/tree_gc.go
@@ -23,9 +23,9 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/monitoring"
 	"github.com/google/trillian/storage"
+	"k8s.io/klog/v2"
 )
 
 const (
@@ -93,10 +93,10 @@ func (gc *DeletedTreeGC) Run(ctx context.Context) {
 
 		count, err := gc.RunOnce(ctx)
 		if err != nil {
-			glog.Errorf("DeletedTreeGC.Run: %v", err)
+			klog.Errorf("DeletedTreeGC.Run: %v", err)
 		}
 		if count > 0 {
-			glog.Infof("DeletedTreeGC.Run: successfully deleted %v trees", count)
+			klog.Infof("DeletedTreeGC.Run: successfully deleted %v trees", count)
 		}
 
 		d := gc.minRunInterval + time.Duration(rand.Int63n(gc.minRunInterval.Nanoseconds()))
@@ -138,7 +138,7 @@ func (gc *DeletedTreeGC) RunOnce(ctx context.Context) (int, error) {
 			continue
 		}
 
-		glog.Infof("DeletedTreeGC.RunOnce: Hard-deleting tree %v after %v", tree.TreeId, durationSinceDelete)
+		klog.Infof("DeletedTreeGC.RunOnce: Hard-deleting tree %v after %v", tree.TreeId, durationSinceDelete)
 		if err := storage.HardDeleteTree(ctx, gc.admin, tree.TreeId); err != nil {
 			errs = append(errs, fmt.Errorf("error hard-deleting tree %v: %v", tree.TreeId, err))
 			incHardDeleteCounter(tree.TreeId, false, deleteErrReason)
diff --git a/server/interceptor/interceptor.go b/server/interceptor/interceptor.go
index 7e404ca3..f8170a8a 100644
--- a/server/interceptor/interceptor.go
+++ b/server/interceptor/interceptor.go
@@ -22,7 +22,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/monitoring"
 	"github.com/google/trillian/quota"
@@ -33,6 +32,7 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
+	"k8s.io/klog/v2"
 )
 
 const (
@@ -161,7 +161,7 @@ func (tp *trillianProcessor) Before(ctx context.Context, req interface{}, method
 	defer spanEnd()
 	info, err := newRPCInfo(req)
 	if err != nil {
-		glog.Warningf("Failed to read tree info: %v", err)
+		klog.Warningf("Failed to read tree info: %v", err)
 		incRequestDeniedCounter(badInfoReason, 0, "")
 		return ctx, err
 	}
@@ -191,7 +191,7 @@ func (tp *trillianProcessor) Before(ctx context.Context, req interface{}, method
 				incRequestDeniedCounter(insufficientTokensReason, info.treeID, info.quotaUsers)
 				return ctx, status.Errorf(codes.ResourceExhausted, "quota exhausted: %v", err)
 			}
-			glog.Warningf("(quotaDryRun) Request %+v not denied due to dry run mode: %v", req, err)
+			klog.Warningf("(quotaDryRun) Request %+v not denied due to dry run mode: %v", req, err)
 		}
 		quota.Metrics.IncAcquired(info.tokens, info.specs, err == nil)
 		if err = innerCtx.Err(); err != nil {
@@ -211,7 +211,7 @@ func (tp *trillianProcessor) After(ctx context.Context, resp interface{}, method
 	defer spanEnd()
 	switch {
 	case tp.info == nil:
-		glog.Warningf("After called with nil rpcInfo, resp = [%+v], handlerErr = [%v]", resp, handlerErr)
+		klog.Warningf("After called with nil rpcInfo, resp = [%+v], handlerErr = [%v]", resp, handlerErr)
 		return
 	case tp.info.tokens == 0:
 		// After() currently only does quota processing
@@ -268,7 +268,7 @@ func (tp *trillianProcessor) After(ctx context.Context, resp interface{}, method
 			// in its impl).
 			err := tp.parent.qm.PutTokens(ctx, tokens, refunds)
 			if err != nil {
-				glog.Warningf("Failed to replenish %v tokens: %v", tokens, err)
+				klog.Warningf("Failed to replenish %v tokens: %v", tokens, err)
 			}
 			quota.Metrics.IncReturned(tokens, refunds, err == nil)
 		}()
diff --git a/server/log_rpc_server.go b/server/log_rpc_server.go
index 12a1e724..e734f96a 100644
--- a/server/log_rpc_server.go
+++ b/server/log_rpc_server.go
@@ -19,7 +19,6 @@ import (
 	"fmt"
 	"strconv"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/extension"
 	"github.com/google/trillian/monitoring"
@@ -32,6 +31,7 @@ import (
 	"github.com/transparency-dev/merkle/rfc6962"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
+	"k8s.io/klog/v2"
 )
 
 // TODO: There is no access control in the server yet and clients could easily modify
@@ -530,7 +530,7 @@ func (t *TrillianLogRPCServer) GetEntryAndProof(ctx context.Context, req *trilli
 func (t *TrillianLogRPCServer) commitAndLog(ctx context.Context, logID int64, tx storage.ReadOnlyLogTreeTX, op string) error {
 	err := tx.Commit(ctx)
 	if err != nil {
-		glog.Warningf("%v: Commit failed for %v: %v", logID, op, err)
+		klog.Warningf("%v: Commit failed for %v: %v", logID, op, err)
 	}
 	return err
 }
@@ -538,7 +538,7 @@ func (t *TrillianLogRPCServer) commitAndLog(ctx context.Context, logID int64, tx
 func (t *TrillianLogRPCServer) closeAndLog(ctx context.Context, logID int64, tx storage.ReadOnlyLogTreeTX, op string) {
 	err := tx.Close()
 	if err != nil {
-		glog.Warningf("%v: Close failed for %v: %v", logID, op, err)
+		klog.Warningf("%v: Close failed for %v: %v", logID, op, err)
 	}
 }
 
diff --git a/storage/cloudspanner/admin.go b/storage/cloudspanner/admin.go
index 40653496..02547f5c 100644
--- a/storage/cloudspanner/admin.go
+++ b/storage/cloudspanner/admin.go
@@ -21,7 +21,6 @@ import (
 	"time"
 
 	"cloud.google.com/go/spanner"
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/storage"
 	"github.com/google/trillian/storage/cloudspanner/spannerpb"
@@ -31,6 +30,7 @@ import (
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/durationpb"
 	"google.golang.org/protobuf/types/known/timestamppb"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -60,7 +60,7 @@ func reverseTreeStateMap(m map[trillian.TreeState]spannerpb.TreeState) map[spann
 	reverse := make(map[spannerpb.TreeState]trillian.TreeState)
 	for k, v := range m {
 		if x, ok := reverse[v]; ok {
-			glog.Fatalf("Duplicate values for key %v: %v and %v", v, x, k)
+			klog.Fatalf("Duplicate values for key %v: %v and %v", v, x, k)
 		}
 		reverse[v] = k
 	}
@@ -71,7 +71,7 @@ func reverseTreeTypeMap(m map[trillian.TreeType]spannerpb.TreeType) map[spannerp
 	reverse := make(map[spannerpb.TreeType]trillian.TreeType)
 	for k, v := range m {
 		if x, ok := reverse[v]; ok {
-			glog.Fatalf("Duplicate values for key %v: %v and %v", v, x, k)
+			klog.Fatalf("Duplicate values for key %v: %v and %v", v, x, k)
 		}
 		reverse[v] = k
 	}
@@ -146,7 +146,7 @@ func (t *adminTX) Close() error {
 	}
 	// tx will be committed by ReadWriteTransaction(), so only close readonly tx here
 	if stx, ok := t.tx.(*spanner.ReadOnlyTransaction); ok {
-		glog.V(1).Infof("Closed admin %p", stx)
+		klog.V(1).Infof("Closed admin %p", stx)
 		stx.Close()
 	}
 	t.tx = nil
diff --git a/storage/cloudspanner/log_storage.go b/storage/cloudspanner/log_storage.go
index c48e4b83..acf530f3 100644
--- a/storage/cloudspanner/log_storage.go
+++ b/storage/cloudspanner/log_storage.go
@@ -26,7 +26,6 @@ import (
 	"time"
 
 	"cloud.google.com/go/spanner"
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/storage"
 	"github.com/google/trillian/storage/cache"
@@ -38,6 +37,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/types/known/timestamppb"
+	"k8s.io/klog/v2"
 )
 
 const (
@@ -164,7 +164,7 @@ func (ls *logStorage) GetActiveLogIDs(ctx context.Context) ([]int64, error) {
 		ids = append(ids, id)
 		return nil
 	}); err != nil {
-		glog.Warningf("GetActiveLogIDs: %v", err)
+		klog.Warningf("GetActiveLogIDs: %v", err)
 		return nil, fmt.Errorf("problem executing getActiveLogIDsSQL: %v", err)
 	}
 	return ids, nil
@@ -344,7 +344,7 @@ func (ls *logStorage) AddSequencedLeaves(ctx context.Context, tree *trillian.Tre
 			if err != nil {
 				// If failed because of a duplicate insert, set the status correspondingly.
 				if status.Code(err) == codes.AlreadyExists {
-					glog.Infof("Found already exists: index=%v, id=%v", l.LeafIndex, l.LeafIdentityHash)
+					klog.Infof("Found already exists: index=%v, id=%v", l.LeafIndex, l.LeafIdentityHash)
 					res[i].Status = status.New(codes.FailedPrecondition, "conflicting LeafIndex or LeafIdentityHash").Proto()
 					return
 				}
@@ -390,7 +390,7 @@ func (ls *logStorage) readDupeLeaves(ctx context.Context, logID int64, dupes map
 	if numDupes == 0 {
 		return nil
 	}
-	glog.V(2).Infof("dupe rowsToRead: %v", numDupes)
+	klog.V(2).Infof("dupe rowsToRead: %v", numDupes)
 
 	ids := make([][]byte, 0, numDupes)
 	for k := range dupes {
@@ -399,13 +399,13 @@ func (ls *logStorage) readDupeLeaves(ctx context.Context, logID int64, dupes map
 	dupesRead := 0
 	tx := ls.ts.client.Single()
 	err := readLeaves(ctx, tx, logID, ids, func(l *trillian.LogLeaf) {
-		glog.V(2).Infof("Found already exists dupe: %v", l)
+		klog.V(2).Infof("Found already exists dupe: %v", l)
 		dupesRead++
 
 		indices := dupes[string(l.LeafIdentityHash)]
-		glog.V(2).Infof("Indices %v", indices)
+		klog.V(2).Infof("Indices %v", indices)
 		if len(indices) == 0 {
-			glog.Warningf("Logic error: Spanner returned a leaf %x, but it matched no requested index", l.LeafIdentityHash)
+			klog.Warningf("Logic error: Spanner returned a leaf %x, but it matched no requested index", l.LeafIdentityHash)
 			return
 		}
 		for _, i := range indices {
@@ -494,7 +494,7 @@ func (tx *logTX) StoreSignedLogRoot(ctx context.Context, root *trillian.SignedLo
 
 	var logRoot types.LogRootV1
 	if err := logRoot.UnmarshalBinary(root.LogRoot); err != nil {
-		glog.Warningf("Failed to parse log root: %x %v", root.LogRoot, err)
+		klog.Warningf("Failed to parse log root: %x %v", root.LogRoot, err)
 		return err
 	}
 
diff --git a/storage/cloudspanner/storage_provider.go b/storage/cloudspanner/storage_provider.go
index 335039fc..9a2847ba 100644
--- a/storage/cloudspanner/storage_provider.go
+++ b/storage/cloudspanner/storage_provider.go
@@ -25,10 +25,10 @@ import (
 	"time"
 
 	"cloud.google.com/go/spanner"
-	"github.com/golang/glog"
 	"github.com/google/trillian/monitoring"
 	"github.com/google/trillian/storage"
 	"google.golang.org/api/option"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -65,10 +65,10 @@ func warn() {
 		r, _ := gzip.NewReader(b)
 		if err := r.Close(); err != nil {
 			// No need to exit, it's an unlikely error and doesn't affect operation.
-			glog.Warningf("Close()=%v", err)
+			klog.Warningf("Close()=%v", err)
 		}
 		t, _ := ioutil.ReadAll(r)
-		glog.Warningf("WARNING\n%s\nCloudspanner is an experimental storage implementation, and only supports Logs currently.", string(t))
+		klog.Warningf("WARNING\n%s\nCloudspanner is an experimental storage implementation, and only supports Logs currently.", string(t))
 	})
 }
 
diff --git a/storage/cloudspanner/tree_storage.go b/storage/cloudspanner/tree_storage.go
index 11091a11..51842cf2 100644
--- a/storage/cloudspanner/tree_storage.go
+++ b/storage/cloudspanner/tree_storage.go
@@ -23,7 +23,6 @@ import (
 	"time"
 
 	"cloud.google.com/go/spanner"
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/storage"
 	"github.com/google/trillian/storage/cache"
@@ -35,6 +34,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/proto"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -120,7 +120,7 @@ func (t *treeStorage) latestSTH(ctx context.Context, stx spanRead, treeID int64)
 		return nil, err
 	}
 	if th == nil {
-		glog.Warningf("no head found for treeID %v", treeID)
+		klog.Warningf("no head found for treeID %v", treeID)
 		return nil, storage.ErrTreeNeedsInit
 	}
 	return th, nil
@@ -270,7 +270,7 @@ func (t *treeTX) Commit(ctx context.Context) error {
 	}
 	switch stx := t.stx.(type) {
 	case *spanner.ReadOnlyTransaction:
-		glog.V(1).Infof("Closed readonly tx %p", stx)
+		klog.V(1).Infof("Closed readonly tx %p", stx)
 		stx.Close()
 		return nil
 	case *spanner.ReadWriteTransaction:
@@ -289,7 +289,7 @@ func (t *treeTX) Close() error {
 		return ErrTransactionClosed
 	}
 	if stx, ok := t.stx.(*spanner.ReadOnlyTransaction); ok {
-		glog.V(1).Infof("Closed snapshot %p", stx)
+		klog.V(1).Infof("Closed snapshot %p", stx)
 		stx.Close()
 	}
 	return nil
diff --git a/storage/memory/admin_storage.go b/storage/memory/admin_storage.go
index 226cb634..904f4e58 100644
--- a/storage/memory/admin_storage.go
+++ b/storage/memory/admin_storage.go
@@ -20,11 +20,11 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/storage"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/timestamppb"
+	"k8s.io/klog/v2"
 )
 
 // NewAdminStorage returns a storage.AdminStorage implementation backed by
@@ -131,7 +131,7 @@ func (t *adminTX) CreateTree(ctx context.Context, tr *trillian.Tree) (*trillian.
 	defer t.ms.mu.Unlock()
 	t.ms.trees[id] = newTree(meta)
 
-	glog.V(1).Infof("trees: %v", t.ms.trees)
+	klog.V(1).Infof("trees: %v", t.ms.trees)
 
 	return meta, nil
 }
diff --git a/storage/memory/provider.go b/storage/memory/provider.go
index 8e33eeb3..2f5aaf57 100644
--- a/storage/memory/provider.go
+++ b/storage/memory/provider.go
@@ -15,14 +15,14 @@
 package memory
 
 import (
-	"github.com/golang/glog"
 	"github.com/google/trillian/monitoring"
 	"github.com/google/trillian/storage"
+	"k8s.io/klog/v2"
 )
 
 func init() {
 	if err := storage.RegisterProvider("memory", newMemoryStorageProvider); err != nil {
-		glog.Fatalf("Failed to register storage provider memory: %v", err)
+		klog.Fatalf("Failed to register storage provider memory: %v", err)
 	}
 }
 
diff --git a/storage/memory/tree_debug.go b/storage/memory/tree_debug.go
index 7487bfe2..fe708798 100644
--- a/storage/memory/tree_debug.go
+++ b/storage/memory/tree_debug.go
@@ -15,10 +15,10 @@
 package memory
 
 import (
-	"github.com/golang/glog"
 	"github.com/google/btree"
 	"github.com/google/trillian/storage"
 	"github.com/google/trillian/storage/storagepb"
+	"k8s.io/klog/v2"
 )
 
 // This file contains utilities that are not part of the Storage API contracts but may
@@ -27,7 +27,7 @@ import (
 // Dump ascends the tree, logging the items contained.
 func Dump(t *btree.BTree) {
 	t.Ascend(func(i btree.Item) bool {
-		glog.Infof("%#v", i)
+		klog.Infof("%#v", i)
 		return true
 	})
 }
diff --git a/storage/memory/tree_storage.go b/storage/memory/tree_storage.go
index bf70761d..23d66458 100644
--- a/storage/memory/tree_storage.go
+++ b/storage/memory/tree_storage.go
@@ -21,13 +21,13 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/golang/glog"
 	"github.com/google/btree"
 	"github.com/google/trillian"
 	"github.com/google/trillian/storage/cache"
 	"github.com/google/trillian/storage/storagepb"
 	stree "github.com/google/trillian/storage/tree"
 	"google.golang.org/protobuf/proto"
+	"k8s.io/klog/v2"
 )
 
 const degree = 8
@@ -191,7 +191,7 @@ func (t *treeTX) getSubtrees(ctx context.Context, treeRevision int64, ids [][]by
 
 func (t *treeTX) storeSubtrees(ctx context.Context, subtrees []*storagepb.SubtreeProto) error {
 	if len(subtrees) == 0 {
-		glog.Warning("attempted to store 0 subtrees...")
+		klog.Warning("attempted to store 0 subtrees...")
 		return nil
 	}
 
@@ -225,11 +225,11 @@ func (t *treeTX) Commit(ctx context.Context) error {
 	if t.writeRevision > -1 {
 		tiles, err := t.subtreeCache.UpdatedTiles()
 		if err != nil {
-			glog.Warningf("SubtreeCache updated tiles error: %v", err)
+			klog.Warningf("SubtreeCache updated tiles error: %v", err)
 			return err
 		}
 		if err := t.storeSubtrees(ctx, tiles); err != nil {
-			glog.Warningf("TX commit flush error: %v", err)
+			klog.Warningf("TX commit flush error: %v", err)
 			return err
 		}
 	}
diff --git a/storage/mysql/log_storage.go b/storage/mysql/log_storage.go
index 280b3a3a..2d57c7d4 100644
--- a/storage/mysql/log_storage.go
+++ b/storage/mysql/log_storage.go
@@ -25,7 +25,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/monitoring"
 	"github.com/google/trillian/storage"
@@ -37,6 +36,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/types/known/timestamppb"
+	"k8s.io/klog/v2"
 )
 
 const (
@@ -320,7 +320,7 @@ func (t *logTreeTX) DequeueLeaves(ctx context.Context, limit int, cutoffTime tim
 	start := time.Now()
 	stx, err := t.tx.PrepareContext(ctx, selectQueuedLeavesSQL)
 	if err != nil {
-		glog.Warningf("Failed to prepare dequeue select: %s", err)
+		klog.Warningf("Failed to prepare dequeue select: %s", err)
 		return nil, err
 	}
 	defer stx.Close()
@@ -328,7 +328,7 @@ func (t *logTreeTX) DequeueLeaves(ctx context.Context, limit int, cutoffTime tim
 	leaves := make([]*trillian.LogLeaf, 0, limit)
 	rows, err := stx.QueryContext(ctx, t.treeID, cutoffTime.UnixNano(), limit)
 	if err != nil {
-		glog.Warningf("Failed to select rows for work: %s", err)
+		klog.Warningf("Failed to select rows for work: %s", err)
 		return nil, err
 	}
 	defer rows.Close()
@@ -336,7 +336,7 @@ func (t *logTreeTX) DequeueLeaves(ctx context.Context, limit int, cutoffTime tim
 	for rows.Next() {
 		leaf, dqInfo, err := t.dequeueLeaf(rows)
 		if err != nil {
-			glog.Warningf("Error dequeuing leaf: %v", err)
+			klog.Warningf("Error dequeuing leaf: %v", err)
 			return nil, err
 		}
 
@@ -417,7 +417,7 @@ func (t *logTreeTX) QueueLeaves(ctx context.Context, leaves []*trillian.LogLeaf,
 			continue
 		}
 		if err != nil {
-			glog.Warningf("Error inserting %d into LeafData: %s", i, err)
+			klog.Warningf("Error inserting %d into LeafData: %s", i, err)
 			return nil, mysqlToGRPC(err)
 		}
 
@@ -434,7 +434,7 @@ func (t *logTreeTX) QueueLeaves(ctx context.Context, leaves []*trillian.LogLeaf,
 			args...,
 		)
 		if err != nil {
-			glog.Warningf("Error inserting into Unsequenced: %s", err)
+			klog.Warningf("Error inserting into Unsequenced: %s", err)
 			return nil, mysqlToGRPC(err)
 		}
 		leafDuration := time.Since(leafStart)
@@ -499,7 +499,7 @@ func (t *logTreeTX) AddSequencedLeaves(ctx context.Context, leaves []*trillian.L
 	// a savepoint installed before the first insert of the two.
 	const savepoint = "SAVEPOINT AddSequencedLeaves"
 	if _, err := t.tx.ExecContext(ctx, savepoint); err != nil {
-		glog.Errorf("Error adding savepoint: %s", err)
+		klog.Errorf("Error adding savepoint: %s", err)
 		return nil, mysqlToGRPC(err)
 	}
 	// TODO(pavelkalinnikov): Consider performance implication of executing this
@@ -520,7 +520,7 @@ func (t *logTreeTX) AddSequencedLeaves(ctx context.Context, leaves []*trillian.L
 		}
 
 		if _, err := t.tx.ExecContext(ctx, savepoint); err != nil {
-			glog.Errorf("Error updating savepoint: %s", err)
+			klog.Errorf("Error updating savepoint: %s", err)
 			return nil, mysqlToGRPC(err)
 		}
 
@@ -537,7 +537,7 @@ func (t *logTreeTX) AddSequencedLeaves(ctx context.Context, leaves []*trillian.L
 			// Note: No rolling back to savepoint because there is no side effect.
 			continue
 		} else if err != nil {
-			glog.Errorf("Error inserting leaves[%d] into LeafData: %s", i, err)
+			klog.Errorf("Error inserting leaves[%d] into LeafData: %s", i, err)
 			return nil, mysqlToGRPC(err)
 		}
 
@@ -548,11 +548,11 @@ func (t *logTreeTX) AddSequencedLeaves(ctx context.Context, leaves []*trillian.L
 		if isDuplicateErr(err) {
 			res[i].Status = status.New(codes.FailedPrecondition, "conflicting LeafIndex").Proto()
 			if _, err := t.tx.ExecContext(ctx, "ROLLBACK TO "+savepoint); err != nil {
-				glog.Errorf("Error rolling back to savepoint: %s", err)
+				klog.Errorf("Error rolling back to savepoint: %s", err)
 				return nil, mysqlToGRPC(err)
 			}
 		} else if err != nil {
-			glog.Errorf("Error inserting leaves[%d] into SequencedLeafData: %s", i, err)
+			klog.Errorf("Error inserting leaves[%d] into SequencedLeafData: %s", i, err)
 			return nil, mysqlToGRPC(err)
 		}
 
@@ -560,7 +560,7 @@ func (t *logTreeTX) AddSequencedLeaves(ctx context.Context, leaves []*trillian.L
 	}
 
 	if _, err := t.tx.ExecContext(ctx, "RELEASE "+savepoint); err != nil {
-		glog.Errorf("Error releasing savepoint: %s", err)
+		klog.Errorf("Error releasing savepoint: %s", err)
 		return nil, mysqlToGRPC(err)
 	}
 
@@ -598,7 +598,7 @@ func (t *logTreeTX) getLeavesByRangeInternal(ctx context.Context, start, count i
 	args := []interface{}{start, start + count, t.treeID}
 	rows, err := t.tx.QueryContext(ctx, selectLeavesByRangeSQL, args...)
 	if err != nil {
-		glog.Warningf("Failed to get leaves by range: %s", err)
+		klog.Warningf("Failed to get leaves by range: %s", err)
 		return nil, err
 	}
 	defer rows.Close()
@@ -615,7 +615,7 @@ func (t *logTreeTX) getLeavesByRangeInternal(ctx context.Context, start, count i
 			&leaf.ExtraData,
 			&qTimestamp,
 			&iTimestamp); err != nil {
-			glog.Warningf("Failed to scan merkle leaves: %s", err)
+			klog.Warningf("Failed to scan merkle leaves: %s", err)
 			return nil, err
 		}
 		if leaf.LeafIndex != wantIndex {
@@ -635,7 +635,7 @@ func (t *logTreeTX) getLeavesByRangeInternal(ctx context.Context, start, count i
 		ret = append(ret, leaf)
 	}
 	if err := rows.Err(); err != nil {
-		glog.Warningf("Failed to read returned leaves: %s", err)
+		klog.Warningf("Failed to read returned leaves: %s", err)
 		return nil, err
 	}
 
@@ -707,7 +707,7 @@ func (t *logTreeTX) StoreSignedLogRoot(ctx context.Context, root *trillian.Signe
 
 	var logRoot types.LogRootV1
 	if err := logRoot.UnmarshalBinary(root.LogRoot); err != nil {
-		glog.Warningf("Failed to parse log root: %x %v", root.LogRoot, err)
+		klog.Warningf("Failed to parse log root: %x %v", root.LogRoot, err)
 		return err
 	}
 	if len(logRoot.Metadata) != 0 {
@@ -724,7 +724,7 @@ func (t *logTreeTX) StoreSignedLogRoot(ctx context.Context, root *trillian.Signe
 		t.treeTX.writeRevision,
 		[]byte{})
 	if err != nil {
-		glog.Warningf("Failed to store signed root: %s", err)
+		klog.Warningf("Failed to store signed root: %s", err)
 	}
 
 	return checkResultOkAndRowCountIs(res, err, 1)
@@ -741,7 +741,7 @@ func (t *logTreeTX) getLeavesByHashInternal(ctx context.Context, leafHashes [][]
 	args = append(args, t.treeID)
 	rows, err := stx.QueryContext(ctx, args...)
 	if err != nil {
-		glog.Warningf("Query() %s hash = %v", desc, err)
+		klog.Warningf("Query() %s hash = %v", desc, err)
 		return nil, err
 	}
 	defer rows.Close()
@@ -759,7 +759,7 @@ func (t *logTreeTX) getLeavesByHashInternal(ctx context.Context, leafHashes [][]
 		var queueTS int64
 
 		if err := rows.Scan(&leaf.MerkleLeafHash, &leaf.LeafIdentityHash, &leaf.LeafValue, &leaf.LeafIndex, &leaf.ExtraData, &queueTS, &integrateTS); err != nil {
-			glog.Warningf("LogID: %d Scan() %s = %s", t.treeID, desc, err)
+			klog.Warningf("LogID: %d Scan() %s = %s", t.treeID, desc, err)
 			return nil, err
 		}
 		leaf.QueueTimestamp = timestamppb.New(time.Unix(0, queueTS))
@@ -780,7 +780,7 @@ func (t *logTreeTX) getLeavesByHashInternal(ctx context.Context, leafHashes [][]
 		ret = append(ret, leaf)
 	}
 	if err := rows.Err(); err != nil {
-		glog.Warningf("Failed to read returned leaves: %s", err)
+		klog.Warningf("Failed to read returned leaves: %s", err)
 		return nil, err
 	}
 
diff --git a/storage/mysql/provider.go b/storage/mysql/provider.go
index 40622f55..1a21655d 100644
--- a/storage/mysql/provider.go
+++ b/storage/mysql/provider.go
@@ -19,9 +19,9 @@ import (
 	"flag"
 	"sync"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/monitoring"
 	"github.com/google/trillian/storage"
+	"k8s.io/klog/v2"
 
 	// Load MySQL driver
 	_ "github.com/go-sql-driver/mysql"
@@ -50,7 +50,7 @@ func GetDatabase() (*sql.DB, error) {
 
 func init() {
 	if err := storage.RegisterProvider("mysql", newMySQLStorageProvider); err != nil {
-		glog.Fatalf("Failed to register storage provider mysql: %v", err)
+		klog.Fatalf("Failed to register storage provider mysql: %v", err)
 	}
 }
 
diff --git a/storage/mysql/queue.go b/storage/mysql/queue.go
index 73c48561..fd936ec4 100644
--- a/storage/mysql/queue.go
+++ b/storage/mysql/queue.go
@@ -24,9 +24,9 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"google.golang.org/protobuf/types/known/timestamppb"
+	"k8s.io/klog/v2"
 )
 
 const (
@@ -58,7 +58,7 @@ func (t *logTreeTX) dequeueLeaf(rows *sql.Rows) (*trillian.LogLeaf, dequeuedLeaf
 
 	err := rows.Scan(&leafIDHash, &merkleHash, &queueTimestamp)
 	if err != nil {
-		glog.Warningf("Error scanning work rows: %s", err)
+		klog.Warningf("Error scanning work rows: %s", err)
 		return nil, dequeuedLeaf{}, err
 	}
 
@@ -102,7 +102,7 @@ func (t *logTreeTX) UpdateSequencedLeaves(ctx context.Context, leaves []*trillia
 			leaf.LeafIndex,
 			iTimestamp.UnixNano())
 		if err != nil {
-			glog.Warningf("Failed to update sequenced leaves: %s", err)
+			klog.Warningf("Failed to update sequenced leaves: %s", err)
 			return err
 		}
 
@@ -125,7 +125,7 @@ func (t *logTreeTX) removeSequencedLeaves(ctx context.Context, leaves []dequeued
 	// QueueLeaves.
 	stx, err := t.tx.PrepareContext(ctx, deleteUnsequencedSQL)
 	if err != nil {
-		glog.Warningf("Failed to prep delete statement for sequenced work: %v", err)
+		klog.Warningf("Failed to prep delete statement for sequenced work: %v", err)
 		return err
 	}
 	defer stx.Close()
diff --git a/storage/mysql/queue_batching.go b/storage/mysql/queue_batching.go
index ece8060d..d5317aa1 100644
--- a/storage/mysql/queue_batching.go
+++ b/storage/mysql/queue_batching.go
@@ -26,9 +26,9 @@ import (
 	"strings"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"google.golang.org/protobuf/types/known/timestamppb"
+	"k8s.io/klog/v2"
 )
 
 const (
@@ -57,7 +57,7 @@ func (t *logTreeTX) dequeueLeaf(rows *sql.Rows) (*trillian.LogLeaf, dequeuedLeaf
 
 	err := rows.Scan(&leafIDHash, &merkleHash, &queueTimestamp, &queueID)
 	if err != nil {
-		glog.Warningf("Error scanning work rows: %s", err)
+		klog.Warningf("Error scanning work rows: %s", err)
 		return nil, nil, err
 	}
 
@@ -112,7 +112,7 @@ func (t *logTreeTX) UpdateSequencedLeaves(ctx context.Context, leaves []*trillia
 	}
 	result, err := t.tx.ExecContext(ctx, insertSequencedLeafSQL+strings.Join(querySuffix, ","), args...)
 	if err != nil {
-		glog.Warningf("Failed to update sequenced leaves: %s", err)
+		klog.Warningf("Failed to update sequenced leaves: %s", err)
 	}
 	if err := checkResultOkAndRowCountIs(result, err, int64(len(leaves))); err != nil {
 		return err
@@ -133,7 +133,7 @@ func (t *logTreeTX) removeSequencedLeaves(ctx context.Context, queueIDs []dequeu
 	// QueueLeaves.
 	tmpl, err := t.ls.getDeleteUnsequencedStmt(ctx, len(queueIDs))
 	if err != nil {
-		glog.Warningf("Failed to get delete statement for sequenced work: %s", err)
+		klog.Warningf("Failed to get delete statement for sequenced work: %s", err)
 		return err
 	}
 	stx := t.tx.StmtContext(ctx, tmpl)
@@ -144,7 +144,7 @@ func (t *logTreeTX) removeSequencedLeaves(ctx context.Context, queueIDs []dequeu
 	result, err := stx.ExecContext(ctx, args...)
 	if err != nil {
 		// Error is handled by checkResultOkAndRowCountIs() below
-		glog.Warningf("Failed to delete sequenced work: %s", err)
+		klog.Warningf("Failed to delete sequenced work: %s", err)
 	}
 	return checkResultOkAndRowCountIs(result, err, int64(len(queueIDs)))
 }
diff --git a/storage/mysql/tree_storage.go b/storage/mysql/tree_storage.go
index 52ca479b..ffb0159c 100644
--- a/storage/mysql/tree_storage.go
+++ b/storage/mysql/tree_storage.go
@@ -24,12 +24,12 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/storage/cache"
 	"github.com/google/trillian/storage/storagepb"
 	"github.com/google/trillian/storage/tree"
 	"google.golang.org/protobuf/proto"
+	"k8s.io/klog/v2"
 )
 
 // These statements are fixed
@@ -73,12 +73,12 @@ func OpenDB(dbURL string) (*sql.DB, error) {
 	db, err := sql.Open("mysql", dbURL)
 	if err != nil {
 		// Don't log uri as it could contain credentials
-		glog.Warningf("Could not open MySQL database, check config: %s", err)
+		klog.Warningf("Could not open MySQL database, check config: %s", err)
 		return nil, err
 	}
 
 	if _, err := db.ExecContext(context.TODO(), "SET sql_mode = 'STRICT_ALL_TABLES'"); err != nil {
-		glog.Warningf("Failed to set strict mode on mysql db: %s", err)
+		klog.Warningf("Failed to set strict mode on mysql db: %s", err)
 		return nil, err
 	}
 
@@ -124,7 +124,7 @@ func (m *mySQLTreeStorage) getStmt(ctx context.Context, statement string, num in
 
 	s, err := m.db.PrepareContext(ctx, expandPlaceholderSQL(statement, num, first, rest))
 	if err != nil {
-		glog.Warningf("Failed to prepare statement %d: %s", num, err)
+		klog.Warningf("Failed to prepare statement %d: %s", num, err)
 		return nil, err
 	}
 
@@ -144,7 +144,7 @@ func (m *mySQLTreeStorage) setSubtreeStmt(ctx context.Context, num int) (*sql.St
 func (m *mySQLTreeStorage) beginTreeTx(ctx context.Context, tree *trillian.Tree, hashSizeBytes int, subtreeCache *cache.SubtreeCache) (treeTX, error) {
 	t, err := m.db.BeginTx(ctx, nil /* opts */)
 	if err != nil {
-		glog.Warningf("Could not start tree TX: %s", err)
+		klog.Warningf("Could not start tree TX: %s", err)
 		return treeTX{}, err
 	}
 	return treeTX{
@@ -173,8 +173,8 @@ type treeTX struct {
 }
 
 func (t *treeTX) getSubtrees(ctx context.Context, treeRevision int64, ids [][]byte) ([]*storagepb.SubtreeProto, error) {
-	glog.V(2).Infof("getSubtrees(len(ids)=%d)", len(ids))
-	glog.V(4).Infof("getSubtrees(")
+	klog.V(2).Infof("getSubtrees(len(ids)=%d)", len(ids))
+	klog.V(4).Infof("getSubtrees(")
 	if len(ids) == 0 {
 		return nil, nil
 	}
@@ -190,7 +190,7 @@ func (t *treeTX) getSubtrees(ctx context.Context, treeRevision int64, ids [][]by
 
 	// populate args with ids.
 	for _, id := range ids {
-		glog.V(4).Infof("  id: %x", id)
+		klog.V(4).Infof("  id: %x", id)
 		args = append(args, id)
 	}
 
@@ -200,14 +200,14 @@ func (t *treeTX) getSubtrees(ctx context.Context, treeRevision int64, ids [][]by
 
 	rows, err := stx.QueryContext(ctx, args...)
 	if err != nil {
-		glog.Warningf("Failed to get merkle subtrees: %s", err)
+		klog.Warningf("Failed to get merkle subtrees: %s", err)
 		return nil, err
 	}
 	defer rows.Close()
 
 	if rows.Err() != nil {
 		// Nothing from the DB
-		glog.Warningf("Nothing from DB: %s", rows.Err())
+		klog.Warningf("Nothing from DB: %s", rows.Err())
 		return nil, rows.Err()
 	}
 
@@ -218,12 +218,12 @@ func (t *treeTX) getSubtrees(ctx context.Context, treeRevision int64, ids [][]by
 		var subtreeRev int64
 		var nodesRaw []byte
 		if err := rows.Scan(&subtreeIDBytes, &subtreeRev, &nodesRaw); err != nil {
-			glog.Warningf("Failed to scan merkle subtree: %s", err)
+			klog.Warningf("Failed to scan merkle subtree: %s", err)
 			return nil, err
 		}
 		var subtree storagepb.SubtreeProto
 		if err := proto.Unmarshal(nodesRaw, &subtree); err != nil {
-			glog.Warningf("Failed to unmarshal SubtreeProto: %s", err)
+			klog.Warningf("Failed to unmarshal SubtreeProto: %s", err)
 			return nil, err
 		}
 		if subtree.Prefix == nil {
@@ -231,15 +231,15 @@ func (t *treeTX) getSubtrees(ctx context.Context, treeRevision int64, ids [][]by
 		}
 		ret = append(ret, &subtree)
 
-		if glog.V(4) {
-			glog.Infof("  subtree: NID: %x, prefix: %x, depth: %d",
+		if klog.V(4).Enabled() {
+			klog.Infof("  subtree: NID: %x, prefix: %x, depth: %d",
 				subtreeIDBytes, subtree.Prefix, subtree.Depth)
 			for k, v := range subtree.Leaves {
 				b, err := base64.StdEncoding.DecodeString(k)
 				if err != nil {
-					glog.Errorf("base64.DecodeString(%v): %v", k, err)
+					klog.Errorf("base64.DecodeString(%v): %v", k, err)
 				}
-				glog.Infof("     %x: %x", b, v)
+				klog.Infof("     %x: %x", b, v)
 			}
 		}
 	}
@@ -250,17 +250,17 @@ func (t *treeTX) getSubtrees(ctx context.Context, treeRevision int64, ids [][]by
 }
 
 func (t *treeTX) storeSubtrees(ctx context.Context, subtrees []*storagepb.SubtreeProto) error {
-	glog.V(2).Infof("storeSubtrees(len(subtrees)=%d)", len(subtrees))
-	if glog.V(4) {
-		glog.Infof("storeSubtrees(")
+	klog.V(2).Infof("storeSubtrees(len(subtrees)=%d)", len(subtrees))
+	if klog.V(4).Enabled() {
+		klog.Infof("storeSubtrees(")
 		for _, s := range subtrees {
-			glog.Infof("  prefix: %x, depth: %d", s.Prefix, s.Depth)
+			klog.Infof("  prefix: %x, depth: %d", s.Prefix, s.Depth)
 			for k, v := range s.Leaves {
 				b, err := base64.StdEncoding.DecodeString(k)
 				if err != nil {
-					glog.Errorf("base64.DecodeString(%v): %v", k, err)
+					klog.Errorf("base64.DecodeString(%v): %v", k, err)
 				}
-				glog.Infof("     %x: %x", b, v)
+				klog.Infof("     %x: %x", b, v)
 			}
 		}
 	}
@@ -296,7 +296,7 @@ func (t *treeTX) storeSubtrees(ctx context.Context, subtrees []*storagepb.Subtre
 
 	r, err := stx.ExecContext(ctx, args...)
 	if err != nil {
-		glog.Warningf("Failed to set merkle subtrees: %s", err)
+		klog.Warningf("Failed to set merkle subtrees: %s", err)
 		return err
 	}
 	_, _ = r.RowsAffected()
@@ -345,17 +345,17 @@ func (t *treeTX) Commit(ctx context.Context) error {
 	if t.writeRevision > -1 {
 		tiles, err := t.subtreeCache.UpdatedTiles()
 		if err != nil {
-			glog.Warningf("SubtreeCache updated tiles error: %v", err)
+			klog.Warningf("SubtreeCache updated tiles error: %v", err)
 			return err
 		}
 		if err := t.storeSubtrees(ctx, tiles); err != nil {
-			glog.Warningf("TX commit flush error: %v", err)
+			klog.Warningf("TX commit flush error: %v", err)
 			return err
 		}
 	}
 	t.closed = true
 	if err := t.tx.Commit(); err != nil {
-		glog.Warningf("TX commit error: %s, stack:\n%s", err, string(debug.Stack()))
+		klog.Warningf("TX commit error: %s, stack:\n%s", err, string(debug.Stack()))
 		return err
 	}
 	return nil
@@ -364,7 +364,7 @@ func (t *treeTX) Commit(ctx context.Context) error {
 func (t *treeTX) rollbackInternal() error {
 	t.closed = true
 	if err := t.tx.Rollback(); err != nil {
-		glog.Warningf("TX rollback error: %s, stack:\n%s", err, string(debug.Stack()))
+		klog.Warningf("TX rollback error: %s, stack:\n%s", err, string(debug.Stack()))
 		return err
 	}
 	return nil
@@ -378,7 +378,7 @@ func (t *treeTX) Close() error {
 	}
 	err := t.rollbackInternal()
 	if err != nil {
-		glog.Warningf("Rollback error on Close(): %v", err)
+		klog.Warningf("Rollback error on Close(): %v", err)
 	}
 	return err
 }
diff --git a/util/clock/timesource.go b/util/clock/timesource.go
index 43c9a831..76c3cadb 100644
--- a/util/clock/timesource.go
+++ b/util/clock/timesource.go
@@ -20,7 +20,7 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
+	"k8s.io/klog/v2"
 )
 
 // System is a default TimeSource that provides system time.
@@ -128,6 +128,6 @@ func (p *PredefinedFake) Now() time.Time {
 
 // NewTimer creates a timer with the specified delay. Not implemented.
 func (p *PredefinedFake) NewTimer(d time.Duration) Timer {
-	glog.Exitf("PredefinedFake.NewTimer is not implemented")
+	klog.Exitf("PredefinedFake.NewTimer is not implemented")
 	return nil
 }
diff --git a/util/election/runner.go b/util/election/runner.go
index 41316ff2..0d849551 100644
--- a/util/election/runner.go
+++ b/util/election/runner.go
@@ -20,9 +20,9 @@ import (
 	"math/rand"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/util/clock"
 	"github.com/google/trillian/util/election2"
+	"k8s.io/klog/v2"
 )
 
 // Minimum values for configuration intervals.
@@ -106,28 +106,28 @@ func (er *Runner) Run(ctx context.Context, pending chan<- Resignation) {
 		return // The context has been canceled during the sleep.
 	}
 
-	glog.V(1).Infof("%s: start election-monitoring loop ", er.id)
+	klog.V(1).Infof("%s: start election-monitoring loop ", er.id)
 	defer func() {
-		glog.Infof("%s: shutdown election-monitoring loop", er.id)
+		klog.Infof("%s: shutdown election-monitoring loop", er.id)
 		if err := er.election.Close(ctx); err != nil {
-			glog.Warningf("%s: election.Close: %v", er.id, err)
+			klog.Warningf("%s: election.Close: %v", er.id, err)
 		}
 	}()
 
 	for {
 		if err := er.beMaster(ctx, pending); err != nil {
-			glog.Errorf("%s: %v", er.id, err)
+			klog.Errorf("%s: %v", er.id, err)
 			break
 		}
 	}
 }
 
 func (er *Runner) beMaster(ctx context.Context, pending chan<- Resignation) error {
-	glog.V(1).Infof("%s: When I left you, I was but the learner", er.id)
+	klog.V(1).Infof("%s: When I left you, I was but the learner", er.id)
 	if err := er.election.Await(ctx); err != nil {
 		return fmt.Errorf("election.Await() failed: %v", err)
 	}
-	glog.Infof("%s: Now, I am the master", er.id)
+	klog.Infof("%s: Now, I am the master", er.id)
 	er.tracker.Set(er.id, true)
 	defer er.tracker.Set(er.id, false)
 
@@ -141,18 +141,18 @@ func (er *Runner) beMaster(ctx context.Context, pending chan<- Resignation) erro
 
 	select {
 	case <-mctx.Done(): // Mastership context is canceled.
-		glog.Errorf("%s: no longer the master!", er.id)
+		klog.Errorf("%s: no longer the master!", er.id)
 		return mctx.Err()
 
 	case <-timer.Chan():
-		glog.Infof("%s: queue up resignation of mastership", er.id)
+		klog.Infof("%s: queue up resignation of mastership", er.id)
 		done := make(chan struct{})
 		r := Resignation{ID: er.id, er: er, done: done}
 		select {
 		case pending <- r:
 			<-done // Block until acted on.
 		default:
-			glog.Warning("Dropping resignation because operation manager seems to be exiting")
+			klog.Warning("Dropping resignation because operation manager seems to be exiting")
 		}
 	}
 	return nil
@@ -169,8 +169,8 @@ type Resignation struct {
 // Execute performs the pending deliberate resignation for an election runner.
 func (r *Resignation) Execute(ctx context.Context) {
 	defer close(r.done)
-	glog.Infof("%s: deliberately resigning mastership", r.er.id)
+	klog.Infof("%s: deliberately resigning mastership", r.er.id)
 	if err := r.er.election.Resign(ctx); err != nil {
-		glog.Errorf("%s: failed to resign mastership: %v", r.er.id, err)
+		klog.Errorf("%s: failed to resign mastership: %v", r.er.id, err)
 	}
 }
diff --git a/util/election/tracker.go b/util/election/tracker.go
index f774e806..072ddf4a 100644
--- a/util/election/tracker.go
+++ b/util/election/tracker.go
@@ -20,7 +20,7 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/golang/glog"
+	"k8s.io/klog/v2"
 )
 
 // MasterTracker tracks the current mastership state across multiple IDs.
@@ -48,7 +48,7 @@ func (mt *MasterTracker) Set(id string, isMaster bool) {
 	defer mt.mu.Unlock()
 	wasMaster, ok := mt.masterFor[id]
 	if ok && isMaster == wasMaster {
-		glog.Warningf("toggle masterFor[%s] from %v to %v!", id, wasMaster, isMaster)
+		klog.Warningf("toggle masterFor[%s] from %v to %v!", id, wasMaster, isMaster)
 	}
 	mt.masterFor[id] = isMaster
 	if isMaster && !wasMaster {
diff --git a/util/election2/etcd/election.go b/util/election2/etcd/election.go
index 458e0ce6..cadc0fdd 100644
--- a/util/election2/etcd/election.go
+++ b/util/election2/etcd/election.go
@@ -20,10 +20,10 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/util/election2"
 	clientv3 "go.etcd.io/etcd/client/v3"
 	"go.etcd.io/etcd/client/v3/concurrency"
+	"k8s.io/klog/v2"
 )
 
 const resignID = "<resign>"
@@ -79,7 +79,7 @@ func (e *Election) WithMastership(ctx context.Context) (context.Context, error)
 	go func() {
 		defer func() {
 			cancel()
-			glog.Infof("%s: canceled mastership context", e.resourceID)
+			klog.Infof("%s: canceled mastership context", e.resourceID)
 		}()
 
 		for rsp := range ch {
@@ -88,10 +88,10 @@ func (e *Election) WithMastership(ctx context.Context) (context.Context, error)
 				conquerorID := string(kv.Value)
 				// TODO(pavelkalinnikov): conquerorID can be resignID too. Serialize a
 				// protobuf with all mastership details instead of ID string.
-				glog.Warningf("%s: mastership overtaken by %s", e.resourceID, conquerorID)
+				klog.Warningf("%s: mastership overtaken by %s", e.resourceID, conquerorID)
 				break
 			} else if string(kv.Value) == resignID {
-				glog.Infof("%s: canceling context due to resignation", e.resourceID)
+				klog.Infof("%s: canceling context due to resignation", e.resourceID)
 				break
 			}
 		}
@@ -117,7 +117,7 @@ func (e *Election) Resign(ctx context.Context) error {
 // method should be called after Close.
 func (e *Election) Close(ctx context.Context) error {
 	if err := e.Resign(ctx); err != nil && err != concurrency.ErrElectionNotLeader {
-		glog.Errorf("%s: Resign(): %v", e.resourceID, err)
+		klog.Errorf("%s: Resign(): %v", e.resourceID, err)
 	}
 	// Session's Close revokes the underlying lease, which results in removing
 	// the election-related keys. This achieves the effect of resignation even if
@@ -161,7 +161,7 @@ func (f *Factory) NewElection(ctx context.Context, resourceID string) (election2
 		session:    session,
 		election:   election,
 	}
-	glog.Infof("Election created: %+v", el)
+	klog.Infof("Election created: %+v", el)
 
 	return &el, nil
 }
diff --git a/util/process.go b/util/process.go
index 5889083c..83aeab46 100644
--- a/util/process.go
+++ b/util/process.go
@@ -21,7 +21,7 @@ import (
 	"os/signal"
 	"syscall"
 
-	"github.com/golang/glog"
+	"k8s.io/klog/v2"
 )
 
 // AwaitSignal waits for standard termination signals, then runs the given
@@ -36,9 +36,9 @@ func AwaitSignal(ctx context.Context, doneFn func()) {
 	// Wait for a signal or context cancellation.
 	select {
 	case sig := <-sigs:
-		glog.Warningf("Signal received: %v", sig)
+		klog.Warningf("Signal received: %v", sig)
 		doneFn()
 	case <-ctx.Done():
-		glog.Infof("AwaitSignal canceled: %v", ctx.Err())
+		klog.Infof("AwaitSignal canceled: %v", ctx.Err())
 	}
 }


Test patch:
diff --git a/client/backoff/backoff_test.go b/client/backoff/backoff_test.go
index 68d1a9a0..60ffe3ba 100644
--- a/client/backoff/backoff_test.go
+++ b/client/backoff/backoff_test.go
@@ -23,7 +23,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 
-	_ "github.com/golang/glog"
+	_ "k8s.io/klog/v2"
 )
 
 func TestBackoff(t *testing.T) {
diff --git a/client/mutating_client_test.go b/client/mutating_client_test.go
index e8e680fd..5a116f4e 100644
--- a/client/mutating_client_test.go
+++ b/client/mutating_client_test.go
@@ -18,10 +18,10 @@ import (
 	"context"
 	"math/rand"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/types"
 	"google.golang.org/grpc"
+	"k8s.io/klog/v2"
 )
 
 // MutatingLogClient supports applying mutations to the return values of the TrillianLogClient
@@ -67,7 +67,7 @@ func (c *MutatingLogClient) GetInclusionProofByHash(ctx context.Context, in *tri
 	if c.mutateInclusionProof {
 		h := rand.Intn(len(resp.Proof))
 		if len(resp.Proof[h].Hashes) == 0 {
-			glog.Warningf("Inclusion proof not modified because treesize = 0")
+			klog.Warningf("Inclusion proof not modified because treesize = 0")
 			return resp, nil
 		}
 		i := rand.Intn(len(resp.Proof[h].Hashes))
@@ -85,7 +85,7 @@ func (c *MutatingLogClient) GetConsistencyProof(ctx context.Context, in *trillia
 	}
 	if c.mutateConsistencyProof {
 		if len(resp.Proof.Hashes) == 0 {
-			glog.Warningf("Consistency proof not modified because len(Hashes) = 0")
+			klog.Warningf("Consistency proof not modified because len(Hashes) = 0")
 			return resp, nil
 		}
 		i := rand.Intn(len(resp.Proof.Hashes))
diff --git a/cmd/flags_test.go b/cmd/flags_test.go
index 9115255b..4663a323 100644
--- a/cmd/flags_test.go
+++ b/cmd/flags_test.go
@@ -19,7 +19,7 @@ import (
 	"os"
 	"testing"
 
-	_ "github.com/golang/glog"
+	_ "k8s.io/klog/v2"
 )
 
 func TestParseFlags(t *testing.T) {
diff --git a/crypto/keys/der/der_test.go b/crypto/keys/der/der_test.go
index ffa5412c..b4908603 100644
--- a/crypto/keys/der/der_test.go
+++ b/crypto/keys/der/der_test.go
@@ -25,7 +25,7 @@ import (
 	"github.com/google/trillian/crypto/keyspb"
 	"google.golang.org/protobuf/proto"
 
-	_ "github.com/golang/glog"
+	_ "k8s.io/klog/v2"
 )
 
 const (
diff --git a/crypto/keys/pem/pem_test.go b/crypto/keys/pem/pem_test.go
index 0c1c4f41..7674a53a 100644
--- a/crypto/keys/pem/pem_test.go
+++ b/crypto/keys/pem/pem_test.go
@@ -25,7 +25,7 @@ import (
 	"github.com/google/trillian/testonly"
 	"google.golang.org/protobuf/proto"
 
-	_ "github.com/golang/glog"
+	_ "k8s.io/klog/v2"
 )
 
 const (
diff --git a/crypto/keys/pkcs11/pkcs11_test.go b/crypto/keys/pkcs11/pkcs11_test.go
index 6d9fed29..455ce430 100644
--- a/crypto/keys/pkcs11/pkcs11_test.go
+++ b/crypto/keys/pkcs11/pkcs11_test.go
@@ -20,7 +20,7 @@ package pkcs11
 import (
 	"testing"
 
-	_ "github.com/golang/glog"
+	_ "k8s.io/klog/v2"
 )
 
 func TestPkcs11(t *testing.T) {
diff --git a/docs/storage/commit_log/simkafka/kafka_test.go b/docs/storage/commit_log/simkafka/kafka_test.go
index 7d9d7d23..017fa7b3 100644
--- a/docs/storage/commit_log/simkafka/kafka_test.go
+++ b/docs/storage/commit_log/simkafka/kafka_test.go
@@ -18,7 +18,7 @@ import (
 	"reflect"
 	"testing"
 
-	_ "github.com/golang/glog"
+	_ "k8s.io/klog/v2"
 )
 
 func TestReadEmpty(t *testing.T) {
diff --git a/integration/admin/admin_integration_test.go b/integration/admin/admin_integration_test.go
index c38f9bb5..be3619ec 100644
--- a/integration/admin/admin_integration_test.go
+++ b/integration/admin/admin_integration_test.go
@@ -21,7 +21,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/trillian"
 	"github.com/google/trillian/server/interceptor"
@@ -36,6 +35,7 @@ import (
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/timestamppb"
+	"k8s.io/klog/v2"
 
 	sa "github.com/google/trillian/server/admin"
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
@@ -553,7 +553,7 @@ type testServer struct {
 func (ts *testServer) closeAll() {
 	if ts.conn != nil {
 		if err := ts.conn.Close(); err != nil {
-			glog.Errorf("testServer: conn.Close()=%v", err)
+			klog.Errorf("testServer: conn.Close()=%v", err)
 		}
 	}
 	if ts.server != nil {
@@ -561,7 +561,7 @@ func (ts *testServer) closeAll() {
 	}
 	if ts.lis != nil {
 		if err := ts.lis.Close(); err != nil {
-			glog.Errorf("testServer: lis.Close()=%v", err)
+			klog.Errorf("testServer: lis.Close()=%v", err)
 		}
 	}
 	if ts.cleanup != nil {
@@ -601,7 +601,7 @@ func setupAdminServer(ctx context.Context, t *testing.T) (*testServer, error) {
 	trillian.RegisterTrillianAdminServer(ts.server, sa.New(registry, nil /* allowedTreeTypes */))
 	go func() {
 		if err := ts.server.Serve(ts.lis); err != nil {
-			glog.Errorf("server.Serve()=%v", err)
+			klog.Errorf("server.Serve()=%v", err)
 		}
 	}()
 
diff --git a/integration/log_integration_test.sh b/integration/log_integration_test.sh
index 24305193..96544ffd 100755
--- a/integration/log_integration_test.sh
+++ b/integration/log_integration_test.sh
@@ -34,8 +34,7 @@ go test \
   -timeout=${GO_TEST_TIMEOUT:-5m} \
   ./ \
   --log_rpc_server="${TRILLIAN_SERVER}" \
-  --treeid ${TEST_TREE_ID} \
-  --alsologtostderr
+  --treeid ${TEST_TREE_ID}
 RESULT=$?
 set -e
 popd
diff --git a/merkle/smt/node/id_test.go b/merkle/smt/node/id_test.go
index 296dd7f3..3c215ed8 100644
--- a/merkle/smt/node/id_test.go
+++ b/merkle/smt/node/id_test.go
@@ -19,7 +19,7 @@ import (
 	"strconv"
 	"testing"
 
-	_ "github.com/golang/glog" // Add flags for the overarching test-all command.
+	_ "k8s.io/klog/v2" // Add flags for the overarching test-all command.
 )
 
 func TestNewIDWithLast(t *testing.T) {
diff --git a/server/errors/errors_test.go b/server/errors/errors_test.go
index b74995c4..bbde3218 100644
--- a/server/errors/errors_test.go
+++ b/server/errors/errors_test.go
@@ -19,9 +19,9 @@ import (
 	"errors"
 	"testing"
 
-	_ "github.com/golang/glog"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
+	_ "k8s.io/klog/v2"
 )
 
 func TestWrapError(t *testing.T) {
diff --git a/storage/cache/suffix_test.go b/storage/cache/suffix_test.go
index d91b6829..5f679de1 100644
--- a/storage/cache/suffix_test.go
+++ b/storage/cache/suffix_test.go
@@ -22,7 +22,7 @@ import (
 	"math/big"
 	"testing"
 
-	_ "github.com/golang/glog" // Enable glog flags.
+	_ "k8s.io/klog/v2" // Enable glog flags.
 )
 
 func h2b(h string) []byte {
diff --git a/storage/mysql/storage_test.go b/storage/mysql/storage_test.go
index 32bb6789..998c4cc6 100644
--- a/storage/mysql/storage_test.go
+++ b/storage/mysql/storage_test.go
@@ -26,7 +26,6 @@ import (
 	"os"
 	"testing"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/storage"
 	"github.com/google/trillian/storage/testdb"
@@ -35,6 +34,7 @@ import (
 	"github.com/google/trillian/types"
 	"github.com/transparency-dev/merkle/compact"
 	"github.com/transparency-dev/merkle/rfc6962"
+	"k8s.io/klog/v2"
 )
 
 func TestNodeRoundTrip(t *testing.T) {
@@ -155,7 +155,7 @@ func createSomeNodes(count int) []stree.Node {
 		r[i].ID = compact.NewNodeID(0, uint64(i))
 		h := sha256.Sum256([]byte{byte(i)})
 		r[i].Hash = h[:]
-		glog.V(3).Infof("Node to store: %v", r[i].ID)
+		klog.V(3).Infof("Node to store: %v", r[i].ID)
 	}
 	return r
 }
@@ -294,7 +294,7 @@ var DB *sql.DB
 func TestMain(m *testing.M) {
 	flag.Parse()
 	if !testdb.MySQLAvailable() {
-		glog.Errorf("MySQL not available, skipping all MySQL storage tests")
+		klog.Errorf("MySQL not available, skipping all MySQL storage tests")
 		return
 	}
 
@@ -303,7 +303,7 @@ func TestMain(m *testing.M) {
 	DB, done = openTestDBOrDie()
 
 	if v, err := getVersion(DB); err == nil {
-		glog.Infof("MySQL version '%v'", v)
+		klog.Infof("MySQL version '%v'", v)
 	}
 	status := m.Run()
 	done(context.Background())
diff --git a/storage/testdb/testdb.go b/storage/testdb/testdb.go
index 78947cc5..c1970ff8 100644
--- a/storage/testdb/testdb.go
+++ b/storage/testdb/testdb.go
@@ -27,9 +27,9 @@ import (
 	"testing"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/testonly"
 	"golang.org/x/sys/unix"
+	"k8s.io/klog/v2"
 
 	_ "github.com/go-sql-driver/mysql" // mysql driver
 )
@@ -121,7 +121,7 @@ func newEmptyDB(ctx context.Context) (*sql.DB, func(context.Context), error) {
 	done := func(ctx context.Context) {
 		defer db.Close()
 		if _, err := db.ExecContext(ctx, fmt.Sprintf("DROP DATABASE %v", name)); err != nil {
-			glog.Warningf("Failed to drop test database %q: %v", name, err)
+			klog.Warningf("Failed to drop test database %q: %v", name, err)
 		}
 	}
 
diff --git a/storage/testdb/testdb_test.go b/storage/testdb/testdb_test.go
index 2ecafd0e..dd0b520b 100644
--- a/storage/testdb/testdb_test.go
+++ b/storage/testdb/testdb_test.go
@@ -17,7 +17,7 @@ package testdb
 import (
 	"testing"
 
-	_ "github.com/golang/glog"
+	_ "k8s.io/klog/v2"
 )
 
 func TestMySQLWarning(t *testing.T) {
diff --git a/storage/testonly/fake_node_reader.go b/storage/testonly/fake_node_reader.go
index 7b4bd337..e571632b 100644
--- a/storage/testonly/fake_node_reader.go
+++ b/storage/testonly/fake_node_reader.go
@@ -19,10 +19,10 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian/storage/tree"
 	"github.com/transparency-dev/merkle/compact"
 	"github.com/transparency-dev/merkle/rfc6962"
+	"k8s.io/klog/v2"
 )
 
 // This is a fake implementation of a NodeReader intended for use in testing Merkle path code.
@@ -47,7 +47,7 @@ func NewFakeNodeReader(nodes []tree.Node) *FakeNodeReader {
 		id := node.ID
 		if _, ok := nodeMap[id]; ok {
 			// Duplicate mapping - the test data is invalid so don't continue.
-			glog.Fatalf("NewFakeNodeReader duplicate mapping for: %+v in:\n%v", id, nodes)
+			klog.Fatalf("NewFakeNodeReader duplicate mapping for: %+v in:\n%v", id, nodes)
 		}
 		nodeMap[id] = node
 	}
@@ -110,7 +110,7 @@ func NewMultiFakeNodeReaderFromLeaves(batches []LeafBatch) *MultiFakeNodeReader
 	lastBatchRevision := int64(0)
 	for _, batch := range batches {
 		if batch.TreeRevision <= lastBatchRevision {
-			glog.Fatalf("Batches out of order revision: %d, last: %d in:\n%v", batch.TreeRevision,
+			klog.Fatalf("Batches out of order revision: %d, last: %d in:\n%v", batch.TreeRevision,
 				lastBatchRevision, batches)
 		}
 
diff --git a/testonly/flagsaver/flagsaver.go b/testonly/flagsaver/flagsaver.go
index 738b12e7..f9a35bfc 100644
--- a/testonly/flagsaver/flagsaver.go
+++ b/testonly/flagsaver/flagsaver.go
@@ -27,7 +27,7 @@ import (
 	"flag"
 	"strings"
 
-	"github.com/golang/glog"
+	"k8s.io/klog/v2"
 )
 
 // Stash holds flag values so that they can be restored at the end of a test.
@@ -68,6 +68,6 @@ func Save() *Stash {
 // state that could cause subsequent tests to misbehave.
 func (s *Stash) MustRestore() {
 	if err := s.Restore(); err != nil {
-		glog.Fatalf("MustRestore(): failed to restore flags: %v %v", err, *s)
+		klog.Fatalf("MustRestore(): failed to restore flags: %v %v", err, *s)
 	}
 }
diff --git a/testonly/flagsaver/flagsaver_test.go b/testonly/flagsaver/flagsaver_test.go
index 79529134..13deaf08 100644
--- a/testonly/flagsaver/flagsaver_test.go
+++ b/testonly/flagsaver/flagsaver_test.go
@@ -19,7 +19,7 @@ import (
 	"testing"
 	"time"
 
-	_ "github.com/golang/glog"
+	_ "k8s.io/klog/v2"
 )
 
 var (
diff --git a/testonly/integration/logenv.go b/testonly/integration/logenv.go
index 6e75740b..f15b938b 100644
--- a/testonly/integration/logenv.go
+++ b/testonly/integration/logenv.go
@@ -23,9 +23,9 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
+	"k8s.io/klog/v2"
 
 	"github.com/google/trillian"
 	"github.com/google/trillian/extension"
@@ -158,8 +158,8 @@ func NewLogEnvWithRegistryAndGRPCOptions(ctx context.Context, numSequencers int,
 	go func(wg *sync.WaitGroup, grpcServer *grpc.Server, lis net.Listener) {
 		defer wg.Done()
 		if err := grpcServer.Serve(lis); err != nil {
-			glog.Errorf("gRPC server stopped: %v", err)
-			glog.Flush()
+			klog.Errorf("gRPC server stopped: %v", err)
+			klog.Flush()
 		}
 	}(&wg, grpcServer, lis)
 
diff --git a/testonly/matchers/atleast_test.go b/testonly/matchers/atleast_test.go
index 9bbb9547..228f1913 100644
--- a/testonly/matchers/atleast_test.go
+++ b/testonly/matchers/atleast_test.go
@@ -17,7 +17,7 @@ package matchers
 import (
 	"testing"
 
-	_ "github.com/golang/glog"
+	_ "k8s.io/klog/v2"
 )
 
 func TestAtLeast(t *testing.T) {
diff --git a/testonly/matchers/proto_test.go b/testonly/matchers/proto_test.go
index f905554c..af298246 100644
--- a/testonly/matchers/proto_test.go
+++ b/testonly/matchers/proto_test.go
@@ -17,9 +17,9 @@ package matchers
 import (
 	"testing"
 
-	_ "github.com/golang/glog"
 	"github.com/google/trillian"
 	"google.golang.org/protobuf/proto"
+	_ "k8s.io/klog/v2"
 )
 
 func TestProtoEquals(t *testing.T) {
diff --git a/testonly/mdm/mdm.go b/testonly/mdm/mdm.go
index 95bf4c34..a517665d 100644
--- a/testonly/mdm/mdm.go
+++ b/testonly/mdm/mdm.go
@@ -24,11 +24,11 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/client"
 	"github.com/google/trillian/monitoring"
 	"github.com/google/trillian/types"
+	"k8s.io/klog/v2"
 )
 
 // MergeDelayMonitor submits leaves to a Log and measures merge delay.
@@ -83,7 +83,7 @@ func NewMonitor(ctx context.Context, logID int64, cl trillian.TrillianLogClient,
 
 // Monitor runs merge delay monitoring until its context is cancelled or an error occurs.
 func (m *MergeDelayMonitor) Monitor(ctx context.Context) error {
-	glog.Infof("starting %d parallel monitor instances", m.opts.ParallelAdds)
+	klog.Infof("starting %d parallel monitor instances", m.opts.ParallelAdds)
 	errs := make(chan error, m.opts.ParallelAdds)
 	var wg sync.WaitGroup
 	wg.Add(m.opts.ParallelAdds)
@@ -98,16 +98,16 @@ func (m *MergeDelayMonitor) Monitor(ctx context.Context) error {
 
 	ticker := time.NewTicker(m.opts.EmitInterval)
 	defer ticker.Stop()
-	glog.V(1).Infof("start stats ticker every %v", m.opts.EmitInterval)
+	klog.V(1).Infof("start stats ticker every %v", m.opts.EmitInterval)
 	go func(c <-chan time.Time) {
 		for range c {
 			countT, totalT := m.Stats(true)
 			if countT > 0 {
-				glog.Infof("new leaves: %d in %f secs, average %f secs", countT, totalT, totalT/float64(countT))
+				klog.Infof("new leaves: %d in %f secs, average %f secs", countT, totalT, totalT/float64(countT))
 			}
 			countF, totalF := m.Stats(false)
 			if countF > 0 {
-				glog.Infof("dup leaves: %d in %f secs, average %f secs", countF, totalF, totalF/float64(countF))
+				klog.Infof("dup leaves: %d in %f secs, average %f secs", countF, totalF, totalF/float64(countF))
 			}
 		}
 	}(ticker.C)
@@ -116,7 +116,7 @@ func (m *MergeDelayMonitor) Monitor(ctx context.Context) error {
 	close(errs)
 	var lastErr error
 	for err := range errs {
-		glog.Errorf("monitor failure: %v", err)
+		klog.Errorf("monitor failure: %v", err)
 		lastErr = err
 	}
 	return lastErr
@@ -135,7 +135,7 @@ func (m *MergeDelayMonitor) monitor(ctx context.Context, idx int) error {
 		}
 
 		// Add the leaf data and wait for its inclusion.
-		glog.V(1).Infof("[%d] submit new=%t leaf and wait for inclusion (within %v)", idx, createNew, m.opts.Deadline)
+		klog.V(1).Infof("[%d] submit new=%t leaf and wait for inclusion (within %v)", idx, createNew, m.opts.Deadline)
 		cctx, cancel := context.WithTimeout(ctx, m.opts.Deadline)
 		start := time.Now()
 		err := m.client[idx].AddLeaf(cctx, data)
@@ -145,7 +145,7 @@ func (m *MergeDelayMonitor) monitor(ctx context.Context, idx int) error {
 		}
 		mergeDelay := time.Since(start)
 		mergeDelayDist.Observe(mergeDelay.Seconds(), logIDLabel, newLeafLabel[createNew])
-		glog.V(1).Infof("[%d] merge delay for new=%t leaf = %v", idx, createNew, mergeDelay)
+		klog.V(1).Infof("[%d] merge delay for new=%t leaf = %v", idx, createNew, mergeDelay)
 
 		select {
 		case <-ctx.Done():
diff --git a/testonly/mdm/mdmtest/main.go b/testonly/mdm/mdmtest/main.go
index ca320cfd..d4ba2719 100644
--- a/testonly/mdm/mdmtest/main.go
+++ b/testonly/mdm/mdmtest/main.go
@@ -22,7 +22,6 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/golang/glog"
 	"github.com/google/trillian"
 	"github.com/google/trillian/client"
 	"github.com/google/trillian/monitoring"
@@ -33,6 +32,7 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/protobuf/types/known/durationpb"
+	"k8s.io/klog/v2"
 )
 
 var (
@@ -52,7 +52,7 @@ func main() {
 	flag.Parse()
 	ctx := context.Background()
 	if err := innerMain(ctx); err != nil {
-		glog.Exit(err)
+		klog.Exit(err)
 	}
 }
 
@@ -62,17 +62,17 @@ func innerMain(ctx context.Context) error {
 		mf = prometheus.MetricFactory{}
 		http.Handle("/metrics", promhttp.Handler())
 		server := http.Server{Addr: *metricsEndpoint, Handler: nil}
-		glog.Infof("Serving metrics at %v", *metricsEndpoint)
+		klog.Infof("Serving metrics at %v", *metricsEndpoint)
 		go func() {
 			err := server.ListenAndServe()
-			glog.Warningf("Metrics server exited: %v", err)
+			klog.Warningf("Metrics server exited: %v", err)
 		}()
 	}
 
 	dialOpts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
 	c, err := grpc.Dial(*rpcServer, dialOpts...)
 	if err != nil {
-		glog.Exitf("Failed to create log client conn: %v", err)
+		klog.Exitf("Failed to create log client conn: %v", err)
 	}
 	cl := trillian.NewTrillianLogClient(c)
 
@@ -80,7 +80,7 @@ func innerMain(ctx context.Context) error {
 	if len(*adminServer) > 0 {
 		ac, err = grpc.Dial(*adminServer, dialOpts...)
 		if err != nil {
-			glog.Exitf("Failed to create admin client conn: %v", err)
+			klog.Exitf("Failed to create admin client conn: %v", err)
 		}
 	}
 	adminCl := trillian.NewTrillianAdminClient(ac)
@@ -98,15 +98,15 @@ func innerMain(ctx context.Context) error {
 		}
 		tree, err := client.CreateAndInitTree(ctx, &req, adminCl, cl)
 		if err != nil {
-			glog.Exitf("failed to create ephemeral tree: %v", err)
+			klog.Exitf("failed to create ephemeral tree: %v", err)
 		}
 		*logID = tree.TreeId
-		glog.Infof("testing against ephemeral tree %d", *logID)
+		klog.Infof("testing against ephemeral tree %d", *logID)
 		defer func() {
 			req := &trillian.DeleteTreeRequest{TreeId: *logID}
-			glog.Infof("Soft-delete transient Trillian Log with TreeID=%d", *logID)
+			klog.Infof("Soft-delete transient Trillian Log with TreeID=%d", *logID)
 			if _, err := adminCl.DeleteTree(ctx, req); err != nil {
-				glog.Errorf("failed to DeleteTree(%d): %v", *logID, err)
+				klog.Errorf("failed to DeleteTree(%d): %v", *logID, err)
 			}
 		}()
 	}
diff --git a/types/logroot_test.go b/types/logroot_test.go
index dd32f711..527274ce 100644
--- a/types/logroot_test.go
+++ b/types/logroot_test.go
@@ -19,7 +19,7 @@ import (
 	"reflect"
 	"testing"
 
-	_ "github.com/golang/glog" // Don't crash when --logtostderr is supplied
+	_ "k8s.io/klog/v2" // Don't crash when --logtostderr is supplied
 )
 
 func TestLogRoot(t *testing.T) {