Golang, SigNoz 및 OpenTelemetry에서 측정 및 관찰 가능

45351 단어 metricsapisignozgo
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 semper vai ter a primeira vez... Brincadeiras a parte, hoje veremos como integrar um serviço criado com golang com o SigNoz usando OpenTelemetry

보라! Primeiro passo é ter um serviço para metrificar! rsrs... Vamos criar algo muito simples para não perdermos tempo. Golang에서 완전한 SigNoz 및 API 통합으로 초점을 맞춥니다.

애플리케이션



Iniciaremos com:

mkdir go-signoz-otl
cd go-signoz-otl
go mod init github.com/booscaaa/go-signoz-otl


Vamos 구성 nossa 마이그레이션 de produtos para o example.

migrate create -ext sql -dir database/migrations -seq create_product_table


노소 아르키보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');


Mãos에서 Mãos로 이동하면 lib sqlx를 사용하여 PostgreSQL에서 커넥터를 처음으로 사용할 수 있습니다.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 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!
프리미로 터미널 없음:

go run adapter/http/main.go


세군도 터미널 없음:

curl --location --request GET 'localhost:3000/product'


시그노즈



Com aplicação pronta, vamos iniciar as devidas implementações para integrar as métricas com o SigNoz e ver a magia acontecer!
SigNoz 나 nossa máquina의 초기 설치 작업은 docker-compose 또는 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 bast acessar o endereço no seu navegador.



Crie uma conta e acesse o painel do SigNoz.



어떤 대시보드도 사용할 수 없습니다.

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

Vamos는 DADOS 은행, CRIANDO UM Wrapper do sqlx, lib otelsqlx, com isso vamos conseguir captar informações de query 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.
프리미로 터미널 없음:

go run adapter/http/main.go


세군도 터미널 없음:

while :
do
    curl --location --request GET 'localhost:3000/product'
done


Voltando para o painel do SigNoz basta esperar aplicação aparecer no dashboard.



Clicando no app que acabou de aparecer já conseguimos analisar dados muito Importantes como:
  • 미디어 템포 드 카다 요청.
  • 다음으로 요청 수량.
  • 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.







    좋은 웹페이지 즐겨찾기