Golang, SigNoz 및 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:
mkdir go-signoz-otl
cd go-signoz-otl
go mod init github.com/booscaaa/go-signoz-otl
migrate create -ext sql -dir database/migrations -seq create_product_table
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');
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)
}
}
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)
}
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,
}
}
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
}
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,
}
}
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
}
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,
}
}
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)
}
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
}
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")
}
{
"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
}
}
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:
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.
Reference
이 문제에 관하여(Golang, SigNoz 및 OpenTelemetry에서 측정 및 관찰 가능), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/booscaaa/golang-observabilidade-e-metricas-com-signoz-e-opentelemetry-447e텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)