mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			430 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			430 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
| /* 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
 | |
| }
 |