2024-04-26 13:09:54 +00:00
|
|
|
package gelfexporter
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"sync"
|
|
|
|
|
2024-05-20 06:20:13 +00:00
|
|
|
"git.ma-al.com/maal-libraries/observer/pkg/attr"
|
|
|
|
"git.ma-al.com/maal-libraries/observer/pkg/level"
|
|
|
|
"git.ma-al.com/maal-libraries/observer/pkg/syslog"
|
2024-04-30 10:41:05 +00:00
|
|
|
"go.opentelemetry.io/otel/attribute"
|
2024-04-26 13:09:54 +00:00
|
|
|
"go.opentelemetry.io/otel/sdk/trace"
|
|
|
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
|
|
|
"gopkg.in/Graylog2/go-gelf.v2/gelf"
|
|
|
|
)
|
|
|
|
|
|
|
|
var _ trace.SpanExporter = &Exporter{}
|
|
|
|
|
|
|
|
// New creates an Exporter with the passed options.
|
|
|
|
func New(options ...Option) (*Exporter, error) {
|
|
|
|
cfg, err := newConfig(options...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
writer, err := gelf.NewUDPWriter(cfg.GelfUrl)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Exporter{
|
|
|
|
gelfWriter: writer,
|
2024-09-25 09:23:06 +00:00
|
|
|
appName: cfg.AppName,
|
2024-11-14 11:00:44 +00:00
|
|
|
tag: cfg.Tag,
|
2024-04-26 13:09:54 +00:00
|
|
|
}, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
type Exporter struct {
|
|
|
|
gelfWriter *gelf.UDPWriter
|
2024-04-30 10:41:05 +00:00
|
|
|
appName string
|
2024-11-14 11:00:44 +00:00
|
|
|
tag string
|
2024-04-30 10:41:05 +00:00
|
|
|
stoppedMu sync.RWMutex
|
|
|
|
stopped bool
|
2024-04-26 13:09:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
e.stoppedMu.RLock()
|
|
|
|
stopped := e.stopped
|
|
|
|
e.stoppedMu.RUnlock()
|
|
|
|
if stopped {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(spans) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
stubs := tracetest.SpanStubsFromReadOnlySpans(spans)
|
|
|
|
|
|
|
|
for i := range stubs {
|
|
|
|
stub := &stubs[i]
|
2024-04-30 10:41:05 +00:00
|
|
|
|
|
|
|
var attributes = make(map[string]interface{})
|
2024-09-24 14:05:58 +00:00
|
|
|
|
2024-04-30 10:41:05 +00:00
|
|
|
for _, attr := range stub.Attributes {
|
|
|
|
attributes[string(attr.Key)] = GetByType(attr.Value)
|
2024-09-24 14:05:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, attr := range stub.Resource.Attributes() {
|
|
|
|
attributes[string(attr.Key)] = GetByType(attr.Value)
|
2024-04-26 13:09:54 +00:00
|
|
|
}
|
|
|
|
|
2024-04-30 10:41:05 +00:00
|
|
|
for i := range stub.Events {
|
|
|
|
event := stub.Events[i]
|
|
|
|
|
|
|
|
var gelf GELFMessage = GELFMessage{
|
2024-05-16 11:45:13 +00:00
|
|
|
Host: e.appName,
|
2024-04-30 10:41:05 +00:00
|
|
|
ShortMessage: event.Name,
|
|
|
|
Timestamp: stub.StartTime,
|
2024-05-16 11:45:13 +00:00
|
|
|
// Defaults to ALERT since we consider lack of the level a serious error that should be fixed ASAP.
|
|
|
|
// Otherwise some dangerous unexpected behaviour could go unnoticed.
|
2024-09-24 14:24:19 +00:00
|
|
|
Level: syslog.ALERT,
|
2024-04-30 10:41:05 +00:00
|
|
|
}
|
2024-05-16 11:45:13 +00:00
|
|
|
for _, attrKV := range event.Attributes {
|
2024-05-17 13:31:35 +00:00
|
|
|
if attrKV.Key == attr.LogMessageLongKey {
|
2024-05-16 11:45:13 +00:00
|
|
|
gelf.LongMessage = attrKV.Value.AsString()
|
2024-04-30 10:41:05 +00:00
|
|
|
continue
|
|
|
|
}
|
2024-05-17 13:46:25 +00:00
|
|
|
if attrKV.Key == attr.LogMessageShortKey {
|
|
|
|
gelf.ShortMessage = attrKV.Value.AsString()
|
|
|
|
continue
|
|
|
|
}
|
2024-04-30 10:41:05 +00:00
|
|
|
|
2024-05-16 11:45:13 +00:00
|
|
|
if attrKV.Key == attr.SeverityLevelKey {
|
|
|
|
gelf.Level = level.FromString(attrKV.Value.AsString()).IntoSyslogLevel()
|
2024-04-30 10:41:05 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-05-16 11:45:13 +00:00
|
|
|
attributes[string(attrKV.Key)] = GetByType(attrKV.Value)
|
2024-04-30 10:41:05 +00:00
|
|
|
}
|
|
|
|
|
2024-09-24 14:24:19 +00:00
|
|
|
gelf.ExtraFields = attributes
|
2024-04-30 10:41:05 +00:00
|
|
|
Log(e.gelfWriter, gelf)
|
|
|
|
}
|
2024-04-26 13:09:54 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-30 10:41:05 +00:00
|
|
|
func GetByType(val attribute.Value) interface{} {
|
|
|
|
switch val.Type() {
|
|
|
|
case attribute.INVALID:
|
|
|
|
return "invalid value"
|
|
|
|
case attribute.BOOL:
|
|
|
|
return val.AsBool()
|
|
|
|
case attribute.INT64:
|
|
|
|
return val.AsInt64()
|
|
|
|
case attribute.FLOAT64:
|
|
|
|
return val.AsFloat64()
|
|
|
|
case attribute.STRING:
|
|
|
|
return val.AsString()
|
|
|
|
case attribute.BOOLSLICE:
|
|
|
|
return val.AsBoolSlice()
|
|
|
|
case attribute.INT64SLICE:
|
|
|
|
return val.AsInt64Slice()
|
|
|
|
case attribute.FLOAT64SLICE:
|
|
|
|
return val.AsFloat64Slice()
|
|
|
|
case attribute.STRINGSLICE:
|
|
|
|
return val.AsStringSlice()
|
|
|
|
}
|
|
|
|
|
|
|
|
return "invalid value"
|
|
|
|
}
|
|
|
|
|
2024-04-26 13:09:54 +00:00
|
|
|
// Shutdown is called to stop the exporter, it performs no action.
|
|
|
|
func (e *Exporter) Shutdown(ctx context.Context) error {
|
|
|
|
e.stoppedMu.Lock()
|
|
|
|
e.stopped = true
|
|
|
|
e.stoppedMu.Unlock()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// // MarshalLog is the marshaling function used by the logging system to represent this Exporter.
|
|
|
|
// func (e *Exporter) MarshalLog() interface{} {
|
|
|
|
// return struct {
|
|
|
|
// Type string
|
|
|
|
// WithTimestamps bool
|
|
|
|
// }{
|
|
|
|
// Type: "stdout",
|
|
|
|
// WithTimestamps: e.timestamps,
|
|
|
|
// }
|
|
|
|
// }
|