# HG changeset patch
# Node ID a09411e67804
# Parent  c612113b1cd4
diff --git a/access/access.go b/access/access.go
--- a/access/access.go
+++ b/access/access.go
@@ -7,7 +7,7 @@ import (
 	"strings"
 	"sync"
 
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 )
 
 const (
@@ -42,7 +42,7 @@ func Setup(repositoriesPath, adminRepo s
 
 func Teardown() {
 	if err := os.Remove(hgwebConfigPath); err != nil {
-		log.Warnf(
+		zap.S().Warnf(
 			"failed to remove temporary hgweb config from %q",
 			hgwebConfigPath,
 		)
@@ -93,7 +93,7 @@ func check(user, repo, action string) bo
 
 	r, err := enforcer.Enforce(user, repo, action)
 	if err != nil {
-		log.Errorf(
+		zap.S().Errorf(
 			"failed to authenticate (%q, %q, %q): %v",
 			user,
 			repo,
diff --git a/access/enforcer.go b/access/enforcer.go
--- a/access/enforcer.go
+++ b/access/enforcer.go
@@ -5,7 +5,7 @@ import (
 	"sync"
 
 	"github.com/casbin/casbin/v2"
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 )
 
 var (
@@ -20,12 +20,12 @@ func refreshEnforcer() error {
 	modelFile := filepath.Join(AdminRepoPath(), modelFilename)
 	policyFile := filepath.Join(AdminRepoPath(), policyFilename)
 
-	log.Debugf("reading model from %q", modelFile)
-	log.Debugf("reading policy from %q", policyFile)
+	zap.S().Debugf("reading model from %q", modelFile)
+	zap.S().Debugf("reading policy from %q", policyFile)
 
 	e, err := casbin.NewEnforcer(modelFile, policyFile)
 	if err != nil {
-		log.Errorf("failed to create new enforcer: %v", err)
+		zap.S().Errorf("failed to create new enforcer: %v", err)
 
 		return err
 	}
diff --git a/access/users.go b/access/users.go
--- a/access/users.go
+++ b/access/users.go
@@ -8,7 +8,7 @@ import (
 	"strings"
 	"sync"
 
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 	"golang.org/x/crypto/ssh"
 )
 
@@ -33,7 +33,7 @@ func refreshKeys() error {
 
 	if _, err := os.Stat(keysPath); err != nil {
 		if os.IsNotExist(err) {
-			log.Error("keys directory not found, no one will be to access hgkeeper")
+			zap.S().Error("keys directory not found, no one will be to access hgkeeper")
 			return nil
 		}
 	}
@@ -56,7 +56,7 @@ func refreshKeys() error {
 func loadSshKey(filename, username string) {
 	buffer, err := ioutil.ReadFile(filename)
 	if err != nil {
-		log.Warnf("failed to read keyfile for user %s: %v", username, err)
+		zap.S().Warnf("failed to read keyfile for user %s: %v", username, err)
 		return
 	}
 
@@ -68,7 +68,7 @@ func loadSshKey(filename, username strin
 		pubkey, _, _, buffer, err = ssh.ParseAuthorizedKey(buffer)
 		if err != nil {
 			if !strings.HasSuffix(err.Error(), "ssh: no key found") {
-				log.Warnf("failed to parse key file for user %s: %v", username, err)
+				zap.S().Warnf("failed to parse key file for user %s: %v", username, err)
 			}
 
 			continue
@@ -76,7 +76,7 @@ func loadSshKey(filename, username strin
 
 		fingerprint := ssh.FingerprintSHA256(pubkey)
 		if duppedname, found := usernames[fingerprint]; found {
-			log.Warnf(
+			zap.S().Warnf(
 				"duplicate key found for user %s. existing key was for %s.",
 				username,
 				duppedname,
@@ -88,7 +88,7 @@ func loadSshKey(filename, username strin
 		counter++
 	}
 
-	log.Infof("loaded %d keys for user %q", counter, username)
+	zap.S().Infof("loaded %d keys for user %q", counter, username)
 }
 
 // UsernameFromFingerprint looks up a username from an SSH key's fingerprint
diff --git a/authorized_keys/command.go b/authorized_keys/command.go
--- a/authorized_keys/command.go
+++ b/authorized_keys/command.go
@@ -4,8 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	log "github.com/sirupsen/logrus"
-
+	"go.uber.org/zap"
 	"keep.imfreedom.org/grim/hgkeeper/access"
 	"keep.imfreedom.org/grim/hgkeeper/globals"
 )
@@ -18,7 +17,8 @@ type Command struct {
 func (c *Command) Run(g *globals.Globals) error {
 	// we're generating output for another command so we need to turn off our
 	// normal logging.
-	log.SetLevel(log.FatalLevel)
+	logger, _ := zap.Config{}.Build()
+	zap.ReplaceGlobals(logger)
 
 	if err := access.Setup(g.ReposPath, g.AdminRepo); err != nil {
 		return err
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -5,8 +5,8 @@ require (
 	github.com/casbin/casbin/v2 v2.65.2
 	github.com/gliderlabs/ssh v0.3.5
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
-	github.com/sirupsen/logrus v1.9.0
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.0
+	go.uber.org/zap v1.24.0
 	golang.org/x/crypto v0.7.0
 	gopkg.in/ini.v1 v1.67.0
 )
@@ -16,8 +16,10 @@ require (
 	github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	go.uber.org/atomic v1.10.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
 	golang.org/x/sys v0.6.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
 go 1.20
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -20,11 +20,23 @@ github.com/kballard/go-shellquote v0.0.0
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
-github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
+go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
@@ -58,3 +70,4 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8W
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/hg/hg.go b/hg/hg.go
--- a/hg/hg.go
+++ b/hg/hg.go
@@ -9,7 +9,7 @@ import (
 	"strings"
 	"sync"
 
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 
 	"keep.imfreedom.org/grim/hgkeeper/access"
 )
@@ -69,7 +69,7 @@ func (c *Command) Setup(repoName, userna
 func (c *Command) Teardown() {
 	if c.tmpHgrc != "" {
 		if err := os.Remove(c.tmpHgrc); err != nil {
-			log.Warnf("failed to remove %q: %v", c.tmpHgrc, err)
+			zap.S().Warnf("failed to remove %q: %v", c.tmpHgrc, err)
 		}
 		c.tmpHgrc = ""
 	}
@@ -117,21 +117,21 @@ func (c *Command) ExecPiped(repoName, us
 	go func() {
 		defer pstdin.Close()
 		if _, err := io.Copy(pstdin, stdin); err != nil {
-			log.Errorf("Failed to read stdin: %v", err)
+			zap.S().Errorf("Failed to read stdin: %v", err)
 		}
 	}()
 
 	go func() {
 		defer wg.Done()
 		if _, err := io.Copy(stdout, pstdout); err != nil {
-			log.Errorf("Failed to write stdout: %v", err)
+			zap.S().Errorf("Failed to write stdout: %v", err)
 		}
 	}()
 
 	go func() {
 		defer wg.Done()
 		if _, err := io.Copy(stderr, pstderr); err != nil {
-			log.Errorf("Failed to write stderr: %v", err)
+			zap.S().Errorf("Failed to write stderr: %v", err)
 		}
 	}()
 
diff --git a/hg/hgweb.go b/hg/hgweb.go
--- a/hg/hgweb.go
+++ b/hg/hgweb.go
@@ -5,7 +5,7 @@ import (
 	"os/exec"
 	"strings"
 
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 )
 
 func TemplatesDir() (string, error) {
@@ -13,7 +13,7 @@ func TemplatesDir() (string, error) {
 
 	if err != nil || templatesDir == "" {
 		if err != nil {
-			log.Warnf("failed to read web.templates setting from hg: %v", err)
+			zap.S().Warnf("failed to read web.templates setting from hg: %v", err)
 		}
 
 		args := []string{
@@ -27,7 +27,7 @@ func TemplatesDir() (string, error) {
 		raw, err := cmd.CombinedOutput()
 
 		if err != nil {
-			log.Debugf("raw: %#v\n", string(raw))
+			zap.S().Debugf("raw: %#v\n", string(raw))
 			return "", err
 		}
 
@@ -36,7 +36,7 @@ func TemplatesDir() (string, error) {
 			return "", fmt.Errorf("failed to find the templates directory")
 		}
 
-		log.Infof("using %s as the templates directory\n", templatesDir)
+		zap.S().Infof("using %s as the templates directory\n", templatesDir)
 	}
 
 	return templatesDir, nil
diff --git a/http/authorized_keys.go b/http/authorized_keys.go
--- a/http/authorized_keys.go
+++ b/http/authorized_keys.go
@@ -5,8 +5,7 @@ import (
 	"net/http"
 	"strings"
 
-	log "github.com/sirupsen/logrus"
-
+	"go.uber.org/zap"
 	"keep.imfreedom.org/grim/hgkeeper/access"
 )
 
@@ -25,7 +24,7 @@ func authorizedKeysHandler(externalHostn
 			w.WriteHeader(http.StatusNotFound)
 			fmt.Fprintf(w, "failed to find fingerprint %q", fp)
 
-			log.Errorf("failed to find fingerprint for %s: %v", fp, err)
+			zap.S().Errorf("failed to find fingerprint for %s: %v", fp, err)
 
 			return
 		}
diff --git a/http/logger.go b/http/logger.go
--- a/http/logger.go
+++ b/http/logger.go
@@ -5,7 +5,7 @@ import (
 	"strings"
 	"time"
 
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 )
 
 type loggerResponseWriter struct {
@@ -99,7 +99,7 @@ func Logger(h http.Handler) http.Handler
 				userAgent = "-"
 			}
 
-			log.Infof(
+			zap.S().Infof(
 				"%s - %s [%s] \"%s %s %s\" %d %d %q %q\n",
 				addr,
 				username,
diff --git a/http/server.go b/http/server.go
--- a/http/server.go
+++ b/http/server.go
@@ -4,7 +4,7 @@ import (
 	"net/http"
 	"path/filepath"
 
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 	"keep.imfreedom.org/grim/hgkeeper/hg"
 	"keep.imfreedom.org/grim/hgkeeper/hgweb"
 )
@@ -42,7 +42,7 @@ func (s *Server) Listen() error {
 		return err
 	}
 	staticPath := filepath.Join(templatesDir, "static")
-	log.Infof("serving static files from %s", staticPath)
+	zap.S().Infof("serving static files from %s", staticPath)
 	fileServer := http.FileServer(http.Dir(staticPath))
 
 	mux := http.NewServeMux()
@@ -51,27 +51,27 @@ func (s *Server) Listen() error {
 
 	if s.externalHostname != "" {
 		mux.Handle("/hgk/authorized_keys", authorizedKeysHandler(s.externalHostname, s.externalPort))
-		log.Infof("added /hgk/authorized_keys endpoint with external hostname %s and export port %s", s.externalHostname, s.externalPort)
+		zap.S().Infof("added /hgk/authorized_keys endpoint with external hostname %s and export port %s", s.externalHostname, s.externalPort)
 	} else {
-		log.Infof("no external hostname specified, not adding /hgk/authorized_keys endpoint")
+		zap.S().Infof("no external hostname specified, not adding /hgk/authorized_keys endpoint")
 	}
 
 	mux.Handle("/static/", http.StripPrefix("/static", fileServer))
 
 	if s.hgw != nil {
-		log.Infof("enabling HGWeb CGI server")
+		zap.S().Infof("enabling HGWeb CGI server")
 		hgwHandler, err := s.hgw.Handler()
 		if err != nil {
 			return err
 		}
 		mux.Handle("/", hgwHandler)
 	} else {
-		log.Infof("not enabling HGWeb CGI server as requested")
+		zap.S().Infof("not enabling HGWeb CGI server as requested")
 	}
 
 	s.server.Handler = Logger(mux)
 
-	log.Infof("http listening on %s", s.listenAddr)
+	zap.S().Infof("http listening on %s", s.listenAddr)
 
 	return s.server.ListenAndServe()
 }
@@ -79,11 +79,11 @@ func (s *Server) Listen() error {
 func (s *Server) Close() {
 	if s.hgw != nil {
 		if err := s.hgw.Close(); err != nil {
-			log.Warnf("failed to close hgweb: %v", err)
+			zap.S().Warnf("failed to close hgweb: %v", err)
 		}
 	}
 
 	if err := s.server.Close(); err != nil {
-		log.Warnf("failed to shutdown http server: %v", err)
+		zap.S().Warnf("failed to shutdown http server: %v", err)
 	}
 }
diff --git a/main.go b/main.go
--- a/main.go
+++ b/main.go
@@ -4,7 +4,8 @@ import (
 	"os"
 
 	"github.com/alecthomas/kong"
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
 
 	"keep.imfreedom.org/grim/hgkeeper/authorized_keys"
 	"keep.imfreedom.org/grim/hgkeeper/globals"
@@ -26,18 +27,32 @@ type commands struct {
 }
 
 func init() {
-	log.SetOutput(os.Stdout)
-	log.SetLevel(log.DebugLevel)
-	fmter := &log.TextFormatter{
-		FullTimestamp: true,
-	}
+	levelEncoder := zapcore.CapitalColorLevelEncoder
 	switch os.Getenv("TERM") {
 	case "win":
 		fallthrough
 	case "dumb":
-		fmter.DisableColors = true
+		levelEncoder = zapcore.CapitalLevelEncoder
 	}
-	log.SetFormatter(fmter)
+
+	logger, _ := zap.Config{
+		Encoding:         "console",
+		Level:            zap.NewAtomicLevelAt(zapcore.DebugLevel),
+		OutputPaths:      []string{"stdout"},
+		ErrorOutputPaths: []string{"stderr"},
+		EncoderConfig: zapcore.EncoderConfig{
+			MessageKey: "message",
+
+			LevelKey:    "level",
+			EncodeLevel: levelEncoder,
+
+			TimeKey:    "time",
+			EncodeTime: zapcore.ISO8601TimeEncoder,
+		},
+	}.Build()
+
+	defer logger.Sync()
+	zap.ReplaceGlobals(logger)
 }
 
 func main() {
diff --git a/serve/command.go b/serve/command.go
--- a/serve/command.go
+++ b/serve/command.go
@@ -6,7 +6,7 @@ import (
 	"os/signal"
 	"syscall"
 
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 
 	"keep.imfreedom.org/grim/hgkeeper/access"
 	"keep.imfreedom.org/grim/hgkeeper/globals"
@@ -42,7 +42,7 @@ func (c *Command) Run(g *globals.Globals
 	var sshServer *ssh.Server
 
 	if c.DisableSSH {
-		log.Info("SSH server has been disabled")
+		zap.S().Info("SSH server has been disabled")
 	} else {
 		var err error
 		sshServer, err = ssh.NewServer(c.SSHHostKeysPath, g.ReposPath, access.AdminRepoPath())
@@ -79,7 +79,7 @@ func (c *Command) Run(g *globals.Globals
 				return err
 			}
 		case s := <-signalChan:
-			log.Infof("Captured %v signal.  Exiting...", s)
+			zap.S().Infof("Captured %v signal.  Exiting...", s)
 			return nil
 		}
 	}
diff --git a/ssh/commands/init.go b/ssh/commands/init.go
--- a/ssh/commands/init.go
+++ b/ssh/commands/init.go
@@ -5,7 +5,7 @@ import (
 	"path/filepath"
 
 	"github.com/gliderlabs/ssh"
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 
 	"keep.imfreedom.org/grim/hgkeeper/access"
 	"keep.imfreedom.org/grim/hgkeeper/hg"
@@ -32,7 +32,7 @@ func (i *Init) Run(session ssh.Session, 
 		return err
 	}
 
-	log.Info("refreshing access control for new repo")
+	zap.S().Info("refreshing access control for new repo")
 
 	return access.Refresh()
 }
diff --git a/ssh/commands/serve.go b/ssh/commands/serve.go
--- a/ssh/commands/serve.go
+++ b/ssh/commands/serve.go
@@ -5,7 +5,7 @@ import (
 	"path/filepath"
 
 	"github.com/gliderlabs/ssh"
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 
 	"keep.imfreedom.org/grim/hgkeeper/access"
 	"keep.imfreedom.org/grim/hgkeeper/hg"
@@ -36,7 +36,7 @@ func (s *Serve) Run(session ssh.Session,
 	}
 
 	if s.repoName == access.AdminRepo() {
-		log.Info("admin repo updated, refreshing access control")
+		zap.S().Info("admin repo updated, refreshing access control")
 
 		return access.Refresh()
 	}
diff --git a/ssh/keys.go b/ssh/keys.go
--- a/ssh/keys.go
+++ b/ssh/keys.go
@@ -5,7 +5,7 @@ import (
 	"io/ioutil"
 	"path/filepath"
 
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 	"golang.org/x/crypto/ssh"
 )
 
@@ -23,20 +23,20 @@ func (s *Server) setHostKeysPath(hostKey
 
 			data, err := ioutil.ReadFile(path)
 			if err != nil {
-				log.Warnf("failed to read %s", path)
+				zap.S().Warnf("failed to read %s", path)
 				continue
 			}
 
 			key, err := ssh.ParsePrivateKey(data)
 			if err != nil {
-				log.Warnf("%s is not an ssh private key", path)
+				zap.S().Warnf("%s is not an ssh private key", path)
 				continue
 			}
 
 			s.server.AddHostKey(key)
 			found = true
 
-			log.Infof("added host key from %s", path)
+			zap.S().Infof("added host key from %s", path)
 		}
 	}
 
diff --git a/ssh/server.go b/ssh/server.go
--- a/ssh/server.go
+++ b/ssh/server.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 
 	"github.com/gliderlabs/ssh"
-	log "github.com/sirupsen/logrus"
+	"go.uber.org/zap"
 	gossh "golang.org/x/crypto/ssh"
 
 	"keep.imfreedom.org/grim/hgkeeper/access"
@@ -40,14 +40,14 @@ func NewServer(hostKeysPath, reposPath, 
 func (s *Server) publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 	username, err := access.UsernameFromPubkey(key)
 	if err != nil {
-		log.Warnf("authentication failure, unknown key %s", gossh.FingerprintSHA256(key))
+		zap.S().Warnf("authentication failure, unknown key %s", gossh.FingerprintSHA256(key))
 
 		return false
 	}
 
 	ctx.SetValue("username", username)
 
-	log.Infof(
+	zap.S().Infof(
 		"%q authenticated with %s",
 		username,
 		gossh.FingerprintSHA256(key),
@@ -66,7 +66,7 @@ func (s *Server) sessionHandler(session 
 		return
 	}
 
-	log.Infof(
+	zap.S().Infof(
 		"%s@%s requested command %q",
 		username,
 		session.RemoteAddr(),
@@ -75,13 +75,13 @@ func (s *Server) sessionHandler(session 
 
 	cmd, err := commands.Find(session.RawCommand(), s.reposPath)
 	if err != nil {
-		log.Warnf("failed to find command for %q, %v", session.RawCommand(), err)
+		zap.S().Warnf("failed to find command for %q, %v", session.RawCommand(), err)
 
 		return
 	}
 
 	if err := cmd.Run(session, username); err != nil {
-		log.Warnf(
+		zap.S().Warnf(
 			"%s@%s command %q failed: %v",
 			username,
 			session.RemoteAddr(),
@@ -90,10 +90,10 @@ func (s *Server) sessionHandler(session 
 		)
 
 		if err := session.Exit(255); err != nil {
-			log.Errorf("session failed to exit: %v", err)
+			zap.S().Errorf("session failed to exit: %v", err)
 		}
 	} else {
-		log.Infof(
+		zap.S().Infof(
 			"%s@%s command %q succeed",
 			username,
 			session.RemoteAddr(),
@@ -101,7 +101,7 @@ func (s *Server) sessionHandler(session 
 		)
 
 		if err := session.Exit(0); err != nil {
-			log.Errorf("session failed to exit: %v", err)
+			zap.S().Errorf("session failed to exit: %v", err)
 		}
 	}
 }
@@ -109,7 +109,7 @@ func (s *Server) sessionHandler(session 
 func (s *Server) Listen(addr string) error {
 	s.server.Addr = addr
 
-	log.Infof("ssh listening on %s", s.server.Addr)
+	zap.S().Infof("ssh listening on %s", s.server.Addr)
 
 	if err := s.server.ListenAndServe(); err != nil {
 		if err != ssh.ErrServerClosed {
