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 }