blob: 549ff48c81d2934c96611b5c6e31791e98b01286 [file] [log] [blame]
Girish Kumar2ed051b2020-07-28 16:35:25 +00001// Copyright 2017 Michal Witkowski. All Rights Reserved.
2// See LICENSE for licensing terms.
3
4package grpc_ctxtags
5
6import (
7 "reflect"
8)
9
10// RequestFieldExtractorFunc is a user-provided function that extracts field information from a gRPC request.
11// It is called from tags middleware on arrival of unary request or a server-stream request.
12// Keys and values will be added to the context tags of the request. If there are no fields, you should return a nil.
13type RequestFieldExtractorFunc func(fullMethod string, req interface{}) map[string]interface{}
14
15type requestFieldsExtractor interface {
16 // ExtractRequestFields is a method declared on a Protobuf message that extracts fields from the interface.
17 // The values from the extracted fields should be set in the appendToMap, in order to avoid allocations.
18 ExtractRequestFields(appendToMap map[string]interface{})
19}
20
21// CodeGenRequestFieldExtractor is a function that relies on code-generated functions that export log fields from requests.
22// These are usually coming from a protoc-plugin that generates additional information based on custom field options.
23func CodeGenRequestFieldExtractor(fullMethod string, req interface{}) map[string]interface{} {
24 if ext, ok := req.(requestFieldsExtractor); ok {
25 retMap := make(map[string]interface{})
26 ext.ExtractRequestFields(retMap)
27 if len(retMap) == 0 {
28 return nil
29 }
30 return retMap
31 }
32 return nil
33}
34
35// TagBasedRequestFieldExtractor is a function that relies on Go struct tags to export log fields from requests.
36// These are usually coming from a protoc-plugin, such as Gogo protobuf.
37//
38// message Metadata {
39// repeated string tags = 1 [ (gogoproto.moretags) = "log_field:\"meta_tags\"" ];
40// }
41//
42// The tagName is configurable using the tagName variable. Here it would be "log_field".
43func TagBasedRequestFieldExtractor(tagName string) RequestFieldExtractorFunc {
44 return func(fullMethod string, req interface{}) map[string]interface{} {
45 retMap := make(map[string]interface{})
46 reflectMessageTags(req, retMap, tagName)
47 if len(retMap) == 0 {
48 return nil
49 }
50 return retMap
51 }
52}
53
54func reflectMessageTags(msg interface{}, existingMap map[string]interface{}, tagName string) {
55 v := reflect.ValueOf(msg)
56 // Only deal with pointers to structs.
57 if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
58 return
59 }
60 // Deref the pointer get to the struct.
61 v = v.Elem()
62 t := v.Type()
63 for i := 0; i < v.NumField(); i++ {
64 field := v.Field(i)
65 kind := field.Kind()
66 // Only recurse down direct pointers, which should only be to nested structs.
67 if kind == reflect.Ptr {
68 reflectMessageTags(field.Interface(), existingMap, tagName)
69 }
70 // In case of arrays/splices (repeated fields) go down to the concrete type.
71 if kind == reflect.Array || kind == reflect.Slice {
72 if field.Len() == 0 {
73 continue
74 }
75 kind = field.Index(0).Kind()
76 }
77 // Only be interested in
78 if (kind >= reflect.Bool && kind <= reflect.Float64) || kind == reflect.String {
79 if tag := t.Field(i).Tag.Get(tagName); tag != "" {
80 existingMap[tag] = field.Interface()
81 }
82 }
83 }
84 return
85}