使用Skywalking-go自动进行监控增强
版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | https://vearne.cc
1. 引言
使用Skywalking-go,我们可以基于Golang build提供的-toolexec参数,实现了编译期劫持,达到在对代码几乎无侵入的情况下,实现埋点。
Skywalking对大量的框架(比如: gin)提供了支持, 然后这种支持仅限于增加了tracing信息。我们知道完整的监控往往离不开3个重要部分Metrics、Tracing、Logging。那些需要埋点的位置,往往这3种信息都是需要的,是否可以修改Skywalking-go,来达到代码增强的过程同时添加Metrics、Tracing、Logging呢?答案是肯定的。
本文的代码可参考
1)改动后的Skywalking-go
注意: 如果读者有自己改动的计划,建议好好阅读官方文档,并参考萌叔的改动代码
git diff f9929c6c9275b12f70ec2368544244f27a92f768
2) 测试项目
2.解决问题
2.1 Tracing
tracing保持不变
2.2 Logging
Skywalking默认提供了对zap
、logrus
的劫持,因此可以直接在插件对应的拦截器中增加对应的日志即可
package gin
import (
"fmt"
"github.com/gin-gonic/gin"
// 需要增加的import
"github.com/apache/skywalking-go/plugins/core/log"
"github.com/apache/skywalking-go/plugins/core/operator"
"github.com/apache/skywalking-go/plugins/core/prom"
"github.com/apache/skywalking-go/plugins/core/tracing"
)
type HTTPInterceptor struct {
}
...
func (h *HTTPInterceptor) AfterInvoke(invocation operator.Invocation, result ...interface{}) error {
...
// add logging
log.Infof("url:%v", context.Request.URL.Path)
...
return nil
}
经过上述改动以后,插件拦截器新增的日志,也会输出到和业务日志相同的位置
2024-01-08 13:51:24 | info | url:/ping | {"SW_CTX": "[sw-test,e6966b62ade911eeb5565626e1cdcfe1@10.2.130.14,N/A,N/A,-1]"}
2024-01-08 13:51:24 | info | sw-test/main.go:76 | ping | {"setRes": 0, "SW_CTX": "[sw-test,e6966b62ade911eeb5565626e1cdcfe1@10.2.130.14,e6964894ade911eeb5565626e1cdcfe1.98.39164466848770001,e6964894ade911eeb5565626e1cdcfe1.98.39164466848770002,0]"}
可以看出每条日志都被添加了tracing信息,这有助于我们把tracing和对应的日志关联起来
zap
日志增强的示例
通过劫持之后,plugin和业务app使用的是同一个logger
func New(core zapcore.Core, options ...Option) (skywalking_result_0 *Logger) {
defer skywalking_enhance_automaticLoggerBindgo_uber_org_zap_New(&core,&options,&skywalking_result_0);
/*
这个defer函数把返回的新创建的logger对象skywalking_result_0,保存在一个全局变量中
"New", "NewNop", "NewProduction", "NewDevelopment", "NewExample"也做了类似动作
*/
if core == nil {
return NewNop()
}
log := &Logger{
core: core,
errorOutput: zapcore.Lock(os.Stderr),
addStack: zapcore.FatalLevel + 1,
clock: zapcore.DefaultClock,
}
return log.WithOptions(options...)
}
2.2 Metrics
Skywalking-go也有提供的Metrics,但它的实现比较弱,另外萌叔的项目几乎都使用的是Prometheus + Grafana,因此我们需要在插件中加入Prometheus的Metrics.
注意: 以下只叙述概要,完整代码见vearne/skywalking-go
1) PromOperator
首先需要修改Operator的全局定义
type Operator interface {
Tracing() interface{} // to TracingOperator
Logger() interface{} // to LogOperator
Tools() interface{} // to ToolsOperator
DebugStack() []byte // Getting the stack of the current goroutine, for getting details when plugin broken.
Entity() interface{} // Get the entity of the service
Metrics() interface{} // to MetricsOperator
LogReporter() interface{} // to LogReporter
// 增加PromMetrics
PromMetrics() interface{} // to PromOperator
}
PromOperator
的接口定义位于 prom.go
PromOperator
的接口实现为PromWrapper
, 位于prometheus.go
2)在plugin中(此处为gin) 使用Metrics的示例
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/apache/skywalking-go/plugins/core/log"
"github.com/apache/skywalking-go/plugins/core/operator"
// 需要增加的import
"github.com/apache/skywalking-go/plugins/core/prom"
"github.com/apache/skywalking-go/plugins/core/tracing"
)
type HTTPInterceptor struct {
}
...
func (h *HTTPInterceptor) AfterInvoke(invocation operator.Invocation, result ...interface{}) error {
// add metrics
httpReqTotal := prom.GetOrNewCounterVec(
"gin_requests_total",
"Total number of gin HTTP requests made",
[]string{"method", "path", "status"},
)
httpReqTotal.With(map[string]string{
"method": context.Request.Method,
"path": context.Request.URL.Path,
"status": fmt.Sprintf("%d", context.Writer.Status()),
}).(prom.Counter).Inc()
return nil
}
3)劫持 promhttp.Handler()
一般我们对prometheus的使用方式如下:
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log"
"net/http"
)
var cpuTemp prometheus.Gauge
func main() {
cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "cpu_temperature_celsius",
Help: "Current temperature of the CPU.",
})
prometheus.MustRegister(cpuTemp)
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":9090", nil))
}
prometheus.MustRegister()
会把监控指标cpuTemp注册到prometheus.DefaultRegister
上,为了使得插件中DefaultRegister和业务应用中使用Register
是同一个,需要为Prometheus开发一个代码增强插件,劫持promhttp.Handler()
import (
"github.com/apache/skywalking-go/plugins/core/log"
"github.com/apache/skywalking-go/plugins/core/operator"
"github.com/apache/skywalking-go/plugins/core/prom"
"github.com/prometheus/client_golang/prometheus"
)
type RegistryInterceptor struct {
}
func (h *RegistryInterceptor) BeforeInvoke(invocation operator.Invocation) error {
return nil
}
func (h *RegistryInterceptor) AfterInvoke(invocation operator.Invocation, result ...interface{}) error {
log.Infof("-----register prometheus------, %p\n", prometheus.DefaultRegisterer)
prom.SetRegistry(prometheus.DefaultRegisterer)
return nil
}
3. 业务应用
package main
import (
"context"
// 必须引入依赖包
_ "github.com/apache/skywalking-go"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/redis/go-redis/v9"
zlog "github.com/vearne/zaplog"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
"net/http"
)
var rdb *redis.Client
func main() {
zlog.InitLogger("/tmp/aa.log", "debug")
// 添加Prometheus的相关监控
// /metrics
go func() {
r := gin.Default()
/*
promhttp.Handler()调用后
prometheus.DefaultRegister会被注入到全局变量中
*/
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
r.Run(":9090")
}()
//prometheus.NewRegistry()
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
...
})
r.GET("/hello2", func(c *gin.Context) {
...
})
r.GET("/ping", func(c *gin.Context) {
...
})
r.Run(":8000")
}
注意: go.mod中 需要用replace引入萌叔改动后的Skywalking-go
replace github.com/apache/skywalking-go => github.com/vearne/skywalking-go v0.3.0-1
require (
github.com/apache/skywalking-go v0.3.0-1
...
)
结果:监控指标使用正常
总结:
Skywalking不仅仅可以用来增加tracing信息,而是应该被视为一个非常强大的代码增强工具,它还有很大的潜力。