задний план:
Прежде всего, нам нужно представить проекты Google с открытым исходным кодом ветеранского уровня:Dapper
1. Введение
блог woo woo woo.cn на.com/stingy/afraid/AP…, эта статья является хорошим введением
Фактически для текущей системы APM (Application Performance Management) включены следующие три пункта, будь то системные показатели (Prometheus), либо лог (ELK), либо отслеживание ссылок (Skywalking, zipkin, jeager), все постепенно приближаются к середине
Это основная система отслеживания ссылок с открытым исходным кодом на рынке. Мы можем взглянуть на общую разницу. В настоящее время последние три являются наиболее популярными. Они на самом деле совместимы друг с другом, потому что реализована спецификация opentracing!
Среди наиболее рекомендуемых:Jeager(проекты, инкубированные в 2017 г.), причина в том, что по мереВыпускная программа CNCF, системная архитектура пространства роста и оригинальный звук облака более совместимы.
Есть много других, которые не были представлены ниже, например, Meituan’sCAT, фбxhprof, АлиСоколиный глаз
2. спецификация открытой трассировки
Ссылка на официальный сайт открыть tracing.IO/guides/Java…
Каноническая ссылка GitHub.com/открыть трассировку…
Отслеживание ссылок (2) — сбор данных распределенной системы отслеживания ссылок, Эти две статьи относительно хороши, они разработаны на основе открытой спецификации трассировки.
1. Архитектура
Официальная схема дизайна находится здесь: Это общая схема архитектуры дизайна.
- 1. Трассировка на самом деле коллектор
- 2. Метрика — это системный мониторинг, вроде какой-то информации о JVM, но не дает информации о Go
- 3. Ядром является его наблюдаемая аналитическая платформа, которая предоставляет функции анализа и запросов, недоступные во многих фреймворках ведения журналов/трассировки (поэтому я также полагаю, что ее можно интегрировать в бизнес с наименьшими затратами. Наш язык разработки — Go , но слишком много скрытого кода, предоставляемого go-sdk, который не подходит для прямого использования, за которым следует слишком сильная связь с нашим бизнесом, поэтому я не рекомендую здесь прямое использование, помните о дальнейшей инкапсуляции API)
2. Быстрый старт
- 1. Установите Skywalking 8.0.1,woohoo.apache.org/wish you/closer. …
- 2. Прочитайте документацию 8.0,небо APM.GitHub.IO/document - можно...
- 3. Скачайте Go-SDK,GitHub.com/sky APM/go2 — это…, из которых v0.4.0 поддерживает только версию 8.0, так что нужно быть внимательным.
На основе подготовки апелляции мы можем быстро разработать зонды Go
- 1. Начните ходить по небу
➜ bin ./startup.sh
SkyWalking OAP started successfully!
SkyWalking Web Application started successfully!
3. Скрытая точка Go для межсервисных вызовов
Для Java-разработчиков, использующих SkyWalking, проще говоря, часто используемые фреймворки разработки были автоматически похоронены, но для Go все нужно хоронить вручную, и код нужно писать самим, а не официальным людям.
В настоящее время Go поддерживает фреймворк Gin, но помните, что это всего лишь демонстрационная версия.
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))
}
сервер-1 вызывает сервер-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))
}
Окончательный эффект вызова следующий: это простая диаграмма последовательности
Это простой вызов между процессами.
4. Некоторые детали, которые вам нужно знать
Go-sky's sdk, 0.4 сильно изменился по сравнению с 0.3, большая часть которого в том, что есть более персонализированные настройки функций, а вторая - в поддержке протокола v3, о чем будет рассказано позже.
1. Репортер
используется для отправки данных
Это процесс инициализации, очень простой
reporter.NewGRPCReporter(common.SkyAddr, reporter.WithCheckInterval(time.Second))
Есть в основном три основных метода!
Запустить, отправить, закрыть, три метода. . . очень просто и удобно
2. трассер
Tracer используется для трассировки и соответствия спецификации открытой трассировки.
// 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. пролет (сердцевина)
пролет является ядром нашей главной заботы, которую можно понимать как ядро всех
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 на самом деле является структурой ключ-значение.
2. Диапазон входа
Как правило, он используется для ввода службыEntrySpan
Обычно это относится, например, к вводуserver-1
илиserver-2
, чтобы создать диапазон входа
//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, а затем продолжить логирование
3. Выходной диапазон
На самом деле это нельзя назвать выходом из Span.Это всего лишь сервисный вызов.Можно понять,что мой сервис А вызывает сервис Б,а А нужно использовать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)
На самом деле, глядя на древовидную диаграмму, это очень просто увидеть.
4. Локальный диапазон
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
Приведенные выше коды Сервер-1 и Сервер-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"}]}
Удачный звонок можно найти! ! ! ! ! ! !
График звонков:
6. Модернизация Go-Skywalking
Основной язык программирования с нашей стороны — Go, спецификация op-tracing в бизнесе не используется, но trace_id переносится в заголовок запроса. Общая логика заключается в том, что когда сервер 1 вызывает сервер 2, он будет нести заголовок запроса, trace_id = 1. Когда сервер 2 получает его, он создает и добавляет trace_id сервера 2. В это время trace_id = 2_1 , а все наши функции передаются .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, и нет графа вызовов, полного графа ссылок, и мы не можем выяснить, какие проблемы возникли. Главное, вы не знаете, какая служба вам звонит, поэтому сложнее отследить бизнес-проблемы.
Поэтому я хочу положиться на сборщик прыжков по небу, чтобы собрать логи и сделать единый дисплей!
1. На основе асинхронного режима
1. Регистратор бизнеса остается без изменений, запустите службу для использования журналов регистратора (kafka в бизнесе) и используйте клиент для ходьбы по небу.
2. Нам нужно только преобразовать репортер, то есть нам нужно только преобразовать репортер в метод логгера
3. Посмотрите на простой репортер, предоставленный чиновником,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"
},
// ...
]
Если мы сможем разобрать его и отправить в небо, просто попробуй
Однако на самом деле это невозможно, и его нужно обрабатывать дважды. . . . . . . . . . .
7. Модуль сигнализации
Ссылаться на
GitHub.com/Apache/Просто спросите…
небо APM.GitHub.IO/document - можно...