Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
July 13, 2022 07:36 pm GMT

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.

Image description

Crie uma conta e acesse o painel do SigNoz.

Image description

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.

Image description

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.

Image description

Image description

Image description

Image description


Original Link: https://dev.to/booscaaa/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry-447e

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To