最低成本整合 SkyWalking 到 Go 業務代碼

【導讀】Skywalking 是一款十分流行的 APM 工具,本文介紹了一種該平臺低成本接入 go 業務的方式。

  1. 介紹

其實對於現在 APM(Application Performance Management)系統,以下三點都包含了,不管是系統指標(Prometheus),還是日誌(ELK),還是鏈路追蹤(Skywalking,zipkin,jeager),都逐步向中間靠攏

image-20201029132759719

這個是目前市場上主流的開源的 鏈路追蹤系統,我們可以看一下大致的區別,目前比較火的其實是後三者,他們其實都可以互相兼容,因爲實現了 opentracing 規範!

其中比較推薦的是:Jeager(17 年孵化的項目),原因就是作爲 CNCF 畢業項目 ,成長空間和雲原聲的系統架構比較兼容性好。

下面還有很多沒有介紹到的,比如美團的 CAT,fb 的 xhprof , 阿里的鷹眼

image-20201029132943685

2. opentracing 規範

1、架構

官方給的設計圖在這裏 : 這個就是整體的設計架構圖.

  1. 快速開始

基於上訴準備,我們就可以快速的開發 Go 的探針

➜  bin ./startup.sh
SkyWalking OAP started successfully!
SkyWalking Web Application started successfully!
複製代碼

3、Go 的跨服務調用的探針埋點

對於 Java 開發者使用 SkyWalking 來說,簡單來說常用的開發框架他都做了自動埋點,但是對於 Go 來說啥都需要手動埋點,需要我們自己寫代碼,而不是官方的人去寫代碼。

目前來說 Go 這邊支持了 Gin 框架,但是切記,這只是一個 Demo

import (
 "fmt"
 "github.com/SkyAPM/go2sky"
 "github.com/SkyAPM/go2sky/multi_server/common"
 "github.com/SkyAPM/go2sky/reporter"
 "github.com/gin-gonic/gin"
 "net/http"
 "time"

 gg "github.com/SkyAPM/go2sky/plugins/gin"
)

const (
 server_name_2 = "server-2"
 server_port_2 = 8082
)

func main() {
 r := gin.New()
  // SkyAddr 是skywaling的grpc地址,默認是localhost:11800 , 默認心跳檢測時間是1s
 rp, err := reporter.NewGRPCReporter(common.SkyAddr, reporter.WithCheckInterval(time.Second))
 common.PanicError(err)
  // 初始化一個 tracer,一個服務只需要一個tracer,其含義是這個服務名稱
 tracer, err := go2sky.NewTracer(server_name_2, go2sky.WithReporter(rp))
 common.PanicError(err)
  // gin 使用 sky自帶的middleware, 說實話0.4代碼比0.3強太多了!
 r.Use(gg.Middleware(r, tracer))

  // 自定義一個接口
 r.POST("/user/info", func(context *gin.Context) {
    // LocalSpan可以理解爲本地日誌的tracer,一般用戶當前應用
  span, ctx, err := tracer.CreateLocalSpan(context.Request.Context())
  common.PanicError(err)
    // 每一個span都有一個名字去標實操作的名稱!
  span.SetOperationName("UserInfo")
    // 記住重新設置一個ctx,再其次這個ctx不是gin的ctx,而是httprequest的ctx
  context.Request = context.Request.WithContext(ctx)
    
    // 。。。。
    params := new(common.Params)
  err = context.BindJSON(params)
  common.PanicError(err)

  span.Log(time.Now()"[UserInfo]", fmt.Sprintf(server_name_2+" satrt, req : %+v", params))
  local := gin.H{
   "msg": fmt.Sprintf(server_name_2+" time : %s", time.Now().Format("15:04:05")),
  }
  context.JSON(200, local)
  span.Log(time.Now()"[UserInfo]", fmt.Sprintf(server_name_2+" end, resp : %s"local))
    // 切記最後要設置span - end,不然就是一個非閉環的
  span.End()
 })

 common.PanicError(http.ListenAndServe(fmt.Sprintf(":%d", server_port_2), r))
}

server-1 調用 server -2

import (
 "bytes"
 "encoding/json"
 "fmt"
 "github.com/SkyAPM/go2sky"
 "github.com/SkyAPM/go2sky/multi_server/common"
 "github.com/SkyAPM/go2sky/propagation"
 "github.com/SkyAPM/go2sky/reporter"
 v3 "github.com/SkyAPM/go2sky/reporter/grpc/language-agent"
 "github.com/gin-gonic/gin"
 "io/ioutil"
 "net/http"
 "time"

 gg "github.com/SkyAPM/go2sky/plugins/gin"
)

const (
 server_name        = "server-1"
 server_port        = 8081
 remote_server_name = "server-2"
 remote_server_addr = "localhost:8082"
 remoto_path        = "/user/info"
)

func main() {

  // 這些都一樣
 r := gin.New()
 rp, err := reporter.NewGRPCReporter(common.SkyAddr, reporter.WithCheckInterval(time.Second))
 common.PanicError(err)
 tracer, err := go2sky.NewTracer(server_name, go2sky.WithReporter(rp))
 common.PanicError(err)
 r.Use(gg.Middleware(r, tracer))

  // 調用接口
 r.GET("/trace", func(context *gin.Context) {
  span, ctx, err := tracer.CreateLocalSpan(context.Request.Context())
  common.PanicError(err)
  span.SetOperationName("Trace")

  context.Request = context.Request.WithContext(ctx)
  span.Log(time.Now()"[Trace]", fmt.Sprintf(server_name+" satrt, params : %s", time.Now().Format("15:04:05")))

  result := make([]map[string]interface{}, 0)

    //1、請求一次
  {
   url := fmt.Sprintf("http://%s%s", remote_server_addr, remoto_path)

   params := common.Params{
    Name: server_name + time.Now().Format("15:04:05"),
   }
   buffer := &bytes.Buffer{}
   _ = json.NewEncoder(buffer).Encode(params)
   req, err := http.NewRequest(http.MethodPost, url, buffer)
   common.PanicError(err)

   // op_name 是每一個操作的名稱
   reqSpan, err := tracer.CreateExitSpan(context.Request.Context()"invoke - "+remote_server_name, fmt.Sprintf("localhost:8082/user/info"), func(header string) error {
    req.Header.Set(propagation.Header, header)
    return nil
   })
   common.PanicError(err)
   reqSpan.SetComponent(2)                         //HttpClient,看 https://github.com/apache/skywalking/blob/master/docs/en/guides/Component-library-settings.md , 目錄在component-libraries.yml文件配置
   reqSpan.SetSpanLayer(v3.SpanLayer_RPCFramework) // rpc 調用

   resp, err := http.DefaultClient.Do(req)
   common.PanicError(err)
   defer resp.Body.Close()

   reqSpan.Log(time.Now()"[HttpRequest]", fmt.Sprintf("開始請求,請求服務:%s,請求地址:%s,請求參數:%+v", remote_server_name, url, params))
   body, err := ioutil.ReadAll(resp.Body)
   common.PanicError(err)
   fmt.Printf("接受到消息: %s\n", body)
   reqSpan.Tag(go2sky.TagHTTPMethod, http.MethodPost)
   reqSpan.Tag(go2sky.TagURL, url)
   reqSpan.Log(time.Now()"[HttpRequest]", fmt.Sprintf("結束請求,響應結果: %s", body))
   reqSpan.End()
   res := map[string]interface{}{}
   err = json.Unmarshal(body, &res)
   common.PanicError(err)
   result = append(result, res)
  }

    //2 、再請求一次
  {
   url := fmt.Sprintf("http://%s%s", remote_server_addr, remoto_path)

   params := common.Params{
    Name: server_name + time.Now().Format("15:04:05"),
   }
   buffer := &bytes.Buffer{}
   _ = json.NewEncoder(buffer).Encode(params)
   req, err := http.NewRequest(http.MethodPost, url, buffer)
   common.PanicError(err)

   // 出去必須用這個攜帶header
   reqSpan, err := tracer.CreateExitSpan(context.Request.Context()"invoke - "+remote_server_name, fmt.Sprintf("localhost:8082/user/info"), func(header string) error {
    req.Header.Set(propagation.Header, header)
    return nil
   })
   common.PanicError(err)
   reqSpan.SetComponent(2)                         //HttpClient,看 https://github.com/apache/skywalking/blob/master/docs/en/guides/Component-library-settings.md , 目錄在component-libraries.yml文件配置
   reqSpan.SetSpanLayer(v3.SpanLayer_RPCFramework) // rpc 調用

   resp, err := http.DefaultClient.Do(req)
   common.PanicError(err)
   defer resp.Body.Close()

   reqSpan.Log(time.Now()"[HttpRequest]", fmt.Sprintf("開始請求,請求服務:%s,請求地址:%s,請求參數:%+v", remote_server_name, url, params))
   body, err := ioutil.ReadAll(resp.Body)
   common.PanicError(err)
   fmt.Printf("接受到消息: %s\n", body)

   reqSpan.Tag(go2sky.TagHTTPMethod, http.MethodPost)
   reqSpan.Tag(go2sky.TagURL, url)
   reqSpan.Log(time.Now()"[HttpRequest]", fmt.Sprintf("結束請求,響應結果: %s", body))
   reqSpan.End()
   res := map[string]interface{}{}
   err = json.Unmarshal(body, &res)
   common.PanicError(err)
   result = append(result, res)
  }

    // 設置響應結果
  local := gin.H{
   "msg": result,
  }
  context.JSON(200, local)
  span.Log(time.Now()"[Trace]", fmt.Sprintf(server_name+" end, resp : %s"local))
  span.End()
  {
   span, ctx, err := tracer.CreateEntrySpan(context.Request.Context()"Send", func() (s string, e error) {
    return "", nil
   })
   context.Request = context.Request.WithContext(ctx)
   common.PanicError(err)
   span.SetOperationName("Send")
   //span.Error(time.Now()"[Error]""time is too long")
   span.Log(time.Now()"[Info]""send resp")
   span.End()
  }
 })

 common.PanicError(http.ListenAndServe(fmt.Sprintf(":%d", server_port), r))

}

最終調用效果如下:是一個簡單的時序圖

image-20200913165902725

這就是一個跨進程的簡單調用。

4、需要知道的一些細節

go-sky 的 sdk,0.4 對於 0.3 的改動很大,其中很大一部分在於個性化的功能設置更多了,再其次就是支持 v3 協議,後面再說。

1、reporter

就是用來發送數據的

這個就是一個初始化的過程,很簡單

reporter.NewGRPCReporter(common.SkyAddr, reporter.WithCheckInterval(time.Second))

主要就三個核心方法!

image-20200913170324887

啓動,發送,關閉,三個方法。。。很簡單和方便

2、tracer

tracer 就是用來追蹤的,遵守 open trace 規範。

// NewTracer return a new go2sky Tracer
func NewTracer(service string, opts ...TracerOption) (tracer *Tracer, err error) {
 if service == "" {
  return nil, errParameter
 }
 t := &Tracer{
  service:  service,
  initFlag: 0,
 }
 for _, opt := range opts {
  opt(t)
 }

 if t.reporter != nil {
  if t.instance == "" {
   id, err := idgen.UUID()
   if err != nil {
    return nil, err
   }
   t.instance = id + "@" + tool.IPV4()
  }
    // 調用了reporter的啓動方法,所以對於需要創建tracer的是不需要自己啓動reporter的
  t.reporter.Boot(t.service, t.instance)
  t.initFlag = 1
 }
 return t, nil
}

3、span(核心)

span 就是我們主要關注的核心,可以理解爲就是全部的核心

1、方法介紹

type Span interface {
   SetOperationName(string) // 每一個span唯一的id就是OperationName,這個最好創建的時候有規範
   GetOperationName() string
   SetPeer(string) // 這個是設置兄弟節點,不知道是幹啥了。。。。
   SetSpanLayer(v3.SpanLayer) // 這個主要是設置類型,比如你是RPC,DB,MQ?
   SetComponent(int32)// 這個是設置 Span的類型,比如HTTP客戶端/MYSQL客戶端,相對於上面的,這個類型更加具體
   Tag(Tag, string)// tag是標籤
   Log(time.Time, ...string) // 日誌功能
   Error(time.Time, ...string)
   End()// 每一個span都需要設置一個結尾標識符
   IsEntry() bool
   IsExit() bool
}

span 其實就是這種 key - value 結構。

image-20200913173309904

2、EntrySpan

一般是進入哪個服務了就使用EntrySpan

這裏一般指的是比如進入 server-1或者server-2 , 就去創建一個 entry span

//Middleware gin middleware return HandlerFunc  with tracing.
func Middleware(engine *gin.Engine, tracer *go2sky.Tracer) gin.HandlerFunc {
 if engine == nil || tracer == nil {
  return func(c *gin.Context) {
   c.Next()
  }
 }
 m := new(middleware)

 return func(c *gin.Context) {
  m.routeMapOnce.Do(func() {
   routes := engine.Routes()
   /// 。。。 不用care
  })
  var operationName string
  handlerName := c.HandlerName()
  if routeInfo, ok := m.routeMap[c.Request.Method][handlerName]; ok {
   operationName = routeInfo.operationName
  }
  if operationName == "" {
   operationName = c.Request.Method
  }
    // 創建
    //1、參數是ctx
    //2、每一個span都有一個id,比如一般是以路由名稱爲id的
    //3、會掉函數,主要是獲取header(這個頭的介紹:https://github.com/apache/skywalking/blob/master/docs/en/protocols/Skywalking-Cross-Process-Propagation-Headers-Protocol-v3.md )
  span, ctx, err := tracer.CreateEntrySpan(c.Request.Context(), operationName, func() (string, error) {
   return c.Request.Header.Get(propagation.Header), nil
  })
  if err != nil {
   c.Next()
   return
  }
    // 設置該span的類型,這裏一般是根據:這個文件確定的,默認也沒提供幾個id,go-sdk裏
  span.SetComponent(httpServerComponentID)
  span.Tag(go2sky.TagHTTPMethod, c.Request.Method)
  span.Tag(go2sky.TagURL, c.Request.Host+c.Request.URL.Path)
  span.SetSpanLayer(v3.SpanLayer_Http)

    // 這裏值得注意,沒有使用gin的ctx
  c.Request = c.Request.WithContext(ctx)

  c.Next()

  if len(c.Errors) > 0 {
   span.Error(time.Now(), c.Errors.String())
  }
    // end,設置code
  span.Tag(go2sky.TagStatusCode, strconv.Itoa(c.Writer.Status()))
  // end
    span.End()
 }
}

其實這個 API 相當好理解哇,就是拿到頭,設置 trace_id,然後繼續打 log

3、ExitSpan

其實也不能叫做退出 Span,他只是服務調用,可以理解爲 我 A 服務調用 B 服務,A 需要使用ExitSpan發送探針,去創建一個兒子,然後 B 服務收到後,使用EntrySpan去接受,它認了認個父親。這樣就有關係了

url := fmt.Sprintf("http://%s%s", remote_server_addr, remoto_path)
params := common.Params{
  Name: server_name + time.Now().Format("15:04:05"),
}
buffer := &bytes.Buffer{}
_ = json.NewEncoder(buffer).Encode(params)
// 創建一個http.Request
req, err := http.NewRequest(http.MethodPost, url, buffer)
common.PanicError(err)

// 創建一個Tracer
reqSpan, err := tracer.CreateExitSpan(context.Request.Context()"invoke - "+remote_server_name, fmt.Sprintf("localhost:8082/user/info"), func(header string) error {
  req.Header.Set(propagation.Header, header)
  return nil
})
common.PanicError(err)
// 設置爲HttpClient類型
reqSpan.SetComponent(2)
// rpc 調用
reqSpan.SetSpanLayer(v3.SpanLayer_RPCFramework) 

reqSpan.Tag(go2sky.TagHTTPMethod, http.MethodPost)
reqSpan.Tag(go2sky.TagURL, url)

// 記錄開始日誌
reqSpan.Log(time.Now()"[HttpRequest]", fmt.Sprintf("開始請求,請求服務:%s,請求地址:%s,請求參數:%+v", remote_server_name, url, params))
// 直接去調用請求
resp, err := http.DefaultClient.Do(req)
common.PanicError(err)
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
common.PanicError(err)
// 記錄響應日誌
reqSpan.Log(time.Now()"[HttpRequest]", fmt.Sprintf("結束請求,響應結果: %s", body))
// 結束。其實還應該記錄狀態碼
reqSpan.End()
res := map[string]interface{}{}
err = json.Unmarshal(body, &res)
common.PanicError(err)
result = append(result, res)

其實看樹狀圖很簡單的就看出來了。

image-20200913174055349

4、LocalSpan

LocalSpan可以理解爲進程 / 線程內部的 Span,它如果沒有指定,父親,是沒有父子交互的,也就是這個鏈路串不起來。

span, ctx, err := tracer.CreateLocalSpan(context.Request.Context())
common.PanicError(err)
// 設置id
span.SetOperationName("UserInfo")
context.Request = context.Request.WithContext(ctx)
params := new(common.Params)
err = context.BindJSON(params)
common.PanicError(err)

span.Log(time.Now()"[UserInfo]", fmt.Sprintf(server_name_2+" satrt, req : %+v", params))
local := gin.H{
  "msg": fmt.Sprintf(server_name_2+" time : %s", time.Now().Format("15:04:05")),
}
context.JSON(200, local)
span.Log(time.Now()"[UserInfo]", fmt.Sprintf(server_name_2+" end, resp : %s"local))
span.End()

5、源碼分析

// CreateLocalSpan creates and starts a span for local usage
func (t *Tracer) CreateLocalSpan(ctx context.Context, opts ...SpanOption) (s Span, c context.Context, err error) {
  // ctx爲空 異常
 if ctx == nil {
  return nil, nil, errParameter
 }
  // 是否是一個不需要操作的ctx,這個意思就是這個ctx的是一個NoopSpan,需要兒子不需要任何操作
 if s, c = t.createNoop(ctx); s != nil {
  return
 }
  // 初始化一個
 ds := newLocalSpan(t)
 for _, opt := range opts {
  opt(ds)
 }
  // 這個就是去獲取ctx的一個segmentSpan,主要就是trace_id
 parentSpan, ok := ctx.Value(ctxKeyInstance).(segmentSpan)
 if !ok {
  parentSpan = nil
 }
  // 核心步驟
 s, err = newSegmentSpan(ds, parentSpan)
 if err != nil {
  return nil, nil, err
 }
 return s, context.WithValue(ctx, ctxKeyInstance, s), nil
}

s, err = newSegmentSpan(ds, parentSpan)

type segmentSpanImpl struct {
 defaultSpan // span
 SegmentContext // 上下文信息
}
func newSegmentSpan(defaultSpan *defaultSpan, parentSpan segmentSpan) (s segmentSpan, err error) {
 ssi := &segmentSpanImpl{
  defaultSpan: *defaultSpan,
 }
  // span 其實所有的都是segmentSpan,除了noop,所以核心邏輯在這裏
 err = ssi.createSegmentContext(parentSpan)
 if err != nil {
  return nil, err
 }
  // 創建父親節點,一個進程內的一個鏈路只能創建一次
 if parentSpan == nil || !parentSpan.segmentRegister() {
  rs := newSegmentRoot(ssi)
  err = rs.createRootSegmentContext(parentSpan)
  if err != nil {
   return nil, err
  }
  s = rs
 } else {
  s = ssi
 }
 return
}

err = ssi.createSegmentContext(parentSpan)

func (s *segmentSpanImpl) createSegmentContext(parent segmentSpan) (err error) {
 if parent == nil {// 父親爲空
    // 創建一個新的上下文
  s.SegmentContext = SegmentContext{}
  if len(s.defaultSpan.Refs) > 0 {// ref>0,後面解釋
   s.TraceID = s.defaultSpan.Refs[0].TraceID// 獲取第一個trace_id
  } else {
   s.TraceID, err = idgen.GenerateGlobalID()// 通過id生成器,生產一個id
   if err != nil {
    return err
   }
  }
 } else {
    // 上下文信息傳遞
  s.SegmentContext = parent.context()
  s.ParentSegmentID = s.SegmentID
  s.ParentSpanID = s.SpanID
  s.SpanID = atomic.AddInt32(s.Context().spanIDGenerator, 1)
 }
 if s.SegmentContext.FirstSpan == nil {
  s.SegmentContext.FirstSpan = s
 }
 return
}

所以我想說的是所有的都是LocalSpan

// CreateEntrySpan creates and starts an entry span for incoming request
func (t *Tracer) CreateEntrySpan(ctx context.Context, operationName string, extractor propagation.Extractor) (s Span, nCtx context.Context, err error) {
 if ctx == nil || operationName == "" || extractor == nil {
  return nil, nil, errParameter
 }
 if s, nCtx = t.createNoop(ctx); s != nil {
  return
 }
 header, err := extractor()
 if err != nil {
  return
 }
 var refSc *propagation.SpanContext
 if header != "" {
    //解析頭部
  refSc = &propagation.SpanContext{}
  err = refSc.DecodeSW8(header)
  if err != nil {
   return
  }
 }
  // 設置WithContext,申明父親的trace_id
 s, nCtx, err = t.CreateLocalSpan(ctx, WithContext(refSc), WithSpanType(SpanTypeEntry))
 if err != nil {
  return
 }
 s.SetOperationName(operationName)
 return
}

5、Go 和 Java 應用一起 Happy

上面的 Server-1 和 Server-2 代碼不變,新增一個 Java 的請求

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;
import java.util.Map;

@SpringBootApplication
public class SpringSkywalkingApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringSkywalkingApplication.class, args);
    }
    @Bean
    public RestTemplate rt() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(new SimpleClientHttpRequestFactory());
        return restTemplate;
    }
    @RestController
    @RequestMapping("/rpc")
    public static class DemoRpcController {
        @Autowired
        private RestTemplate template;
        @GetMapping("/go")
        public Map invoke() {
            return template.getForObject("http://localhost:8081/trace", Map.class);
        }
    }
}

啓動的時候,加上 JVM 參數

-javaagent:/Users/dong/software/apache-skywalking-apm-bin/agent/skywalking-agent.jar
-Dskywalking.agent.service_name=java-api
-Dskywalking.collector.backend_service=localhost:11800

然後就可以愉快的使用了

➜  java curl http://localhost:8888/rpc/go
{"msg":[{"msg":"server-2 time : 18:36:30"},{"msg":"server-2 time : 18:36:30"}]}

可以發現成功調用!!!!!!!

image-20200913183754478

調用圖:

image-20200913184145812

6、改造 Go-Skywalking

我們這邊主要的編程語言是 Go,業務中並未使用 op-tracing 規範,只是通過請求頭攜帶 trace_id。大致邏輯就是 server-1 調用 server-2 會攜帶一個請求頭,trace_id=1, 當 server-2 收到的時候創建一個並且 append server-2 的 trace_id,此時 trace_id=2_1 ,我們所有的 func 都傳遞了 ctx,所以很輕鬆的在日誌裏記錄 trace_id。

logger.Infoc(ctx, "[GetCityMonthlyXXXXList] start,params=%+v"&request.Params)
2020/07/25 11:28:35 service_request.go:119: [INFO] [trace_id=xxxxxx] [HttpRequest] start,server_name=xxxxx,url=http://xxxx:13071/ucenter/v1/user/black_detail,params=map[type:2 user_id:907744]

但是我們業務只有一個簡單的依靠 elk 進行的過濾,並沒有調用圖,全鏈路圖,並不能找到出現了哪些問題。最主要的是,你都不知道哪個服務調用你,因此造成業務問題追查起來比較難。

因此想依靠 skywalking 的收集器將日誌收集起來,做統一的展示!

1、基於異步模式

1、業務的 Logger 不變,啓動一個服務消費 Logger 的日誌 (業務中是 kafka),採用 sky-walking 的 go 客戶端

2、我們只需要改造 reporter 即可,也就是隻需要將 reporter 改造成 logger 方式

3、看官方提供的簡單的 reporter, NewLogReporter()

還是上面的程序,我們改成日誌

rp, err := reporter.NewLogReporter()
common.PanicError(err)

然後再看一下我們的輸出

go2sky-log2020/09/13 19:17:45 Segment-bfd2097af5b211eab1203c15c2d23e34: [
    {
        "Refs": null,
        "StartTime""2020-09-13T19:17:45.942056+08:00",
        "EndTime""2020-09-13T19:17:45.968277+08:00",
        "OperationName""invoke - server-2",
        "Peer""localhost:8082/user/info",
        "Layer": 2,
        "ComponentID": 2,
        "Tags"[
            {
                "key""http.method",
                "value""POST"
            },
            {
                "key""url",
                "value""http://localhost:8082/user/info"
            }
        ],
        "Logs"[
            {
                "time": 1599995865968,
                "data"[
                    {
                        "key""[HttpRequest]",
                        "value""開始請求,請求服務:server-2,請求地址:http://localhost:8082/user/info,請求參數:{Name:server-119:17:45}"
                    }
                ]
            },
            {
                "time": 1599995865968,
                "data"[
                    {
                        "key""[HttpRequest]",
                        "value""結束請求,響應結果: {\"msg\":\"server-2 time : 19:17:45\"}"
                    }
                ]
            }
        ],
        "IsError": false,
        "SpanType": 1,
        "TraceID""bfd2093ef5b211eab1203c15c2d23e34",
        "SegmentID""bfd2097af5b211eab1203c15c2d23e34",
        "SpanID": 2,
        "ParentSpanID": 1,
        "ParentSegmentID""bfd2097af5b211eab1203c15c2d23e34"
    },
 // ... 
]

如果我們能解析出來,發送給 sky 就可以了,簡單試試

但是,實際上是不可以的,需要對其進行二次加工。。。。。。。。。。。

7、報警模塊

https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/backend-alarm.md#list-of-all-potential-metrics-name

https://juejin.im/post/6844903954770313224

參考

https://github.com/apache/skywalking

https://skyapm.github.io/document-cn-translation-of-skywalking/zh/8.0.0/

Demo 地址

https://gitee.com/Anthony-Dong/skywalking-demo

轉自:AnthonyDong

juejin.cn/post/6871928187123826702

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/UiVwasmzzZeuOs1rJ_K2yA