An Interest In:
Web News this Week
- April 19, 2024
- April 18, 2024
- April 17, 2024
- April 16, 2024
- April 15, 2024
- April 14, 2024
- April 13, 2024
Golang, observabilidade e mtricas com SigNoz e OpenTelemetry
Quem nunca passou por um aperto com uma api, endpoit, servio ou qualquer coisa em produo e simplesmente no achou o problema ou demorou muito tempo para metrificar e descobrir o gargalo que fazia o sistema cair? , aquela hora do dia que o sistema simplesmente ficava inutilizvel e ningum sabia explicar o motivo? Se voc no passou por isso sempre vai ter a primeira vez... Brincadeiras a parte, hoje veremos como integrar um servio criado com golang com o SigNoz usando OpenTelemetry
Bora l! Primeiro passo ter um servio para metrificar! rsrs... Vamos criar algo muito simples para no perdermos tempo. O foco aqui a integrao com o SigNoz e no uma API completa com Golang.
Aplicao
mkdir go-signoz-otlcd go-signoz-otlgo mod init github.com/booscaaa/go-signoz-otl
Vamos configurar nossa migration de produtos para o exemplo.
migrate create -ext sql -dir database/migrations -seq create_product_table
No nosso arquivo database/migrations/000001_create_product_table.up.sql
CREATE TABLE product( id serial primary key not null, name varchar(100) not null);INSERT INTO product (name) VALUES ('Cadeira'), ('Mesa'), ('Toalha'), ('Fogo'), ('Batedeira'), ('Pia'), ('Torneira'), ('Forno'), ('Gaveta'), ('Copo');
Com a migration em mos, bora criar j de incio nosso conector com o postgres usando a lib sqlx.adapter/postgres/connector.go
package postgresimport ( "context" "log" "github.com/golang-migrate/migrate/v4" "github.com/jmoiron/sqlx" "github.com/spf13/viper" _ "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/source/file" _ "github.com/lib/pq")// GetConnection return connection pool from postgres drive SQLXfunc GetConnection(context context.Context) *sqlx.DB { databaseURL := viper.GetString("database.url") db, err := sqlx.ConnectContext( context, "postgres", databaseURL, ) if err != nil { log.Fatal(err) } return db}// RunMigrations run scripts on path database/migrationsfunc RunMigrations() { databaseURL := viper.GetString("database.url") m, err := migrate.New("file://database/migrations", databaseURL) if err != nil { log.Println(err) } if err := m.Up(); err != nil { log.Println(err) }}
Vamos criar as abstraes e implementaes no nosso dominio/adapters da aplicao.
core/domain/product.go
package domainimport ( "context" "github.com/gin-gonic/gin")// Product is entity of table product database columntype Product struct { ID int32 `json:"id" db:"id"` Name string `json:"name" db:"name"`}// ProductService is a contract of http adapter layertype ProductService interface { Fetch(*gin.Context)}// ProductUseCase is a contract of business rule layertype ProductUseCase interface { Fetch(context.Context) (*[]Product, error)}// ProductRepository is a contract of database connection adapter layertype ProductRepository interface { Fetch(context.Context) (*Product, error)}
core/usecase/productusecase/new.go
package productusecaseimport "github.com/booscaaa/go-signoz-otl/core/domain"type usecase struct { repository domain.ProductRepository}// New returns contract implementation of ProductUseCasefunc New(repository domain.ProductRepository) domain.ProductUseCase { return &usecase{ repository: repository, }}
core/usecase/productusecase/fetch.go
package productusecaseimport ( "context" "github.com/booscaaa/go-signoz-otl/core/domain")func (usecase usecase) Fetch(ctx context.Context) (*[]domain.Product, error) { products, err := usecase.repository.Fetch(ctx) if err != nil { return nil, err } return products, err}
adapter/postgres/productrepository/new.go
package productrepositoryimport ( "github.com/booscaaa/go-signoz-otl/core/domain" "github.com/jmoiron/sqlx")type repository struct { db *sqlx.DB}// New returns contract implementation of ProductRepositoryfunc New(db *sqlx.DB) domain.ProductRepository { return &repository{ db: db, }}
adapter/postgres/productrepository/fetch.go
package productrepositoryimport ( "context" "github.com/booscaaa/go-signoz-otl/core/domain")func (repository repository) Fetch(ctx context.Context) (*[]domain.Product, error) { products := []domain.Product{} err := repository.db.SelectContext(ctx, &products, "SELECT * FROM product;") if err != nil { return nil, err } return &products, nil}
adapter/http/productservice/new.go
package productserviceimport "github.com/booscaaa/go-signoz-otl/core/domain"type service struct { usecase domain.ProductUseCase}// New returns contract implementation of ProductServicefunc New(usecase domain.ProductUseCase) domain.ProductService { return &service{ usecase: usecase, }}
adapter/http/productservice/fetch.go
package productserviceimport ( "net/http" "github.com/gin-gonic/gin")func (service service) Fetch(c *gin.Context) { products, err := service.usecase.Fetch(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, err) return } c.JSON(http.StatusOK, products)}
di/product.go
package diimport ( "github.com/booscaaa/go-signoz-otl/adapter/http/productservice" "github.com/booscaaa/go-signoz-otl/adapter/postgres/productrepository" "github.com/booscaaa/go-signoz-otl/core/domain" "github.com/booscaaa/go-signoz-otl/core/usecase/productusecase" "github.com/jmoiron/sqlx")func ConfigProductDI(conn *sqlx.DB) domain.ProductService { productRepository := productrepository.New(conn) productUsecase := productusecase.New(productRepository) productService := productservice.New(productUsecase) return productService}
adapter/http/main.go
package mainimport ( "context" "github.com/booscaaa/go-signoz-otl/adapter/postgres" "github.com/booscaaa/go-signoz-otl/di" "github.com/gin-gonic/gin" "github.com/spf13/viper")func init() { viper.SetConfigFile(`config.json`) err := viper.ReadInConfig() if err != nil { panic(err) }}func main() { ctx := context.Background() conn := postgres.GetConnection(ctx) defer conn.Close() postgres.RunMigrations() productService := di.ConfigProductDI(conn) router := gin.Default() router.GET("/product", productService.Fetch) router.Run(":3000")}
config.json
{ "database": { "url": "postgres://postgres:postgres@localhost:5432/devtodb" }, "server": { "port": "3000" }, "otl": { "service_name": "devto_goapp", "otel_exporter_otlp_endpoint": "localhost:4317", "insecure_mode": true }}
Por fim basta rodar a aplicao e ver se tudo ficou funcionando certinho!
No primeiro terminal:
go run adapter/http/main.go
No segundo terminal:
curl --location --request GET 'localhost:3000/product'
SigNoz
Com a aplicao pronta, vamos iniciar as devidas implementaes para integrar as mtricas com o SigNoz e ver a magia acontecer!
Primeiro passo ento instalarmos o SigNoz na nossa mquina, para isso usaremos o docker-compose.
git clone -b main https://github.com/SigNoz/signoz.git && cd signoz/deploy/docker-compose -f docker/clickhouse-setup/docker-compose.yaml up -d
Feito isso basta acessar a o endereo localhost:3301 no seu navegador.
Crie uma conta e acesse o painel do SigNoz.
No Dashboard inicial ainda no temos nada que nos interesse, mas fique a vontade para explorar os dados ja existentes da aplicao.
Por fim vamos realizar a integrao e analisar os dados que sero mostrados no SigNoz.
Vamos comear alterando o conector com o banco de dados, criando um wrapper do sqlx com a lib otelsqlx, com isso vamos conseguir captar informaes de queries que sero executadas no banco.
core/postgres/connector.go
package postgresimport ( "context" "log" "github.com/golang-migrate/migrate/v4" "github.com/jmoiron/sqlx" "github.com/spf13/viper" "github.com/uptrace/opentelemetry-go-extra/otelsql" "github.com/uptrace/opentelemetry-go-extra/otelsqlx" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" _ "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/source/file" _ "github.com/lib/pq" sdktrace "go.opentelemetry.io/otel/sdk/trace")// GetConnection return connection pool from postgres drive SQLXfunc GetConnection(context context.Context, provider *sdktrace.TracerProvider) *sqlx.DB { databaseURL := viper.GetString("database.url") db, err := otelsqlx.ConnectContext( context, "postgres", databaseURL, otelsql.WithAttributes(semconv.DBSystemPostgreSQL), otelsql.WithTracerProvider(provider), ) if err != nil { log.Fatal(err) } return db}// RunMigrations run scripts on path database/migrationsfunc RunMigrations() { databaseURL := viper.GetString("database.url") m, err := migrate.New("file://database/migrations", databaseURL) if err != nil { log.Println(err) } if err := m.Up(); err != nil { log.Println(err) }}
Feito isso criaremos o arquivo util/tracer.go
para inicializar a captura das informaes.
package utilimport ( "context" "log" "github.com/spf13/viper" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/sdk/resource" "google.golang.org/grpc/credentials" sdktrace "go.opentelemetry.io/otel/sdk/trace")var ( ServiceName = "" CollectorURL = "" Insecure = false)func InitTracer() *sdktrace.TracerProvider { ServiceName = viper.GetString("otl.service_name") CollectorURL = viper.GetString("otl.otel_exporter_otlp_endpoint") Insecure = viper.GetBool("otl.insecure_mode") secureOption := otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")) if Insecure { secureOption = otlptracegrpc.WithInsecure() } ctx := context.Background() exporter, err := otlptrace.New( ctx, otlptracegrpc.NewClient( secureOption, otlptracegrpc.WithEndpoint(CollectorURL), ), ) if err != nil { log.Fatal(err) } resources, err := resource.New( ctx, resource.WithAttributes( attribute.String("service.name", ServiceName), attribute.String("library.language", "go"), ), ) if err != nil { log.Printf("Could not set resources: %v", err) } provider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), sdktrace.WithResource(resources), ) otel.SetTracerProvider( provider, ) return provider}
E por ltimo, mas no menos importante, vamos configurar o middleware para o gin no arquivo adapter/http/main.go
package mainimport ( "context" "github.com/booscaaa/go-signoz-otl/adapter/postgres" "github.com/booscaaa/go-signoz-otl/di" "github.com/booscaaa/go-signoz-otl/util" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" "github.com/gin-gonic/gin" "github.com/spf13/viper")func init() { viper.SetConfigFile(`config.json`) err := viper.ReadInConfig() if err != nil { panic(err) }}func main() { tracerProvider := util.InitTracer() ctx := context.Background() conn := postgres.GetConnection(ctx, tracerProvider) defer conn.Close() postgres.RunMigrations() productService := di.ConfigProductDI(conn) router := gin.Default() router.Use(otelgin.Middleware(util.ServiceName)) router.GET("/product", productService.Fetch) router.Run(":3000")}
Vamos rodar novamente a aplicao e criar um script para realizar diversas chamadas na api.
No primeiro terminal:
go run adapter/http/main.go
No segundo terminal:
while :do curl --location --request GET 'localhost:3000/product'done
Voltando para o painel do SigNoz basta esperar a aplicao aparecer no dashboard.
Clicando no app que acabou de aparecer j conseguimos analisar dados muito importantes como:
- Media de tempo de cada request.
- Quantidade de requests por segundo.
- Qual o endpoint mais acessado da aplicao.
- Porcentagem de erros que ocorreram.
E ao clicar em uma request que por ventura demorou muito para retornar ou deu erro, chegaremos a uma nova tela onde possivel analisar o tempo interno de cada camada, alm de ver exatamente a query que pode estar causando problemas na aplicao.
Original Link: https://dev.to/booscaaa/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry-447e
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To