Hook 훅 시스템
Hook 은 JSON 작업 전후에 커스텀 로직을 삽입하여 로그 기록, 성능 모니터링, 검증 등의 기능을 구현할 수 있습니다.
Hook 인터페이스
go
type Hook interface {
Before(ctx HookContext) error
After(ctx HookContext, result any, err error) (any, error)
}메서드 설명
| 메서드 | 설명 |
|---|---|
Before(ctx HookContext) error | 작업 전에 호출, 오류를 반환하면 작업 중단 |
After(ctx HookContext, result any, err error) (any, error) | 작업 후에 호출, 결과 수정 또는 오류 반환 가능 |
HookContext 구조체
HookContext 는 작업의 컨텍스트 정보를 제공합니다.
go
type HookContext struct {
Operation string // 작업 타입: "get", "set", "delete", "marshal", "unmarshal"
JSONStr string // 입력 JSON 문자열 (marshal 시 비어있을 수 있음). 보안 경고: 민감한 데이터가 포함될 수 있음
Path string // 대상 경로 (marshal/unmarshal 시 비어있을 수 있음)
Value any // set 작업의 값
Config *Config // 활성 설정
StartTime time.Time // 작업 시작 시간
}필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
Operation | string | 작업 타입, 값: get, set, delete, marshal, unmarshal |
JSONStr | string | 입력 JSON 문자열 (보안 경고: 민감한 데이터가 포함될 수 있음) |
Path | string | 대상 경로 표현식 |
Value | any | set 작업의 값 |
Config | *Config | 현재 사용 중인 설정 |
StartTime | time.Time | 작업 시작 시간 |
HookFunc 어댑터
HookFunc 은 구조체 어댑터로, 함수를 Hook 으로 사용할 수 있게 합니다. Before 또는 After 중 하나만 필요한 시나리오에 적합합니다.
go
type HookFunc struct {
BeforeFn func(ctx HookContext) error
AfterFn func(ctx HookContext, result any, err error) (any, error)
}예제
go
// After 만 필요한 경우
p.AddHook(&json.HookFunc{
AfterFn: func(ctx json.HookContext, result any, err error) (any, error) {
log.Printf("%s completed in %v", ctx.Operation, time.Since(ctx.StartTime))
return result, err
},
})
// Before 만 필요한 경우
p.AddHook(&json.HookFunc{
BeforeFn: func(ctx json.HookContext) error {
log.Printf("starting %s on path %s", ctx.Operation, ctx.Path)
return nil
},
})편리한 Hook 팩토리 함수
LoggingHook
로그 기록 Hook 을 생성합니다.
go
func LoggingHook(logger interface{ Info(msg string, args ...any) }) Hookgo
p.AddHook(json.LoggingHook(slog.Default()))TimingHook
작업 소요 시간을 기록하는 Hook 을 생성합니다.
go
func TimingHook(recorder interface{ Record(op string, duration time.Duration) }) Hookgo
p.AddHook(json.TimingHook(myMetricsRecorder))ValidationHook
작업 전에 입력을 검증하는 Hook 을 생성합니다.
go
func ValidationHook(validator func(jsonStr, path string) error) Hookgo
p.AddHook(json.ValidationHook(func(jsonStr, path string) error {
if len(jsonStr) > 1_000_000 {
return errors.New("JSON too large")
}
return nil
}))ErrorHook
오류를 가로채서 처리하는 Hook 을 생성합니다.
go
func ErrorHook(handler func(ctx HookContext, err error) error) Hookgo
p.AddHook(json.ErrorHook(func(ctx json.HookContext, err error) error {
sentry.CaptureException(err)
return err // 원래 오류 또는 변환된 오류 반환
}))커스텀 Hook 구현
전체 예제
go
package main
import (
"fmt"
"log/slog"
"time"
"github.com/cybergodev/json"
)
// 로그 Hook
type LoggingHook struct {
logger *slog.Logger
}
func (h *LoggingHook) Before(ctx json.HookContext) error {
h.logger.Info("operation starting", "op", ctx.Operation, "path", ctx.Path)
return nil
}
func (h *LoggingHook) After(ctx json.HookContext, result any, err error) (any, error) {
h.logger.Info("operation completed",
"op", ctx.Operation,
"path", ctx.Path,
"duration", time.Since(ctx.StartTime),
"error", err)
return result, err
}
func main() {
cfg := json.DefaultConfig()
p, err := json.New(cfg)
if err != nil {
panic(err)
}
defer p.Close()
// 커스텀 Hook 추가
p.AddHook(&LoggingHook{logger: slog.Default()})
// 프로세서 사용...
val, err := p.Get(`{"name": "test"}`, "name")
if err != nil {
panic(err)
}
fmt.Println(val)
}HookFunc 로 간소화
go
// 완료 시간만 기록하면 되는 경우
p.AddHook(&json.HookFunc{
AfterFn: func(ctx json.HookContext, result any, err error) (any, error) {
fmt.Printf("%s took %v\n", ctx.Operation, time.Since(ctx.StartTime))
return result, err
},
})훅 설정
Config 로 추가
go
cfg := json.DefaultConfig()
cfg.Hooks = []json.Hook{
json.LoggingHook(slog.Default()),
json.TimingHook(myRecorder),
}
p, err := json.New(cfg)
if err != nil {
panic(err)
}Processor 로 추가
go
p, err := json.New()
if err != nil {
panic(err)
}
p.AddHook(json.LoggingHook(slog.Default()))
p.AddHook(json.TimingHook(myRecorder))실행 순서
Before 훅
- 추가 순서대로 실행
- 어떤 Hook 이든 오류를 반환하면 작업 중단
After 훅
- 추가 역순으로 실행
- 각 Hook 은 모두 실행됨 (앞서 오류가 반환되어도)
go
// 추가 순서: A, B, C
p.AddHook(hookA)
p.AddHook(hookB)
p.AddHook(hookC)
// 실행 순서:
// Before: A.Before → B.Before → C.Before
// After: C.After → B.After → A.After모범 사례
1. 로그 기록
go
p.AddHook(json.LoggingHook(slog.Default()))2. 성능 모니터링
go
type MetricsRecorder struct{}
func (m *MetricsRecorder) Record(op string, duration time.Duration) {
metrics.Histogram("json_operation_duration", duration, "op", op)
}
p.AddHook(json.TimingHook(&MetricsRecorder{}))3. 입력 검증
go
p.AddHook(json.ValidationHook(func(jsonStr, path string) error {
if len(jsonStr) > 10*1024*1024 { // 10MB
return errors.New("JSON payload too large")
}
return nil
}))4. 오류 추적
go
p.AddHook(json.ErrorHook(func(ctx json.HookContext, err error) error {
if err != nil {
sentry.WithTags(map[string]string{
"operation": ctx.Operation,
"path": ctx.Path,
}).CaptureException(err)
}
return err
}))5. 감사 로그
go
type AuditHook struct {
auditLogger *slog.Logger
}
func (h *AuditHook) Before(ctx json.HookContext) error {
return nil
}
func (h *AuditHook) After(ctx json.HookContext, result any, err error) (any, error) {
if ctx.Operation == "set" || ctx.Operation == "delete" {
h.auditLogger.Info("data modification",
"operation", ctx.Operation,
"path", ctx.Path,
"success", err == nil)
}
return result, err
}