Jaeger 官方提供一个 all-in-one 的 docker 镜像,用于快速搭建测试环境
$ docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:1.18
然后,您可以导航到http://localhost:16686以访问Jaeger UI。
go get -u github.com/uber/jaeger-client-go
// NewTracer 创建一个jaeger Tracer
func NewTracer(serviceName, addr string) (opentracing.Tracer, io.Closer, error) {
cfg := jaegercfg.Configuration{
ServiceName: serviceName,
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
BufferFlushInterval: 1 * time.Second,
},
}
sender, err := jaeger.NewUDPTransport(addr, 0)
if err != nil {
return nil, nil, err
}
reporter := jaeger.NewRemoteReporter(sender)
// Initialize tracer with a logger and a metrics factory
tracer, closer, err := cfg.NewTracer(
jaegercfg.Logger(jaeger.StdLogger),
jaegercfg.Reporter(reporter),
)
return tracer, closer, err
}
func main() {
tracer, io, err := utils.NewTracer(common.SRV_NAME_APIGATEWAY, common.JAEGER_ADDR)
if err != nil {
log.L().Bg().Fatal("unable to create tracer", zap.Error(err))
}
defer io.Close()
opentracing.SetGlobalTracer(tracer)
......
}
// TracerWrapper tracer 中间件
func TracerWrapper(c *gin.Context) {
md := make(map[string]string)
spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
sp := opentracing.GlobalTracer().StartSpan(c.Request.URL.Path, opentracing.ChildOf(spanCtx))
defer sp.Finish()
if err := opentracing.GlobalTracer().Inject(sp.Context(),
opentracing.TextMap,
opentracing.TextMapCarrier(md)); err != nil {
zap.Error(err)
}
ctx := context.TODO()
ctx = opentracing.ContextWithSpan(ctx, sp)
ctx = metadata.NewContext(ctx, md)
c.Set(contextTracerKey, ctx)
c.Next()
statusCode := c.Writer.Status()
ext.HTTPStatusCode.Set(sp, uint16(statusCode))
ext.HTTPMethod.Set(sp, c.Request.Method)
ext.HTTPUrl.Set(sp, c.Request.URL.EscapedPath())
if statusCode >= http.StatusInternalServerError {
ext.Error.Set(sp, true)
}
}
在gin路由中使用tracer中间件
func InitRouter() *gin.Engine {
engine := gin.New()
// 使用zap日志库
engine.Use(ginzap.Ginzap(zap.L(), time.RFC3339, true))
engine.Use(ginzap.RecoveryWithZap(zap.L(), true))
g := engine.Group("/api/v1")
g.Use(middlewares.TracerWrapper)
{
......
}
详细用法请参考 jaeger-client-go相关文档及测试用例。
日志的实现参考自jaeger
官方给出的样例:jaeger-examples
// Logger is a simplified abstraction of the zap.Logger
type Logger interface {
Info(msg string, fields ...zapcore.Field)
Debug(msg string, fields ...zapcore.Field)
Error(logToSpan bool, msg string, fields ...zapcore.Field)
Fatal(msg string, fields ...zapcore.Field)
With(fields ...zapcore.Field) Logger
}
logger
并实现3.1的接口// logger delegates all calls to the underlying zap.Logger
type logger struct {
logger *zap.Logger
}
// Info logs an info msg with fields
func (l logger) Info(msg string, fields ...zapcore.Field) {
l.logger.Info(msg, fields...)
}
// Debug logs an debug msg with fields
func (l logger) Debug(msg string, fields ...zapcore.Field) {
l.logger.Debug(msg, fields...)
}
// Error logs an error msg with fields
func (l logger) Error(logToSpan bool, msg string, fields ...zapcore.Field) {
l.logger.Error(msg, fields...)
}
// Fatal logs a fatal error msg with fields
func (l logger) Fatal(msg string, fields ...zapcore.Field) {
l.logger.Fatal(msg, fields...)
}
// With creates a child logger, and optionally adds some context fields to that logger.
func (l logger) With(fields ...zapcore.Field) Logger {
return logger{logger: l.logger.With(fields...)}
}
span
的spanLogger
,实现打印日志到span的函数,并实现3.1的接口type spanLogger struct {
logger *zap.Logger
span opentracing.Span
}
func (sl spanLogger) logToSpan(level string, msg string, fields ...zapcore.Field) {
// TODO rather than always converting the fields, we could wrap them into a lazy logger
fa := fieldAdapter(make([]log.Field, 0, 2+len(fields)))
fa = append(fa, log.String("event", msg))
fa = append(fa, log.String("level", level))
for _, field := range fields {
field.AddTo(&fa)
}
sl.span.LogFields(fa...)
}
func (sl spanLogger) Info(msg string, fields ...zapcore.Field) {
sl.logToSpan("info", msg, fields...)
sl.logger.Info(msg, fields...)
}
func (sl spanLogger) Debug(msg string, fields ...zapcore.Field) {
sl.logToSpan("debug", msg, fields...)
sl.logger.Info(msg, fields...)
}
func (sl spanLogger) Error(setErrFlag bool, msg string, fields ...zapcore.Field) {
sl.logToSpan("error", msg, fields...)
// 由调用者决定是否设置错误警告到span
if setErrFlag {
ext.Error.Set(sl.span, setErrFlag)
}
sl.logger.Error(msg, fields...)
}
func (sl spanLogger) Fatal(msg string, fields ...zapcore.Field) {
sl.logToSpan("fatal", msg, fields...)
ext.Error.Set(sl.span, true)
sl.logger.Fatal(msg, fields...)
}
// With creates a child logger, and optionally adds some context fields to that logger.
func (sl spanLogger) With(fields ...zapcore.Field) Logger {
return spanLogger{logger: sl.logger.With(fields...), span: sl.span}
}
// Factory is the default logging wrapper that can create
// logger instances either for a given Context or context-less.
type Factory struct {
logger *zap.Logger
}
// NewFactory creates a new Factory.
func NewFactory(logger *zap.Logger) Factory {
return Factory{logger: logger}
}
// Bg creates a context-unaware logger.
func (b Factory) Bg() Logger {
return logger(b)
}
// For returns a context-aware Logger. If the context
// contains an OpenTracing span, all logging calls are also
// echo-ed into the span.
func (b Factory) For(ctx context.Context) Logger {
if span := opentracing.SpanFromContext(ctx); span != nil {
// TODO for Jaeger span extract trace/span IDs as fields
return spanLogger{span: span, logger: b.logger}
}
return b.Bg()
}
// With creates a child logger, and optionally adds some context fields to that logger.
func (b Factory) With(fields ...zapcore.Field) Factory {
return Factory{logger: b.logger.With(fields...)}
}
zap
日志组件详情请参考Gin + Zap + Go Module 的结合使用。
注意,因为这里logger
和spanLogger
是在zap.Logger
的基础上又封装了一层,所以需要配置一项:zap.AddCallerSkip(1),否则会导致打印出来的日志行号与实际产生日志的位置不一致。
// 设置日志级别、输出格式和日志文件的路径
func SetLogs(logLevel zapcore.Level, logFormat, fileName, srvName string) {
encoderConfig := zapcore.EncoderConfig{
TimeKey: TIME_KEY,
LevelKey: LEVLE_KEY,
NameKey: NAME_KEY,
CallerKey: CALLER_KEY,
MessageKey: MESSAGE_KEY,
StacktraceKey: STACKTRACE_KEY,
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder, // 大写编码器
EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 UTC 时间格式
EncodeDuration: zapcore.SecondsDurationEncoder, //
EncodeCaller: zapcore.ShortCallerEncoder, // 短路径编码器(相对路径+行号)
EncodeName: zapcore.FullNameEncoder,
}
// 设置日志输出格式
var encoder zapcore.Encoder
switch logFormat {
case LOGFORMAT_JSON:
encoder = zapcore.NewJSONEncoder(encoderConfig)
default:
encoder = zapcore.NewConsoleEncoder(encoderConfig)
}
// 添加日志切割归档功能
hook := lumberjack.Logger{
Filename: fileName, // 日志文件路径
MaxSize: MAX_SIZE, // 每个日志文件保存的最大尺寸 单位:M
MaxBackups: MAX_BACKUPS, // 日志文件最多保存多少个备份
MaxAge: MAX_AGE, // 文件最多保存多少天
Compress: true, // 是否压缩
}
core := zapcore.NewCore(
encoder, // 编码器配置
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stderr), zapcore.AddSync(&hook)), // 打印到控制台和文件
zap.NewAtomicLevelAt(logLevel), // 日志级别
)
opts := []zap.Option{
// 开启文件及行号
zap.AddCaller(),
// 开启开发模式,堆栈跟踪
zap.Development(),
zap.AddStacktrace(zapcore.FatalLevel),
// 避免zap始终将包装器(wrapper)代码报告为调用方。
zap.AddCallerSkip(1),
}
// 构造日志
logger := zap.New(core, opts...)
zap.ReplaceGlobals(logger)
_globalL = NewFactory(zap.L().With(zap.String("service", srvName)))
}
_globalL
,并提供一个获取改全局变量的方法L()
package log
var (
_globalL Factory
)
func L() Factory {
return _globalL
}
... 此处省略一万字 ...
// 校验手机号码
if err := utils.ValidateMobileNumber(req.Mobile); err != nil {
log.L().Bg().Error(false, "手机号格式错误", zap.Error(err))
utils.AbortWithError(c, errors.ErrMobileNumberIncorrect)
return
}
... 此处省略一万字 ...
if !utils.VerifyPassword(req.Passwd, user.PassWord) {
log.L().For(ctx).Error(true, "用户名或密码不正确")
return errors.New("用户名或密码不正确")
}