First merge from develop to main (WIP) #7
@@ -3,6 +3,7 @@ server:
|
|||||||
address: :8000
|
address: :8000
|
||||||
external_url: https://example.com
|
external_url: https://example.com
|
||||||
debug_mode: false
|
debug_mode: false
|
||||||
|
log_level: debug
|
||||||
database:
|
database:
|
||||||
type: postgres
|
type: postgres
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
@@ -38,4 +39,4 @@ kyc:
|
|||||||
ali_access_key_id: example
|
ali_access_key_id: example
|
||||||
ali_access_key_secret: example
|
ali_access_key_secret: example
|
||||||
tracer:
|
tracer:
|
||||||
otel_controller_endpoint:
|
otel_controller_endpoint: localhost:4317
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -29,11 +29,9 @@ func Init() {
|
|||||||
conf := &config{}
|
conf := &config{}
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
// Dont generate config when using dev mode
|
// Dont generate config when using dev mode
|
||||||
slog.Error("[Config] Can't read config!", "err", err)
|
log.Fatalln("[Config] Can't read config!")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if err := viper.Unmarshal(conf); err != nil {
|
if err := viper.Unmarshal(conf); err != nil {
|
||||||
slog.Error("[Condig] Can't unmarshal config!", "err", err)
|
log.Fatalln("[Condig] Can't unmarshal config!")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ type server struct {
|
|||||||
Address string `yaml:"address"`
|
Address string `yaml:"address"`
|
||||||
ExternalUrl string `yaml:"external_url"`
|
ExternalUrl string `yaml:"external_url"`
|
||||||
DebugMode string `yaml:"debug_mode"`
|
DebugMode string `yaml:"debug_mode"`
|
||||||
|
LogLevel string `yaml:"log_level"`
|
||||||
|
ServiceName string `yaml:"service_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type database struct {
|
type database struct {
|
||||||
@@ -25,6 +27,7 @@ type database struct {
|
|||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
|
ServiceName string `yaml:"service_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cache struct {
|
type cache struct {
|
||||||
@@ -33,11 +36,13 @@ type cache struct {
|
|||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
Password string `yaml:"passowrd"`
|
Password string `yaml:"passowrd"`
|
||||||
DB int `yaml:"db"`
|
DB int `yaml:"db"`
|
||||||
|
ServiceName string `yaml:"service_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type search struct {
|
type search struct {
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
ApiKey string `yaml:"api_key"`
|
ApiKey string `yaml:"api_key"`
|
||||||
|
ServiceName string `yaml:"service_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type email struct {
|
type email struct {
|
||||||
|
|||||||
11
data/data.go
11
data/data.go
@@ -1,6 +1,7 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"nixcn-cms/data/drivers"
|
"nixcn-cms/data/drivers"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ var Database *gorm.DB
|
|||||||
var Redis redis.UniversalClient
|
var Redis redis.UniversalClient
|
||||||
var MeiliSearch meilisearch.ServiceManager
|
var MeiliSearch meilisearch.ServiceManager
|
||||||
|
|
||||||
func Init() {
|
func Init(ctx context.Context) {
|
||||||
// Init database
|
// Init database
|
||||||
dbType := viper.GetString("database.type")
|
dbType := viper.GetString("database.type")
|
||||||
exDSN := drivers.ExternalDSN{
|
exDSN := drivers.ExternalDSN{
|
||||||
@@ -27,21 +28,21 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if dbType != "postgres" {
|
if dbType != "postgres" {
|
||||||
slog.Error("[Database] Only support postgras db!")
|
slog.ErrorContext(ctx, "[Database] Only support postgras db!")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conect to db
|
// Conect to db
|
||||||
db, err := drivers.Postgres(exDSN)
|
db, err := drivers.Postgres(exDSN)
|
||||||
if err != nil {
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto migrate
|
// Auto migrate
|
||||||
err = db.AutoMigrate(&User{}, &Event{}, &Attendance{}, &Client{})
|
err = db.AutoMigrate(&User{}, &Event{}, &Attendance{}, &Client{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("[Database] Error migrating database!", "err", err)
|
slog.ErrorContext(ctx, "[Database] Error migrating database!", "err", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
Database = db
|
Database = db
|
||||||
@@ -57,7 +58,7 @@ func Init() {
|
|||||||
}
|
}
|
||||||
rdb, err := drivers.Redis(rDSN)
|
rdb, err := drivers.Redis(rDSN)
|
||||||
if err != nil {
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
Redis = rdb
|
Redis = rdb
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
package drivers
|
package drivers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/meilisearch/meilisearch-go"
|
"github.com/meilisearch/meilisearch-go"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MeiliSearch(dsn MeiliDSN) meilisearch.ServiceManager {
|
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{
|
httpClient := &http.Client{
|
||||||
Transport: otelTransport,
|
Transport: otelTransport,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"nixcn-cms/logger"
|
"nixcn-cms/logger"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/plugin/opentelemetry/tracing"
|
"gorm.io/plugin/opentelemetry/tracing"
|
||||||
@@ -20,13 +22,28 @@ func SplitHostPort(url string) (host, port string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Postgres(dsn ExternalDSN) (*gorm.DB, error) {
|
func Postgres(dsn ExternalDSN) (*gorm.DB, error) {
|
||||||
|
serviceName := viper.GetString("database.service_name")
|
||||||
|
|
||||||
host, port := SplitHostPort(dsn.Host)
|
host, port := SplitHostPort(dsn.Host)
|
||||||
conn := "host=" + host + " user=" + dsn.Username + " password=" + dsn.Password + " dbname=" + dsn.Name + " port=" + port + " sslmode=disable TimeZone=" + config.TZ()
|
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
|
db, err := gorm.Open(postgres.Open(conn), &gorm.Config{
|
||||||
if err := db.Use(tracing.NewPlugin()); err != nil {
|
Logger: logger.GormLogger(),
|
||||||
slog.Error("[Database] Error starting otel plugin!", "err", err)
|
})
|
||||||
|
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
|
return db, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,14 @@ import (
|
|||||||
|
|
||||||
"github.com/redis/go-redis/extra/redisotel/v9"
|
"github.com/redis/go-redis/extra/redisotel/v9"
|
||||||
"github.com/redis/go-redis/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) {
|
func Redis(dsn RedisDSN) (redis.UniversalClient, error) {
|
||||||
|
serviceName := viper.GetString("cache.service_name")
|
||||||
|
|
||||||
// Connect to Redis
|
// Connect to Redis
|
||||||
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
|
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
|
||||||
Addrs: dsn.Hosts,
|
Addrs: dsn.Hosts,
|
||||||
@@ -18,17 +23,22 @@ func Redis(dsn RedisDSN) (redis.UniversalClient, error) {
|
|||||||
DB: dsn.DB,
|
DB: dsn.DB,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := redisotel.InstrumentMetrics(rdb); err != nil {
|
attrs := []attribute.KeyValue{
|
||||||
slog.Error("[Redis] Error starting otel metrics plugin!", "err", err)
|
semconv.DBSystemRedis,
|
||||||
|
attribute.String("db.instance", serviceName),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := redisotel.InstrumentTracing(rdb); err != nil {
|
if err := redisotel.InstrumentMetrics(rdb, redisotel.WithAttributes(attrs...)); err != nil {
|
||||||
slog.Error("[Redis] Error starting otel tracing plugin!", "err", err)
|
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()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Ping Redis
|
|
||||||
_, err := rdb.Ping(ctx).Result()
|
_, err := rdb.Ping(ctx).Result()
|
||||||
|
|
||||||
return rdb, err
|
return rdb, err
|
||||||
}
|
}
|
||||||
|
|||||||
10
devenv.nix
10
devenv.nix
@@ -10,6 +10,7 @@
|
|||||||
just
|
just
|
||||||
watchexec
|
watchexec
|
||||||
fvm
|
fvm
|
||||||
|
podman
|
||||||
];
|
];
|
||||||
|
|
||||||
dotenv = {
|
dotenv = {
|
||||||
@@ -29,12 +30,21 @@
|
|||||||
javascript.corepack.enable = true;
|
javascript.corepack.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
env.PODMAN_COMPOSE_PROVIDER = "none";
|
||||||
|
|
||||||
processes = {
|
processes = {
|
||||||
client-cms = {
|
client-cms = {
|
||||||
exec = "pnpm run dev";
|
exec = "pnpm run dev";
|
||||||
cwd = "./client/cms";
|
cwd = "./client/cms";
|
||||||
};
|
};
|
||||||
backend.exec = "just watch-back";
|
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 = {
|
services = {
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -17,11 +17,17 @@ require (
|
|||||||
github.com/redis/go-redis/extra/redisotel/v9 v9.17.2
|
github.com/redis/go-redis/extra/redisotel/v9 v9.17.2
|
||||||
github.com/redis/go-redis/v9 v9.17.2
|
github.com/redis/go-redis/v9 v9.17.2
|
||||||
github.com/spf13/viper v1.21.0
|
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/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 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/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 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
|
go.opentelemetry.io/otel/trace v1.39.0
|
||||||
golang.org/x/crypto v0.46.0
|
golang.org/x/crypto v0.46.0
|
||||||
golang.org/x/text v0.33.0
|
golang.org/x/text v0.33.0
|
||||||
|
|||||||
16
go.sum
16
go.sum
@@ -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.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 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
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 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/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.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||||
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/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 h1:PI7pt9pkSnimWcp5sQhUA9OzLbc3Ba4sL+VEUTNsxrk=
|
||||||
go.opentelemetry.io/contrib/propagators/b3 v1.39.0/go.mod h1:5gV/EzPnfYIwjzj+6y8tbGW2PKWhcsz5e/7twptRVQY=
|
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 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
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 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 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 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/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 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U=
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g=
|
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 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
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 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
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 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
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=
|
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||||
|
|||||||
2
justfile
2
justfile
@@ -48,4 +48,4 @@ dev-client-cms: install-cms
|
|||||||
devenv up client-cms --verbose
|
devenv up client-cms --verbose
|
||||||
|
|
||||||
dev-back: clean install-back gen-back
|
dev-back: clean install-back gen-back
|
||||||
devenv up backend postgres redis meilisearch --verbose
|
devenv up backend postgres redis meilisearch lgtm --verbose
|
||||||
|
|||||||
@@ -7,15 +7,57 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
type otlpHandler struct {
|
type multiHandler struct {
|
||||||
slog.Handler
|
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() {
|
func Init() {
|
||||||
levelStr := strings.ToLower(os.Getenv("LOG_LEVEL"))
|
levelStr := strings.ToLower(viper.GetString("server.log_level"))
|
||||||
var level slog.Level
|
var level slog.Level
|
||||||
switch levelStr {
|
switch levelStr {
|
||||||
case "debug":
|
case "debug":
|
||||||
@@ -34,7 +76,7 @@ func Init() {
|
|||||||
writer = io.MultiWriter(os.Stdout, file)
|
writer = io.MultiWriter(os.Stdout, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &slog.HandlerOptions{
|
localHandler := slog.NewJSONHandler(writer, &slog.HandlerOptions{
|
||||||
Level: level,
|
Level: level,
|
||||||
AddSource: true,
|
AddSource: true,
|
||||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||||
@@ -43,21 +85,13 @@ func Init() {
|
|||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
otelHandler := otelslog.NewHandler(viper.GetString("server.service_name"))
|
||||||
|
|
||||||
|
combinedHandler := &multiHandler{
|
||||||
|
handlers: []slog.Handler{localHandler, otelHandler},
|
||||||
}
|
}
|
||||||
|
|
||||||
baseHandler := slog.NewJSONHandler(writer, opts)
|
slog.SetDefault(slog.New(combinedHandler))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
7
main.go
7
main.go
@@ -12,9 +12,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logger.Init()
|
|
||||||
config.Init()
|
config.Init()
|
||||||
|
|
||||||
|
// OTEL
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
shutdown := tracer.Init(ctx)
|
shutdown := tracer.Init(ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -25,6 +25,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
data.Init()
|
logger.Init()
|
||||||
server.Start()
|
data.Init(ctx)
|
||||||
|
server.Start(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
@@ -24,6 +25,8 @@ func GinLogger() gin.HandlerFunc {
|
|||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
var errorMessage string
|
var errorMessage string
|
||||||
if len(c.Errors) > 0 {
|
if len(c.Errors) > 0 {
|
||||||
errorMessage = c.Errors.String()
|
errorMessage = c.Errors.String()
|
||||||
@@ -43,11 +46,11 @@ func GinLogger() gin.HandlerFunc {
|
|||||||
|
|
||||||
status := c.Writer.Status()
|
status := c.Writer.Status()
|
||||||
if len(c.Errors) > 0 || status >= 500 {
|
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 {
|
} 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 {
|
} else {
|
||||||
slog.Info("HTTP_SUCCESS", fields...)
|
slog.InfoContext(ctx, fmt.Sprintf("%d %s %s", c.Writer.Status(), c.Request.Method, c.Request.RequestURI), fields...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"nixcn-cms/middleware"
|
"nixcn-cms/middleware"
|
||||||
"time"
|
"time"
|
||||||
@@ -11,14 +13,16 @@ import (
|
|||||||
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
|
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start() {
|
func Start(ctx context.Context) {
|
||||||
if !viper.GetBool("server.debug_mode") {
|
if !viper.GetBool("server.debug_mode") {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
gin.DisableConsoleColor()
|
gin.DisableConsoleColor()
|
||||||
}
|
}
|
||||||
|
|
||||||
r := gin.New()
|
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)
|
Router(r)
|
||||||
|
|
||||||
@@ -26,13 +30,14 @@ func Start() {
|
|||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: viper.GetString("server.address"),
|
Addr: viper.GetString("server.address"),
|
||||||
Handler: r,
|
Handler: r,
|
||||||
|
BaseContext: func(net.Listener) context.Context { return ctx },
|
||||||
ReadTimeout: 10 * time.Second,
|
ReadTimeout: 10 * time.Second,
|
||||||
WriteTimeout: 10 * time.Second,
|
WriteTimeout: 10 * time.Second,
|
||||||
MaxHeaderBytes: 1 << 20,
|
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 {
|
if err := server.ListenAndServe(); err != nil {
|
||||||
slog.Error("[Server] Error starting server!", "err", err)
|
slog.ErrorContext(ctx, "[Server] Error starting server!", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,13 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.opentelemetry.io/otel"
|
"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/exporters/otlp/otlptrace/otlptracegrpc"
|
||||||
|
"go.opentelemetry.io/otel/log/global"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"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"
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
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"
|
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,
|
res, err := resource.New(ctx,
|
||||||
resource.WithAttributes(
|
resource.WithAttributes(
|
||||||
semconv.ServiceNameKey.String("nixcn-cms-backend"),
|
semconv.ServiceNameKey.String(viper.GetString("server.service_name")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if err != nil {
|
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(
|
tp := sdktrace.NewTracerProvider(
|
||||||
sdktrace.WithBatcher(exporter),
|
sdktrace.WithBatcher(traceExporter),
|
||||||
sdktrace.WithResource(res),
|
sdktrace.WithResource(res),
|
||||||
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
||||||
)
|
)
|
||||||
|
|
||||||
otel.SetTracerProvider(tp)
|
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(
|
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
|
||||||
propagation.TraceContext{},
|
propagation.TraceContext{},
|
||||||
propagation.Baggage{},
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user