应用性能监控

接入GO应用

2024-07-16 03:04:45

通过OpenTelemetry上报Go应用数据

在监控Go应用之前,您需要通过客户端将应用数据上报至APM服务端。本小节介绍如何通过OpenTelemetry Go SDK上报Go应用数据。

前提条件

完成vpce接入。

背景信息

OpenTelemetry Go SDK提供了Go语言的分布式链路追踪能力,您可以直接使用OTLP gRPC或者HTTP协议向APM服务端上报数据。

接入步骤

1.  添加OpenTelemetry Go依赖。

package main
 import (
     "context"
     "flag"
     "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
     "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
     "go.opentelemetry.io/otel"
     "go.opentelemetry.io/otel/baggage"
     "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
     "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
     "go.opentelemetry.io/otel/sdk/resource"
     sdktrace "go.opentelemetry.io/otel/sdk/trace"
     semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
     "go.opentelemetry.io/otel/trace"
     "io"
     "log"
     "net/http"
     "net/http/httptrace"
     "time"
 )

2.  查看接入点信息。

应用列表的接入指引会根据您所在资源池提供“通过 HTTP 上报数据”和“通过 gRPC 上报数据”的ENDPOINT(天翼云vpc网络接入点)、鉴权TOKEN信息。

3.  初始化OpenTelemetry Go SDK。

方式1:使用HTTP协议上报数据。(注意需将<token>和<endpoint>替换成相应地域的接入点信息。)

func initProvider() func() {
   ctx := context.Background()
   auth := map[string]string{
       "x-ctg-authorization": "<token>", // 替换成token信息
   }
   traceClient := otlptracegrpc.NewClient(
       otlptracegrpc.WithInsecure(),
       otlptracegrpc.WithEndpoint("<endpoint>"),    //替换成otel http的上报地址。
       otlptracehttp.WithURLPath("/api/traces"),
       otlptracegrpc.WithHeaders(auth),
       otlptracegrpc.WithDialOption(grpc.WithBlock()))
   log.Println("start to connect to server")
   traceExp, err := otlptrace.New(ctx, traceClient)
   handleErr(err, "Failed to create the collector trace exporter")
   res, err := resource.New(ctx,
       resource.WithFromEnv(),
       resource.WithProcess(),
       resource.WithTelemetrySDK(),
       resource.WithHost(),
       resource.WithAttributes(
             // 在可观测链路 OpenTelemetry 版后端显示的服务名称。
             semconv.ServiceNameKey.String("otel-go-client-demo"),
             semconv.HostNameKey.String(""),
       ),
   )
   handleErr(err, "failed to create resource")
   bsp := sdktrace.NewBatchSpanProcessor(traceExp)
   tracerProvider := sdktrace.NewTracerProvider(
       sdktrace.WithSampler(sdktrace.AlwaysSample()),
       sdktrace.WithResource(res),
       sdktrace.WithSpanProcessor(bsp),
   )
   otel.SetTracerProvider(tracerProvider)
   return func() {
       cxt, cancel := context.WithTimeout(ctx, time.Second)
       defer cancel()
       if err := traceExp.Shutdown(cxt); err != nil {
             otel.Handle(err)
       }
   }
 }

方式2:使用gRPC协议上报数据。(注意需将<token>和<endpoint>替换成相应地域的接入点信息。)

func initProviderGrpc() func() {
   ctx := context.Background()
   auth := map[string]string{
       "x-ctg-authorization": "<token>", //接入流程里面获取token信息
   }
   traceClient := otlptracegrpc.NewClient(
       otlptracegrpc.WithInsecure(),
       otlptracegrpc.WithEndpoint("<endpoint>"),  //替换成grpc接入信息
       otlptracegrpc.WithHeaders(auth),
       otlptracegrpc.WithDialOption(grpc.WithBlock()))
   log.Println("start to connect to server")
   traceExp, err := otlptrace.New(ctx, traceClient)
   handleErr(err, "Failed to create the collector trace exporter")
   res, err := resource.New(ctx,
       resource.WithFromEnv(),
       resource.WithProcess(),
       resource.WithTelemetrySDK(),
       resource.WithHost(),
       resource.WithAttributes(
           // 在可观测链路 OpenTelemetry 版后端显示的服务名称。
           semconv.ServiceNameKey.String("otel-go-client-demo_provider"),
           semconv.HostNameKey.String(""),
       ),
   )
   handleErr(err, "failed to create resource")
   bsp := sdktrace.NewBatchSpanProcessor(traceExp)
   tracerProvider := sdktrace.NewTracerProvider(
       sdktrace.WithSampler(sdktrace.AlwaysSample()),
       sdktrace.WithResource(res),
       sdktrace.WithSpanProcessor(bsp),
   )
   otel.SetTracerProvider(tracerProvider)
   return func() {
       cxt, cancel := context.WithTimeout(ctx, time.Second)
       defer cancel()
       if err := traceExp.Shutdown(cxt); err != nil {
           otel.Handle(err)
       }
   }
 }

4.  client端上报例子。

func sendReq(url string, ctx context.Context) {
 // 构造一个trace client
   client := http.Client{
       Transport: otelhttp.NewTransport(
             http.DefaultTransport,
             otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
                 return otelhttptrace.NewClientTrace(ctx)
             }),
       ),
   }
 
   tr := otel.Tracer("example/client")
   err := func(ctx context.Context) error {
       ctx, span := tr.Start(ctx, "say hello", trace.WithAttributes(semconv.PeerService("ExampleService")))
       defer span.End()
       ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx))
       req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
       res, err := client.Do(req)
       if err != nil {
             panic(err)
       }
       _, err = io.ReadAll(res.Body)
       _ = res.Body.Close()
       return err
   }(ctx)
 
     if err != nil {
         log.Fatal(err)
     }
   }
 
   func doClient(url string, ctx context.Context) {
     // 不断请求数据
     for {
         sendReq(url, ctx)
         time.Sleep(3 * time.Second)
     }
   }
 
   func main() {
     shutdown := initProvider()
     defer shutdown()
     url := flag.String("server", "http://localhost:8070/hello", "server url")
     flag.Parse()
     bag, _ := baggage.Parse("username=xxx")
     ctx := baggage.ContextWithBaggage(context.Background(), bag)
     doClient(*url, ctx)
 }

5.  服务端上报例子。

func handler() {
   uk := attribute.Key("username") //加点属性
 
   helloHandler := func(w http.ResponseWriter, req *http.Request) {
       ctx := req.Context()
     //继续调用下一个服务最终效果是client-》hello-》hello1
       sendReq("http://localhost:8071/hello1", ctx)
   }
 
   helloHandler1 := func(w http.ResponseWriter, req *http.Request) {
       ctx := req.Context()
       span := trace.SpanFromContext(ctx)
       bag := baggage.FromContext(ctx)
       span.AddEvent("handling this...", trace.WithAttributes(uk.String(bag.Member("username").Value())))
       _, _ = io.WriteString(w, "Hello, world!\n")
   }
 
   otelHandler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "Hello")
   otelHandler1 := otelhttp.NewHandler(http.HandlerFunc(helloHandler1), "Hello1")
 
   http.Handle("/hello", otelHandler)
   http.Handle("/hello1", otelHandler1)
 }
 
   func main() {
     flag.Parse()
     shutdown := initProvider()
     defer shutdown()
     handler()
     err := http.ListenAndServe(":" + *serverPort, nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts.
     if err != nil {
       log.Fatal(err)
     }
 }

6、通过以上步骤,最后就在APM控制台的应用列表页面选择目标应用,查看监控数据。具体参考示例

通过SkyWalking SDK上报Go应用数据

在监控Go应用之前,您需要通过客户端将应用数据上报至APM服务端。本小节介绍如何通过SkyWalking SDK上报Go应用数据。

接入步骤

1.  添加SkyWalking Go依赖。

package main
 
 import (
     "context"
     "flag"
     "fmt"
     "github.com/SkyAPM/go2sky"
     httpPlugin "github.com/SkyAPM/go2sky/plugins/http"
     "github.com/SkyAPM/go2sky/reporter"
     "io/ioutil"
     "log"
     "net/http"
     "time"
 )

2.  查看接入点信息。

应用列表的接入指引会根据您所在资源池提供v3版本接入点(Skywalking 8.*)的ENDPOINT(天翼云vpc网络接入点)、鉴权TOKEN信息。

3.  初始化SkyWalking Go SDK。

使用gRPC协议上报数据。(注意需将<token>和<endpoint>替换成相应地域的接入点信息。)

serverPort := flag.String("port", "9891", "server port")
     token := flag.String("token", "<token>", "token") // 鉴权信息
     endpoint := flag.String("endpoint", "<endpoint>", "endpoint") // grpc端口
     authMap := map[string]string{
         "x-ctg-authorization": *token,
     }
     flag.Parse()
     report, err := reporter.NewGRPCReporter(*endpoint, reporter.WithAuthHeader(authMap))
     //report, err := reporter.NewLogReporter()
     tracer, err := go2sky.NewTracer(service, go2sky.WithReporter(report))

4.  client端上报例子。

func do(tracer *go2sky.Tracer, serverPort string, client *http.Client) {
   for {
       time.Sleep(5*time.Second)
       doClient(tracer, client, "client", "http://localhost:" + serverPort + "/helloserver")
   }
 }
 
 func doClient(tracer *go2sky.Tracer, client *http.Client, spanName string, url string) {
   clientReq, err1 := http.NewRequest(http.MethodGet, url, nil)
   parentSpan, newCtx, _ := tracer.CreateLocalSpan(context.Background())
   parentSpan.SetOperationName(spanName)
   defer parentSpan.End()
   clientReq = clientReq.WithContext(newCtx)
   res, err1 := client.Do(clientReq)
   if err1 != nil {
       fmt.Println(err1, "send req error")
       return
   }
   defer res.Body.Close()
   body, err1 := ioutil.ReadAll(res.Body)
   fmt.Println(string(body))
 }

5.  服务端上报例子。

route := http.NewServeMux()
     route.HandleFunc("/helloserver", func(writer http.ResponseWriter, request *http.Request) {
     upstreamURL := "http://localhost:9891/hello1"
         client, err := httpPlugin.NewClient(tracer)
         clientReq, err1 := http.NewRequest(http.MethodGet, upstreamURL, nil)
         if err1 != nil {
             writer.WriteHeader(http.StatusInternalServerError)
             log.Printf("unable to create http request error: %v \n", err)
             return
         }
         subSpan, newCtx, _ := tracer.CreateLocalSpan(request.Context())
         subSpan.SetOperationName("server2")
         defer subSpan.End()
         clientReq = clientReq.WithContext(newCtx)
         res, err1 := client.Do(clientReq)
         if err1 != nil {
             writer.WriteHeader(http.StatusInternalServerError)
             log.Printf("unable to do http request error: %v \n", err)
             return
         }
         defer res.Body.Close()
         body, err1 := ioutil.ReadAll(res.Body)
         if err1 != nil {
             writer.WriteHeader(http.StatusInternalServerError)
             log.Printf("read http response error: %v \n", err)
             return
         }
         writer.WriteHeader(res.StatusCode)
         _, _ = writer.Write(body)
   })
 
   sm, err := httpPlugin.NewServerMiddleware(tracer)
   if err != nil {
       log.Fatalf("create server middleware error %v \n", err)
   }
   err = http.ListenAndServe(":" + *serverPort, sm(route))
   if err != nil {
       log.Fatal(err)
   }

6.  通过以上步骤,最后就在APM控制台的应用列表页面选择目标应用,查看监控数据。

通过SkyWalking SDK上报Go应用数据

在监控Go应用之前,您需要通过客户端将应用数据上报至APM服务端。本小节介绍如何通过SkyWalking SDK上报Go应用数据。

接入步骤

1.  添加SkyWalking Go依赖。

package main
 
 import (
     "context"
     "flag"
     "fmt"
     "github.com/SkyAPM/go2sky"
     httpPlugin "github.com/SkyAPM/go2sky/plugins/http"
     "github.com/SkyAPM/go2sky/reporter"
     "io/ioutil"
     "log"
     "net/http"
     "time"
 )

2.  查看接入点信息。

应用列表的接入指引会根据您所在资源池提供v3版本接入点(Skywalking 8.*)的ENDPOINT(天翼云vpc网络接入点)、鉴权TOKEN信息。

3.  初始化SkyWalking Go SDK。

使用gRPC协议上报数据。(注意需将<token>和<endpoint>替换成相应地域的接入点信息。)

serverPort := flag.String("port", "9891", "server port")
     token := flag.String("token", "<token>", "token") // 鉴权信息
     endpoint := flag.String("endpoint", "<endpoint>", "endpoint") // grpc端口
     authMap := map[string]string{
         "x-ctg-authorization": *token,
     }
     flag.Parse()
     report, err := reporter.NewGRPCReporter(*endpoint, reporter.WithAuthHeader(authMap))
     //report, err := reporter.NewLogReporter()
     tracer, err := go2sky.NewTracer(service, go2sky.WithReporter(report))

4.  client端上报例子。

func do(tracer *go2sky.Tracer, serverPort string, client *http.Client) {
   for {
       time.Sleep(5*time.Second)
       doClient(tracer, client, "client", "http://localhost:" + serverPort + "/helloserver")
   }
 }
 
 func doClient(tracer *go2sky.Tracer, client *http.Client, spanName string, url string) {
   clientReq, err1 := http.NewRequest(http.MethodGet, url, nil)
   parentSpan, newCtx, _ := tracer.CreateLocalSpan(context.Background())
   parentSpan.SetOperationName(spanName)
   defer parentSpan.End()
   clientReq = clientReq.WithContext(newCtx)
   res, err1 := client.Do(clientReq)
   if err1 != nil {
       fmt.Println(err1, "send req error")
       return
   }
   defer res.Body.Close()
   body, err1 := ioutil.ReadAll(res.Body)
   fmt.Println(string(body))
 }

5.  服务端上报例子。

route := http.NewServeMux()
     route.HandleFunc("/helloserver", func(writer http.ResponseWriter, request *http.Request) {
     upstreamURL := "http://localhost:9891/hello1"
         client, err := httpPlugin.NewClient(tracer)
         clientReq, err1 := http.NewRequest(http.MethodGet, upstreamURL, nil)
         if err1 != nil {
             writer.WriteHeader(http.StatusInternalServerError)
             log.Printf("unable to create http request error: %v \n", err)
             return
         }
         subSpan, newCtx, _ := tracer.CreateLocalSpan(request.Context())
         subSpan.SetOperationName("server2")
         defer subSpan.End()
         clientReq = clientReq.WithContext(newCtx)
         res, err1 := client.Do(clientReq)
         if err1 != nil {
             writer.WriteHeader(http.StatusInternalServerError)
             log.Printf("unable to do http request error: %v \n", err)
             return
         }
         defer res.Body.Close()
         body, err1 := ioutil.ReadAll(res.Body)
         if err1 != nil {
             writer.WriteHeader(http.StatusInternalServerError)
             log.Printf("read http response error: %v \n", err)
             return
         }
         writer.WriteHeader(res.StatusCode)
         _, _ = writer.Write(body)
   })
 
   sm, err := httpPlugin.NewServerMiddleware(tracer)
   if err != nil {
       log.Fatalf("create server middleware error %v \n", err)
   }
   err = http.ListenAndServe(":" + *serverPort, sm(route))
   if err != nil {
       log.Fatal(err)
   }

6.  通过以上步骤,最后就在APM控制台的应用列表页面选择目标应用,查看监控数据。


vvIsNE8SrBvg