mirror of
https://github.com/grafana/grafana.git
synced 2024-12-26 00:41:20 -06:00
614f1af190
* Tempo: remove deprecated model package usage (remove replace) * fix root test
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
|
|
}
|