pkg/translator/faro/keyval.go (244 lines of code) (raw):
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package faro // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/faro"
import (
"fmt"
"maps"
"slices"
"strconv"
"strings"
faroTypes "github.com/grafana/faro/pkg/go"
om "github.com/wk8/go-ordered-map/v2"
)
// keyVal is an ordered map of string to interface
type keyVal = om.OrderedMap[string, any]
// newKeyVal creates new empty keyVal
func newKeyVal() *keyVal {
return om.New[string, any]()
}
// keyValFromMap will instantiate keyVal from a map[string]string
func keyValFromMap(m map[string]string) *keyVal {
kv := newKeyVal()
for _, k := range slices.Sorted(maps.Keys(m)) {
keyValAdd(kv, k, m[k])
}
return kv
}
// keyValFromFloatMap will instantiate keyVal from a map[string]float64
func keyValFromFloatMap(m map[string]float64) *keyVal {
kv := newKeyVal()
for _, k := range slices.Sorted(maps.Keys(m)) {
kv.Set(k, m[k])
}
return kv
}
// mergeKeyVal will merge source in target
func mergeKeyVal(target *keyVal, source *keyVal) {
for el := source.Oldest(); el != nil; el = el.Next() {
target.Set(el.Key, el.Value)
}
}
// mergeKeyValWithPrefix will merge source in target, adding a prefix to each key being merged in
func mergeKeyValWithPrefix(target *keyVal, source *keyVal, prefix string) {
for el := source.Oldest(); el != nil; el = el.Next() {
target.Set(fmt.Sprintf("%s%s", prefix, el.Key), el.Value)
}
}
// keyValAdd adds a key + value string pair to kv
func keyValAdd(kv *keyVal, key string, value string) {
if len(value) > 0 {
kv.Set(key, value)
}
}
// keyValToInterfaceSlice converts keyVal to []interface{}, typically used for logging
func keyValToInterfaceSlice(kv *keyVal) []any {
slice := make([]any, kv.Len()*2)
idx := 0
for el := kv.Oldest(); el != nil; el = el.Next() {
slice[idx] = el.Key
idx++
slice[idx] = el.Value
idx++
}
return slice
}
// logToKeyVal represents a Log object as keyVal
func logToKeyVal(l faroTypes.Log) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroTimestamp, l.Timestamp.Format(string(faroTypes.TimeFormatRFC3339Milli)))
keyValAdd(kv, faroKind, string(faroTypes.KindLog))
keyValAdd(kv, faroLogMessage, l.Message)
keyValAdd(kv, faroLogLevel, string(l.LogLevel))
mergeKeyValWithPrefix(kv, keyValFromMap(l.Context), faroContextPrefix)
mergeKeyVal(kv, traceToKeyVal(l.Trace))
mergeKeyVal(kv, actionToKeyVal(l.Action))
return kv
}
// exceptionToKeyVal represents an Exception object as keyVal
func exceptionToKeyVal(e faroTypes.Exception) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroTimestamp, e.Timestamp.Format(string(faroTypes.TimeFormatRFC3339Milli)))
keyValAdd(kv, faroKind, string(faroTypes.KindException))
keyValAdd(kv, faroExceptionType, e.Type)
keyValAdd(kv, faroExceptionValue, e.Value)
keyValAdd(kv, faroExceptionStacktrace, exceptionToString(e))
mergeKeyVal(kv, traceToKeyVal(e.Trace))
mergeKeyValWithPrefix(kv, keyValFromMap(e.Context), faroContextPrefix)
mergeKeyVal(kv, actionToKeyVal(e.Action))
return kv
}
// exceptionMessage string is concatenating of the Exception.Type and Exception.Value
func exceptionMessage(e faroTypes.Exception) string {
return fmt.Sprintf("%s: %s", e.Type, e.Value)
}
// exceptionToString is the string representation of an Exception
func exceptionToString(e faroTypes.Exception) string {
stacktrace := exceptionMessage(e)
if e.Stacktrace != nil {
for _, frame := range e.Stacktrace.Frames {
stacktrace += frameToString(frame)
}
}
return stacktrace
}
// frameToString function converts a Frame into a human readable string
func frameToString(frame faroTypes.Frame) string {
module := ""
if len(frame.Module) > 0 {
module = frame.Module + "|"
}
return fmt.Sprintf("\n at %s (%s%s:%v:%v)", frame.Function, module, frame.Filename, frame.Lineno, frame.Colno)
}
// measurementToKeyVal representation of the measurement object
func measurementToKeyVal(m faroTypes.Measurement) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroTimestamp, m.Timestamp.Format(string(faroTypes.TimeFormatRFC3339Milli)))
keyValAdd(kv, faroKind, string(faroTypes.KindMeasurement))
keyValAdd(kv, faroMeasurementType, m.Type)
mergeKeyValWithPrefix(kv, keyValFromMap(m.Context), faroContextPrefix)
for _, k := range slices.Sorted(maps.Keys(m.Values)) {
keyValAdd(kv, k, fmt.Sprintf("%f", m.Values[k]))
}
mergeKeyVal(kv, traceToKeyVal(m.Trace))
values := make(map[string]float64, len(m.Values))
for key, value := range m.Values {
values[key] = value
}
mergeKeyValWithPrefix(kv, keyValFromFloatMap(values), faroMeasurementValuePrefix)
mergeKeyVal(kv, actionToKeyVal(m.Action))
return kv
}
// eventToKeyVal produces key -> value representation of Event metadata
func eventToKeyVal(e faroTypes.Event) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroTimestamp, e.Timestamp.Format(string(faroTypes.TimeFormatRFC3339Milli)))
keyValAdd(kv, faroKind, string(faroTypes.KindEvent))
keyValAdd(kv, faroEventName, e.Name)
keyValAdd(kv, faroEventDomain, e.Domain)
if e.Attributes != nil {
mergeKeyValWithPrefix(kv, keyValFromMap(e.Attributes), faroEventDataPrefix)
}
mergeKeyVal(kv, actionToKeyVal(e.Action))
mergeKeyVal(kv, traceToKeyVal(e.Trace))
return kv
}
// actionToKeyVal produces key->value representation of the Action metadata
func actionToKeyVal(a faroTypes.Action) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroActionID, a.ID)
keyValAdd(kv, faroActionName, a.Name)
keyValAdd(kv, faroActionParentID, a.ParentID)
return kv
}
// metaToKeyVal produces key->value representation of the metadata
func metaToKeyVal(m faroTypes.Meta) *keyVal {
kv := newKeyVal()
mergeKeyVal(kv, sdkToKeyVal(m.SDK))
mergeKeyVal(kv, appToKeyVal(m.App))
mergeKeyVal(kv, userToKeyVal(m.User))
mergeKeyVal(kv, sessionToKeyVal(m.Session))
mergeKeyVal(kv, pageToKeyVal(m.Page))
mergeKeyVal(kv, browserToKeyVal(m.Browser))
mergeKeyVal(kv, k6ToKeyVal(m.K6))
mergeKeyVal(kv, viewToKeyVal(m.View))
mergeKeyVal(kv, geoToKeyVal(m.Geo))
return kv
}
// sdkToKeyVal produces key->value representation of Sdk metadata
func sdkToKeyVal(sdk faroTypes.SDK) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroSDKName, sdk.Name)
keyValAdd(kv, faroSDKVersion, sdk.Version)
if len(sdk.Integrations) > 0 {
integrations := make([]string, len(sdk.Integrations))
for i, integration := range sdk.Integrations {
integrations[i] = sdkIntegrationToString(integration)
}
keyValAdd(kv, faroSDKIntegrations, strings.Join(integrations, ","))
}
return kv
}
// sdkIntegrationToString is the string representation of an SDKIntegration
func sdkIntegrationToString(i faroTypes.SDKIntegration) string {
return fmt.Sprintf("%s:%s", i.Name, i.Version)
}
// appToKeyVal produces key-> value representation of App metadata
func appToKeyVal(a faroTypes.App) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroAppName, a.Name)
keyValAdd(kv, faroAppNamespace, a.Namespace)
keyValAdd(kv, faroAppRelease, a.Release)
keyValAdd(kv, faroAppVersion, a.Version)
keyValAdd(kv, faroAppEnvironment, a.Environment)
return kv
}
// userToKeyVal produces a key->value representation User metadata
func userToKeyVal(u faroTypes.User) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroUserEmail, u.Email)
keyValAdd(kv, faroUserID, u.ID)
keyValAdd(kv, faroUsername, u.Username)
mergeKeyValWithPrefix(kv, keyValFromMap(u.Attributes), faroUserAttrPrefix)
return kv
}
// sessionToKeyVal produces key->value representation of the Session metadata
func sessionToKeyVal(s faroTypes.Session) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroSessionID, s.ID)
mergeKeyValWithPrefix(kv, keyValFromMap(s.Attributes), faroSessionAttrPrefix)
return kv
}
// pageToKeyVal produces key->val representation of Page metadata
func pageToKeyVal(p faroTypes.Page) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroPageID, p.ID)
keyValAdd(kv, faroPageURL, p.URL)
mergeKeyValWithPrefix(kv, keyValFromMap(p.Attributes), faroPageAttrPrefix)
return kv
}
// browserToKeyVal produces key->value representation of the Browser metadata
func browserToKeyVal(b faroTypes.Browser) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroBrowserName, b.Name)
keyValAdd(kv, faroBrowserVersion, b.Version)
keyValAdd(kv, faroBrowserOS, b.OS)
keyValAdd(kv, faroBrowserMobile, fmt.Sprintf("%v", b.Mobile))
keyValAdd(kv, faroBrowserUserAgent, b.UserAgent)
keyValAdd(kv, faroBrowserLanguage, b.Language)
keyValAdd(kv, faroBrowserViewportWidth, b.ViewportWidth)
keyValAdd(kv, faroBrowserViewportHeight, b.ViewportHeight)
if brandsArray, err := b.Brands.AsBrandsArray(); err == nil {
for i, brand := range brandsArray {
keyValAdd(kv, fmt.Sprintf("%s%d_%s", faroBrowserBrandPrefix, i, faroBrand), brand.Brand)
keyValAdd(kv, fmt.Sprintf("%s%d_%s", faroBrowserBrandPrefix, i, faroBrandVersion), brand.Version)
}
return kv
}
if brandsString, err := b.Brands.AsBrandsString(); err == nil {
keyValAdd(kv, faroBrowserBrands, brandsString)
return kv
}
return kv
}
// k6ToKeyVal produces a key->value representation K6 metadata
func k6ToKeyVal(k faroTypes.K6) *keyVal {
kv := newKeyVal()
if k.IsK6Browser {
keyValAdd(kv, faroIsK6Browser, strconv.FormatBool(k.IsK6Browser))
}
return kv
}
// viewToKeyVal produces a key->value representation View metadata
func viewToKeyVal(v faroTypes.View) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroViewName, v.Name)
return kv
}
// geoToKeyVal produces a key->value representation Geo metadata
func geoToKeyVal(g faroTypes.Geo) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroGeoContinentIso, g.ContinentISOCode)
keyValAdd(kv, faroGeoCountryIso, g.CountryISOCode)
keyValAdd(kv, faroGeoSubdivisionIso, g.SubdivisionISO)
keyValAdd(kv, faroGeoCity, g.City)
keyValAdd(kv, faroGeoASNOrg, g.ASNOrg)
keyValAdd(kv, faroGeoASNID, g.ASNID)
return kv
}
// traceToKeyVal produces a key->value representation of the trace context object
func traceToKeyVal(tc faroTypes.TraceContext) *keyVal {
kv := newKeyVal()
keyValAdd(kv, faroTraceID, tc.TraceID)
keyValAdd(kv, faroSpanID, tc.SpanID)
return kv
}