Golang, observabilidade e métricas com SigNoz e OpenTelemetry

Quem nunca passou por um aperto com uma api, endpoit, serviço ou qualquer coisa em produção e simplesmente não 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 s…


This content originally appeared on DEV Community and was authored by Vinícius Boscardin

Quem nunca passou por um aperto com uma api, endpoit, serviço ou qualquer coisa em produção e simplesmente não 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 inutilizável e ninguém sabia explicar o motivo? Se você não passou por isso sempre vai ter a primeira vez... Brincadeiras a parte, hoje veremos como integrar um serviço criado com golang com o SigNoz usando OpenTelemetry

Bora lá! Primeiro passo é ter um serviço para metrificar! rsrs... Vamos criar algo muito simples para não perdermos tempo. O foco aqui é a integração com o SigNoz e não uma API completa com Golang.

Aplicação

mkdir go-signoz-otl
cd go-signoz-otl
go 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'),
    ('Fogão'),
    ('Batedeira'),
    ('Pia'),
    ('Torneira'),
    ('Forno'),
    ('Gaveta'),
    ('Copo');

Com a migration em mãos, bora criar já de início nosso conector com o postgres usando a lib sqlx.
adapter/postgres/connector.go

package postgres

import (
    "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 SQLX
func 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/migrations
func 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 abstrações e implementações no nosso dominio/adapters da aplicação.

core/domain/product.go

package domain

import (
    "context"

    "github.com/gin-gonic/gin"
)

// Product is entity of table product database column
type Product struct {
    ID   int32  `json:"id" db:"id"`
    Name string `json:"name" db:"name"`
}

// ProductService is a contract of http adapter layer
type ProductService interface {
    Fetch(*gin.Context)
}

// ProductUseCase is a contract of business rule layer
type ProductUseCase interface {
    Fetch(context.Context) (*[]Product, error)
}

// ProductRepository is a contract of database connection adapter layer
type ProductRepository interface {
    Fetch(context.Context) (*Product, error)
}

core/usecase/productusecase/new.go

package productusecase

import "github.com/booscaaa/go-signoz-otl/core/domain"

type usecase struct {
    repository domain.ProductRepository
}

// New returns contract implementation of ProductUseCase
func New(repository domain.ProductRepository) domain.ProductUseCase {
    return &usecase{
        repository: repository,
    }
}

core/usecase/productusecase/fetch.go

package productusecase

import (
    "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 productrepository

import (
    "github.com/booscaaa/go-signoz-otl/core/domain"
    "github.com/jmoiron/sqlx"
)

type repository struct {
    db *sqlx.DB
}

// New returns contract implementation of ProductRepository
func New(db *sqlx.DB) domain.ProductRepository {
    return &repository{
        db: db,
    }
}

adapter/postgres/productrepository/fetch.go

package productrepository

import (
    "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 productservice

import "github.com/booscaaa/go-signoz-otl/core/domain"

type service struct {
    usecase domain.ProductUseCase
}

// New returns contract implementation of ProductService
func New(usecase domain.ProductUseCase) domain.ProductService {
    return &service{
        usecase: usecase,
    }
}

adapter/http/productservice/fetch.go

package productservice

import (
    "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 di

import (
    "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 main

import (
    "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 aplicação 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 aplicação pronta, vamos iniciar as devidas implementações para integrar as métricas com o SigNoz e ver a magia acontecer!
Primeiro passo então é instalarmos o SigNoz na nossa máquina, 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 endereço localhost:3301 no seu navegador.

Image description

Crie uma conta e acesse o painel do SigNoz.

Image description

No Dashboard inicial ainda não temos nada que nos interesse, mas fique a vontade para explorar os dados ja existentes da aplicação.

Por fim vamos realizar a integração e analisar os dados que serão mostrados no SigNoz.

Vamos começar alterando o conector com o banco de dados, criando um wrapper do sqlx com a lib otelsqlx, com isso vamos conseguir captar informações de queries que serão executadas no banco.

core/postgres/connector.go

package postgres

import (
    "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 SQLX
func 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/migrations
func 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 informações.

package util

import (
    "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 não menos importante, vamos configurar o middleware para o gin no arquivo adapter/http/main.go

package main

import (
    "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 aplicação 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 aplicação 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 aplicação.
  • 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, além de ver exatamente a query que pode estar causando problemas na aplicação.

Image description

Image description

Image description

Image description


This content originally appeared on DEV Community and was authored by Vinícius Boscardin


Print Share Comment Cite Upload Translate Updates
APA

Vinícius Boscardin | Sciencx (2022-07-13T19:36:58+00:00) Golang, observabilidade e métricas com SigNoz e OpenTelemetry. Retrieved from https://www.scien.cx/2022/07/13/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry/

MLA
" » Golang, observabilidade e métricas com SigNoz e OpenTelemetry." Vinícius Boscardin | Sciencx - Wednesday July 13, 2022, https://www.scien.cx/2022/07/13/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry/
HARVARD
Vinícius Boscardin | Sciencx Wednesday July 13, 2022 » Golang, observabilidade e métricas com SigNoz e OpenTelemetry., viewed ,<https://www.scien.cx/2022/07/13/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry/>
VANCOUVER
Vinícius Boscardin | Sciencx - » Golang, observabilidade e métricas com SigNoz e OpenTelemetry. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/07/13/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry/
CHICAGO
" » Golang, observabilidade e métricas com SigNoz e OpenTelemetry." Vinícius Boscardin | Sciencx - Accessed . https://www.scien.cx/2022/07/13/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry/
IEEE
" » Golang, observabilidade e métricas com SigNoz e OpenTelemetry." Vinícius Boscardin | Sciencx [Online]. Available: https://www.scien.cx/2022/07/13/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry/. [Accessed: ]
rf:citation
» Golang, observabilidade e métricas com SigNoz e OpenTelemetry | Vinícius Boscardin | Sciencx | https://www.scien.cx/2022/07/13/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.