The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/api/frontendlogging/grafana_javascript_agent_pa...

429 lines
12 KiB

/* This file is mostly copied over from https://github.com/grafana/agent/tree/main/pkg/integrations/v2/app_agent_receiver,
as soon as we can use agent as a dependency this can be refactored
*/
package frontendlogging
import (
"encoding/hex"
"fmt"
"sort"
"strings"
"time"
om "github.com/wk8/go-ordered-map"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// KeyVal is an ordered map of string to interface
type KeyVal = om.OrderedMap
// NewKeyVal creates new empty KeyVal
func NewKeyVal() *KeyVal {
return om.New()
}
func KeyValAdd(kv *KeyVal, key string, value string) {
if len(value) > 0 {
kv.Set(key, value)
}
}
// 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)
}
}
// KeyValFromMap will instantiate KeyVal from a map[string]string
func KeyValFromMap(m map[string]string) *KeyVal {
kv := NewKeyVal()
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
KeyValAdd(kv, k, m[k])
}
return kv
}
// Payload is the body of the receiver request
type Payload struct {
Exceptions []Exception `json:"exceptions,omitempty"`
Logs []Log `json:"logs,omitempty"`
Measurements []Measurement `json:"measurements,omitempty"`
Meta Meta `json:"meta,omitempty"`
Traces *Traces `json:"traces,omitempty"`
}
// Frame struct represents a single stacktrace frame
type Frame struct {
Function string `json:"function,omitempty"`
Module string `json:"module,omitempty"`
Filename string `json:"filename,omitempty"`
Lineno int `json:"lineno,omitempty"`
Colno int `json:"colno,omitempty"`
}
// String function converts a Frame into a human readable string
func (frame Frame) String() 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)
}
// 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)
}
}
// Stacktrace is a collection of Frames
type Stacktrace struct {
Frames []Frame `json:"frames,omitempty"`
}
// Exception struct controls all the data regarding an exception
type Exception struct {
Type string `json:"type,omitempty"`
Value string `json:"value,omitempty"`
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
Timestamp time.Time `json:"timestamp"`
Trace TraceContext `json:"trace,omitempty"`
}
// Message string is concatenating of the Exception.Type and Exception.Value
func (e Exception) Message() string {
return fmt.Sprintf("%s: %s", e.Type, e.Value)
}
// String is the string representation of an Exception
func (e Exception) String() string {
var stacktrace = e.Message()
if e.Stacktrace != nil {
for _, frame := range e.Stacktrace.Frames {
stacktrace += frame.String()
}
}
return stacktrace
}
// KeyVal representation of the exception object
func (e Exception) KeyVal() *KeyVal {
kv := NewKeyVal()
KeyValAdd(kv, "timestamp", e.Timestamp.String())
KeyValAdd(kv, "kind", "exception")
KeyValAdd(kv, "type", e.Type)
KeyValAdd(kv, "value", e.Value)
KeyValAdd(kv, "stacktrace", e.String())
MergeKeyVal(kv, e.Trace.KeyVal())
return kv
}
// TraceContext holds trace id and span id associated to an entity (log, exception, measurement...).
type TraceContext struct {
TraceID string `json:"trace_id"`
SpanID string `json:"span_id"`
}
// KeyVal representation of the trace context object.
func (tc TraceContext) KeyVal() *KeyVal {
retv := NewKeyVal()
KeyValAdd(retv, "traceID", tc.TraceID)
KeyValAdd(retv, "spanID", tc.SpanID)
return retv
}
// Traces wraps the otel traces model.
type Traces struct {
ptrace.Traces
}
// UnmarshalJSON unmarshals Traces model.
func (t *Traces) UnmarshalJSON(b []byte) error {
unmarshaler := ptrace.JSONUnmarshaler{}
td, err := unmarshaler.UnmarshalTraces(b)
if err != nil {
return err
}
*t = Traces{td}
return nil
}
// MarshalJSON marshals Traces model to json.
func (t Traces) MarshalJSON() ([]byte, error) {
marshaler := ptrace.JSONMarshaler{}
return marshaler.MarshalTraces(t.Traces)
}
// SpanSlice unpacks Traces entity into a slice of Spans.
func (t Traces) SpanSlice() []ptrace.Span {
spans := make([]ptrace.Span, 0)
rss := t.ResourceSpans()
for i := 0; i < rss.Len(); i++ {
rs := rss.At(i)
ilss := rs.ScopeSpans()
for j := 0; j < ilss.Len(); j++ {
s := ilss.At(j).Spans()
for si := 0; si < s.Len(); si++ {
spans = append(spans, s.At(si))
}
}
}
return spans
}
// SpanToKeyVal returns KeyVal representation of a Span.
func SpanToKeyVal(s ptrace.Span) *KeyVal {
traceID := s.TraceID()
traceIDHex := hex.EncodeToString(traceID[:])
spanID := s.SpanID()
spanIDHex := hex.EncodeToString(spanID[:])
parentSpanID := s.ParentSpanID()
parentSpanIDHex := hex.EncodeToString(parentSpanID[:])
kv := NewKeyVal()
if s.StartTimestamp() > 0 {
KeyValAdd(kv, "timestamp", s.StartTimestamp().AsTime().String())
}
if s.EndTimestamp() > 0 {
KeyValAdd(kv, "end_timestamp", s.StartTimestamp().AsTime().String())
}
KeyValAdd(kv, "kind", "span")
KeyValAdd(kv, "traceID", traceIDHex)
KeyValAdd(kv, "spanID", spanIDHex)
KeyValAdd(kv, "span_kind", s.Kind().String())
KeyValAdd(kv, "name", s.Name())
KeyValAdd(kv, "parent_spanID", parentSpanIDHex)
s.Attributes().Range(func(k string, v pcommon.Value) bool {
KeyValAdd(kv, "attr_"+k, fmt.Sprintf("%v", v))
return true
})
return kv
}
// LogLevel is log level enum for incoming app logs
type LogLevel string
const (
// LogLevelTrace is "trace"
LogLevelTrace LogLevel = "trace"
// LogLevelDebug is "debug"
LogLevelDebug LogLevel = "debug"
// LogLevelInfo is "info"
LogLevelInfo LogLevel = "info"
// LogLevelWarning is "warning"
LogLevelWarning LogLevel = "warn"
// LogLevelError is "error"
LogLevelError LogLevel = "error"
)
// LogContext is a string to string map structure that
// represents the context of a log message
type LogContext map[string]string
// Log struct controls the data that come into a Log message
type Log struct {
Message string `json:"message,omitempty"`
LogLevel LogLevel `json:"level,omitempty"`
Context LogContext `json:"context,omitempty"`
Timestamp time.Time `json:"timestamp"`
Trace TraceContext `json:"trace,omitempty"`
}
// KeyVal representation of a Log object
func (l Log) KeyVal() *KeyVal {
kv := NewKeyVal()
KeyValAdd(kv, "timestamp", l.Timestamp.String())
KeyValAdd(kv, "kind", "log")
KeyValAdd(kv, "message", l.Message)
KeyValAdd(kv, "level", string(l.LogLevel))
MergeKeyValWithPrefix(kv, KeyValFromMap(l.Context), "context_")
MergeKeyVal(kv, l.Trace.KeyVal())
return kv
}
func (l Log) KeyValContext() *KeyVal {
kv := NewKeyVal()
MergeKeyValWithPrefix(kv, KeyValFromMap(l.Context), "context_")
return kv
}
// Measurement holds the data for user provided measurements
type Measurement struct {
Values map[string]float64 `json:"values,omitempty"`
Timestamp time.Time `json:"timestamp,omitempty"`
Trace TraceContext `json:"trace,omitempty"`
Type string `json:"type,omitempty"`
}
// KeyVal representation of the Measurement object
func (m Measurement) KeyVal() *KeyVal {
kv := NewKeyVal()
KeyValAdd(kv, "timestamp", m.Timestamp.String())
KeyValAdd(kv, "kind", "measurement")
keys := make([]string, 0, len(m.Values))
for k := range m.Values {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
KeyValAdd(kv, k, fmt.Sprintf("%f", m.Values[k]))
}
MergeKeyVal(kv, m.Trace.KeyVal())
return kv
}
// SDK holds metadata about the app agent that produced the event
type SDK struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Integrations []SDKIntegration `json:"integrations,omitempty"`
}
// KeyVal produces key->value representation of Sdk metadata
func (sdk SDK) KeyVal() *KeyVal {
kv := NewKeyVal()
KeyValAdd(kv, "name", sdk.Name)
KeyValAdd(kv, "version", sdk.Version)
if len(sdk.Integrations) > 0 {
integrations := make([]string, len(sdk.Integrations))
for i, integration := range sdk.Integrations {
integrations[i] = integration.String()
}
KeyValAdd(kv, "integrations", strings.Join(integrations, ","))
}
return kv
}
// SDKIntegration holds metadata about a plugin/integration on the app agent that collected and sent the event
type SDKIntegration struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
}
func (i SDKIntegration) String() string {
return fmt.Sprintf("%s:%s", i.Name, i.Version)
}
// User holds metadata about the user related to an app event
type User struct {
Email string `json:"email,omitempty"`
ID string `json:"id,omitempty"`
Username string `json:"username,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
}
// KeyVal produces a key->value representation User metadata
func (u User) KeyVal() *KeyVal {
kv := NewKeyVal()
KeyValAdd(kv, "email", u.Email)
KeyValAdd(kv, "id", u.ID)
KeyValAdd(kv, "username", u.Username)
MergeKeyValWithPrefix(kv, KeyValFromMap(u.Attributes), "attr_")
return kv
}
// Meta holds metadata about an app event
type Meta struct {
SDK SDK `json:"sdk,omitempty"`
App App `json:"app,omitempty"`
User User `json:"user,omitempty"`
Session Session `json:"session,omitempty"`
Page Page `json:"page,omitempty"`
Browser Browser `json:"browser,omitempty"`
}
// KeyVal produces key->value representation of the app event metadatga
func (m Meta) KeyVal() *KeyVal {
kv := NewKeyVal()
MergeKeyValWithPrefix(kv, m.SDK.KeyVal(), "sdk_")
MergeKeyValWithPrefix(kv, m.App.KeyVal(), "app_")
MergeKeyValWithPrefix(kv, m.User.KeyVal(), "user_")
MergeKeyValWithPrefix(kv, m.Session.KeyVal(), "session_")
MergeKeyValWithPrefix(kv, m.Page.KeyVal(), "page_")
MergeKeyValWithPrefix(kv, m.Browser.KeyVal(), "browser_")
return kv
}
// Session holds metadata about the browser session the event originates from
type Session struct {
ID string `json:"id,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
}
// KeyVal produces key->value representation of the Session metadata
func (s Session) KeyVal() *KeyVal {
kv := NewKeyVal()
KeyValAdd(kv, "id", s.ID)
MergeKeyValWithPrefix(kv, KeyValFromMap(s.Attributes), "attr_")
return kv
}
// Page holds metadata about the web page event originates from
type Page struct {
ID string `json:"id,omitempty"`
URL string `json:"url,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
}
// KeyVal produces key->val representation of Page metadata
func (p Page) KeyVal() *KeyVal {
kv := NewKeyVal()
KeyValAdd(kv, "id", p.ID)
KeyValAdd(kv, "url", p.URL)
MergeKeyValWithPrefix(kv, KeyValFromMap(p.Attributes), "attr_")
return kv
}
// App holds metadata about the application event originates from
type App struct {
Name string `json:"name,omitempty"`
Release string `json:"release,omitempty"`
Version string `json:"version,omitempty"`
Environment string `json:"environment,omitempty"`
}
// KeyVal produces key-> value representation of App metadata
func (a App) KeyVal() *KeyVal {
kv := NewKeyVal()
KeyValAdd(kv, "name", a.Name)
KeyValAdd(kv, "release", a.Release)
KeyValAdd(kv, "version", a.Version)
KeyValAdd(kv, "environment", a.Environment)
return kv
}
// Browser holds metadata about a client's browser
type Browser struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
OS string `json:"os,omitempty"`
Mobile bool `json:"mobile,omitempty"`
}
// KeyVal produces key->value representation of the Browser metadata
func (b Browser) KeyVal() *KeyVal {
kv := NewKeyVal()
KeyValAdd(kv, "name", b.Name)
KeyValAdd(kv, "version", b.Version)
KeyValAdd(kv, "os", b.OS)
KeyValAdd(kv, "mobile", fmt.Sprintf("%v", b.Mobile))
return kv
}