Written by
on
on
reflect 어떻게 쓰지 - 2
Reflect를 사용해서 좀 더 복잡한 로직을 수행하는 예제이다. 예를 들어 API 서버의 들어오는 Request와 나가는 Response 를 모두 서버에 로그로 남겨두는 경우, 해당 Request와 Response 의 특정 필드는 개인정보에 해당하므로 비식별화한 후 저장하도록 하게 하고싶다고 한다. 그리고 이때 Request 와 Response는 모두 JSON 으로 주고 받는다.
- 앱로그
- 트랜잭션로그
재사용 가능한 middleware/transaction.go
개선사항
- data 가 포인터이든, 값이든 대응 할 수 있도록 type 검사를 수행하고, map[string]string을 대신에 기존 data의 타입을 복사해서 새로운 변수를 만든다. reflect.New를 이용하여 파라미터로 넘어온 특정 타입의 변수를 만들수 있다. 단, 포인터로 만들어지므로 주의해서 쓰도록 한다.
if reflect.TypeOf(data).Kind() == reflect.Ptr {
buffdata = reflect.New(reflect.TypeOf(data).Elem()).Interface()
} else {
buffdata = reflect.New(reflect.TypeOf(data)).Interface()
}
buffdata 라는 data와 같은 타입의 변수를 만들었으니 해당 변수에 copy한다. copier라는 라이브러리를 사용하였고 struct에서 struct로 복사할 수 있게 해주는 라이브러리이다.
if err := copier.Copy(buffdata, data); err != nil {
log.Error(c, "Fail to copy data for response log")
}
- 로그를 남기는 것은 async로 처리하여, 속도를 높힌다 ShouldBindJson는 Request를 특정 struct로 binding 하기 위해 사용하는 것인데, 이를 Wrapping하여 Request 로그를 남기는 로직을 추가한 것이므로, 뒤에 로직같은 경우엔 비동기로 처리하는 것이 좋다.
wg.Add(1)
go func(){
defer wg.Done()
deidentify(reflect.ValueOf(buffdata))
headers, _ := json.Marshal(c.Request.Header)
body, _ := json.Marshal(buffdata)
log.Transaction(c, log.TransactionLog{
Type: log.LogTypeRequest,
URL: c.Request.URL.String(),
Method: c.Request.Method,
Headers: headers,
Body: body})
}()
wg.Wait()
- 비식별화 로직 한 함수에 정리하여 비식별화 로직과 request/response 처리 로직 분리
=> 이전 비식별화 + request 처리 혼재 / 비식별화 + response 처리 혼재
-
Parameter가 포인터이면 안되는 함수 => 해당 value 종류가 reflect.Ptr / reflect.Interface 이면 Elem() 처리
-
Nested Struct JSON을 처리할때 DeepFields, DeepTag를 이용해서 Field와 Tag를 각각 모두 뽑아내어 처리하기 => 비식별화 함수 로직 자체를 깔끔하게 해서 한 함수 내에서 처리
Old
func DeepFields(iface interface{}) []reflect.Value {
fields := make([]reflect.Value, 0)
ifaceValue := reflect.ValueOf(iface)
ifaceType := reflect.TypeOf(iface)
for i := 0; i < ifaceType.NumField(); i++ {
v := ifaceValue.Field(i)
switch v.Kind() {
case reflect.Struct:
fields = append(fields, DeepFields(v.Interface())...)
default:
fields = append(fields, v)
}
}
return fields
}
NEW
func deidentify(v reflect.Value) {
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
v = v.Elem()
}
switch v.Kind() {
case reflect.Slice, reflect.Array:
for idx := 0; idx < v.Len(); idx++ {
deidentify(v.Index(idx))
}
case reflect.Struct:
for idx := 0; idx < v.Type().NumField(); idx++ {
if v.Type().Field(idx).Tag.Get("deidentify") == "true" {
mask(v.Field(idx))
}
deidentify(v.Field(idx))
}
}
}
func mask(v reflect.Value) {
switch v.Kind() {
case reflect.String:
values := []rune(v.String())
var result string
masked := make([]string, len(values))
for idx := range masked {
masked[idx] = "*"
}
v.SetString(strings.Join(masked, ""))
}
}
Rune을 쓰는 이유!