First merge from develop to main (WIP) #7

Merged
sugar merged 199 commits from develop into main 2026-01-27 17:47:07 +00:00
16 changed files with 229 additions and 84 deletions
Showing only changes of commit 5f6eb9f2a2 - Show all commits

View File

@@ -3,6 +3,7 @@ server:
address: :8000
external_url: https://example.com
debug_mode: false
log_level: debug
database:
type: postgres
host: 127.0.0.1
@@ -38,4 +39,4 @@ kyc:
ali_access_key_id: example
ali_access_key_secret: example
tracer:
otel_controller_endpoint:
otel_controller_endpoint: localhost:4317

View File

@@ -1,7 +1,7 @@
package config
import (
"log/slog"
"log"
"os"
"strings"
@@ -29,11 +29,9 @@ func Init() {
conf := &config{}
if err := viper.ReadInConfig(); err != nil {
// Dont generate config when using dev mode
slog.Error("[Config] Can't read config!", "err", err)
os.Exit(1)
log.Fatalln("[Config] Can't read config!")
}
if err := viper.Unmarshal(conf); err != nil {
slog.Error("[Condig] Can't unmarshal config!", "err", err)
os.Exit(1)
log.Fatalln("[Condig] Can't unmarshal config!")
}
}

View File

@@ -17,6 +17,8 @@ type server struct {
Address string `yaml:"address"`
ExternalUrl string `yaml:"external_url"`
DebugMode string `yaml:"debug_mode"`
LogLevel string `yaml:"log_level"`
ServiceName string `yaml:"service_name"`
}
type database struct {
@@ -25,6 +27,7 @@ type database struct {
Name string `yaml:"name"`
Username string `yaml:"username"`
Password string `yaml:"password"`
ServiceName string `yaml:"service_name"`
}
type cache struct {
@@ -33,11 +36,13 @@ type cache struct {
Username string `yaml:"username"`
Password string `yaml:"passowrd"`
DB int `yaml:"db"`
ServiceName string `yaml:"service_name"`
}
type search struct {
Host string `yaml:"host"`
ApiKey string `yaml:"api_key"`
ServiceName string `yaml:"service_name"`
}
type email struct {

View File

@@ -1,6 +1,7 @@
package data
import (
"context"
"nixcn-cms/data/drivers"
"os"
@@ -16,7 +17,7 @@ var Database *gorm.DB
var Redis redis.UniversalClient
var MeiliSearch meilisearch.ServiceManager
func Init() {
func Init(ctx context.Context) {
// Init database
dbType := viper.GetString("database.type")
exDSN := drivers.ExternalDSN{
@@ -27,21 +28,21 @@ func Init() {
}
if dbType != "postgres" {
slog.Error("[Database] Only support postgras db!")
slog.ErrorContext(ctx, "[Database] Only support postgras db!")
os.Exit(1)
}
// Conect to db
db, err := drivers.Postgres(exDSN)
if err != nil {
slog.Error("[Database] Error connecting to db!", "err", err)
slog.ErrorContext(ctx, "[Database] Error connecting to db!", "err", err)
os.Exit(1)
}
// Auto migrate
err = db.AutoMigrate(&User{}, &Event{}, &Attendance{}, &Client{})
if err != nil {
slog.Error("[Database] Error migrating database!", "err", err)
slog.ErrorContext(ctx, "[Database] Error migrating database!", "err", err)
os.Exit(1)
}
Database = db
@@ -57,7 +58,7 @@ func Init() {
}
rdb, err := drivers.Redis(rDSN)
if err != nil {
slog.Error("[Redis] Error connecting to Redis!", "err", err)
slog.ErrorContext(ctx, "[Redis] Error connecting to Redis!", "err", err)
os.Exit(1)
}
Redis = rdb

View File

@@ -1,14 +1,23 @@
package drivers
import (
"fmt"
"net/http"
"github.com/meilisearch/meilisearch-go"
"github.com/spf13/viper"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func MeiliSearch(dsn MeiliDSN) meilisearch.ServiceManager {
otelTransport := otelhttp.NewTransport(http.DefaultTransport)
serviceName := viper.GetString("search.service_name")
otelTransport := otelhttp.NewTransport(
http.DefaultTransport,
otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
return fmt.Sprintf("%s %s", serviceName, r.Method)
}),
)
httpClient := &http.Client{
Transport: otelTransport,

View File

@@ -6,6 +6,8 @@ import (
"nixcn-cms/logger"
"strings"
"github.com/spf13/viper"
"go.opentelemetry.io/otel/attribute"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/plugin/opentelemetry/tracing"
@@ -20,13 +22,28 @@ func SplitHostPort(url string) (host, port string) {
}
func Postgres(dsn ExternalDSN) (*gorm.DB, error) {
serviceName := viper.GetString("database.service_name")
host, port := SplitHostPort(dsn.Host)
conn := "host=" + host + " user=" + dsn.Username + " password=" + dsn.Password + " dbname=" + dsn.Name + " port=" + port + " sslmode=disable TimeZone=" + config.TZ()
db, err := gorm.Open(postgres.Open(conn), &gorm.Config{Logger: logger.GormLogger()})
// Use otel gorm plugin
if err := db.Use(tracing.NewPlugin()); err != nil {
slog.Error("[Database] Error starting otel plugin!", "err", err)
db, err := gorm.Open(postgres.Open(conn), &gorm.Config{
Logger: logger.GormLogger(),
})
if err != nil {
return nil, err
}
err = db.Use(tracing.NewPlugin(
tracing.WithAttributes(
attribute.String("peer.service", serviceName),
attribute.String("db.instance", dsn.Name),
),
))
if err != nil {
slog.Error("[Database] Error starting otel plugin!", "name", serviceName, "err", err)
}
return db, err
}

View File

@@ -6,9 +6,14 @@ import (
"github.com/redis/go-redis/extra/redisotel/v9"
"github.com/redis/go-redis/v9"
"github.com/spf13/viper"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)
func Redis(dsn RedisDSN) (redis.UniversalClient, error) {
serviceName := viper.GetString("cache.service_name")
// Connect to Redis
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
Addrs: dsn.Hosts,
@@ -18,17 +23,22 @@ func Redis(dsn RedisDSN) (redis.UniversalClient, error) {
DB: dsn.DB,
})
if err := redisotel.InstrumentMetrics(rdb); err != nil {
slog.Error("[Redis] Error starting otel metrics plugin!", "err", err)
attrs := []attribute.KeyValue{
semconv.DBSystemRedis,
attribute.String("db.instance", serviceName),
}
if err := redisotel.InstrumentTracing(rdb); err != nil {
slog.Error("[Redis] Error starting otel tracing plugin!", "err", err)
if err := redisotel.InstrumentMetrics(rdb, redisotel.WithAttributes(attrs...)); err != nil {
slog.Error("[Redis] Error starting otel metrics plugin!", "name", serviceName, "err", err)
}
if err := redisotel.InstrumentTracing(rdb, redisotel.WithAttributes(attrs...)); err != nil {
slog.Error("[Redis] Error starting otel tracing plugin!", "name", serviceName, "err", err)
}
// Ping redis
ctx := context.Background()
// Ping Redis
_, err := rdb.Ping(ctx).Result()
return rdb, err
}

View File

@@ -10,6 +10,7 @@
just
watchexec
fvm
podman
];
dotenv = {
@@ -29,12 +30,21 @@
javascript.corepack.enable = true;
};
env.PODMAN_COMPOSE_PROVIDER = "none";
processes = {
client-cms = {
exec = "pnpm run dev";
cwd = "./client/cms";
};
backend.exec = "just watch-back";
lgtm.exec = ''
podman rm -f lgtm || true
podman run --name lgtm \
-p 3000:3000 -p 4317:4317 -p 4318:4318 \
-e OTEL_METRIC_EXPORT_INTERVAL=5000 \
docker.io/grafana/otel-lgtm:latest
'';
};
services = {

8
go.mod
View File

@@ -17,11 +17,17 @@ require (
github.com/redis/go-redis/extra/redisotel/v9 v9.17.2
github.com/redis/go-redis/v9 v9.17.2
github.com/spf13/viper v1.21.0
go.opentelemetry.io/contrib/bridges/otelslog v0.14.0
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.64.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0
go.opentelemetry.io/otel/log v0.15.0
go.opentelemetry.io/otel/sdk v1.39.0
go.opentelemetry.io/otel/sdk/log v0.15.0
go.opentelemetry.io/otel/sdk/metric v1.39.0
go.opentelemetry.io/otel/trace v1.39.0
golang.org/x/crypto v0.46.0
golang.org/x/text v0.33.0

16
go.sum
View File

@@ -294,24 +294,36 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/otelslog v0.14.0 h1:eypSOd+0txRKCXPNyqLPsbSfA0jULgJcGmSAdFAnrCM=
go.opentelemetry.io/contrib/bridges/otelslog v0.14.0/go.mod h1:CRGvIBL/aAxpQU34ZxyQVFlovVcp67s4cAmQu8Jh9mc=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.64.0 h1:7IKZbAYwlwLXAdu7SVPhzTjDjogWZxP4MIa7rovY+PU=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.64.0/go.mod h1:+TF5nf3NIv2X8PGxqfYOaRnAoMM43rUA2C3XsN2DoWA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/contrib/propagators/b3 v1.39.0 h1:PI7pt9pkSnimWcp5sQhUA9OzLbc3Ba4sL+VEUTNsxrk=
go.opentelemetry.io/contrib/propagators/b3 v1.39.0/go.mod h1:5gV/EzPnfYIwjzj+6y8tbGW2PKWhcsz5e/7twptRVQY=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v3pa5PgVf2xoFMi5YtNR06WtS7ve5pcvLtM=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0/go.mod h1:JM31r0GGZ/GU94mX8hN4D8v6e40aFlUECSQ48HaLgHM=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g=
go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY=
go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE=
go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=

View File

@@ -48,4 +48,4 @@ dev-client-cms: install-cms
devenv up client-cms --verbose
dev-back: clean install-back gen-back
devenv up backend postgres redis meilisearch --verbose
devenv up backend postgres redis meilisearch lgtm --verbose

View File

@@ -7,15 +7,57 @@ import (
"os"
"strings"
"github.com/spf13/viper"
"go.opentelemetry.io/contrib/bridges/otelslog"
"go.opentelemetry.io/otel/trace"
)
type otlpHandler struct {
slog.Handler
type multiHandler struct {
handlers []slog.Handler
}
func (m *multiHandler) Enabled(ctx context.Context, l slog.Level) bool {
for _, h := range m.handlers {
if h.Enabled(ctx, l) {
return true
}
}
return false
}
func (m *multiHandler) Handle(ctx context.Context, r slog.Record) error {
span := trace.SpanFromContext(ctx)
if span.SpanContext().HasTraceID() {
r.AddAttrs(
slog.String("trace_id", span.SpanContext().TraceID().String()),
slog.String("span_id", span.SpanContext().SpanID().String()),
)
}
for _, h := range m.handlers {
_ = h.Handle(ctx, r)
}
return nil
}
func (m *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
newHandlers := make([]slog.Handler, len(m.handlers))
for i, h := range m.handlers {
newHandlers[i] = h.WithAttrs(attrs)
}
return &multiHandler{handlers: newHandlers}
}
func (m *multiHandler) WithGroup(name string) slog.Handler {
newHandlers := make([]slog.Handler, len(m.handlers))
for i, h := range m.handlers {
newHandlers[i] = h.WithGroup(name)
}
return &multiHandler{handlers: newHandlers}
}
func Init() {
levelStr := strings.ToLower(os.Getenv("LOG_LEVEL"))
levelStr := strings.ToLower(viper.GetString("server.log_level"))
var level slog.Level
switch levelStr {
case "debug":
@@ -34,7 +76,7 @@ func Init() {
writer = io.MultiWriter(os.Stdout, file)
}
opts := &slog.HandlerOptions{
localHandler := slog.NewJSONHandler(writer, &slog.HandlerOptions{
Level: level,
AddSource: true,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
@@ -43,21 +85,13 @@ func Init() {
}
return a
},
})
otelHandler := otelslog.NewHandler(viper.GetString("server.service_name"))
combinedHandler := &multiHandler{
handlers: []slog.Handler{localHandler, otelHandler},
}
baseHandler := slog.NewJSONHandler(writer, opts)
handler := &otlpHandler{baseHandler}
logger := slog.New(handler)
slog.SetDefault(logger)
}
func (h *otlpHandler) Handle(ctx context.Context, r slog.Record) error {
span := trace.SpanFromContext(ctx)
if span.SpanContext().HasTraceID() {
r.AddAttrs(
slog.String("trace_id", span.SpanContext().TraceID().String()),
slog.String("span_id", span.SpanContext().SpanID().String()),
)
}
return h.Handler.Handle(ctx, r)
slog.SetDefault(slog.New(combinedHandler))
}

View File

@@ -12,9 +12,9 @@ import (
)
func main() {
logger.Init()
config.Init()
// OTEL
ctx := context.Background()
shutdown := tracer.Init(ctx)
defer func() {
@@ -25,6 +25,7 @@ func main() {
}
}()
data.Init()
server.Start()
logger.Init()
data.Init(ctx)
server.Start(ctx)
}

View File

@@ -3,6 +3,7 @@ package middleware
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log/slog"
"time"
@@ -24,6 +25,8 @@ func GinLogger() gin.HandlerFunc {
c.Next()
ctx := c.Request.Context()
var errorMessage string
if len(c.Errors) > 0 {
errorMessage = c.Errors.String()
@@ -43,11 +46,11 @@ func GinLogger() gin.HandlerFunc {
status := c.Writer.Status()
if len(c.Errors) > 0 || status >= 500 {
slog.Error("HTTP_ERROR", fields...)
slog.ErrorContext(ctx, fmt.Sprintf("%d %s %s", c.Writer.Status(), c.Request.Method, c.Request.RequestURI), fields...)
} else if status >= 400 {
slog.Warn("HTTP_CLIENT_ERROR", fields...)
slog.WarnContext(ctx, fmt.Sprintf("%d %s %s", c.Writer.Status(), c.Request.Method, c.Request.RequestURI), fields...)
} else {
slog.Info("HTTP_SUCCESS", fields...)
slog.InfoContext(ctx, fmt.Sprintf("%d %s %s", c.Writer.Status(), c.Request.Method, c.Request.RequestURI), fields...)
}
}
}

View File

@@ -1,7 +1,9 @@
package server
import (
"context"
"log/slog"
"net"
"net/http"
"nixcn-cms/middleware"
"time"
@@ -11,14 +13,16 @@ import (
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
)
func Start() {
func Start(ctx context.Context) {
if !viper.GetBool("server.debug_mode") {
gin.SetMode(gin.ReleaseMode)
gin.DisableConsoleColor()
}
r := gin.New()
r.Use(gin.Recovery(), middleware.GinLogger(), otelgin.Middleware("nixcn-cms-backend"))
r.Use(otelgin.Middleware(viper.GetString("server.service_name")))
r.Use(middleware.GinLogger())
r.Use(gin.Recovery())
Router(r)
@@ -26,13 +30,14 @@ func Start() {
server := &http.Server{
Addr: viper.GetString("server.address"),
Handler: r,
BaseContext: func(net.Listener) context.Context { return ctx },
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
slog.Info("[Server] Starting server on " + viper.GetString("server.address"))
slog.InfoContext(ctx, "[Server] Starting server on "+viper.GetString("server.address"))
if err := server.ListenAndServe(); err != nil {
slog.Error("[Server] Error starting server!", "err", err)
slog.ErrorContext(ctx, "[Server] Error starting server!", "err", err)
}
}

View File

@@ -7,8 +7,13 @@ import (
"github.com/spf13/viper"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/log/global"
"go.opentelemetry.io/otel/propagation"
sdklog "go.opentelemetry.io/otel/sdk/log"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
@@ -20,39 +25,67 @@ func Init(ctx context.Context) func(context.Context) error {
endpoint = "localhost:4317"
}
clientCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
exporter, err := otlptracegrpc.New(clientCtx,
otlptracegrpc.WithEndpoint(endpoint),
otlptracegrpc.WithInsecure(),
)
if err != nil {
slog.Error("[Tracer] Failed to create trace exporter", "err", err)
return func(context.Context) error { return nil }
}
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceNameKey.String("nixcn-cms-backend"),
semconv.ServiceNameKey.String(viper.GetString("server.service_name")),
),
)
if err != nil {
slog.Error("[Tracer] Failed to create resource", "err", err)
slog.Error("[OTEL] Failed to create resource", "err", err)
}
traceExporter, _ := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint(endpoint),
otlptracegrpc.WithInsecure(),
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithBatcher(traceExporter),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.AlwaysSample()),
)
otel.SetTracerProvider(tp)
metricExporter, _ := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithEndpoint(endpoint),
otlpmetricgrpc.WithInsecure(),
)
mp := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter,
sdkmetric.WithInterval(5*time.Second))),
)
otel.SetMeterProvider(mp)
logExporter, _ := otlploggrpc.New(ctx,
otlploggrpc.WithEndpoint(endpoint),
otlploggrpc.WithInsecure(),
)
lp := sdklog.NewLoggerProvider(
sdklog.WithResource(res),
sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)),
)
global.SetLoggerProvider(lp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
return tp.Shutdown
return func(shutCtx context.Context) error {
var errs []error
if err := tp.Shutdown(shutCtx); err != nil {
errs = append(errs, err)
}
if err := mp.Shutdown(shutCtx); err != nil {
errs = append(errs, err)
}
if err := lp.Shutdown(shutCtx); err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {
return errs[0]
}
return nil
}
}