github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/cat/appdata.go (about) 1 package cat 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 8 "github.com/lulzWill/go-agent/internal/jsonx" 9 ) 10 11 // AppDataHeader represents a decoded AppData header. 12 type AppDataHeader struct { 13 CrossProcessID string 14 TransactionName string 15 QueueTimeInSeconds float64 16 ResponseTimeInSeconds float64 17 ContentLength int64 18 TransactionGUID string 19 } 20 21 var ( 22 errInvalidAppDataJSON = errors.New("invalid transaction data JSON") 23 errInvalidAppDataCrossProcessID = errors.New("cross process ID is not a string") 24 errInvalidAppDataTransactionName = errors.New("transaction name is not a string") 25 errInvalidAppDataQueueTimeInSeconds = errors.New("queue time is not a float64") 26 errInvalidAppDataResponseTimeInSeconds = errors.New("response time is not a float64") 27 errInvalidAppDataContentLength = errors.New("content length is not a float64") 28 errInvalidAppDataTransactionGUID = errors.New("transaction GUID is not a string") 29 ) 30 31 // MarshalJSON marshalls an AppDataHeader as raw JSON. 32 func (appData *AppDataHeader) MarshalJSON() ([]byte, error) { 33 buf := bytes.NewBufferString("[") 34 35 jsonx.AppendString(buf, appData.CrossProcessID) 36 37 buf.WriteString(",") 38 jsonx.AppendString(buf, appData.TransactionName) 39 40 buf.WriteString(",") 41 jsonx.AppendFloat(buf, appData.QueueTimeInSeconds) 42 43 buf.WriteString(",") 44 jsonx.AppendFloat(buf, appData.ResponseTimeInSeconds) 45 46 buf.WriteString(",") 47 jsonx.AppendInt(buf, appData.ContentLength) 48 49 buf.WriteString(",") 50 jsonx.AppendString(buf, appData.TransactionGUID) 51 52 // The mysterious unused field. We don't need to round trip this, so we'll 53 // just hardcode it to false. 54 buf.WriteString(",false]") 55 return buf.Bytes(), nil 56 } 57 58 // UnmarshalJSON unmarshalls an AppDataHeader from raw JSON. 59 func (appData *AppDataHeader) UnmarshalJSON(data []byte) error { 60 var ok bool 61 var v interface{} 62 63 if err := json.Unmarshal(data, &v); err != nil { 64 return err 65 } 66 67 arr, ok := v.([]interface{}) 68 if !ok { 69 return errInvalidAppDataJSON 70 } 71 if len(arr) < 7 { 72 return errUnexpectedArraySize{ 73 label: "unexpected number of application data elements", 74 expected: 7, 75 actual: len(arr), 76 } 77 } 78 79 if appData.CrossProcessID, ok = arr[0].(string); !ok { 80 return errInvalidAppDataCrossProcessID 81 } 82 83 if appData.TransactionName, ok = arr[1].(string); !ok { 84 return errInvalidAppDataTransactionName 85 } 86 87 if appData.QueueTimeInSeconds, ok = arr[2].(float64); !ok { 88 return errInvalidAppDataQueueTimeInSeconds 89 } 90 91 if appData.ResponseTimeInSeconds, ok = arr[3].(float64); !ok { 92 return errInvalidAppDataResponseTimeInSeconds 93 } 94 95 cl, ok := arr[4].(float64) 96 if !ok { 97 return errInvalidAppDataContentLength 98 } 99 // Content length is specced as int32, but not all agents are consistent on 100 // this in practice. Let's handle it as int64 to maximise compatibility. 101 appData.ContentLength = int64(cl) 102 103 if appData.TransactionGUID, ok = arr[5].(string); !ok { 104 return errInvalidAppDataTransactionGUID 105 } 106 107 // As above, we don't bother decoding the unused field here. It just has to 108 // be present (which was checked earlier with the length check). 109 110 return nil 111 }