initial commit
This commit is contained in:
68
pkg/gelf_exporter/config.go
Normal file
68
pkg/gelf_exporter/config.go
Normal file
@ -0,0 +1,68 @@
|
||||
package gelfexporter
|
||||
|
||||
type config struct {
|
||||
GelfUrl string
|
||||
}
|
||||
|
||||
// newConfig creates a validated Config configured with options.
|
||||
func newConfig(options ...Option) (config, error) {
|
||||
cfg := config{}
|
||||
for _, opt := range options {
|
||||
cfg = opt.apply(cfg)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Option sets the value of an option for a Config.
|
||||
type Option interface {
|
||||
apply(config) config
|
||||
}
|
||||
|
||||
// WithWriter sets the export stream destination.
|
||||
// func WithWriter(w io.Writer) Option {
|
||||
// return writerOption{w}
|
||||
// }
|
||||
|
||||
// type writerOption struct {
|
||||
// W *gelf.Writer
|
||||
// }
|
||||
|
||||
// func (o writerOption) apply(cfg config) config {
|
||||
// cfg.Writer = o.W
|
||||
// return cfg
|
||||
// }
|
||||
|
||||
func WithGelfUrl(url string) Option {
|
||||
return gelfUrlOption(url)
|
||||
}
|
||||
|
||||
type gelfUrlOption string
|
||||
|
||||
func (o gelfUrlOption) apply(cfg config) config {
|
||||
cfg.GelfUrl = string(o)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// // WithPrettyPrint prettifies the emitted output.
|
||||
// func WithPrettyPrint() Option {
|
||||
// return prettyPrintOption(true)
|
||||
// }
|
||||
|
||||
// type prettyPrintOption bool
|
||||
|
||||
// func (o prettyPrintOption) apply(cfg config) config {
|
||||
// cfg.PrettyPrint = bool(o)
|
||||
// return cfg
|
||||
// }
|
||||
|
||||
// // WithoutTimestamps sets the export stream to not include timestamps.
|
||||
// func WithoutTimestamps() Option {
|
||||
// return timestampsOption(false)
|
||||
// }
|
||||
|
||||
// type timestampsOption bool
|
||||
|
||||
// func (o timestampsOption) apply(cfg config) config {
|
||||
// cfg.Timestamps = bool(o)
|
||||
// return cfg
|
||||
// }
|
46
pkg/gelf_exporter/gelf.go
Normal file
46
pkg/gelf_exporter/gelf.go
Normal file
@ -0,0 +1,46 @@
|
||||
package gelfexporter
|
||||
|
||||
import (
|
||||
"log"
|
||||
"maal/tracer/pkg/level"
|
||||
"time"
|
||||
|
||||
"gopkg.in/Graylog2/go-gelf.v2/gelf"
|
||||
)
|
||||
|
||||
type GELFMessage struct {
|
||||
// Name of the application
|
||||
Host string `json:"host"`
|
||||
// Short, descriptive message
|
||||
ShortMessage string `json:"short_message"`
|
||||
// Optional long message.
|
||||
LongMessage string `json:"long_message,omitempty"`
|
||||
// Timestamp in Unix
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
// Severity level matching Syslog standard.
|
||||
Level level.SyslogLevel `json:"level"`
|
||||
|
||||
// All additional field names must start with an underline.
|
||||
ExtraFields map[string]interface{} `json:"extrafields,omitempty"`
|
||||
}
|
||||
|
||||
func Log(writer *gelf.UDPWriter, msg GELFMessage) {
|
||||
if writer != nil {
|
||||
err := writer.WriteMessage(msg.GELFFormat())
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g GELFMessage) GELFFormat() *gelf.Message {
|
||||
return &gelf.Message{
|
||||
Version: "1.1",
|
||||
Host: g.Host,
|
||||
Short: g.ShortMessage,
|
||||
Full: g.LongMessage,
|
||||
TimeUnix: float64(g.Timestamp.Unix()),
|
||||
Level: int32(g.Level),
|
||||
Extra: g.ExtraFields,
|
||||
}
|
||||
}
|
104
pkg/gelf_exporter/trace.go
Normal file
104
pkg/gelf_exporter/trace.go
Normal file
@ -0,0 +1,104 @@
|
||||
package gelfexporter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
"gopkg.in/Graylog2/go-gelf.v2/gelf"
|
||||
)
|
||||
|
||||
var zeroTime time.Time
|
||||
|
||||
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,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// Exporter is an implementation of trace.SpanSyncer that writes spans to stdout.
|
||||
type Exporter struct {
|
||||
timestamps bool
|
||||
gelfWriter *gelf.UDPWriter
|
||||
|
||||
stoppedMu sync.RWMutex
|
||||
stopped bool
|
||||
}
|
||||
|
||||
// ExportSpans writes spans in json format to stdout.
|
||||
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]
|
||||
// Remove timestamps
|
||||
if !e.timestamps {
|
||||
stub.StartTime = zeroTime
|
||||
stub.EndTime = zeroTime
|
||||
for j := range stub.Events {
|
||||
ev := &stub.Events[j]
|
||||
ev.Time = zeroTime
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("stub: %v\n", stub)
|
||||
|
||||
Log(e.gelfWriter, GELFMessage{
|
||||
Host: "test",
|
||||
ShortMessage: "test",
|
||||
})
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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,
|
||||
// }
|
||||
// }
|
84
pkg/level/level.go
Normal file
84
pkg/level/level.go
Normal file
@ -0,0 +1,84 @@
|
||||
package level
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type SyslogLevel uint8
|
||||
|
||||
const (
|
||||
EMERG SyslogLevel = iota
|
||||
ALERT
|
||||
CRIT
|
||||
ERR
|
||||
WARNING
|
||||
NOTICE
|
||||
INFO
|
||||
DEBUG
|
||||
)
|
||||
|
||||
// Level Keyword Description
|
||||
// 0 emergencies System is unusable
|
||||
// 1 alerts Immediate action is needed
|
||||
// 2 critical Critical conditions exist
|
||||
// 3 errors Error conditions exist
|
||||
// 4 warnings Warning conditions exist
|
||||
// 5 notification Normal, but significant, conditions exist
|
||||
// 6 informational Informational messages
|
||||
// 7 debugging Debugging messages
|
||||
|
||||
func (l SyslogLevel) String() string {
|
||||
switch l {
|
||||
case EMERG:
|
||||
return "EMERG"
|
||||
case ALERT:
|
||||
return "ALERT"
|
||||
case CRIT:
|
||||
return "CRIT"
|
||||
case ERR:
|
||||
return "ERR"
|
||||
case WARNING:
|
||||
return "WARN"
|
||||
case NOTICE:
|
||||
return "NOTICE"
|
||||
case INFO:
|
||||
return "INFO"
|
||||
case DEBUG:
|
||||
return "DEBUG"
|
||||
default:
|
||||
return "CRIT"
|
||||
}
|
||||
}
|
||||
|
||||
func LevelFromString(level string) SyslogLevel {
|
||||
switch level {
|
||||
case "EMERG":
|
||||
return EMERG
|
||||
case "ALERT":
|
||||
return ALERT
|
||||
case "CRIT":
|
||||
return CRIT
|
||||
case "ERR":
|
||||
return ERR
|
||||
case "WARN":
|
||||
return WARNING
|
||||
case "NOTICE":
|
||||
return NOTICE
|
||||
case "INFO":
|
||||
return INFO
|
||||
case "DEBUG":
|
||||
return DEBUG
|
||||
default:
|
||||
return CRIT
|
||||
}
|
||||
}
|
||||
|
||||
func (lvl SyslogLevel) SetAttribute(att ...attribute.KeyValue) trace.SpanStartEventOption {
|
||||
|
||||
att = append(att, attribute.Int("level", int(lvl)))
|
||||
|
||||
return trace.WithAttributes(
|
||||
att...,
|
||||
)
|
||||
}
|
160
pkg/tracer/middleware.go
Normal file
160
pkg/tracer/middleware.go
Normal file
@ -0,0 +1,160 @@
|
||||
package tracer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
gelfexporter "maal/tracer/pkg/gelf_exporter"
|
||||
"os"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
fiberOpentelemetry "github.com/psmarcin/fiber-opentelemetry/pkg/fiber-otel"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
trc "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||
trace "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
TracingError error = nil
|
||||
TP trc.TracerProvider
|
||||
)
|
||||
|
||||
type CustomExporter struct {
|
||||
jaeger *otlptrace.Exporter
|
||||
stdouttrace *stdouttrace.Exporter
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
AppName string
|
||||
JaegerUrl string
|
||||
GelfUrl string
|
||||
Version string
|
||||
}
|
||||
|
||||
func NewCustomExporter(jaegerUrl string) (trc.SpanExporter, error) {
|
||||
var jaeg *otlptrace.Exporter
|
||||
var outrace *stdouttrace.Exporter
|
||||
var err error
|
||||
|
||||
outrace, err = stdouttrace.New(
|
||||
stdouttrace.WithWriter(os.Stdout),
|
||||
stdouttrace.WithPrettyPrint(),
|
||||
stdouttrace.WithoutTimestamps(),
|
||||
)
|
||||
if err != nil {
|
||||
return &CustomExporter{}, err
|
||||
}
|
||||
|
||||
if len(jaegerUrl) > 0 {
|
||||
jaeg = otlptracehttp.NewUnstarted(otlptracehttp.WithEndpointURL(jaegerUrl))
|
||||
}
|
||||
|
||||
return &CustomExporter{
|
||||
jaeger: jaeg,
|
||||
stdouttrace: outrace,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (e *CustomExporter) ExportSpans(ctx context.Context, spans []trc.ReadOnlySpan) error {
|
||||
if TracingError == nil {
|
||||
if e.jaeger != nil {
|
||||
err := e.jaeger.ExportSpans(ctx, spans)
|
||||
return err
|
||||
} else {
|
||||
return e.printOnlyOnError(ctx, spans)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *CustomExporter) printOnlyOnError(ctx context.Context, spans []trc.ReadOnlySpan) error {
|
||||
var err error
|
||||
for _, s := range spans {
|
||||
if s.Status().Code == codes.Error {
|
||||
err = e.stdouttrace.ExportSpans(ctx, spans)
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *CustomExporter) Shutdown(ctx context.Context) error {
|
||||
if e.jaeger != nil {
|
||||
e.stdouttrace.Shutdown(ctx)
|
||||
return e.jaeger.Shutdown(ctx)
|
||||
} else {
|
||||
return e.stdouttrace.Shutdown(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func newResource(config Config) *resource.Resource {
|
||||
r := resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceNameKey.String(config.AppName),
|
||||
semconv.ServiceVersionKey.String(config.Version),
|
||||
attribute.String("service.provider", "maal"),
|
||||
)
|
||||
return r
|
||||
}
|
||||
|
||||
func NewTracer(config Config) func(*fiber.Ctx) error {
|
||||
l := log.New(os.Stdout, "", 0)
|
||||
|
||||
var tracerProviders []trc.TracerProviderOption
|
||||
|
||||
otlpExporter := otlptracehttp.NewUnstarted(otlptracehttp.WithEndpointURL(config.JaegerUrl))
|
||||
|
||||
gelfExporter, err := gelfexporter.New(gelfexporter.WithGelfUrl(config.GelfUrl))
|
||||
if err != nil {
|
||||
l.Fatal(err)
|
||||
}
|
||||
tracerProviders = append(tracerProviders, trc.WithBatcher(otlpExporter))
|
||||
tracerProviders = append(tracerProviders, trc.WithBatcher(gelfExporter))
|
||||
|
||||
tracerProviders = append(tracerProviders, trc.WithResource(newResource(config)))
|
||||
|
||||
TP = *trc.NewTracerProvider(tracerProviders...)
|
||||
|
||||
otel.SetTracerProvider(&TP)
|
||||
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
|
||||
if err != TracingError {
|
||||
TracingError = err
|
||||
log.Println(err)
|
||||
}
|
||||
}))
|
||||
|
||||
tracer := TP.Tracer("fiber-otel-router")
|
||||
|
||||
return fiberOpentelemetry.New(
|
||||
fiberOpentelemetry.Config{
|
||||
Tracer: tracer,
|
||||
SpanName: "{{ .Method }} {{ .Path }}",
|
||||
TracerStartAttributes: []trace.SpanStartOption{
|
||||
trace.WithSpanKind(trace.SpanKindServer),
|
||||
trace.WithNewRoot(),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func ShutdownTracer() {
|
||||
if err := TP.Shutdown(context.Background()); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Handler(fc *fiber.Ctx) (context.Context, trace.Span) {
|
||||
simpleCtx, span := fiberOpentelemetry.Tracer.Start(fc.UserContext(), fc.OriginalURL())
|
||||
fc.SetUserContext(simpleCtx)
|
||||
|
||||
span.SetAttributes(attribute.String("service.layer", "handler"))
|
||||
|
||||
return simpleCtx, span
|
||||
}
|
Reference in New Issue
Block a user