最低成本整合 SkyWalking 到 Go 業務代碼
【導讀】Skywalking 是一款十分流行的 APM 工具,本文介紹了一種該平臺低成本接入 go 業務的方式。
- 介紹
其實對於現在 APM(Application Performance Management)系統,以下三點都包含了,不管是系統指標(Prometheus),還是日誌(ELK),還是鏈路追蹤(Skywalking,zipkin,jeager),都逐步向中間靠攏
image-20201029132759719
這個是目前市場上主流的開源的 鏈路追蹤系統,我們可以看一下大致的區別,目前比較火的其實是後三者,他們其實都可以互相兼容,因爲實現了 opentracing 規範!
其中比較推薦的是:Jeager(17 年孵化的項目),原因就是作爲 CNCF 畢業項目 ,成長空間和雲原聲的系統架構比較兼容性好。
下面還有很多沒有介紹到的,比如美團的 CAT,fb 的 xhprof , 阿里的鷹眼
image-20201029132943685
2. opentracing 規範
1、架構
官方給的設計圖在這裏 : 這個就是整體的設計架構圖.
- 快速開始
-
1、安裝 skywalking 8.0.1 ,
-
2、閱讀 8.0 的文檔,
-
3、下載 Go-SDK ,github.com/SkyAPM/go2s… ,其中 v0.4.0 才支持了 8.0 版本,所以需要注意。
基於上訴準備,我們就可以快速的開發 Go 的探針
- 1、啓動 skywalking
➜ 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