github.com/jtzjtz/kit@v1.0.2/middleware/access_log/access_log.go (about)

     1  package access_log
     2  
     3  /*日志采集*/
     4  import (
     5  	"bytes"
     6  	"fmt"
     7  	"github.com/gin-gonic/gin"
     8  	"github.com/json-iterator/go"
     9  	"github.com/jtzjtz/kit/http"
    10  	"github.com/jtzjtz/kit/log"
    11  	"io/ioutil"
    12  	"os"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  var json = jsoniter.ConfigCompatibleWithStandardLibrary
    19  var accessChannel chan *accessResponse
    20  
    21  const (
    22  	LOG_ROOT_PATH = "/data/accesslog/"
    23  	LOG_EXTENSION = ".log"
    24  )
    25  
    26  type result struct {
    27  	Code int         `json:"code"`
    28  	Msg  string      `json:"msg"`
    29  	Data interface{} `json:"data"`
    30  }
    31  type accessResponse struct {
    32  	Ctx             *gin.Context
    33  	RequestBody     string
    34  	ResponseBody    string
    35  	RequestTime     string
    36  	RequestTimeUnix string
    37  	HandleCount     int
    38  }
    39  type bodyLogWriter struct {
    40  	gin.ResponseWriter
    41  	body *bytes.Buffer
    42  }
    43  
    44  func (w bodyLogWriter) Write(b []byte) (int, error) {
    45  	w.body.Write(b)
    46  	return w.ResponseWriter.Write(b)
    47  }
    48  func (w bodyLogWriter) WriteString(s string) (int, error) {
    49  	w.body.WriteString(s)
    50  	return w.ResponseWriter.WriteString(s)
    51  }
    52  
    53  type accessLogParams struct {
    54  	channelBufferNum    int32  //channel 缓存区
    55  	goroutineHandleNum  int32  //goruntine 协程启动数量,消费处理log日志信息
    56  	logFullRes          bool   //是否记录全部响应数据
    57  	timeOutSendMail     int32  //发送邮件的过期时间
    58  	noNeedRouteSendMail string //不需要发送邮件地址路由地址
    59  }
    60  
    61  //设置默认值
    62  var ap = accessLogParams{
    63  	channelBufferNum:    5,
    64  	goroutineHandleNum:  1,
    65  	logFullRes:          false,
    66  	timeOutSendMail:     0,
    67  	noNeedRouteSendMail: "",
    68  }
    69  
    70  //设置参数
    71  func SetAp(channelBufferNum, goroutineHandleNum int32, logFullRes bool) {
    72  	ap.channelBufferNum = channelBufferNum
    73  	ap.goroutineHandleNum = goroutineHandleNum
    74  	ap.logFullRes = logFullRes
    75  }
    76  
    77  //设置请求超时处理参数
    78  func SetTimeOutAp(timeOutSendMail int32, noNeedRouteSendMail string) {
    79  	ap.timeOutSendMail = timeOutSendMail
    80  	ap.noNeedRouteSendMail = noNeedRouteSendMail
    81  }
    82  
    83  //异步处理日志信息
    84  func handleAccessLog(env, serviceName string, acsChannel chan *accessResponse) {
    85  	for access := range acsChannel {
    86  		ctx := access.Ctx
    87  
    88  		if strings.Contains(ctx.Request.RequestURI, "ping") == true || strings.Contains(ctx.Request.RequestURI, "favicon") == true || ctx.Request.RequestURI == "/" {
    89  			//过滤不需要记日志的url地址
    90  			continue
    91  		}
    92  		scmResCode := 0
    93  		scmResMsg := ""
    94  		responseBody := access.ResponseBody
    95  		if responseBody != "" {
    96  			res := result{}
    97  			err := json.Unmarshal([]byte(responseBody), &res)
    98  			if err == nil {
    99  				scmResCode = res.Code
   100  				if ap.logFullRes == true {
   101  					scmResMsg = responseBody
   102  				} else {
   103  					scmResMsg = res.Msg
   104  				}
   105  			}
   106  		}
   107  		requestBody := access.RequestBody
   108  		requestTime, _ := strconv.ParseInt(access.RequestTimeUnix, 10, 64)
   109  
   110  		requestUri := ctx.Request.RequestURI
   111  		costTimeStr := fmt.Sprintf("%.3f", float64(time.Now().UnixNano()-requestTime)/1e9)
   112  		//costTime, _ := strconv.ParseFloat(costTimeStr, 64)
   113  		//if ap.timeOutSendMail > 0 && int32(costTime) > ap.timeOutSendMail && strings.Contains(ap.noNeedRouteSendMail, requestUri) == false {
   114  		//	emailContent := fmt.Sprintf("请求地址:%s 请求方式:%s <br/>请求时间:%s<br/>请求耗时:%#v<br/> 请求参数:%s<br/> 响应状态码:%d<br/> 响应数据:%s<br/>", ctx.Request.RequestURI, ctx.Request.Method, access.RequestTime, costTime, requestBody, ctx.Writer.Status(), scmResMsg)
   115  		//	println(emailContent) //发送邮件通知
   116  		//}
   117  		accessMap := make(map[string]string)
   118  
   119  		accessMap["X-App-Info"] = ctx.GetHeader("X-App-Info")
   120  		accessMap["X-SESSION-ID"] = ctx.GetHeader("X-SESSION-ID")
   121  
   122  		accessMap["request_cost"] = costTimeStr //请求消耗时间
   123  		accessMap["method"] = ctx.Request.Method
   124  		if ctx.GetHeader("Authorization") != "" {
   125  			accessMap["token"] = ctx.GetHeader("Authorization")
   126  		} else {
   127  			accessMap["token"] = ctx.GetHeader("X-Token")
   128  
   129  		}
   130  		accessMap["app_ver"] = ctx.GetHeader("X-App-Version")
   131  		accessMap["request_length"] = strconv.Itoa(int(ctx.Request.ContentLength))
   132  		accessMap["content_type"] = ctx.ContentType()
   133  		accessMap["client_ip"] = ctx.GetHeader("X-Real-Ip")
   134  		accessMap["request_timestamp"] = access.RequestTime
   135  		accessMap["request_body"] = requestBody // post get 的参数
   136  		accessMap["host"] = ctx.Request.Host
   137  		accessMap["service_name"] = serviceName //微服务名字
   138  		hostName, _ := os.Hostname()
   139  		accessMap["server_name"] = hostName //当前服务器hostname
   140  		accessMap["Forwarded-Host"] = ctx.GetHeader("X-Forwarded-For")
   141  		accessMap["request_uri"] = requestUri
   142  		accessMap["response_status"] = strconv.Itoa(ctx.Writer.Status()) //http status
   143  		accessMap["user_agent"] = ctx.Request.UserAgent()                //用户端类型
   144  		accessMap["http_referer"] = ctx.Request.Referer()
   145  		accessMap["response_code"] = strconv.Itoa(scmResCode) //业务code
   146  		accessMap["response_msg"] = scmResMsg                 //返回的数据
   147  		accessMap["traceid"] = http.GetTraceId(ctx)
   148  		accessMap["customerid"] = ctx.GetHeader("CustomerId")
   149  		accessMap["env"] = env
   150  		log.LogAccess(accessMap)
   151  
   152  	}
   153  }
   154  
   155  //记录日志中间件
   156  func AccessLogMiddleware(env, projectName string) gin.HandlerFunc {
   157  	accessChannel = make(chan *accessResponse, ap.channelBufferNum)
   158  	for i := 0; i < int(ap.goroutineHandleNum); i++ {
   159  		go func(i int) {
   160  			handleAccessLog(env, projectName, accessChannel)
   161  		}(i)
   162  	}
   163  
   164  	return func(ctx *gin.Context) {
   165  		requestTime := time.Now().String()
   166  		requestTimeUnix := strconv.FormatInt(time.Now().UnixNano(), 10)
   167  		requestBody := ""
   168  		if ctx.Request.Method == "POST" {
   169  			requestBodyByte, err := ctx.GetRawData()
   170  			if err == nil {
   171  				requestBody = string(requestBodyByte)
   172  				ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(requestBodyByte)) //把读过的字节流重新放到body
   173  			}
   174  		}
   175  		blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer}
   176  		ctx.Writer = blw
   177  		ctx.Next()
   178  		accessChannel <- &accessResponse{Ctx: ctx, RequestBody: requestBody, ResponseBody: blw.body.String(), RequestTime: requestTime, RequestTimeUnix: requestTimeUnix}
   179  	}
   180  }