-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhook.go
More file actions
80 lines (71 loc) · 2.42 KB
/
Copy pathhook.go
File metadata and controls
80 lines (71 loc) · 2.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package drops
import (
"context"
"time"
)
// QueryEvent carries observability information about a single driver
// operation. It is passed to a Hook after the operation completes (or
// fails) so the hook can log, trace, emit metrics, etc. without altering
// control flow.
type QueryEvent struct {
// Kind names the operation: "exec", "query", "begin", "commit",
// "rollback", "ping".
Kind string
// SQL is the rendered statement text. Empty for begin/commit/
// rollback/ping.
SQL string
// Args are the bound parameters, in $1, $2, ... order. They may
// contain secrets — redact before logging in untrusted contexts.
Args []any
// Duration is the elapsed time of the operation.
Duration time.Duration
// Err is the error returned by the operation, or nil on success.
Err error
}
// Hook observes driver operations performed via a DB. It is purely
// observational — the operation has already happened by the time the
// hook is invoked, and the hook's return value (if any) is discarded.
//
// Hooks must be safe for concurrent use: a single DB may issue queries
// from multiple goroutines, and each will invoke the hook independently.
//
// To compose multiple hooks, use ChainHooks.
type Hook func(ctx context.Context, e QueryEvent)
// ChainHooks returns a Hook that invokes each given hook in order.
// nil hooks are skipped. A panic inside any hook is recovered so the
// remaining hooks still run and the caller of CallHook is never
// crashed by a buggy observer.
func ChainHooks(hooks ...Hook) Hook {
// Common-case fast paths so a chain of zero or one doesn't allocate
// an unnecessary closure.
switch len(hooks) {
case 0:
return nil
case 1:
return hooks[0]
}
cp := append([]Hook(nil), hooks...)
return func(ctx context.Context, e QueryEvent) {
for _, h := range cp {
CallHook(h, ctx, e)
}
}
}
// CallHook invokes h with (ctx, e), tolerating both a nil hook and a
// hook that panics. It is the safe entrypoint every dialect uses to
// emit events: a buggy user-supplied Hook (nil dereference,
// out-of-bounds index in a logger format string, etc.) must NOT take
// down the caller's request goroutine.
//
// Adapters typically call this from their internal `emit` helper:
//
// func (c *Cache) emit(ctx context.Context, e drops.QueryEvent) {
// drops.CallHook(c.hook, ctx, e)
// }
func CallHook(h Hook, ctx context.Context, e QueryEvent) {
if h == nil {
return
}
defer func() { _ = recover() }()
h(ctx, e)
}