Compare commits
23 Commits
992fcb7940
...
v0.0.0-lat
Author | SHA1 | Date | |
---|---|---|---|
a2b61ea706 | |||
3024e888c7 | |||
d7b45a1439 | |||
7dd3dc70d5 | |||
031177f30c | |||
86e9128c68 | |||
16dbdeec3e | |||
265731010e | |||
f5819972a4 | |||
c70a285e70 | |||
d119563c7d | |||
372f4367ed | |||
77ab12c3ac | |||
076196c03e | |||
4f4a7e09c5 | |||
d4dc790298 | |||
3c51f5575b | |||
e835318689 | |||
e9c3ae1a7b | |||
fc92995cc8 | |||
ab5b70704d | |||
6fb12e9e9d | |||
f64eac90a1 |
@@ -8,52 +8,51 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.ma-al.com/gora_filip/observer/pkg/level"
|
"git.ma-al.com/maal-libraries/observer/pkg/attr/layer_attr"
|
||||||
"git.ma-al.com/gora_filip/observer/pkg/tracer"
|
"git.ma-al.com/maal-libraries/observer/pkg/event"
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/exporters"
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/exporters/console_exporter"
|
||||||
|
tracing "git.ma-al.com/maal-libraries/observer/pkg/fiber_tracing"
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/level"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AttributesX struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
main := fiber.New()
|
||||||
|
|
||||||
main := fiber.New(fiber.Config{
|
exps := make([]exporters.TraceExporter, 0)
|
||||||
StreamRequestBody: true,
|
exps = append(exps, exporters.DevConsoleExporter(console_exporter.ProcessorOptions{}))
|
||||||
})
|
gelfExp, err := exporters.GelfExporter()
|
||||||
|
if err == nil {
|
||||||
|
exps = append(exps, gelfExp)
|
||||||
|
}
|
||||||
|
jaegerExp, err := exporters.OtlpHTTPExporter(otlptracehttp.WithEndpointURL("http://localhost:4318/v1/traces"))
|
||||||
|
if err == nil {
|
||||||
|
exps = append(exps, jaegerExp)
|
||||||
|
}
|
||||||
|
|
||||||
main.Use(tracer.NewTracer(tracer.Config{
|
main.Use(tracing.NewMiddleware(tracing.Config{
|
||||||
AppName: "test",
|
AppName: "example",
|
||||||
JaegerUrl: "http://localhost:4318/v1/traces",
|
Version: "0.0.0",
|
||||||
GelfUrl: "192.168.220.30:12201",
|
ServiceProvider: "maal",
|
||||||
Version: "1",
|
Exporters: exps,
|
||||||
}))
|
}))
|
||||||
defer tracer.ShutdownTracer()
|
defer tracing.ShutdownTracer()
|
||||||
|
|
||||||
main.Get("/", func(c *fiber.Ctx) error {
|
main.Use(func(c *fiber.Ctx) error {
|
||||||
ctx, span := tracer.Handler(c)
|
span := tracing.SpanFromContext(c)
|
||||||
defer span.End()
|
span.AddEvent("pushed into a span an event from middleware")
|
||||||
|
|
||||||
span.AddEvent(
|
span = trace.SpanFromContext(c.UserContext())
|
||||||
"smthing is happening",
|
span.AddEvent("span also available from c.UserContext()")
|
||||||
trace.WithAttributes(
|
return c.Next()
|
||||||
tracer.LongMessage("smthing is happening - long"),
|
|
||||||
tracer.JsonAttr("smth", map[string]interface{}{
|
|
||||||
"xd": 1,
|
|
||||||
}),
|
|
||||||
tracer.Level(level.ALERT),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
err := Serv(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return tracer.RecordError(span, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.SendString("xd")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
main.Get("/", Handler)
|
||||||
|
main.Get("/just/some/more/complex/path/:with/params", Handler)
|
||||||
|
|
||||||
// handle interrupts (shutdown)
|
// handle interrupts (shutdown)
|
||||||
c := make(chan os.Signal, 1)
|
c := make(chan os.Signal, 1)
|
||||||
signal.Notify(c, os.Interrupt)
|
signal.Notify(c, os.Interrupt)
|
||||||
@@ -69,8 +68,29 @@ func main() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Handler(c *fiber.Ctx) error {
|
||||||
|
ctx, span := tracing.FStart(c, layer_attr.Handler{
|
||||||
|
Level: level.DEBUG,
|
||||||
|
}.AsOpts())
|
||||||
|
defer span.End()
|
||||||
|
event.NewInSpan(event.Event{
|
||||||
|
Level: level.WARN,
|
||||||
|
ShortMessage: "a warning event",
|
||||||
|
}, span)
|
||||||
|
|
||||||
|
err := Serv(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return event.NewErrInSpan(event.Error{Err: err, Level: level.ERR}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
func Serv(ctx context.Context) *fiber.Error {
|
func Serv(ctx context.Context) *fiber.Error {
|
||||||
ctx, span := tracer.Service(ctx, "name of the span")
|
ctx, span := tracing.Start(ctx, "service span", layer_attr.Service{
|
||||||
|
Level: level.INFO,
|
||||||
|
Name: "some service",
|
||||||
|
}.AsOpts())
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
for range []int{1, 2, 3} {
|
for range []int{1, 2, 3} {
|
||||||
@@ -86,7 +106,10 @@ func Serv(ctx context.Context) *fiber.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Repo(ctx context.Context) error {
|
func Repo(ctx context.Context) error {
|
||||||
ctx, span := tracer.Repository(ctx, "name of the span")
|
ctx, span := tracing.Start(ctx, "repo span", layer_attr.Repo{
|
||||||
|
Level: level.DEBUG,
|
||||||
|
Name: "some repo",
|
||||||
|
}.AsOpts())
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
for range []int{1, 2, 3} {
|
for range []int{1, 2, 3} {
|
||||||
|
5
example/requests.hurl
Normal file
5
example/requests.hurl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
GET http://127.0.0.1:3344/
|
||||||
|
HTTP 500
|
||||||
|
|
||||||
|
GET http://127.0.0.1:3344/just/some/more/complex/path/jjj/params
|
||||||
|
HTTP 500
|
8
go.mod
8
go.mod
@@ -1,15 +1,12 @@
|
|||||||
module git.ma-al.com/gora_filip
|
module git.ma-al.com/maal-libraries/observer
|
||||||
|
|
||||||
go 1.21.0
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.ma-al.com/gora_filip/observer v0.0.0-20240430124205-be03e0ce4205
|
|
||||||
github.com/gofiber/fiber/v2 v2.52.4
|
github.com/gofiber/fiber/v2 v2.52.4
|
||||||
github.com/psmarcin/fiber-opentelemetry v1.2.0
|
github.com/psmarcin/fiber-opentelemetry v1.2.0
|
||||||
go.opentelemetry.io/otel v1.26.0
|
go.opentelemetry.io/otel v1.26.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.26.0
|
|
||||||
go.opentelemetry.io/otel/sdk v1.26.0
|
go.opentelemetry.io/otel/sdk v1.26.0
|
||||||
go.opentelemetry.io/otel/trace v1.26.0
|
go.opentelemetry.io/otel/trace v1.26.0
|
||||||
gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0
|
gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0
|
||||||
@@ -30,6 +27,7 @@ require (
|
|||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.52.0 // indirect
|
github.com/valyala/fasthttp v1.52.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
||||||
golang.org/x/net v0.23.0 // indirect
|
golang.org/x/net v0.23.0 // indirect
|
||||||
|
4
go.sum
4
go.sum
@@ -1,5 +1,3 @@
|
|||||||
git.ma-al.com/gora_filip/observer v0.0.0-20240430124205-be03e0ce4205 h1:tuJ7e4EAWx/IbVESEO6l3yNoGiUAoaXWssMc26Ft3Hs=
|
|
||||||
git.ma-al.com/gora_filip/observer v0.0.0-20240430124205-be03e0ce4205/go.mod h1:NLwhsfm3SE3YwR+3z4DbH82OBjRPcE+M/GPGEc7DPUM=
|
|
||||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
@@ -59,8 +57,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDp
|
|||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38=
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.26.0 h1:0W5o9SzoR15ocYHEQfvfipzcNog1lBxOLfnex91Hk6s=
|
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.26.0/go.mod h1:zVZ8nz+VSggWmnh6tTsJqXQ7rU4xLwRtna1M4x5jq58=
|
|
||||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
||||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
||||||
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
|
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
|
||||||
|
4
justfile
Normal file
4
justfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
run:
|
||||||
|
go run example/main.go
|
||||||
|
test-example:
|
||||||
|
hurl example/requests.hurl
|
268
pkg/attr/attr.go
Normal file
268
pkg/attr/attr.go
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
package attr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/level"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/semconv/v1.25.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyValue = attribute.KeyValue
|
||||||
|
type Key = attribute.Key
|
||||||
|
type Value = attribute.Value
|
||||||
|
|
||||||
|
type IntoTraceAttribute interface {
|
||||||
|
IntoTraceAttribute() attribute.KeyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntoTraceAttributes interface {
|
||||||
|
IntoTraceAttributes() []attribute.KeyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func CollectAttributes(attrs ...interface{}) []attribute.KeyValue {
|
||||||
|
collected := make([]attribute.KeyValue, len(attrs))
|
||||||
|
for _, a := range attrs {
|
||||||
|
switch a.(type) {
|
||||||
|
case []attribute.KeyValue:
|
||||||
|
collected = append(collected, a.([]attribute.KeyValue)...)
|
||||||
|
case attribute.KeyValue:
|
||||||
|
collected = append(collected, a.(attribute.KeyValue))
|
||||||
|
case IntoTraceAttribute:
|
||||||
|
collected = append(collected, a.(IntoTraceAttribute).IntoTraceAttribute())
|
||||||
|
case IntoTraceAttributes:
|
||||||
|
collected = append(collected, a.(IntoTraceAttributes).IntoTraceAttributes()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return collected
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAttributes(attrs ...interface{}) trace.SpanStartEventOption {
|
||||||
|
return trace.WithAttributes(CollectAttributes(attrs...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SeverityLevelKey = attribute.Key("level")
|
||||||
|
LogMessageLongKey = attribute.Key("log_message.long")
|
||||||
|
LogMessageShortKey = attribute.Key("log_message.short")
|
||||||
|
EnduserResponseMessageKey = attribute.Key("enduser.response_message")
|
||||||
|
SessionLanguageIdKey = attribute.Key("session.language_id")
|
||||||
|
SessionCountryIdKey = attribute.Key("session.country_id")
|
||||||
|
SessionCurrencyIdKey = attribute.Key("session.currency_id")
|
||||||
|
ProcessThreadsAvailableKey = attribute.Key("process.threads_available")
|
||||||
|
ServiceLayerKey = attribute.Key("service.layer")
|
||||||
|
ServiceLayerNameKey = attribute.Key("service.layer_name")
|
||||||
|
DBExecutionTimeMsKey = attribute.Key("db.execution_time_ms")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceArchitectureLayer string
|
||||||
|
|
||||||
|
const (
|
||||||
|
LayerFrameworkMiddleware ServiceArchitectureLayer = "framework_middleware"
|
||||||
|
LayerHandler = "handler"
|
||||||
|
LayerService = "service"
|
||||||
|
LayerRepository = "repository"
|
||||||
|
LayerORM = "orm"
|
||||||
|
LayerUtil = "util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build an attribute with a value formatted as json
|
||||||
|
func JsonAttr(key string, jsonEl map[string]interface{}) attribute.KeyValue {
|
||||||
|
jsonStr, _ := json.Marshal(jsonEl)
|
||||||
|
return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(string(jsonStr))}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// An attribute informing about the severity or importance of an event using our own standard of log levels that
|
||||||
|
// can map to syslog level.
|
||||||
|
func SeverityLevel(lvl level.SeverityLevel) attribute.KeyValue {
|
||||||
|
return attribute.String(string(SeverityLevelKey), lvl.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogMessage(short string, expanded string) []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 2)
|
||||||
|
attrs = append(attrs, LogMessageShort(short), LogMessageLong(expanded))
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// An attribute which value could be used as the full message field within the GELF format.
|
||||||
|
func LogMessageLong(msg string) attribute.KeyValue {
|
||||||
|
return attribute.String(string(LogMessageLongKey), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An attribute which value could be used as the short message field within the GELF format.
|
||||||
|
func LogMessageShort(msg string) attribute.KeyValue {
|
||||||
|
return attribute.String(string(LogMessageShortKey), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A message provided to the end user. For example, in case of API server errors it might be desired
|
||||||
|
// to provide to the user a message that does not leak too many details instead of sending an original
|
||||||
|
// (for a given package) error message.
|
||||||
|
func EnduserResponseMessage(msg string) attribute.KeyValue {
|
||||||
|
return attribute.String(string(EnduserResponseMessageKey), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect the call stack to retrieve the information about a call site location including
|
||||||
|
// function name, file path, and line number.
|
||||||
|
func SourceCodeLocation(skipLevelsInCallStack int) []attribute.KeyValue {
|
||||||
|
pc, file, line, _ := runtime.Caller(1 + skipLevelsInCallStack)
|
||||||
|
funcName := runtime.FuncForPC(pc).Name()
|
||||||
|
|
||||||
|
return []attribute.KeyValue{
|
||||||
|
{
|
||||||
|
Key: semconv.CodeFunctionKey,
|
||||||
|
Value: attribute.StringValue(funcName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: semconv.CodeFilepathKey,
|
||||||
|
Value: attribute.StringValue(file),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: semconv.CodeLineNumberKey,
|
||||||
|
Value: attribute.IntValue(line),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use within some panic handler to generate an attribute that will contain a stack trace.
|
||||||
|
func PanicStackTrace() attribute.KeyValue {
|
||||||
|
stackTrace := string(debug.Stack())
|
||||||
|
return semconv.ExceptionStacktrace(stackTrace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds attributes describing a server.
|
||||||
|
func Server(address string, port int) []attribute.KeyValue {
|
||||||
|
return []attribute.KeyValue{
|
||||||
|
{
|
||||||
|
Key: semconv.ServerAddressKey,
|
||||||
|
Value: attribute.StringValue(address),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: semconv.ServerPortKey,
|
||||||
|
Value: attribute.IntValue(port),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Investigates the running process to derive attributes that describe it. This will only
|
||||||
|
// try to retrive these details which provide any valuable information at the start of a
|
||||||
|
// process.
|
||||||
|
func ProcessStart() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 5)
|
||||||
|
executablePath, err := os.Executable()
|
||||||
|
if err == nil {
|
||||||
|
attrs = append(attrs, semconv.ProcessExecutablePath(executablePath))
|
||||||
|
}
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err == nil {
|
||||||
|
attrs = append(attrs, semconv.HostName(hostname))
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimeVersion := runtime.Version()
|
||||||
|
cpuThreads := runtime.NumCPU()
|
||||||
|
pid := os.Getpid()
|
||||||
|
attrs = append(attrs, semconv.ProcessParentPID(pid), semconv.ProcessRuntimeVersion(runtimeVersion), attribute.KeyValue{
|
||||||
|
Key: ProcessThreadsAvailableKey,
|
||||||
|
Value: attribute.IntValue(cpuThreads),
|
||||||
|
})
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id of an end user's session.
|
||||||
|
func SessionId(id string) attribute.KeyValue {
|
||||||
|
return attribute.KeyValue{
|
||||||
|
Key: semconv.SessionIDKey,
|
||||||
|
Value: attribute.StringValue(id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id of a language associated with a user's session.
|
||||||
|
func SessionLanguageId(id uint) attribute.KeyValue {
|
||||||
|
return attribute.KeyValue{
|
||||||
|
Key: SessionLanguageIdKey,
|
||||||
|
Value: attribute.IntValue(int(id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id of a country associated with a user's session.
|
||||||
|
func SessionCountryId(id uint) attribute.KeyValue {
|
||||||
|
return attribute.KeyValue{
|
||||||
|
Key: SessionCountryIdKey,
|
||||||
|
Value: attribute.IntValue(int(id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id of a currency associated with a user's session.
|
||||||
|
func SessionCurrencyId(id uint) attribute.KeyValue {
|
||||||
|
return attribute.KeyValue{
|
||||||
|
Key: SessionCurrencyIdKey,
|
||||||
|
Value: attribute.IntValue(int(id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render details about session as attributes.
|
||||||
|
func Session(deets SessionDetails) []attribute.KeyValue {
|
||||||
|
return deets.IntoTraceAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A collection of attributes that we at maal frequently attach to user sessions that can
|
||||||
|
// be converted into a collection of trace attributes. All fields are optional.
|
||||||
|
type SessionDetails struct {
|
||||||
|
ID *string
|
||||||
|
PreviousID *string
|
||||||
|
LanguageID *uint
|
||||||
|
CountryID *uint
|
||||||
|
CurrencyID *uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (deets SessionDetails) IntoTraceAttributes() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 4) // most frequently we won't have previous session ID
|
||||||
|
if deets.ID != nil {
|
||||||
|
attrs = append(attrs, SessionId(*deets.ID))
|
||||||
|
}
|
||||||
|
if deets.PreviousID != nil {
|
||||||
|
attrs = append(attrs, attribute.KeyValue{
|
||||||
|
Key: semconv.SessionPreviousIDKey,
|
||||||
|
Value: attribute.StringValue(*deets.PreviousID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if deets.LanguageID != nil {
|
||||||
|
attrs = append(attrs, SessionLanguageId(*deets.LanguageID))
|
||||||
|
}
|
||||||
|
if deets.CountryID != nil {
|
||||||
|
attrs = append(attrs, SessionCountryId(*deets.CountryID))
|
||||||
|
}
|
||||||
|
if deets.CurrencyID != nil {
|
||||||
|
attrs = append(attrs, SessionCurrencyId(*deets.CurrencyID))
|
||||||
|
}
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describes a layer of a web server architecture with some of terms frequently used at maal.
|
||||||
|
func ServiceLayer(layer ServiceArchitectureLayer) attribute.KeyValue {
|
||||||
|
return attribute.KeyValue{
|
||||||
|
Key: ServiceLayerKey,
|
||||||
|
Value: attribute.StringValue(string(layer)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServiceLayerName(name string) attribute.KeyValue {
|
||||||
|
return attribute.KeyValue{
|
||||||
|
Key: ServiceLayerNameKey,
|
||||||
|
Value: attribute.StringValue(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take duration as an execution time of a query measured in milisecongs.
|
||||||
|
func DBExecutionTimeMs(duration time.Duration) attribute.KeyValue {
|
||||||
|
return attribute.KeyValue{
|
||||||
|
Key: DBExecutionTimeMsKey,
|
||||||
|
Value: attribute.Int64Value(duration.Milliseconds()),
|
||||||
|
}
|
||||||
|
}
|
124
pkg/attr/layer_attr/layer_attr.go
Normal file
124
pkg/attr/layer_attr/layer_attr.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package layer_attr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/attr"
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/level"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
// Extra attributes to be attached. Can be also added with [Handler.CustomAttrs] method.
|
||||||
|
Attributes []attribute.KeyValue
|
||||||
|
Level level.SeverityLevel
|
||||||
|
Name string
|
||||||
|
extraSkipInStack int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) IntoTraceAttributes() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 5+len(h.Attributes))
|
||||||
|
attrs = append(attrs, attr.SourceCodeLocation(1+h.extraSkipInStack)...) // 3 attrs added here
|
||||||
|
attrs = append(attrs, attr.ServiceLayer(attr.LayerHandler), attr.SeverityLevel(h.Level))
|
||||||
|
if len(h.Name) > 0 {
|
||||||
|
attrs = append(attrs, attr.ServiceLayerName(h.Name))
|
||||||
|
}
|
||||||
|
attrs = append(attrs, h.Attributes...)
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) CustomAttrs(attrs ...interface{}) Handler {
|
||||||
|
h.Attributes = append(h.Attributes, attr.CollectAttributes(attrs...)...)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) SkipMoreInCallStack(skip int) {
|
||||||
|
h.extraSkipInStack += skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Works the same as [Handler.IntoTraceAttributes]
|
||||||
|
func (h Handler) AsAttrs() []attribute.KeyValue {
|
||||||
|
h.extraSkipInStack += 1
|
||||||
|
return h.IntoTraceAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) AsOpts() trace.SpanStartEventOption {
|
||||||
|
h.extraSkipInStack += 1
|
||||||
|
return trace.WithAttributes(h.IntoTraceAttributes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
// Extra attributes to be attached. Can be also added with [Service.CustomAttrs] method.
|
||||||
|
Attributes []attribute.KeyValue
|
||||||
|
Level level.SeverityLevel
|
||||||
|
Name string
|
||||||
|
extraSkipInStack int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) IntoTraceAttributes() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 6+len(s.Attributes))
|
||||||
|
attrs = append(attrs, attr.SourceCodeLocation(1+s.extraSkipInStack)...)
|
||||||
|
attrs = append(attrs, attr.ServiceLayer(attr.LayerService), attr.SeverityLevel(s.Level))
|
||||||
|
if len(s.Name) > 0 {
|
||||||
|
attrs = append(attrs, attr.ServiceLayerName(s.Name))
|
||||||
|
}
|
||||||
|
attrs = append(attrs, s.Attributes...)
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Works the same as [Service.IntoTraceAttributes]
|
||||||
|
func (s Service) AsAttrs() []attribute.KeyValue {
|
||||||
|
s.extraSkipInStack += 1
|
||||||
|
return s.IntoTraceAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) CustomAttrs(attrs ...interface{}) Service {
|
||||||
|
s.Attributes = append(s.Attributes, attr.CollectAttributes(attrs...)...)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SkipMoreInCallStack(skip int) {
|
||||||
|
s.extraSkipInStack += skip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) AsOpts() trace.SpanStartEventOption {
|
||||||
|
s.extraSkipInStack += 1
|
||||||
|
return trace.WithAttributes(s.IntoTraceAttributes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo struct {
|
||||||
|
// Extra attributes to be attached. Can be also added with [Repo.CustomAttrs] method
|
||||||
|
Attributes []attribute.KeyValue
|
||||||
|
Level level.SeverityLevel
|
||||||
|
Name string
|
||||||
|
extraSkipInStack int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Repo) IntoTraceAttributes() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 6+len(r.Attributes))
|
||||||
|
attrs = append(attrs, attr.SourceCodeLocation(1+r.extraSkipInStack)...)
|
||||||
|
attrs = append(attrs, attr.ServiceLayer(attr.LayerRepository), attr.SeverityLevel(r.Level))
|
||||||
|
if len(r.Name) > 0 {
|
||||||
|
attrs = append(attrs, attr.ServiceLayerName(r.Name))
|
||||||
|
}
|
||||||
|
attrs = append(attrs, r.Attributes...)
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Works the same as [Repo.IntoTraceAttributes]
|
||||||
|
func (r Repo) AsAttrs() []attribute.KeyValue {
|
||||||
|
r.extraSkipInStack += 1
|
||||||
|
return r.IntoTraceAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Repo) CustomAttrs(attrs ...interface{}) Repo {
|
||||||
|
r.Attributes = append(r.Attributes, attr.CollectAttributes(attrs...)...)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
func (r *Repo) SkipMoreInCallStack(skip int) {
|
||||||
|
r.extraSkipInStack += skip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Repo) AsOpts() trace.SpanStartEventOption {
|
||||||
|
r.extraSkipInStack += 1
|
||||||
|
return trace.WithAttributes(r.IntoTraceAttributes()...)
|
||||||
|
}
|
28
pkg/code_location/code_location.go
Normal file
28
pkg/code_location/code_location.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package code_location
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CodeLocation struct {
|
||||||
|
FilePath string
|
||||||
|
FuncName string
|
||||||
|
LineNumber int
|
||||||
|
ColumnNumber int
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromStackTrace(atDepth ...int) CodeLocation {
|
||||||
|
skipLevelsInCallStack := 0
|
||||||
|
|
||||||
|
if len(atDepth) > 1 {
|
||||||
|
skipLevelsInCallStack = atDepth[0]
|
||||||
|
}
|
||||||
|
pc, file, line, _ := runtime.Caller(1 + skipLevelsInCallStack)
|
||||||
|
funcName := runtime.FuncForPC(pc).Name()
|
||||||
|
|
||||||
|
return CodeLocation{
|
||||||
|
FilePath: file,
|
||||||
|
LineNumber: line,
|
||||||
|
FuncName: funcName,
|
||||||
|
}
|
||||||
|
}
|
85
pkg/console_fmt/fmt.go
Normal file
85
pkg/console_fmt/fmt.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package console_fmt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/level"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ColorReset = "\033[0m"
|
||||||
|
ColorRed = "\033[31m"
|
||||||
|
ColorGreen = "\033[32m"
|
||||||
|
ColorYellow = "\033[33m"
|
||||||
|
ColorBlue = "\033[34m"
|
||||||
|
ColorPurple = "\033[35m"
|
||||||
|
ColorCyan = "\033[36m"
|
||||||
|
ColorWhite = "\033[37m"
|
||||||
|
ColorBlackOnYellow = "\033[43m\033[30m"
|
||||||
|
ColorWhiteOnRed = "\033[37m\033[41m"
|
||||||
|
ColorWhiteOnRedBlinking = "\033[37m\033[41m\033[5m"
|
||||||
|
ColorBold = "\033[1m"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Bold(txt string) string {
|
||||||
|
return ColorBold + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func Red(txt string) string {
|
||||||
|
return ColorRed + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func Green(txt string) string {
|
||||||
|
return ColorGreen + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func Yellow(txt string) string {
|
||||||
|
return ColorYellow + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func Blue(txt string) string {
|
||||||
|
return ColorBlue + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func Purple(txt string) string {
|
||||||
|
return ColorPurple + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cyan(txt string) string {
|
||||||
|
return ColorCyan + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func White(txt string) string {
|
||||||
|
return ColorWhite + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func BlackOnYellow(txt string) string {
|
||||||
|
return ColorBlackOnYellow + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func WhiteOnRed(txt string) string {
|
||||||
|
return ColorWhiteOnRed + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func WhiteOnRedBlinking(txt string) string {
|
||||||
|
return ColorWhiteOnRedBlinking + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func SeverityLevelToColor(lvl level.SeverityLevel) string {
|
||||||
|
switch lvl {
|
||||||
|
case level.TRACE:
|
||||||
|
return ColorWhite
|
||||||
|
case level.DEBUG:
|
||||||
|
return ColorPurple
|
||||||
|
case level.INFO:
|
||||||
|
return ColorBlue
|
||||||
|
case level.WARN:
|
||||||
|
return ColorYellow
|
||||||
|
case level.ERR:
|
||||||
|
return ColorRed
|
||||||
|
case level.CRIT:
|
||||||
|
return ColorBlackOnYellow
|
||||||
|
case level.ALERT:
|
||||||
|
return ColorWhiteOnRed
|
||||||
|
default:
|
||||||
|
return ColorWhite
|
||||||
|
}
|
||||||
|
}
|
115
pkg/event/event.go
Normal file
115
pkg/event/event.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/attr"
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/level"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An event that maps well to a log message
|
||||||
|
type Event struct {
|
||||||
|
FullMessage string
|
||||||
|
ShortMessage string
|
||||||
|
Attributes []attribute.KeyValue
|
||||||
|
// Defaults to INFO when converted into attributes and unset
|
||||||
|
Level level.SeverityLevel
|
||||||
|
extraSkipInStack int
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntoEvent interface {
|
||||||
|
IntoEvent() Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) IntoEvent() Event {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) IntoTraceAttributes() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 0)
|
||||||
|
attrs = append(attrs, attr.SourceCodeLocation(1+e.extraSkipInStack)...)
|
||||||
|
attrs = append(attrs, e.Attributes...)
|
||||||
|
if len(e.FullMessage) > 0 {
|
||||||
|
attrs = append(attrs, attr.LogMessageLong(e.FullMessage))
|
||||||
|
}
|
||||||
|
if len(e.ShortMessage) > 0 {
|
||||||
|
attrs = append(attrs, attr.LogMessageShort(e.ShortMessage))
|
||||||
|
}
|
||||||
|
if e.Level == 0 {
|
||||||
|
e.Level = level.INFO
|
||||||
|
}
|
||||||
|
attrs = append(attrs, attr.SeverityLevel(e.Level))
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) AsOpts() trace.EventOption {
|
||||||
|
e.extraSkipInStack += 1
|
||||||
|
return trace.WithAttributes(e.IntoTraceAttributes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) SkipMoreInCallStack(skip int) Event {
|
||||||
|
e.extraSkipInStack += skip
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInSpan[E IntoEvent](ev E, span trace.Span) {
|
||||||
|
event := ev.IntoEvent()
|
||||||
|
event.extraSkipInStack += 1
|
||||||
|
span.AddEvent(event.ShortMessage, trace.WithAttributes(event.IntoTraceAttributes()...))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Err error
|
||||||
|
ExtendedMessage string
|
||||||
|
// Defaults to ALERT when converted into attributes and unset
|
||||||
|
Level level.SeverityLevel
|
||||||
|
Attributes []attribute.KeyValue
|
||||||
|
extraSkipInStack int
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntoErrorEvent interface {
|
||||||
|
IntoErrorEvent() Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) IntoErrorEvent() Error {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) IntoTraceAttributes() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 0)
|
||||||
|
attrs = append(attrs, e.Attributes...)
|
||||||
|
if len(e.ExtendedMessage) > 0 {
|
||||||
|
attrs = append(attrs, attr.LogMessageLong(e.ExtendedMessage))
|
||||||
|
}
|
||||||
|
if e.Level == 0 {
|
||||||
|
e.Level = level.ALERT
|
||||||
|
}
|
||||||
|
attrs = append(attrs, attr.SeverityLevel(e.Level), attr.LogMessageShort(e.Err.Error()))
|
||||||
|
attrs = append(attrs, attr.SourceCodeLocation(1+e.extraSkipInStack)...)
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) AsOpts() trace.EventOption {
|
||||||
|
e.extraSkipInStack += 1
|
||||||
|
return trace.WithAttributes(e.IntoTraceAttributes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) SkipMoreInCallStack(skip int) Error {
|
||||||
|
e.extraSkipInStack += skip
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewErrInSpan[E IntoErrorEvent](err E, span trace.Span) E {
|
||||||
|
er := err.IntoErrorEvent()
|
||||||
|
er.extraSkipInStack += 1
|
||||||
|
span.RecordError(er.Err, er.AsOpts())
|
||||||
|
if er.Level <= level.ERR {
|
||||||
|
span.SetStatus(codes.Error, er.Error())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
218
pkg/exporters/console_exporter/console_exporter.go
Normal file
218
pkg/exporters/console_exporter/console_exporter.go
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
package console_exporter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/attr"
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/level"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TraceFormatter interface {
|
||||||
|
FormatSpanStart(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error)
|
||||||
|
FormatSpanEnd(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error)
|
||||||
|
FormatEvent(event trace.Event, span trace.ReadOnlySpan, selectedAttr []attribute.KeyValue, lvl level.SeverityLevel) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for the exporter.
|
||||||
|
//
|
||||||
|
// Most of options are passed to the formatter.
|
||||||
|
type ProcessorOptions struct {
|
||||||
|
// Try to parse filters from an environment variable with a name provided by this field.
|
||||||
|
// Result will only by applied to unset options. NOT IMPLEMENTED!
|
||||||
|
FilterFromEnvVar *string
|
||||||
|
// Filter the output based on the [level.SeverityLevel].
|
||||||
|
FilterOnLevel level.SeverityLevel
|
||||||
|
// Fields that should be removed from the output.
|
||||||
|
FilterOutFields []attribute.Key
|
||||||
|
// Print only trace events instead of whole traces.
|
||||||
|
EmitEventsOnly bool
|
||||||
|
// Add trace id as an attribute.
|
||||||
|
AddTraceId bool
|
||||||
|
// Print output only when an error is found
|
||||||
|
SkipNonErrors bool
|
||||||
|
// Used only when `EmitEventsOnly` is set to true.
|
||||||
|
TraceFormatter *TraceFormatter
|
||||||
|
SkipEmittingOnSpanStart bool
|
||||||
|
SkipEmittingOnSpanEnd bool
|
||||||
|
SkipAttributesOnSpanEnd bool
|
||||||
|
SkippAttributesOnSpanStart bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Processor struct {
|
||||||
|
lvl level.SeverityLevel
|
||||||
|
removedFields []attribute.Key
|
||||||
|
addTraceId bool
|
||||||
|
onlyErrs bool
|
||||||
|
onlyEvents bool
|
||||||
|
traceFormatter TraceFormatter
|
||||||
|
skipSpanStart bool
|
||||||
|
skipSpanEnd bool
|
||||||
|
skipAttrsOnSpanEnd bool
|
||||||
|
skipAttrsOnSpanStart bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: The configuration might change in future releases
|
||||||
|
func DefaultConsoleExportProcessor() trace.SpanProcessor {
|
||||||
|
fmt := NewPrettyMultilineFormatter()
|
||||||
|
return NewProcessor(ProcessorOptions{
|
||||||
|
FilterFromEnvVar: nil,
|
||||||
|
FilterOutFields: []attribute.Key{},
|
||||||
|
EmitEventsOnly: false,
|
||||||
|
SkipEmittingOnSpanStart: false,
|
||||||
|
SkippAttributesOnSpanStart: true,
|
||||||
|
AddTraceId: false,
|
||||||
|
TraceFormatter: &fmt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProcessor(opts ProcessorOptions) trace.SpanProcessor {
|
||||||
|
var formatter TraceFormatter
|
||||||
|
var lvl level.SeverityLevel
|
||||||
|
|
||||||
|
if opts.TraceFormatter != nil {
|
||||||
|
formatter = *opts.TraceFormatter
|
||||||
|
} else {
|
||||||
|
fmt := NewPrettyMultilineFormatter()
|
||||||
|
formatter = fmt
|
||||||
|
}
|
||||||
|
if opts.FilterOnLevel != level.SeverityLevel(0) {
|
||||||
|
lvl = opts.FilterOnLevel
|
||||||
|
} else {
|
||||||
|
lvl = level.TRACE + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Processor{
|
||||||
|
traceFormatter: formatter,
|
||||||
|
removedFields: opts.FilterOutFields,
|
||||||
|
addTraceId: opts.AddTraceId,
|
||||||
|
onlyEvents: opts.EmitEventsOnly,
|
||||||
|
onlyErrs: opts.SkipNonErrors,
|
||||||
|
skipSpanStart: opts.SkipEmittingOnSpanStart,
|
||||||
|
skipSpanEnd: opts.SkipEmittingOnSpanEnd,
|
||||||
|
skipAttrsOnSpanEnd: opts.SkipAttributesOnSpanEnd,
|
||||||
|
skipAttrsOnSpanStart: opts.SkippAttributesOnSpanStart,
|
||||||
|
lvl: lvl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements [trace.SpanProcessor]
|
||||||
|
func (e *Processor) OnStart(ctx context.Context, span trace.ReadWriteSpan) {
|
||||||
|
if !e.skipSpanStart && !e.onlyEvents {
|
||||||
|
attrs := span.Attributes()
|
||||||
|
filteredAttrs := make([]attribute.KeyValue, 0)
|
||||||
|
severityLvl := level.TRACE
|
||||||
|
|
||||||
|
if !e.skipAttrsOnSpanStart {
|
||||||
|
for i := range attrs {
|
||||||
|
if !slices.Contains(e.removedFields, attrs[i].Key) {
|
||||||
|
filteredAttrs = append(filteredAttrs, attrs[i])
|
||||||
|
}
|
||||||
|
if attrs[i].Key == attr.SeverityLevelKey {
|
||||||
|
severityLvl = level.FromString(attrs[i].Value.AsString())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.addTraceId {
|
||||||
|
filteredAttrs = append(filteredAttrs, attribute.String("trace_id", span.SpanContext().TraceID().String()))
|
||||||
|
}
|
||||||
|
if severityLvl <= e.lvl {
|
||||||
|
line, err := e.traceFormatter.FormatSpanStart(span, filteredAttrs, severityLvl)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("FAILED TO FORMAT SPAN START")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements [trace.SpanProcessor]
|
||||||
|
func (e *Processor) OnEnd(span trace.ReadOnlySpan) {
|
||||||
|
eventsString := ""
|
||||||
|
spanEndString := ""
|
||||||
|
|
||||||
|
for _, event := range span.Events() {
|
||||||
|
attrs := event.Attributes
|
||||||
|
filteredAttrs := make([]attribute.KeyValue, 0)
|
||||||
|
severityLvl := level.TRACE
|
||||||
|
for i := range attrs {
|
||||||
|
if !slices.Contains(e.removedFields, attrs[i].Key) {
|
||||||
|
filteredAttrs = append(filteredAttrs, attrs[i])
|
||||||
|
}
|
||||||
|
if attrs[i].Key == attr.SeverityLevelKey {
|
||||||
|
severityLvl = level.FromString(attrs[i].Value.AsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.addTraceId {
|
||||||
|
filteredAttrs = append(filteredAttrs, attribute.String("trace_id", span.SpanContext().TraceID().String()))
|
||||||
|
}
|
||||||
|
if severityLvl <= e.lvl {
|
||||||
|
eventString, err := e.traceFormatter.FormatEvent(event, span, filteredAttrs, severityLvl)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("FAILED TO FORMAT TRACE EVENT")
|
||||||
|
} else {
|
||||||
|
eventsString += eventString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !e.skipSpanEnd && !e.onlyEvents {
|
||||||
|
attrs := span.Attributes()
|
||||||
|
filteredAttrs := make([]attribute.KeyValue, len(attrs))
|
||||||
|
severityLvl := level.TRACE
|
||||||
|
|
||||||
|
if !e.skipAttrsOnSpanEnd {
|
||||||
|
for i := range attrs {
|
||||||
|
if !slices.Contains(e.removedFields, attrs[i].Key) {
|
||||||
|
filteredAttrs = append(filteredAttrs, attrs[i])
|
||||||
|
}
|
||||||
|
if attrs[i].Key == attr.SeverityLevelKey {
|
||||||
|
severityLvl = level.FromString(attrs[i].Value.AsString())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.addTraceId {
|
||||||
|
filteredAttrs = append(filteredAttrs, attribute.String("trace_id", span.SpanContext().TraceID().String()))
|
||||||
|
}
|
||||||
|
if severityLvl <= e.lvl {
|
||||||
|
spanString, err := e.traceFormatter.FormatSpanEnd(span, filteredAttrs, severityLvl)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("FAILED TO FORMAT SPAN END")
|
||||||
|
} else {
|
||||||
|
spanEndString += spanString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.skipSpanStart {
|
||||||
|
fmt.Printf("%s", spanEndString+eventsString)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s", eventsString+spanEndString)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements [trace.SpanProcessor]
|
||||||
|
func (e *Processor) ForceFlush(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements [trace.SpanExporter]
|
||||||
|
func (e *Processor) Shutdown(ctx context.Context) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
93
pkg/exporters/console_exporter/multiline_formatter.go
Normal file
93
pkg/exporters/console_exporter/multiline_formatter.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package console_exporter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/console_fmt"
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/level"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPrettyMultilineFormatter() TraceFormatter {
|
||||||
|
return &PrettyMultilineFormatter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A formatter that will print only events using a multiline format with colors.
|
||||||
|
// It uses attributes from the [attr] and [semconv] packages.
|
||||||
|
type PrettyMultilineFormatter struct{}
|
||||||
|
|
||||||
|
func (f *PrettyMultilineFormatter) FormatSpanStart(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) {
|
||||||
|
attrs := ""
|
||||||
|
slices.SortFunc(selectedAttrs, func(a, b attribute.KeyValue) int {
|
||||||
|
if a.Key > b.Key {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, kv := range selectedAttrs {
|
||||||
|
if len(kv.Key) > 0 {
|
||||||
|
attrs += fmt.Sprintf("\t%s = %s\n", string(kv.Key), kv.Value.AsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedSpanString := fmt.Sprintf(
|
||||||
|
"%s\n%s",
|
||||||
|
console_fmt.Bold(console_fmt.SeverityLevelToColor(lvl)+fmt.Sprintf("[%s][SpanStart] ", lvl.String())+span.Name()),
|
||||||
|
attrs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return formattedSpanString, nil
|
||||||
|
}
|
||||||
|
func (f *PrettyMultilineFormatter) FormatSpanEnd(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) {
|
||||||
|
attrs := ""
|
||||||
|
slices.SortFunc(selectedAttrs, func(a, b attribute.KeyValue) int {
|
||||||
|
if a.Key > b.Key {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, kv := range selectedAttrs {
|
||||||
|
if len(kv.Key) > 0 {
|
||||||
|
attrs += fmt.Sprintf("\t%s = %s\n", string(kv.Key), kv.Value.AsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedSpanString := fmt.Sprintf(
|
||||||
|
"%s\n%s",
|
||||||
|
console_fmt.Bold(console_fmt.SeverityLevelToColor(lvl)+fmt.Sprintf("[%s][SpanEnd] ", lvl.String())+span.Name()),
|
||||||
|
attrs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return formattedSpanString, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
func (f *PrettyMultilineFormatter) FormatEvent(event trace.Event, span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) {
|
||||||
|
attrs := ""
|
||||||
|
slices.SortFunc(selectedAttrs, func(a, b attribute.KeyValue) int {
|
||||||
|
if a.Key > b.Key {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, kv := range selectedAttrs {
|
||||||
|
if len(kv.Key) > 0 {
|
||||||
|
attrs += fmt.Sprintf("\t%s = %s\n", string(kv.Key), kv.Value.AsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedSpanString := fmt.Sprintf(
|
||||||
|
"%s\n%s",
|
||||||
|
console_fmt.Bold(console_fmt.SeverityLevelToColor(lvl)+fmt.Sprintf("[%s][Event] ", lvl.String())+event.Name),
|
||||||
|
attrs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return formattedSpanString, nil
|
||||||
|
}
|
76
pkg/exporters/exporters.go
Normal file
76
pkg/exporters/exporters.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package exporters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/exporters/console_exporter"
|
||||||
|
gelf_exporter "git.ma-al.com/maal-libraries/observer/pkg/exporters/gelf_exporter"
|
||||||
|
otlphttp_exporter "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Private type preventing implementation of TraceProcessor by external packages.
|
||||||
|
type traceProviderOpt sdktrace.TracerProviderOption
|
||||||
|
|
||||||
|
type TraceExporter interface {
|
||||||
|
IntoTraceProviderOption() traceProviderOpt
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFromSpanExporter(exporter sdktrace.SpanExporter) ExporterWithOptions {
|
||||||
|
return ExporterWithOptions{
|
||||||
|
exporter: exporter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sneaky wrapper that makes it so that the TraceExporter can be created from SpanProcessor.
|
||||||
|
type proc struct {
|
||||||
|
sdktrace.SpanProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p proc) IntoTraceProviderOption() traceProviderOpt {
|
||||||
|
return sdktrace.WithSpanProcessor(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFromSpanProcessor(processor sdktrace.SpanProcessor) TraceExporter {
|
||||||
|
return TraceExporter(proc{
|
||||||
|
SpanProcessor: processor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combined exporter with batch processor config
|
||||||
|
type ExporterWithOptions struct {
|
||||||
|
exporter sdktrace.SpanExporter
|
||||||
|
opts []sdktrace.BatchSpanProcessorOption
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ecfg ExporterWithOptions) AddOption(opt sdktrace.BatchSpanProcessorOption) ExporterWithOptions {
|
||||||
|
ecfg.opts = append(ecfg.opts, opt)
|
||||||
|
return ecfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ecfg ExporterWithOptions) IntoTraceProviderOption() traceProviderOpt {
|
||||||
|
return sdktrace.WithBatcher(ecfg.exporter, ecfg.opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An exporter printing to console with very small delay
|
||||||
|
func DevConsoleExporter(opts ...console_exporter.ProcessorOptions) TraceExporter {
|
||||||
|
var exporter TraceExporter
|
||||||
|
if len(opts) > 0 {
|
||||||
|
exporter = NewFromSpanProcessor(console_exporter.NewProcessor(opts[0]))
|
||||||
|
} else {
|
||||||
|
exporter = NewFromSpanProcessor(console_exporter.DefaultConsoleExportProcessor())
|
||||||
|
}
|
||||||
|
return TraceExporter(exporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default exporter to Graylog.
|
||||||
|
func GelfExporter(opts ...gelf_exporter.Option) (ExporterWithOptions, error) {
|
||||||
|
gelfExp, err := gelf_exporter.New(opts...)
|
||||||
|
return NewFromSpanExporter(gelfExp), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exporter for traces over HTTP. Can be used with Jaeger.
|
||||||
|
// See documentation of [otlhttp_exporter] for details.
|
||||||
|
func OtlpHTTPExporter(opts ...otlphttp_exporter.Option) (ExporterWithOptions, error) {
|
||||||
|
otlpExp, err := otlphttp_exporter.New(context.Background(), opts...)
|
||||||
|
return NewFromSpanExporter(otlpExp), err
|
||||||
|
}
|
@@ -1,12 +1,11 @@
|
|||||||
package gelfexporter
|
package gelfexporter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.ma-al.com/gora_filip/observer/pkg/level"
|
"git.ma-al.com/maal-libraries/observer/pkg/syslog"
|
||||||
"gopkg.in/Graylog2/go-gelf.v2/gelf"
|
"gopkg.in/Graylog2/go-gelf.v2/gelf"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +19,7 @@ type GELFMessage struct {
|
|||||||
// Timestamp in Unix
|
// Timestamp in Unix
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
// Severity level matching Syslog standard.
|
// Severity level matching Syslog standard.
|
||||||
Level level.SyslogLevel `json:"level"`
|
Level syslog.SyslogLevel `json:"level"`
|
||||||
|
|
||||||
// All additional field names must start with an underline.
|
// All additional field names must start with an underline.
|
||||||
ExtraFields map[string]interface{} `json:"extrafields,omitempty"`
|
ExtraFields map[string]interface{} `json:"extrafields,omitempty"`
|
||||||
@@ -32,7 +31,6 @@ func Log(writer *gelf.UDPWriter, msg GELFMessage) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
fmt.Printf("msg: %v sent\n", msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.ma-al.com/gora_filip/observer/pkg/level"
|
"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"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/sdk/trace"
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||||
@@ -63,30 +65,34 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
|
|||||||
attributes[string(attr.Key)] = GetByType(attr.Value)
|
attributes[string(attr.Key)] = GetByType(attr.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes["from"] = "test"
|
|
||||||
|
|
||||||
for i := range stub.Events {
|
for i := range stub.Events {
|
||||||
event := stub.Events[i]
|
event := stub.Events[i]
|
||||||
|
|
||||||
var gelf GELFMessage = GELFMessage{
|
var gelf GELFMessage = GELFMessage{
|
||||||
Host: "salego",
|
Host: e.appName,
|
||||||
ShortMessage: event.Name,
|
ShortMessage: event.Name,
|
||||||
Timestamp: stub.StartTime,
|
Timestamp: stub.StartTime,
|
||||||
Level: level.DEBUG,
|
// Defaults to ALERT since we consider lack of the level a serious error that should be fixed ASAP.
|
||||||
ExtraFields: attributes,
|
// Otherwise some dangerous unexpected behaviour could go unnoticed.
|
||||||
|
Level: syslog.ALERT,
|
||||||
|
ExtraFields: attributes,
|
||||||
}
|
}
|
||||||
for _, attr := range event.Attributes {
|
for _, attrKV := range event.Attributes {
|
||||||
if attr.Key == "long_message_" {
|
if attrKV.Key == attr.LogMessageLongKey {
|
||||||
gelf.LongMessage = attr.Value.AsString()
|
gelf.LongMessage = attrKV.Value.AsString()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if attrKV.Key == attr.LogMessageShortKey {
|
||||||
|
gelf.ShortMessage = attrKV.Value.AsString()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr.Key == "level_" {
|
if attrKV.Key == attr.SeverityLevelKey {
|
||||||
gelf.Level = level.SyslogLevel(attr.Value.AsInt64())
|
gelf.Level = level.FromString(attrKV.Value.AsString()).IntoSyslogLevel()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes[string(attr.Key)] = GetByType(attr.Value)
|
attributes[string(attrKV.Key)] = GetByType(attrKV.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
Log(e.gelfWriter, gelf)
|
Log(e.gelfWriter, gelf)
|
96
pkg/fiber_tracing/fiber_tracing.go
Normal file
96
pkg/fiber_tracing/fiber_tracing.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package fiber_tracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/exporters"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
trc "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
|
||||||
|
trace "go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TracingError error = nil
|
||||||
|
TP trc.TracerProvider
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AppName string
|
||||||
|
Version string
|
||||||
|
// Name of an organization providing the service
|
||||||
|
ServiceProvider string
|
||||||
|
Exporters []exporters.TraceExporter
|
||||||
|
}
|
||||||
|
|
||||||
|
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", config.ServiceProvider),
|
||||||
|
)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: You can use [trace.WithAttributes] as a parameter to opts argument
|
||||||
|
func Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||||
|
return Tracer.Start(ctx, spanName, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: You can use [trace.WithAttributes] as a parameter to opts argument
|
||||||
|
// Returns [c.UserContext] as [context.Context]
|
||||||
|
func FStart(c *fiber.Ctx, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||||
|
return Tracer.Start(c.UserContext(), c.Method()+" "+c.Route().Path, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just like [FStart] but makes it possible to assign custom span name.
|
||||||
|
func FStartRenamed(c *fiber.Ctx, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||||
|
return Tracer.Start(c.UserContext(), spanName, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve span using [fiber.Ctx]
|
||||||
|
func SpanFromContext(c *fiber.Ctx) trace.Span {
|
||||||
|
return trace.SpanFromContext(c.UserContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMiddleware(config Config) func(*fiber.Ctx) error {
|
||||||
|
var tracerProviders []trc.TracerProviderOption
|
||||||
|
|
||||||
|
for _, exp := range config.Exporters {
|
||||||
|
tracerProviders = append(tracerProviders, exp.IntoTraceProviderOption())
|
||||||
|
}
|
||||||
|
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("_maal-fiber-otel_")
|
||||||
|
|
||||||
|
return new(
|
||||||
|
middlewareConfig{
|
||||||
|
Tracer: tracer,
|
||||||
|
TracerStartAttributes: []trace.SpanStartOption{
|
||||||
|
trace.WithSpanKind(trace.SpanKindServer),
|
||||||
|
trace.WithNewRoot(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShutdownTracer() {
|
||||||
|
if err := TP.Shutdown(context.Background()); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
90
pkg/fiber_tracing/middleware.go
Normal file
90
pkg/fiber_tracing/middleware.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package fiber_tracing
|
||||||
|
|
||||||
|
// This was copied from "github.com/psmarcin/fiber-opentelemetry/pkg/fiber-otel"
|
||||||
|
// and slighltly modified but this piece of code is yet to be fully integrated
|
||||||
|
// into the package.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/attr"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Tracer = otel.Tracer("fiber_tracing_middleware")
|
||||||
|
|
||||||
|
// Config defines the config for middleware.
|
||||||
|
type middlewareConfig struct {
|
||||||
|
Tracer trace.Tracer
|
||||||
|
TracerStartAttributes []trace.SpanStartOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigDefault is the default config
|
||||||
|
var configDefault = middlewareConfig{
|
||||||
|
Tracer: Tracer,
|
||||||
|
TracerStartAttributes: []trace.SpanStartOption{
|
||||||
|
trace.WithNewRoot(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to set default values
|
||||||
|
func configDefaults(config ...middlewareConfig) middlewareConfig {
|
||||||
|
// Return default config if nothing provided
|
||||||
|
if len(config) < 1 {
|
||||||
|
return configDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override default config
|
||||||
|
cfg := config[0]
|
||||||
|
|
||||||
|
if len(cfg.TracerStartAttributes) == 0 {
|
||||||
|
cfg.TracerStartAttributes = configDefault.TracerStartAttributes
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func new(config ...middlewareConfig) fiber.Handler {
|
||||||
|
// Set default config
|
||||||
|
cfg := configDefaults(config...)
|
||||||
|
|
||||||
|
// Return new handler
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
spanStartAttributes := []attr.KeyValue{
|
||||||
|
semconv.HTTPMethod(c.Method()),
|
||||||
|
semconv.HTTPTarget(string(c.Request().RequestURI())),
|
||||||
|
semconv.HTTPRoute(c.Route().Path),
|
||||||
|
semconv.HTTPURL(c.OriginalURL()),
|
||||||
|
semconv.HTTPUserAgent(string(c.Request().Header.UserAgent())),
|
||||||
|
semconv.HTTPRequestContentLength(c.Request().Header.ContentLength()),
|
||||||
|
semconv.HTTPScheme(c.Protocol()),
|
||||||
|
semconv.NetTransportTCP,
|
||||||
|
}
|
||||||
|
spanStartAttributes = append(spanStartAttributes, attr.ProcessStart()...)
|
||||||
|
|
||||||
|
opts := []trace.SpanStartOption{
|
||||||
|
trace.WithAttributes(spanStartAttributes...),
|
||||||
|
trace.WithSpanKind(trace.SpanKindServer),
|
||||||
|
}
|
||||||
|
opts = append(opts, cfg.TracerStartAttributes...)
|
||||||
|
|
||||||
|
otelCtx, span := Tracer.Start(
|
||||||
|
c.UserContext(),
|
||||||
|
c.Method()+" "+c.OriginalURL(),
|
||||||
|
opts...,
|
||||||
|
)
|
||||||
|
|
||||||
|
c.SetUserContext(otelCtx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := c.Next()
|
||||||
|
|
||||||
|
statusCode := c.Response().StatusCode()
|
||||||
|
attrs := semconv.HTTPResponseStatusCode(statusCode)
|
||||||
|
|
||||||
|
span.SetAttributes(attrs)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
@@ -1,60 +1,67 @@
|
|||||||
package level
|
package level
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/syslog"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/trace"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SyslogLevel uint8
|
type SeverityLevel uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EMERG SyslogLevel = iota
|
// A magical zero value.
|
||||||
|
// WARN: DO NOT USE IN LOGS OR BASICALLY EVER
|
||||||
|
unset SeverityLevel = iota
|
||||||
|
// This event requires an immediate action. If you suspect that occurence of an event may signal that the
|
||||||
|
// data will get lost, corrupted, or that the application will change its behaviour following the event in
|
||||||
|
// an undesired way, select the ALERT level.
|
||||||
ALERT
|
ALERT
|
||||||
|
// A critical error has occured. Critical errors are such which can be tough to fix or made the users
|
||||||
|
// experience significantly worse but unlike errors that trigger ALERT they can be fixed at any moment.
|
||||||
CRIT
|
CRIT
|
||||||
|
// An error has occured but it is not expected to cause any serious issues. These will be often
|
||||||
|
// `Internal Server Error` responses from an HTTP server.
|
||||||
ERR
|
ERR
|
||||||
WARNING
|
// Signals that something suspicious has happened, for example, a query took too long to execute, gaining access
|
||||||
NOTICE
|
// to a resource took multiple attempts, a conflict was automatically resolved, etc.
|
||||||
|
WARN
|
||||||
|
// Used to inform about standard, expected events of an application, like creation of a new object or a new
|
||||||
|
// log-in from a user. Information that could be:
|
||||||
|
// - used to audit the application,
|
||||||
|
// - resolve customer's complaints,
|
||||||
|
// - track history of significant changes,
|
||||||
|
// - calculate valuable statistics;
|
||||||
|
// should be collected and logged at this level.
|
||||||
INFO
|
INFO
|
||||||
|
// Verbose information that is useful and meaningful to application developers and system administrators.
|
||||||
DEBUG
|
DEBUG
|
||||||
|
// Extremely verbose information that can be used to investigate performance of specific parts of an application.
|
||||||
|
// It is transled to [syslog.DEBUG] by [IntoSyslogLevel].
|
||||||
|
TRACE
|
||||||
)
|
)
|
||||||
|
|
||||||
// Level Keyword Description
|
func (l SeverityLevel) String() string {
|
||||||
// 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 {
|
switch l {
|
||||||
case EMERG:
|
|
||||||
return "EMERG"
|
|
||||||
case ALERT:
|
case ALERT:
|
||||||
return "ALERT"
|
return "ALERT"
|
||||||
case CRIT:
|
case CRIT:
|
||||||
return "CRIT"
|
return "CRIT"
|
||||||
case ERR:
|
case ERR:
|
||||||
return "ERR"
|
return "ERR"
|
||||||
case WARNING:
|
case WARN:
|
||||||
return "WARN"
|
return "WARN"
|
||||||
case NOTICE:
|
|
||||||
return "NOTICE"
|
|
||||||
case INFO:
|
case INFO:
|
||||||
return "INFO"
|
return "INFO"
|
||||||
case DEBUG:
|
case DEBUG:
|
||||||
return "DEBUG"
|
return "DEBUG"
|
||||||
|
case TRACE:
|
||||||
|
return "TRACE"
|
||||||
default:
|
default:
|
||||||
return "CRIT"
|
return "CRIT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LevelFromString(level string) SyslogLevel {
|
func FromString(level string) SeverityLevel {
|
||||||
switch level {
|
switch level {
|
||||||
case "EMERG":
|
|
||||||
return EMERG
|
|
||||||
case "ALERT":
|
case "ALERT":
|
||||||
return ALERT
|
return ALERT
|
||||||
case "CRIT":
|
case "CRIT":
|
||||||
@@ -62,23 +69,39 @@ func LevelFromString(level string) SyslogLevel {
|
|||||||
case "ERR":
|
case "ERR":
|
||||||
return ERR
|
return ERR
|
||||||
case "WARN":
|
case "WARN":
|
||||||
return WARNING
|
return WARN
|
||||||
case "NOTICE":
|
|
||||||
return NOTICE
|
|
||||||
case "INFO":
|
case "INFO":
|
||||||
return INFO
|
return INFO
|
||||||
case "DEBUG":
|
case "DEBUG":
|
||||||
return DEBUG
|
return DEBUG
|
||||||
|
case "TRACE":
|
||||||
|
return TRACE
|
||||||
default:
|
default:
|
||||||
return CRIT
|
return unset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lvl SyslogLevel) SetAttribute(att ...attribute.KeyValue) trace.SpanStartEventOption {
|
func (lvl SeverityLevel) IntoTraceAttribute() attribute.KeyValue {
|
||||||
|
return attribute.String("level", lvl.String())
|
||||||
att = append(att, attribute.Int("level", int(lvl)))
|
}
|
||||||
|
|
||||||
return trace.WithAttributes(
|
func (lvl SeverityLevel) IntoSyslogLevel() syslog.SyslogLevel {
|
||||||
att...,
|
switch lvl {
|
||||||
)
|
case ALERT:
|
||||||
|
return syslog.ALERT
|
||||||
|
case CRIT:
|
||||||
|
return syslog.CRIT
|
||||||
|
case ERR:
|
||||||
|
return syslog.ERR
|
||||||
|
case WARN:
|
||||||
|
return syslog.WARNING
|
||||||
|
case INFO:
|
||||||
|
return syslog.INFO
|
||||||
|
case DEBUG:
|
||||||
|
return syslog.DEBUG
|
||||||
|
case TRACE:
|
||||||
|
return syslog.DEBUG
|
||||||
|
default:
|
||||||
|
return syslog.EMERG
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
84
pkg/syslog/syslog.go
Normal file
84
pkg/syslog/syslog.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package syslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SyslogLevel uint8
|
||||||
|
|
||||||
|
type IntoSyslogLevel interface {
|
||||||
|
IntoSyslogLevel() SyslogLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// System is unusable
|
||||||
|
EMERG SyslogLevel = iota
|
||||||
|
// Immediate action is needed
|
||||||
|
ALERT
|
||||||
|
// Critical condition exists
|
||||||
|
CRIT
|
||||||
|
// An error condition has occured
|
||||||
|
ERR
|
||||||
|
// A suspicious behaviour has been observed
|
||||||
|
WARNING
|
||||||
|
// Significant but acceptable event has occured
|
||||||
|
NOTICE
|
||||||
|
// Informational details
|
||||||
|
INFO
|
||||||
|
// Data useful during debugging
|
||||||
|
DEBUG
|
||||||
|
)
|
||||||
|
|
||||||
|
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) IntoTraceAttribute() attribute.KeyValue {
|
||||||
|
return attribute.String("level", lvl.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lvl SyslogLevel) IntoSyslogLevel() SyslogLevel {
|
||||||
|
return lvl
|
||||||
|
}
|
@@ -1,32 +0,0 @@
|
|||||||
package tracer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"git.ma-al.com/gora_filip/observer/pkg/level"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
|
||||||
"go.opentelemetry.io/otel/codes"
|
|
||||||
"go.opentelemetry.io/otel/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LongMessage(message string) attribute.KeyValue {
|
|
||||||
return attribute.KeyValue{Key: "long_message_", Value: attribute.StringValue(message)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Level(level level.SyslogLevel) attribute.KeyValue {
|
|
||||||
return attribute.KeyValue{Key: "level_", Value: attribute.Int64Value(int64(level))}
|
|
||||||
}
|
|
||||||
|
|
||||||
func JsonAttr(key string, jsonEl map[string]interface{}) attribute.KeyValue {
|
|
||||||
|
|
||||||
jsonStr, _ := json.Marshal(jsonEl)
|
|
||||||
return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(string(jsonStr))}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func RecordError(span trace.Span, err error) error {
|
|
||||||
span.SetStatus(codes.Error, err.Error())
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,202 +0,0 @@
|
|||||||
package tracer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
gelfexporter "git.ma-al.com/gora_filip/observer/pkg/gelf_exporter"
|
|
||||||
"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),
|
|
||||||
gelfexporter.WithAppName("salego"),
|
|
||||||
)
|
|
||||||
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) {
|
|
||||||
spanName := fmt.Sprint(fc.OriginalURL())
|
|
||||||
simpleCtx, span := fiberOpentelemetry.Tracer.Start(fc.UserContext(), spanName)
|
|
||||||
|
|
||||||
fc.SetUserContext(simpleCtx)
|
|
||||||
|
|
||||||
_, file, line, _ := runtime.Caller(1)
|
|
||||||
span.SetAttributes(
|
|
||||||
attribute.String("service.layer", "handler"),
|
|
||||||
attribute.String("file", file),
|
|
||||||
attribute.String("line", fmt.Sprintf("%d", line)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return simpleCtx, span
|
|
||||||
}
|
|
||||||
|
|
||||||
func Service(c context.Context, spanName string) (context.Context, trace.Span) {
|
|
||||||
simpleCtx, span := fiberOpentelemetry.Tracer.Start(c, spanName)
|
|
||||||
var attribs []attribute.KeyValue
|
|
||||||
|
|
||||||
_, file, line, _ := runtime.Caller(1)
|
|
||||||
attribs = append(
|
|
||||||
attribs,
|
|
||||||
attribute.String("service.layer", "service"),
|
|
||||||
attribute.String("file", file),
|
|
||||||
attribute.String("line", fmt.Sprintf("%d", line)),
|
|
||||||
)
|
|
||||||
|
|
||||||
span.SetAttributes(attribs...)
|
|
||||||
|
|
||||||
return simpleCtx, span
|
|
||||||
}
|
|
||||||
|
|
||||||
func Repository(c context.Context, spanName string) (context.Context, trace.Span) {
|
|
||||||
ctx2, span := fiberOpentelemetry.Tracer.Start(c, spanName)
|
|
||||||
var attribs []attribute.KeyValue
|
|
||||||
|
|
||||||
_, file, line, _ := runtime.Caller(1)
|
|
||||||
attribs = append(attribs,
|
|
||||||
attribute.String("file", file),
|
|
||||||
attribute.String("line", fmt.Sprintf("%d", line)),
|
|
||||||
)
|
|
||||||
span.SetAttributes(attribs...)
|
|
||||||
|
|
||||||
return ctx2, span
|
|
||||||
}
|
|
Reference in New Issue
Block a user