错误日志和访问日志是一个服务器必须支持的功能,我们教程里使用的服务器到目前为止还没有这两个功能。正好前两天也写了篇介绍logrus日志库的文章,那么今天的文章里就给我们自己写的服务器加上错误日志和访问日志的功能。在介绍添加访问日志的时候会介绍一种通过编写中间件获取HTTP响应的StausCodeBody的方法。

初始化日志记录器

我们先来做一下初始化工作,在项目里初始化记录错误日志和访问日志的记录器Logger

// ./utils/vlogpackage vlog

import (    "github.com/sirupsen/logrus"    "os")

var ErrorLog *logrus.Loggervar AccessLog *logrus.Loggervar errorLogFile = "./tmp/log/error.log"var accessLogFile = "./tmp/log/access.log"func init () {    initErrorLog()    initAccessLog()}

func initErrorLog() {    ErrorLog = logrus.New()    ErrorLog.SetFormatter(&logrus.JSONFormatter{})    file , err := os.OpenFile(errorLogFile, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0755)    if err != nil {        panic(err)    }    ErrorLog.SetOutput(file)}

func initAccessLog() {    AccessLog = logrus.New()    AccessLog.SetFormatter(&logrus.JSONFormatter{})    file , err := os.OpenFile(accessLogFile, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0755)    if err != nil {        panic(err)    }    AccessLog.SetOutput(file)}
  • 我们新定义一个packageinit函数中来初始化记录器,这样服务器成功启动前就会初始化好记录器。

  • /tmp/log这个目录要提前创建好,执行init函数时会自动创建好access.logerror.log

添加错误日志

我们创建服务器使用的net/http包的Server类型中,有一个ErrorLog字段供开发者设置记录错误日志用的记录器Logger,默认使用的是log包默认的记录器(应该是系统的标准错误):

type Server struct {   Addr    string  // TCP address to listen on, ":http" if empty   Handler Handler // handler to invoke, http.DefaultServeMux if nil   ...   // ErrorLog specifies an optional logger for errors accepting   // connections, unexpected behavior from handlers, and   // underlying FileSystem errors.   // If nil, logging is done via the log package's standard logger.   ErrorLog *log.Logger     ...}

我们之前在创建服务器的时候自己实现了Server类型的对象,那么现在要做的就是将上面初始化好的错误日志的记录器指定给ServerErrorLog字段。

func main() {  ...    // 将logrus的Logger转换为io.Writer    errorWriter := vlog.ErrorLog.Writer()    // 记得关闭io.Writer    defer errorWriter.Close()

    server := &http.Server{        Addr:    ":8080",        Handler: muxRouter,        // 用记录器转换成的io.Writer创建log.Logger        ErrorLog: log.New(vlog.ErrorLog.Writer(), "", 0),    }    ...}

添加好错误日志的记录器后,我们找个路由处理函数,在里面故意制造运行时错误验证一下是否能记录到错误。

func (*HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {   ints := []int{0, 1, 2}   fmt.Fprintf(w, "%v", ints[0:5])}

在上面处理函数中,通过切片表达式越界故意制造了一个运行时错误,打开error.log后能看到文件里已经记录到这个运行时错误及其Stack trace

添加访问日志

Server对象可以设置错误日志的记录器不一样,访问日志只能是我们通过自己编写中间件的方式来实现了。在记录访问日志的中间件里我们会记录ipmethodpathqueryrequest_bodystatusresponse_body这些个字段的内容。

statusresponse_body两个字段来自请求对应的响应。响应在net/http包里是用http.ResponseWriter接口表示的

type ResponseWriter interface {    Header() Header

    Write([]byte) (int, error)

    WriteHeader(statusCode int)}

接口本身以及net/http提供的实现都没有让我们进行读取的方法,所以在编写的用于记录访问日志的中间件里需要对net/http库本身实现的ResponseWriter做一层包装。

利用Go语言结构体类型嵌套匿名类型后,结构体拥有了被嵌套类型的所有导出字段和方法的特性,我们可以很方便地对原来的ResponseWriter做一层包装,然后只重新实现需要更改的方法即可:

type ResponseWithRecorder struct {   http.ResponseWriter   statusCode int   body bytes.Buffer}

func (rec *ResponseWithRecorder) WriteHeader(statusCode int) {   rec.ResponseWriter.WriteHeader(statusCode)   rec.statusCode = statusCode}

func (rec *ResponseWithRecorder) Write(d []byte) (n int, err error) {   n, err = rec.ResponseWriter.Write(d)   if err != nil {      return   }   rec.body.Write(d)

   return}

定义好新的类型后我们重新实现了WriteHeaderWrite方法,在向原来的ReponseWriter中写入后也会向ResponseWriteRecoder.statusCodeResponseWriteRecoder.body写入对应的数据。这样我们就可以在中间件里通过这两个字段访问响应码和响应数据了。

记录访问日志的中间件定义如下:

func AccessLogging (f http.Handler) http.Handler {

    // 创建一个新的handler包装http.HandlerFunc    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {        buf := new(bytes.Buffer)        buf.ReadFrom(r.Body)        logEntry := vlog.AccessLog.WithFields(logrus.Fields{            "ip": r.RemoteAddr,            "method": r.Method,            "path": r.RequestURI,            "query": r.URL.RawQuery,            "request_body": buf.String(),

        })

        wc := &ResponseWithRecorder{            ResponseWriter: w,            statusCode: http.StatusOK,            body: bytes.Buffer{},        }

        // 调用下一个中间件或者最终的handler处理程序        f.ServeHTTP(wc, r)

        defer logEntry.WithFields(logrus.Fields{            "status": wc.statusCode,            "response_body": wc.body.String(),        }).Info()

    })}

Router上应用创建好的AccessLogging中间件后,就可以正常的记录服务器的访问日志了。

// router/router.gofunc RegisterRoutes(r *mux.Router) {    ...    // apply Logging middleware    r.Use(middleware.Logging(), middleware.AccessLogging)    ...}

不过有两点需要注意一下

  • 这里为了演示获取响应数据记录了response_body字段,如果是接口响应内容记录下还可以,但是如果是HTML还是不记录的为好。

  • 初始化ResponseWithRecorder时默认设置了statusCode是因为,服务器正确返回响应时不会显式调用WriteHeader方法,只有在返回NOT_FOUND之类的错误的时候才会调用WriteHeader方法,针对这种情况需要在初始化的时候把statusCode的默认值设置为200

现在再访问服务器后打开access.log会看到刚刚的访问日志,就能看到刚刚请求的urlmethod,客户端IP等信息了。

{"ip":"......","level":"info","method":"GET","msg":"","path":"/index/","query":"","request_body":"","response_body":"Hello World1","status":200,"time":"2020-03-26T04:21:46Z"}

注意:文章只为说明演示方便,获取IP的方法无法获取代理后的真实IP,请悉知。

推荐阅读

  • Go Web编程--SecureCookie实现客户端Session管理


喜欢本文的朋友,欢迎关注“Go语言中文网”:

Go语言中文网启用微信学习交流群,欢迎加微信:274768166,投稿亦欢迎

url能访问但new file()找不到文件_Go Web编程给自己写的服务器添加错误和访问日志...相关推荐

  1. Go Web编程--给自己写的服务器添加错误和访问日志

    错误日志和访问日志是一个服务器必须支持的功能,我们教程里使用的服务器到目前为止还没有这两个功能.正好前两天也写了篇介绍logrus日志库的文章,那么今天的文章里就给我们自己写的服务器加上错误日志和访问 ...

  2. easyui datagrid url不请求请求_Go Web编程--深入学习解析HTTP请求

    之前这个系列的文章一直在讲用Go语言怎么编写HTTP服务器来提供服务,如何给服务器配置路由来匹配请求到对应的处理程序,如何添加中间件把一些通用的处理任务从具体的Handler中解耦出来,以及如何更规范 ...

  3. 总结归纳“windows 找不到文件‘cmd’,命令提示符无法执行和打开”的多种错误方法规避,错误重现以及正确解决方法

    错误重现: 点击启动命令提示符,无任何反应 和微软徽标+R然后输入cmd,提示"windows 找不到文件'cmd',请确认文件名正确后再试一次" 解决方法: ①我尝试过的方法一: ...

  4. PHP TP5入门 二:写接口,添加控制器并访问

    默认访问地址:http://localhost/TP5/tp5/public/index.php/index/hello_world 实现代码: <?php namespace app\inde ...

  5. php file not found,thinkPHP,访问报错File not found,修改.htacc

    原标题:thinkPHP,访问报错File not found,修改.htacc 宝塔面板配置的LAMP,上传thinkPHP,访问报错File not found,修改.htaccess则出现无法加 ...

  6. java中找不到文件是什么情况_java系统找不到指定文件怎么办

    系统找不到指定文件解决方法如下:目录路径太长,这个文件找不到,是因为目录路径太长,windows识别不了这么长的目录,首先,这个目录上级目录已经是windows的.2.检查文件路径是否正确 3.另外, ...

  7. java编译找不到文件_java报错找不到文件解决方法

    刚开始编写java代码时,肯定会遇到各种各样的bug,当然对于初学者这也是能理解的,首先来说一个比较常见的错误,如下: 一般编写新的程序时,都是从Hello,World开始的,比如在DOS上运行jav ...

  8. java.nio.file 找不到_java - 断言该错误:无法访问路径(找不到java.nio.file.Path) - 堆栈内存溢出...

    我想使用Robolectric进行单元测试,但是我正在尝试使用robolectric进行简单测试,因此一开始我很困惑. 我遵循了手册,对示例进行了同样的操作,甚至其他帖子也无济于事. 每次收到错误消息 ...

  9. java io 文件路径_如何从Java项目中的相对路径读取文件? java.io.File找不到指定的路径...

    如何从Java项目中的相对路径读取文件? java.io.File找不到指定的路径 我有一个包含2个包的项目: ListStopWords.txt ListStopWords.txt 在包(2)中我有 ...

最新文章

  1. 《C#精彩实例教程》小组阅读10 -- C#属性与方法
  2. 一种用XAML写Data Converter的方式
  3. Java语言程序设计(基础篇) 第十章 面向对象思考
  4. Windows和Linux双启动,并用在Windows下配置CoLinux启动
  5. 前端学习(1392):多人管理项目12加密
  6. springCloud - 第2篇 - 服务的发现 seeParam
  7. 计算机网络---网络层ARP协议
  8. 【CAD2021】快捷键/基础操作大全
  9. Nginx安装SSL证书
  10. 手机普通浏览器唤起微信打开网页url
  11. Fishermen Gym - 101964E(二分+前缀数组)
  12. linux发行版本排行,2020年10种最受欢迎的Linux发行版排名
  13. java 中文大写金额_java编写的金额转中文大写
  14. Unity IOS游戏内好评
  15. Delphi 10.3.1 Memo打开/保存utf-8不乱码的方法,网上都是胡天!
  16. CAN休眠唤醒压力测试
  17. U盘识别不出来怎么办?
  18. 【1312】【例3.4】昆虫繁殖
  19. 读懂python语言_一文读懂python反射机制
  20. python圆形生成器_python中的生成器

热门文章

  1. 以列表形式输出_04 Python之列表、集合和字典的推导式
  2. onenote复制出来是图片_你真的了解 OneNote 吗?
  3. Linux 中的远程登陆
  4. 1396: 队列问题(2)
  5. Linux系统TCP内核参数优化总结
  6. 【直播提醒】荷小鱼:K12 在线教育应用的开发实践
  7. 有奖调研 | 致云通信短信服务用户的一封信
  8. 驳!?使用游戏引擎是作弊行为的5个依据
  9. linux CentOS6.x 修改主机名(Hostname)
  10. 开工啦,开工啦,2022开工了