gomicro v2--03 日志和中间件

一、如何自定义zap日志

Uber-go Zap(是非常快的、结构化的,分日志级别的Go日志库。

1.简单使用

(1)安装:

go get -u go.uber.org/zap

(2)使用:

2.自定义zap日志类

我经常需要把日志,打印到多个地方,如本地、控制台、远程日志系统如elk(一般通过kafka中转);本地日志经常需要日志切开,我们可以把这些特性放到一个自定义日志类中。从而定义出属于自己系统的日志类。

(1)安装kafka

docker run -d --name zookeeper -p 2181:2181 wurstmeister/zookeeper

docker run -d --name kafka --publish 9092:9092 --link zookeeper --env KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 --env KAFKA_ADVERTISED_HOST_NAME=localhost --env KAFKA_ADVERTISED_PORT=9092 wurstmeister/kafka

(2) 编写日类

package zaplog

import (
	"github.com/Shopify/sarama"
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"log"
	"os"
)

var zaplogger *zap.SugaredLogger

var (
	FileName       = "./latest.log"
	KafkaDsn       = "localhost:9092"
	FileMaxSize    = 10
	FileMaxBackups = 5
	FileMaxAge     = 30
	IsCompress     = false
)


func InitLogger() {
	encoder := getEncoder() //日志格式化方式
	//文件流
	fileWriteSyncer := getFileLogWriter()
	fileCore := zapcore.NewCore(encoder, fileWriteSyncer, zapcore.DebugLevel)
	//控制台流
	consoleWriteSyncer := getConsoleLogWriter()
	consoleCore := zapcore.NewCore(encoder, consoleWriteSyncer, zapcore.DebugLevel)
	卡夫卡流
	kafkaWriteSyncer := getKafkaLogWriter()
	kafkaCore := zapcore.NewCore(encoder, kafkaWriteSyncer, zapcore.DebugLevel)

	logger := zap.New(zapcore.NewTee(fileCore, consoleCore, kafkaCore))
	zaplogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	return zapcore.NewConsoleEncoder(encoderConfig)
}

func getFileLogWriter() zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   FileName,
		MaxSize:    FileMaxSize,
		MaxBackups: FileMaxBackups,
		MaxAge:     FileMaxAge,
		Compress:   IsCompress,
	}
	return zapcore.AddSync(lumberJackLogger)
}

func getConsoleLogWriter() zapcore.WriteSyncer {
	return zapcore.AddSync(os.Stdout)
}

type KafkaWriter struct {
	topic        string
	syncProducer sarama.SyncProducer
}

func (kp *KafkaWriter) Write(p []byte) (n int, err error) {
	_, _, err = kp.syncProducer.SendMessage(&sarama.ProducerMessage{
		Topic: kp.topic,
		Value: sarama.ByteEncoder(p),
	})
	if err != nil {
		log.Println(err)
		return 0, err
	}
	//fmt.Println("send msgs:",string(p))
	return len(p), nil

}

func getKafkaLogWriter() zapcore.WriteSyncer {
	config := sarama.NewConfig()
	config.Producer.RequiredAcks = sarama.WaitForLocal
	config.Producer.Return.Successes = true
	syncProducer, err := sarama.NewSyncProducer([]string{KafkaDsn}, config)
	if err != nil {
		log.Fatal(err)
	}
	return zapcore.AddSync(&KafkaWriter{topic: "zaplog", syncProducer: syncProducer})
}

//对外接口,这几个函数只是为了方便使用,也可以用一个名字很短的函数直接返回 zaplogger

func Info(args ...interface{}) {
	zaplogger.Info(args)
}

func Error(args ...interface{}) {
	zaplogger.Error(args)
}

func Debug(args ...interface{}) {
	zaplogger.Debug(args)
}

func Warn(args ...interface{}) {
	zaplogger.Warn(args)
}

func Fatal(args ...interface{}) {
	zaplogger.Fatal(args)
}

func With(args ...interface{}) *zap.SugaredLogger {
	return zaplogger.With(args)
}

(3)编写单元测试

package zaplog

import (
	"testing"
)

func TestWriter(t *testing.T) {
	InitLogger()
	Info("hhhh","日志打出来了")
	Info("wwww","日志打出来了喔")
}

 二、如何扩展go micro的扩展

1.plugin

Go Micro 构建于 Go 接口之上. 因此这些接口的实现是可插拔的.

默认情况下, go-micro 只提供核心上每个接口的几个实现, 但它是完全可插拔的. 已经有几十个插件, 在这里 github.com/micro/go-plugins 欢迎贡献! 插件可确保 Go Micro 服务在技术发展后长期保持生存。如果要集成插件, 只需将它们链接到单独的文件中并重新生成

创建 plugins.go 文件并导入所需的插件:

package main

import (
        // consul registry
        _ "github.com/micro/go-plugins/registry/consul"
        // rabbitmq transport
        _ "github.com/micro/go-plugins/transport/rabbitmq"
        // kafka broker
        _ "github.com/micro/go-plugins/broker/kafka"
)

通过包含插件文件来构建应用程序:

# assuming files main.go and plugins.go are in the top level dir

# For local use
go build -o service *.go

插件的标志用法:

service --registry=etcdv3 --transport=nats --broker=kafka

或者首选的是使用环境变量来处理 12 因素的应用程序

或者您可以将插件设置为直接在代码中服务的选项

package main

import (
        "github.com/micro/go-micro/v2" 
        // consul registry
        "github.com/micro/go-plugins/registry/consul"
        // rabbitmq transport
        "github.com/micro/go-plugins/transport/rabbitmq"
        // kafka broker
        "github.com/micro/go-plugins/broker/kafka"
)

func main() {
    registry := consul.NewRegistry()
    broker := kafka.NewBroker()
    transport := rabbitmq.NewTransport()

        service := micro.NewService(
                micro.Name("greeter"),
                micro.Registry(registry),
                micro.Broker(broker),
                micro.Transport(transport),
        )

    service.Init()
    service.Run()
}

插件是一个建立在 Go 接口上的概念. 每个包都维护一个高级接口抽象. 只需实现接口并将其作为服务选项传递给它即可.

服务发现接口称为 Registry. 实现此接口的任何内容都可以用作注册表. 这同样适用于其他包.

type Registry interface {
    Register(*Service, ...RegisterOption) error
    Deregister(*Service) error
    GetService(string) ([]*Service, error)
    ListServices() ([]*Service, error)
    Watch() (Watcher, error)
    String() string
}

参阅 go-plugins 更好地了解实现细节.

2.wrapper

相当于,go web中的中间件,可以再客户端发起请求前、或者服务端处理请求前,做出一些公共的处理编写方式如下:

(1)客户端:

引入:

CustomerServer = micro.NewService(
		micro.Name(serviceName),
		micro.Version("latest"),
		micro.Registry(etcd.NewRegistry(
			registry.Addrs(addr),
		)),
	 
		micro.WrapClient(
			 ....//此处引入,你的客户端代码 如 :                
             microOpentracing.NewClientWrapper(opentracing.GlobalTracer())
		),
	)

编写:

// NewClientWrapper accepts an open tracing Trace and returns a Client Wrapper
func NewClientWrapper(ot opentracing.Tracer) client.Wrapper {
	return func(c client.Client) client.Client {
		if ot == nil {
			ot = opentracing.GlobalTracer()
		}
		return &otWrapper{ot, c}
	}
}

(2)服务端:

引入:

 
	// New Server
	CustomerServer = micro.NewService(
		micro.Name(serviceName),
		micro.Version("latest"),
		micro.Registry(etcd.NewRegistry(
			registry.Addrs(addr),
		)),
		micro.WrapHandler(
			microOpentracing.NewHandlerWrapper(opentracing.GlobalTracer()), //此处引入服务端wrapper
		),
	)
 

编写:


// NewHandlerWrapper accepts an opentracing Tracer and returns a Handler Wrapper
func NewHandlerWrapper(ot opentracing.Tracer) server.HandlerWrapper {
	return func(h server.HandlerFunc) server.HandlerFunc {
		return func(ctx context.Context, req server.Request, rsp interface{}) error {
			if ot == nil {
				ot = opentracing.GlobalTracer()
			}
			name := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
			ctx, span, err := StartSpanFromContext(ctx, ot, name)
			if err != nil {
				return err
			}
			defer span.Finish()
			if err = h(ctx, req, rsp); err != nil {
				span.LogFields(opentracinglog.String("error", err.Error()))
				span.SetTag("error", true)
			}
			return err
		}
	}
}

3.参考示例

go micro的很多功能,都可以再下面的项目中,找到参考示例

go micro 实例代码 :GitHub - microhq/examples: Go Micro examples. Moved to go-micro/examples.

三、公共日志处理

对于1.web请求的请求参数,返回值;2.grpc(go micro)公共请求参数,返回值。我们可以通过中间件的方式,再一个地方记录,而不需要每个接口和调用都处理。还有错误日志等等。

下边我们将介绍同过 gin中间件和 go micro定义这些公共日志处理。  

1. gin access日志记录中间件

 zaplog/zaplog_gin_middleware.go

package zaplog

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"time"
)

type bodyLogWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}
func (w bodyLogWriter) WriteString(s string) (int, error) {
	w.body.WriteString(s)
	return w.ResponseWriter.WriteString(s)
}

type AccessLog struct {
	RequestTime     interface{}
	RequestMethod   interface{}
	RequestUrl      interface{}
	RequestProto    interface{}
	RequestUa       interface{}
	RequestReferer  interface{}
	RequestPostData interface{}
	RequestPostBody interface{}
	RequestClientIp interface{}
	ResponseTime    interface{}
	ResponseBody    interface{}
	CostTime        interface{}
}

func ZaplogGinMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {

		bodyLogWriter := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
		c.Writer = bodyLogWriter //拦截write使可重复读取返回值
		//开始时间 ms
		startTime := time.Now().UnixNano() / 1e6
		//处理请求
		c.Next()
		responseBody := bodyLogWriter.body.String()
		//结束时间
		endTime := time.Now().UnixNano() / 1e6
		if c.Request.Method == "POST" {
			c.Request.ParseForm()
		}
		//日志格式
		var accessLog AccessLog
		accessLog.RequestTime = startTime
		accessLog.RequestMethod = c.Request.Method
		accessLog.RequestUrl = c.Request.RequestURI
		accessLog.RequestProto = c.Request.Proto
		accessLog.RequestUa = c.Request.UserAgent()
		accessLog.RequestReferer = c.Request.Referer()
		accessLog.RequestPostData = c.Request.PostForm.Encode()
		body, _ := c.GetRawData()
		accessLog.RequestPostBody = string(body)
		accessLog.RequestClientIp = c.ClientIP()
		accessLog.ResponseTime = endTime
		accessLog.ResponseBody = responseBody
		accessLog.CostTime = fmt.Sprintf("%vms", endTime-startTime)
		str, _ := json.Marshal(accessLog)

		Info("access_log  ", string(str))
	}
}

2.go micro 服务访问日志中间件

 zaplog/zaplog_gomicro_wrapper.go

package zaplog

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/micro/go-micro/v2/server"
	"time"
)

func ZaplogMicroWrapper(fn server.HandlerFunc) server.HandlerFunc {
	return func(ctx context.Context, req server.Request, rsp interface{}) error {
		//开始时间 ms
		startTime := time.Now().UnixNano() / 1e6
		err := fn(ctx, req, rsp)
		//结束时间
		endTime := time.Now().UnixNano() / 1e6
		//日志格式
		accessLogMap := make(map[string]interface{})
		accessLogMap["request_time"] = startTime
		accessLogMap["request_body"] = req.Body()
		accessLogMap["request_service"] = req.Service()
		accessLogMap["request_method"] = req.Method()
		accessLogMap["response_time"] = endTime
		accessLogMap["response"] = rsp
		accessLogMap["cost_time"] = fmt.Sprintf("%vms", endTime-startTime)
		str, _ := json.Marshal(accessLogMap)

		Info("micro_access ", string(str))
		return err
	}
}

3.使用

(1)gateway中使用

main.go

package main

import (
	"gitee.com/gudongkun/haveclient_customer/client/customer_server"
	"gitee.com/gudongkun/haveclient_customer/client/users"
	"gitee.com/gudongkun/haveclient_gateway/zaplog"
	"github.com/gin-gonic/gin"
	"io/ioutil"
)
func main () {
	
	customer_server.InitService("have.client.service.customer","127.0.0.1:2379")
	zaplog.InitLogger() //初始化zap日志

    //关闭gin 日志
	 //gin.SetMode(gin.ReleaseMode)
	gin.DefaultWriter = ioutil.Discard

	r := gin.Default()
	r.Use(zaplog.ZaplogGinMiddleware()) //记录accesslog中间件。
	r.GET("/", func(c *gin.Context){
		u,_ := users.GetUser(c.Request.Context(),1)
		c.JSON(200,u)
	})
	r.Run(":8088")   // 强指定端口,默认8088
}

(2) customer Service使用

package customer_server

import (
	"gitee.com/gudongkun/haveclient_customer/zaplog"
	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/registry"
	"github.com/micro/go-micro/v2/registry/etcd"
	microOpentracing "github.com/micro/go-plugins/wrapper/trace/opentracing/v2"
	"github.com/opentracing/opentracing-go"
)

var CustomerServer micro.Service
var ServerName string


//InitClientService 初始化函数
//参数:
// 	serviceName   string 服务名
//  addr string   etcd 地址
func InitService(serviceName, addr string) {
	ServerName = serviceName
	// New Server
	CustomerServer = micro.NewService(
		micro.Name(serviceName),
		micro.Version("latest"),
		micro.Registry(etcd.NewRegistry(
			registry.Addrs(addr),
		)),
		micro.WrapHandler(
			microOpentracing.NewHandlerWrapper(opentracing.GlobalTracer()), //服务端开启 span
			zaplog.ZaplogMicroWrapper,
		),
		micro.WrapClient(
			// 配置链路追踪为jaeger
			microOpentracing.NewClientWrapper(opentracing.GlobalTracer()), //客户端开启 span ,一般服务端或客户端开启一个就可以
		),
	)
	//CustomerServer.Init()
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值