پرش به مطلب اصلی

اتصال اپ های Go به لاگ نگار

· خواندن 6 دقیقه
نیما نکوئی نیا
Software Developer

اتصال اپلیکیشن‌های Go به لاگ نگار

این مقاله راهنمایی جامع برای توسعه‌دهندگان فراهم می‌کند تا بتوانند اپلیکیشن‌های Go خود را به سرویس لاگ نگار متصل کنند. با استفاده از این راهنما، شما می‌توانید داده‌های لاگ خود را به لاگ نگار ارسال و به شکلی کارآمد مدیریت و مشاهده کنید.

علاوه بر راهنمای متنی به شما پیشنهاد میشود ویدیو یوتیوب همین آموزش را مشاهده کنید:



  1. برای ایجاد دیتای مانیتورینگ مانند لاگ، تریس و متریک در ابتدا نیاز به نصب پکیج های Open Telemetry داریم. برای نصب پکیج های مورد نیاز میتوانید از دستور زیر استفاده کنید:

go get go.opentelemetry.io/otel go.opentelemetry.io/otel go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp go.opentelemetry.io/otel/exporters/otlp/otlptrace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp go.opentelemetry.io/otel/log go.opentelemetry.io/otel/log/global go.opentelemetry.io/otel/propagation go.opentelemetry.io/otel/sdk/log go.opentelemetry.io/otel/sdk/metric go.opentelemetry.io/otel/sdk/resource go.opentelemetry.io/otel/sdk/trace go.opentelemetry.io/otel/semconv/v1.26.0 go.opentelemetry.io/otel/trace



  1. سپس کانفیگ زیر را در یک فایل در پروژه خود اضافه میکنیم:
// telemetry/config.go

package telemetry

import (
"context"
"log"
"os"
"time"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
otelLog "go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/global"
"go.opentelemetry.io/otel/propagation"
sdklog "go.opentelemetry.io/otel/sdk/log"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace"
)

const (
ServiceName = "todo-app"
defaultOtelEndpoint = "http://localhost:4318/v1"
)

var tracer trace.Tracer

var (
logger = global.Logger(ServiceName)
BaseURL = getOtelBaseURL()
)

func getOtelBaseURL() string {
url := os.Getenv("OTEL_BASE_URL")
if url == "" {
return defaultOtelEndpoint
}
return url
}

func newResource() *resource.Resource {
r, err := resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName(ServiceName),
semconv.ServiceVersion("1.0.0"),
),
)

if err != nil {
log.Fatalf("failed to create resource: %v", err)
}

return r
}

func newTraceProvider(exp sdktrace.SpanExporter) *sdktrace.TracerProvider {
r := newResource()

return sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(r),
)
}

func newTraceExporter(ctx context.Context) (sdktrace.SpanExporter, error) {
client := otlptracehttp.NewClient(
otlptracehttp.WithInsecure(),
otlptracehttp.WithEndpointURL(BaseURL+"/traces"),
)

return otlptrace.New(ctx, client)
}

func InitTracer(ctx context.Context) func() {
exporter, err := newTraceExporter(ctx)

if err != nil {
log.Fatalf("failed to trace exporter: %v", err)
}

tp := newTraceProvider(exporter)

otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{}),
)

tracer = tp.Tracer(ServiceName)

return func() {
if err := tp.Shutdown(ctx); err != nil {
log.Println(err)
}
}
}

func newLogExporter(ctx context.Context) (sdklog.Exporter, error) {
return otlploghttp.New(ctx,
otlploghttp.WithInsecure(),
otlploghttp.WithEndpointURL(BaseURL+"/logs"),
)
}

func newLogProvider(exp sdklog.Exporter) *sdklog.LoggerProvider {
res := newResource()
processor := sdklog.NewBatchProcessor(exp)

provider := sdklog.NewLoggerProvider(
sdklog.WithResource(res),
sdklog.WithProcessor(processor),
)

return provider
}

func InitLogger(ctx context.Context) func() {
exp, err := newLogExporter(ctx)
if err != nil {
log.Fatalf("failed to create log exporter: %v", err)
}

lp := newLogProvider(exp)
global.SetLoggerProvider(lp)

return func() {
if err := lp.Shutdown(ctx); err != nil {
log.Println(err)
}
}
}

func newMeterProvider(exp sdkmetric.Exporter) (*sdkmetric.MeterProvider, error) {
res := newResource()

meterProvider := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp,
// Default is 1m. Set to 10s for demonstrative purposes.
sdkmetric.WithInterval(10*time.Second))),
)

return meterProvider, nil
}

func newMeterExporter(ctx context.Context) (sdkmetric.Exporter, error) {
return otlpmetrichttp.New(ctx,
otlpmetrichttp.WithInsecure(),
otlpmetrichttp.WithEndpointURL(BaseURL+"/metrics"))
}

func InitMeter(ctx context.Context) func() {
exp, err := newMeterExporter(ctx)
if err != nil {
log.Fatalf("failed to create metric exporter: %v", err)
}

mp, err := newMeterProvider(exp)
if err != nil {
log.Fatalf("failed to create meter provider: %v", err)
}

otel.SetMeterProvider(mp)

return func() {
if err := mp.Shutdown(ctx); err != nil {
log.Println(err)
}
}
}

func GetTracer() trace.Tracer {
return tracer
}

func GetLogger() otelLog.Logger {
return logger
}



  1. برای استفاده از این کانفیگ باید اون رو در جایی که اپلیکیشن شروع به کار میکنه فعال کنیم:
// main.go

package main

import (
"context"
"goexample/routes"
"goexample/telemetry"
"log"
"net"
"net/http"
)

func main() {
router := http.NewServeMux()

routes.LoadRoutes(router)

ctx := context.Background()

cleanupTracer := telemetry.InitTracer(ctx)
cleanupMeter := telemetry.InitMeter(ctx)
cleanupLogger := telemetry.InitLogger(ctx)

defer func() {
cleanupTracer()
cleanupMeter()
cleanupLogger()
}()

port := ":3090"
server := http.Server{
Addr: port,
BaseContext: func(listener net.Listener) context.Context {
return ctx
},
Handler: router,
}

log.Printf("Server listening on http://localhost%s \n", port)

server.ListenAndServe()

}


  1. سپس برای لاگ کردن همه درخواست هایی که به سمت سرور میاد میتونیم یک middleware ایجاد کنیم:
// middlewares/logger.go

func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()

traceName := r.Method + " - " + r.URL.Path

wrapped := &wrappedWriter{
ResponseWriter: w,
statusCode: http.StatusOK,
}

next.ServeHTTP(wrapped, r.WithContext(ctx))

var record otelLog.Record
record.SetTimestamp(time.Now())
record.SetBody(otelLog.StringValue(traceName))
record.SetSeverity(otelLog.SeverityInfo)
record.AddAttributes(
otelLog.String("url", r.URL.Path),
otelLog.String("method", r.Method),
otelLog.Int("status", wrapped.statusCode),
otelLog.String("host", r.Host),
otelLog.String("user-agent", r.UserAgent()),
otelLog.String("remote-addr", r.RemoteAddr),
otelLog.String("referer", r.Referer()),
)

telemetry.GetLogger().Emit(ctx, record)
log.Println(wrapped.statusCode, r.Method, r.URL.Path, time.Since(start))
})
}

  1. این middleware را در فایل main.go فعال میکنیم.
// main.go

package main

import (
"context"
"goexample/middlewares"
"goexample/routes"
"goexample/telemetry"
"log"
"net"
"net/http"
)

func main() {
router := http.NewServeMux()

routes.LoadRoutes(router)
stack := middlewares.CreateStack(middlewares.Logging)

ctx := context.Background()

cleanupTracer := telemetry.InitTracer(ctx)
cleanupMeter := telemetry.InitMeter(ctx)
cleanupLogger := telemetry.InitLogger(ctx)

defer func() {
cleanupTracer()
cleanupMeter()
cleanupLogger()
}()

port := ":3090"
server := http.Server{
Addr: port,
BaseContext: func(listener net.Listener) context.Context {
return ctx
},
Handler: stack(router),
}

log.Printf("Server listening on http://localhost%s \n", port)

server.ListenAndServe()
}

  1. سپس برای ایجاد کردن اسپن های هر درخواست میتوانید از تریسر ایجاد شده استفاده کنید:
// middlewares/logger.go
// استفاده تریس در میدلور

package middlewares

import (
"goexample/telemetry"
"log"
"net/http"
"time"

otelLog "go.opentelemetry.io/otel/log"
)

type wrappedWriter struct {
http.ResponseWriter
statusCode int
}

func (w *wrappedWriter) WriteHeader(statusCode int) {
w.ResponseWriter.WriteHeader(statusCode)
w.statusCode = statusCode
}

func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()

traceName := r.Method + " - " + r.URL.Path
ctx, span := telemetry.GetTracer().Start(r.Context(), traceName)
defer span.End()

wrapped := &wrappedWriter{
ResponseWriter: w,
statusCode: http.StatusOK,
}

next.ServeHTTP(wrapped, r.WithContext(ctx))

var record otelLog.Record
record.SetTimestamp(time.Now())
record.SetBody(otelLog.StringValue(traceName))
record.SetSeverity(otelLog.SeverityInfo)
record.AddAttributes(
otelLog.String("url", r.URL.Path),
otelLog.String("method", r.Method),
otelLog.Int("status", wrapped.statusCode),
otelLog.String("host", r.Host),
otelLog.String("user-agent", r.UserAgent()),
otelLog.String("remote-addr", r.RemoteAddr),
otelLog.String("referer", r.Referer()),
)

telemetry.GetLogger().Emit(ctx, record)
log.Println(wrapped.statusCode, r.Method, r.URL.Path, time.Since(start))
})
}

// controller/todo.go
// استفاده تریسر در کنترلر

func CreateTodo(w http.ResponseWriter, r *http.Request) {
_, span := telemetry.GetTracer().Start(r.Context(), "CreateTodo")
defer span.End()

var todo Todo
if err := json.NewDecoder(r.Body).Decode(&todo); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

todo.ID = uuid.New().String()
todo.CreatedAt = time.Now()
todo.UpdatedAt = time.Now()

todos[todo.ID] = todo

// Simulate some work
services.ConnectToServices(r.Context())

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(todo)
}

// services/services.go
// استفاده تریسر در سرویس ها

func ConnectToServiceA(ctx context.Context) {
_, span := telemetry.GetTracer().Start(ctx, "ConnectToServiceA")
defer span.End()

time.Sleep(time.Duration(GenerateRandomNumber(30, 100)) * time.Millisecond)
}


  1. برای کانفیگ کردن Open Telemetry Collector باید در ابتدا در پنل لاگ نگار در قسمت اپلیکیشن ها یک اپ جدید ایجاد و برای اپ خود یک API KEY جدید ایجاد کنید و در کانفیگ خود مانند زیر قرار دهید:
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
grpc:
endpoint: 0.0.0.0:4317

processors:
batch:
timeout: 10s

exporters:
debug:
verbosity: detailed
otlphttp/logs:
endpoint: https://api.lognegar.ir/api
headers:
lognegar-api-key: <YOUR_API_KEY>
otlphttp/traces:
endpoint: https://api.lognegar.ir/api
encoding: json
headers:
lognegar-api-key: <YOUR_API_KEY>
otlphttp/metrics:
endpoint: https://api.lognegar.ir/api
encoding: json
headers:
lognegar-api-key: <YOUR_API_KEY>
service:
pipelines:
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/logs]
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/traces]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/metrics]

در انتها شما میتوانید اپلیکیشن خود را اجرا کرده و درخواست هایی به سمت آن ارسال کنید و سپس لاگ ها و تریس و متریک هایی که تولید شده اند را در پنل لاگ نگار در قسمت های مربوطه مشاهده کنید

لینک ریپازیتوری پروژه:

Gihtub