diff --git a/go.mod b/go.mod
index 45ee502e702387f4db673088847e745da2cb4856..b6ce699aec8a7b6956946de718cc49201cd74310 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,6 @@ go 1.20
 
 require (
 	gfx.cafe/ghalliday1/scram v0.0.2
-	gfx.cafe/util/go/gun v0.0.0-20230721185457-c559e86c829c
 	github.com/caddyserver/caddy/v2 v2.7.4
 	github.com/digitalocean/godo v1.102.1
 	github.com/google/uuid v1.3.0
@@ -12,7 +11,6 @@ require (
 	google.golang.org/api v0.126.0
 	k8s.io/apimachinery v0.27.4
 	k8s.io/client-go v0.27.4
-	tuxpa.in/a/zlog v1.61.0
 )
 
 require (
diff --git a/lib/gat/app.go b/lib/gat/app.go
index eafd6e4128a65b8ea505c4b285216d7c309c6c65..b92ddac70e9e7d57d462c062928f881edc3f7603 100644
--- a/lib/gat/app.go
+++ b/lib/gat/app.go
@@ -4,10 +4,11 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
+	"io"
 	"net"
 
 	"github.com/caddyserver/caddy/v2"
-	"tuxpa.in/a/zlog/log"
+	"go.uber.org/zap"
 
 	"gfx.cafe/gfx/pggat/lib/bouncer/frontends/v0"
 	"gfx.cafe/gfx/pggat/lib/fed"
@@ -37,6 +38,8 @@ type App struct {
 	servers []*Server
 
 	keys maps.RWLocked[[8]byte, *Pool]
+
+	log *zap.Logger
 }
 
 func (T *App) CaddyModule() caddy.ModuleInfo {
@@ -49,6 +52,8 @@ func (T *App) CaddyModule() caddy.ModuleInfo {
 }
 
 func (T *App) Provision(ctx caddy.Context) error {
+	T.log = ctx.Logger()
+
 	T.listen = make([]*Listener, 0, len(T.Listen))
 	for _, config := range T.Listen {
 		listener := &Listener{
@@ -101,21 +106,21 @@ func (T *App) serve(server *Server, conn fed.Conn) {
 
 	p := server.lookup(conn)
 	if p == nil {
-		log.Printf("pool not found for client: user=%s database=%s", conn.User(), conn.Database())
+		T.log.Warn("database not found", zap.String("user", conn.User()), zap.String("database", conn.Database()))
 		return
 	}
 
 	backendKey, err := frontends.Authenticate(conn, p.Credentials())
 	if err != nil {
-		log.Printf("error authenticating client: %v", err)
+		T.log.Warn("error authenticating client", zap.Error(err))
 		return
 	}
 
 	T.keys.Store(backendKey, p)
 	defer T.keys.Delete(backendKey)
 
-	if err2 := p.Serve(conn, backendKey); err2 != nil {
-		log.Printf("error serving client: %v", err2)
+	if err2 := p.Serve(conn, backendKey); err2 != nil && !errors.Is(err2, io.EOF) {
+		T.log.Warn("error serving client", zap.Error(err2))
 		return
 	}
 }
@@ -132,7 +137,7 @@ func (T *App) accept(listener *Listener, conn *fed.NetConn) {
 
 	cancelKey, isCanceling, _, user, database, initialParameters, err := frontends.Accept(conn, tlsConfig)
 	if err != nil {
-		log.Printf("error accepting client: %v", err)
+		T.log.Warn("error accepting client", zap.Error(err))
 		return
 	}
 
@@ -152,7 +157,7 @@ func (T *App) accept(listener *Listener, conn *fed.NetConn) {
 		}
 	}
 
-	log.Printf("server not found for client: user=%s database=%s", conn.User(), conn.Database())
+	T.log.Warn("server not found", zap.String("user", conn.User()), zap.String("database", conn.Database()))
 
 	errResp := packets.ErrorResponse{
 		Error: perror.New(
@@ -170,7 +175,7 @@ func (T *App) acceptFrom(listener *Listener) bool {
 		if errors.Is(err, net.ErrClosed) {
 			return false
 		}
-		log.Printf("error accepting client: %v", err)
+		T.log.Warn("error accepting client", zap.Error(err))
 		return true
 	}
 
diff --git a/lib/gat/listen.go b/lib/gat/listen.go
index 1a92f4ae563b58c15c847bcf4d75fa13f4e3182f..dd0f28661af7d9ee259a9d87631968b1129d1ee7 100644
--- a/lib/gat/listen.go
+++ b/lib/gat/listen.go
@@ -6,7 +6,7 @@ import (
 	"net"
 
 	"github.com/caddyserver/caddy/v2"
-	"tuxpa.in/a/zlog/log"
+	"go.uber.org/zap"
 
 	"gfx.cafe/gfx/pggat/lib/fed"
 )
@@ -23,6 +23,8 @@ type Listener struct {
 	ssl SSLServer
 
 	listener net.Listener
+
+	log *zap.Logger
 }
 
 func (T *Listener) accept() (*fed.NetConn, error) {
@@ -34,6 +36,8 @@ func (T *Listener) accept() (*fed.NetConn, error) {
 }
 
 func (T *Listener) Provision(ctx caddy.Context) error {
+	T.log = ctx.Logger()
+
 	if T.SSL != nil {
 		val, err := ctx.LoadModule(T, "SSL")
 		if err != nil {
@@ -52,7 +56,7 @@ func (T *Listener) Start() error {
 		return err
 	}
 
-	log.Printf("listening on %v", T.listener.Addr())
+	T.log.Info("listening", zap.String("address", T.listener.Addr().String()))
 
 	return nil
 }
diff --git a/lib/gat/pool/options.go b/lib/gat/pool/options.go
index fa54ff71ff50331835835a1a6de3b25da893a108..00ee3a49a03bfc6c3ed9b2f1a502f318485f3cb1 100644
--- a/lib/gat/pool/options.go
+++ b/lib/gat/pool/options.go
@@ -1,6 +1,8 @@
 package pool
 
 import (
+	"go.uber.org/zap"
+
 	"gfx.cafe/gfx/pggat/lib/auth"
 	"gfx.cafe/gfx/pggat/lib/util/dur"
 	"gfx.cafe/gfx/pggat/lib/util/strutil"
@@ -57,4 +59,6 @@ type Options struct {
 	PoolingOptions
 
 	ManagementOptions
+
+	Logger *zap.Logger
 }
diff --git a/lib/gat/pool/pool.go b/lib/gat/pool/pool.go
index 3dd2e3fc1e9c7ce2738afecd10dbae0a1bf758bc..4bd0cd4a954beaa2cd128533cbd6962f2456aff8 100644
--- a/lib/gat/pool/pool.go
+++ b/lib/gat/pool/pool.go
@@ -7,7 +7,7 @@ import (
 	"time"
 
 	"github.com/google/uuid"
-	"tuxpa.in/a/zlog/log"
+	"go.uber.org/zap"
 
 	"gfx.cafe/gfx/pggat/lib/auth"
 	"gfx.cafe/gfx/pggat/lib/bouncer/backends/v0"
@@ -103,7 +103,7 @@ func (T *Pool) AddRecipe(name string, r *Recipe) {
 	count := r.AllocateInitial()
 	for i := 0; i < count; i++ {
 		if err := T.scaleUpL1(name, r); err != nil {
-			log.Printf("failed to dial server: %v", err)
+			T.options.Logger.Warn("failed to dial server", zap.Error(err))
 			for j := i; j < count; j++ {
 				r.Free()
 			}
@@ -208,7 +208,7 @@ func (T *Pool) scaleUp() bool {
 
 	err := T.scaleUpL1(name, r)
 	if err != nil {
-		log.Printf("failed to dial server: %v", err)
+		T.options.Logger.Warn("failed to dial server", zap.Error(err))
 		return false
 	}
 
@@ -230,9 +230,12 @@ func (T *Pool) removeServerL1(server *pooledServer) {
 		name := server.GetRecipe()
 		T.serversByRecipe[name] = slices.Delete(T.serversByRecipe[name], server)
 		// update order
-		T.recipeScaleOrder.Update(slices.Index(T.recipeScaleOrder, name), func(n string) int {
-			return len(T.serversByRecipe[n])
-		})
+		index := slices.Index(T.recipeScaleOrder, name)
+		if index != -1 {
+			T.recipeScaleOrder.Update(index, func(n string) int {
+				return len(T.serversByRecipe[n])
+			})
+		}
 	}
 }
 
diff --git a/lib/gat/pool/scaler.go b/lib/gat/pool/scaler.go
index ecf238ab6a221ccd7ca9203db049886d701a5f3d..b758de618e3de98ff1a325115ef5dfcbe1ae192a 100644
--- a/lib/gat/pool/scaler.go
+++ b/lib/gat/pool/scaler.go
@@ -3,7 +3,7 @@ package pool
 import (
 	"time"
 
-	"tuxpa.in/a/zlog/log"
+	"go.uber.org/zap"
 )
 
 type scaler struct {
@@ -80,7 +80,7 @@ func (T *scaler) pendingTimeout() {
 			T.pending.Reset(T.backoff)
 		}
 
-		log.Printf("failed to dial server. trying again in %v", T.backoff)
+		T.pool.options.Logger.Warn("failed to dial server", zap.Duration("backoff", T.backoff))
 
 		return
 	}
diff --git a/lib/gat/poolers/session/pool.go b/lib/gat/poolers/session/pool.go
index 0e0f44dfa4d2e86b487d5e1c95a7a16b904d6b89..5e807022d22fe2d3a2f438b8774e4d50e73c92ec 100644
--- a/lib/gat/poolers/session/pool.go
+++ b/lib/gat/poolers/session/pool.go
@@ -2,6 +2,7 @@ package session
 
 import (
 	"github.com/caddyserver/caddy/v2"
+	"go.uber.org/zap"
 
 	"gfx.cafe/gfx/pggat/lib/auth"
 	"gfx.cafe/gfx/pggat/lib/gat"
@@ -21,6 +22,8 @@ func init() {
 
 type Pool struct {
 	pool.ManagementOptions
+
+	log *zap.Logger
 }
 
 func (T *Pool) CaddyModule() caddy.ModuleInfo {
@@ -32,6 +35,11 @@ func (T *Pool) CaddyModule() caddy.ModuleInfo {
 	}
 }
 
+func (T *Pool) Provision(ctx caddy.Context) error {
+	T.log = ctx.Logger()
+	return nil
+}
+
 func (T *Pool) NewPool(creds auth.Credentials) *gat.Pool {
 	return pool.NewPool(pool.Options{
 		Credentials: creds,
@@ -39,8 +47,11 @@ func (T *Pool) NewPool(creds auth.Credentials) *gat.Pool {
 		PoolingOptions: PoolingOptions,
 
 		ManagementOptions: T.ManagementOptions,
+
+		Logger: T.log,
 	})
 }
 
 var _ gat.Pooler = (*Pool)(nil)
 var _ caddy.Module = (*Pool)(nil)
+var _ caddy.Provisioner = (*Pool)(nil)
diff --git a/lib/gat/poolers/transaction/pool.go b/lib/gat/poolers/transaction/pool.go
index 46e26ecd5902db6e1f13b42eda722c7d2e0b29f6..e8915cd78dbdab1a9f7b45dc27cef748922b21fb 100644
--- a/lib/gat/poolers/transaction/pool.go
+++ b/lib/gat/poolers/transaction/pool.go
@@ -2,6 +2,7 @@ package transaction
 
 import (
 	"github.com/caddyserver/caddy/v2"
+	"go.uber.org/zap"
 
 	"gfx.cafe/gfx/pggat/lib/auth"
 	"gfx.cafe/gfx/pggat/lib/gat"
@@ -21,6 +22,8 @@ func init() {
 
 type Pool struct {
 	pool.ManagementOptions
+
+	log *zap.Logger
 }
 
 func (T *Pool) CaddyModule() caddy.ModuleInfo {
@@ -32,6 +35,11 @@ func (T *Pool) CaddyModule() caddy.ModuleInfo {
 	}
 }
 
+func (T *Pool) Provision(ctx caddy.Context) error {
+	T.log = ctx.Logger()
+	return nil
+}
+
 func (T *Pool) NewPool(creds auth.Credentials) *gat.Pool {
 	return pool.NewPool(pool.Options{
 		Credentials: creds,
@@ -39,6 +47,8 @@ func (T *Pool) NewPool(creds auth.Credentials) *gat.Pool {
 		PoolingOptions: PoolingOptions,
 
 		ManagementOptions: T.ManagementOptions,
+
+		Logger: T.log,
 	})
 }
 
diff --git a/lib/gat/providers/discovery/module.go b/lib/gat/providers/discovery/module.go
index b3c351b1e52cd41312e14385468e2d517e90113c..5a84ae5cfe253bca3c6c8cefa83faabacb9b99a3 100644
--- a/lib/gat/providers/discovery/module.go
+++ b/lib/gat/providers/discovery/module.go
@@ -6,7 +6,7 @@ import (
 	"time"
 
 	"github.com/caddyserver/caddy/v2"
-	"tuxpa.in/a/zlog/log"
+	"go.uber.org/zap"
 
 	"gfx.cafe/gfx/pggat/lib/auth"
 	"gfx.cafe/gfx/pggat/lib/auth/credentials"
@@ -41,6 +41,8 @@ type Module struct {
 
 	pools maps.TwoKey[string, string, *pool.Pool]
 	mu    sync.RWMutex
+
+	log *zap.Logger
 }
 
 func (*Module) CaddyModule() caddy.ModuleInfo {
@@ -53,6 +55,8 @@ func (*Module) CaddyModule() caddy.ModuleInfo {
 }
 
 func (T *Module) Provision(ctx caddy.Context) error {
+	T.log = ctx.Logger()
+
 	if T.Discoverer != nil {
 		val, err := ctx.LoadModule(T, "Discoverer")
 		if err != nil {
@@ -471,7 +475,7 @@ func (T *Module) discoverLoop() {
 		case <-reconcile:
 			err := T.reconcile()
 			if err != nil {
-				log.Printf("failed to reconcile: %v", err)
+				T.log.Warn("failed to reconcile", zap.Error(err))
 			}
 		}
 	}
@@ -480,7 +484,7 @@ func (T *Module) discoverLoop() {
 func (T *Module) addPool(user, database string, p *pool.Pool) {
 	T.mu.Lock()
 	defer T.mu.Unlock()
-	log.Printf("added pool user=%s database=%s", user, database)
+	T.log.Info("added pool", zap.String("user", user), zap.String("database", database))
 	if old, ok := T.pools.Load(user, database); ok {
 		// shouldn't normally get here
 		old.Close()
@@ -496,7 +500,7 @@ func (T *Module) removePool(user, database string) {
 		return
 	}
 	p.Close()
-	log.Printf("removed pool user=%s database=%s", user, database)
+	T.log.Info("removed pool", zap.String("user", user), zap.String("database", database))
 	T.pools.Delete(user, database)
 }
 
diff --git a/lib/gat/providers/pgbouncer/module.go b/lib/gat/providers/pgbouncer/module.go
index eae73261a948cacd05a269cbbecab7036734ef87..e7ee8b49c0c6e3e7eaed78d8e1a1937a608d3020 100644
--- a/lib/gat/providers/pgbouncer/module.go
+++ b/lib/gat/providers/pgbouncer/module.go
@@ -11,7 +11,7 @@ import (
 	"time"
 
 	"github.com/caddyserver/caddy/v2"
-	"tuxpa.in/a/zlog/log"
+	"go.uber.org/zap"
 
 	"gfx.cafe/gfx/pggat/lib/fed"
 	"gfx.cafe/gfx/pggat/lib/gat/poolers/session"
@@ -44,6 +44,8 @@ type Module struct {
 
 	pools maps.TwoKey[string, string, *gat.Pool]
 	mu    sync.RWMutex
+
+	log *zap.Logger
 }
 
 func (*Module) CaddyModule() caddy.ModuleInfo {
@@ -56,6 +58,8 @@ func (*Module) CaddyModule() caddy.ModuleInfo {
 }
 
 func (T *Module) Provision(ctx caddy.Context) error {
+	T.log = ctx.Logger()
+
 	var err error
 	T.Config, err = Load(T.ConfigFile)
 	return err
@@ -100,17 +104,17 @@ func (T *Module) getPassword(user, database string) (string, bool) {
 		client := new(gsql.Client)
 		err := gsql.ExtendedQuery(client, &result, T.Config.PgBouncer.AuthQuery, user)
 		if err != nil {
-			log.Println("auth query failed:", err)
+			T.log.Warn("auth query failed", zap.Error(err))
 			return "", false
 		}
 		err = client.Close()
 		if err != nil {
-			log.Println("auth query failed:", err)
+			T.log.Warn("auth query failed", zap.Error(err))
 			return "", false
 		}
 		err = authPool.ServeBot(client)
 		if err != nil && !errors.Is(err, io.EOF) {
-			log.Println("auth query failed:", err)
+			T.log.Warn("auth query failed", zap.Error(err))
 			return "", false
 		}
 
@@ -176,6 +180,7 @@ func (T *Module) tryCreate(user, database string) *gat.Pool {
 			ServerIdleTimeout:          dur.Duration(T.Config.PgBouncer.ServerIdleTimeout * float64(time.Second)),
 			ServerReconnectInitialTime: serverLoginRetry,
 		},
+		Logger: T.log,
 	}
 
 	switch poolMode {
diff --git a/lib/gsql/query_test.go b/lib/gsql/query_test.go
index 71ef6af29144e047ab26c7f349847c6a226d2b21..2272fce3117d6ec9df1e2b895d3736f8f56f98d4 100644
--- a/lib/gsql/query_test.go
+++ b/lib/gsql/query_test.go
@@ -1,11 +1,10 @@
 package gsql
 
 import (
+	"log"
 	"net"
 	"testing"
 
-	"tuxpa.in/a/zlog/log"
-
 	"gfx.cafe/gfx/pggat/lib/auth/credentials"
 	"gfx.cafe/gfx/pggat/lib/bouncer/backends/v0"
 	"gfx.cafe/gfx/pggat/lib/bouncer/bouncers/v2"
diff --git a/lib/util/slices/sorted_test.go b/lib/util/slices/sorted_test.go
index 49e19e073bf939cd94dfaaed2f793cfde6936b31..bb8dd923b163bc59e0b42eb5ae9e271900a793ec 100644
--- a/lib/util/slices/sorted_test.go
+++ b/lib/util/slices/sorted_test.go
@@ -1,10 +1,9 @@
 package slices
 
 import (
+	"log"
 	"sort"
 	"testing"
-
-	"tuxpa.in/a/zlog/log"
 )
 
 func TestSorted_Insert(t *testing.T) {