gorm的日志模块源码解析

如何让gorm的日志按照我的格式进行输出

这个问题是《如何为gorm日志加traceId》之后,一个群里的朋友问我的。如何让gorm的sql日志不打印到控制台,而打印到自己的日志文件中去。正好我实现了这个功能,就记录一下,并且再把gorm的logger这个线捋一下。

首先我写了一个demo来实现设置我自己的Logger。其实非常简单,只要实现print方法就行了。

package main

import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"log"
) type T struct {
Id int `gorm:"id"`
A int `gorm:"a"`
B int `gorm:"b"`
} func (T) TableName() string {
return "t"
} type MyLogger struct {
} func (logger *MyLogger) Print(values ...interface{}) {
var (
level = values[0]
source = values[1]
) if level == "sql" {
sql := values[3].(string)
log.Println(sql, level, source)
} else {
log.Println(values)
}
} func main() {
db, _ := gorm.Open("mysql", "root:123456@(192.168.33.10:3306)/mysqldemo?charset=utf8&parseTime=True&loc=Local")
defer db.Close() logger := &MyLogger{} db.LogMode(true) db.SetLogger(logger) first := T{}
err := db.Find(&first, "id=1").Error
if err != nil {
panic(err)
} fmt.Println(first)
}

这里的mylogger就是实现了gorm.logger接口。

输出就是按照我logger的输出打印出来了

2019/04/02 09:11:16 SELECT * FROM `t`  WHERE (id=1) sql /Users/yejianfeng/Documents/gopath/src/gorm-log/main.go:50
{1 1 1}

但是这里有个有点奇怪地方,就是这个Print方法里面的values貌似是有隐含内容的,里面的隐含内容有哪些呢?需要追着看下去。

sql的请求怎么进入到Print中的?

我们在db.Find之前只调用过gorm.Open,db.LogMode,db.SetLogger。后面两个函数的逻辑又是极其简单,我们看到Open里面。

重点在这里:

db = &DB{
db: dbSQL,
logger: defaultLogger,
callbacks: DefaultCallback,
dialect: newDialect(dialect, dbSQL),
}

这里的 callbacks 默认是 DefaultCallback。

var DefaultCallback = &Callback{}

type Callback struct {
creates []*func(scope *Scope)
updates []*func(scope *Scope)
deletes []*func(scope *Scope)
queries []*func(scope *Scope)
rowQueries []*func(scope *Scope)
processors []*CallbackProcessor
}

我们这里看到的DefaultCallback是空的,但是实际上,它并不是空的,在callback_query.go这个文件中有个隐藏的init()函数

func init() {
DefaultCallback.Query().Register("gorm:query", queryCallback)
DefaultCallback.Query().Register("gorm:preload", preloadCallback)
DefaultCallback.Query().Register("gorm:after_query", afterQueryCallback)
}

这个init的函数往DefaultCallback.queries里面注册了三个毁掉函数,queryCallback,preloadCallback,afterQueryCallback

然后再结合回db.Find的方法

func (s *DB) Find(out interface{}, where ...interface{}) *DB {
return s.NewScope(out).inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
}

我们看到最终执行的 callCallbacks(s.parent.callbacks.queries) 就是将这三个方法 queryCallback,preloadCallback,afterQueryCallback 逐一调用。

很明显,这三个方法中,和我们有关系的就是queryCallback方法。

func queryCallback(scope *Scope) {
... defer scope.trace(NowFunc())
...
}

这里有个赤裸裸的scope.trace方法

func (scope *Scope) trace(t time.Time) {
if len(scope.SQL) > 0 {
scope.db.slog(scope.SQL, t, scope.SQLVars...)
}
} func (s *DB) slog(sql string, t time.Time, vars ...interface{}) {
if s.logMode == detailedLogMode {
s.print("sql", fileWithLineNum(), NowFunc().Sub(t), sql, vars, s.RowsAffected)
}
} func (s *DB) print(v ...interface{}) {
s.logger.Print(v...)
}

找到了,这里是使用scope.db.slog->db.print->db.logger.Print

这个db.logger就是前面使用SetLogger设置为MyLogger的地方了。

欣赏下这里的print这行:

s.print("sql", fileWithLineNum(), NowFunc().Sub(t), sql, vars, s.RowsAffected)

第一个参数为 level,表示这个是个什么请求,第二个参数为打印sql的代码行号,如/Users/yejianfeng/Documents/gopath/src/gorm-log/main.go:50, 第三个参数是执行时间戳,第四个参数是sql语句,第五个参数是如果有预处理,请求参数,第六个参数是这个sql影响的行数。

好了,这个逻辑圈画完了。对照我们前面的MyLogger的Print,我们要取出什么就记录什么就行了。

type MyLogger struct {
} func (logger *MyLogger) Print(values ...interface{}) {
var (
level = values[0]
source = values[1]
) if level == "sql" {
sql := values[3].(string)
log.Println(sql, level, source)
} else {
log.Println(values)
}
}

总结

从gorm的log也能大概窥探出gorm的代码架构设计了。它的几个结构是核心,DB, Scope, 在Scope中,会注册各种回调方法,creates,updates, querys等,在诸如Find等函数触发了回调调用的时候,才去和真是的DB进行交互。至于日志,就埋藏在这些回调函数之中。

所以《如何为gorm日志加traceId》中如果需要在gorm中增加一个traceId,做不到的原因就是这个gorm.logger没有实现SetContext方法,并且在打印的时候没有将Context输出到Print的参数中。所以除非修改源码中调用db.slog的地方,否则无能为力。

gorm的日志模块源码解析的更多相关文章

  1. php 日志模块源码解析

    php日志模块设计 Monolog 是PHP的一个日志类库解析 整体介绍:monolog日志模块遵循 PSR3 的接口规范.主要有日志格式类接口(格式化日志信息),处理类接口(写日志的驱动,通过扩展写 ...

  2. 「从零单排canal 06」 instance模块源码解析

    基于1.1.5-alpha版本,具体源码笔记可以参考我的github:https://github.com/saigu/JavaKnowledgeGraph/tree/master/code_read ...

  3. 「从零单排canal 05」 server模块源码解析

    基于1.1.5-alpha版本,具体源码笔记可以参考我的github:https://github.com/saigu/JavaKnowledgeGraph/tree/master/code_read ...

  4. 「从零单排canal 07」 parser模块源码解析

    基于1.1.5-alpha版本,具体源码笔记可以参考我的github:https://github.com/saigu/JavaKnowledgeGraph/tree/master/code_read ...

  5. C#软件授权、注册、加密、解密模块源码解析并制作注册机生成license

    最近做了一个绿色免安装软件,领导临时要求加个注册机制,不能让现场工程师随意复制.事出突然,只能在现场开发(离开现场软件就不受我们控了).花了不到两个小时实现了简单的注册机制,稍作整理.        ...

  6. step by step 之餐饮管理系统五(Util模块)------附上篇日志模块源码

    这段时间一直在修改日志模块,现在基本上写好了,也把注释什么的都加上了,昨天邮件发送给mark的园友一直报失败,老是退回来,真是报歉,如下图所示:

  7. python Threading模块源码解析

    查看源码: 这是一个线程控制的类,这个类可以被子类化(继承)在一定的条件限制下,这里有两种方式去明确活动:第一通过传入一个callable 对象也就是调用对象,一种是通过重写这个Thread类的run ...

  8. 30.Serializers模块源码解析

    rest_framework序列化类的继承关系 field类: 序列化基类的基类 BaseSerializer: 继承field 派生ListSerializer序列化类 Serializer: 继承 ...

  9. 【nodejs原理&源码赏析(4)】深度剖析cluster模块源码与node.js多进程(上)

    [摘要] 集群管理模块cluster浅析 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 概述 cluster模块是node.js中用于实现和管理 ...

随机推荐

  1. Webpack的配置与使用

    一.什么是Webpack?     WebPack可以看做是模块打包机.用于分析项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),将 ...

  2. L1正则化比L2正则化更易获得稀疏解的原因

    我们知道L1正则化和L2正则化都可以用于降低过拟合的风险,但是L1正则化还会带来一个额外的好处:它比L2正则化更容易获得稀疏解,也就是说它求得的w权重向量具有更少的非零分量. 为了理解这一点我们看一个 ...

  3. mysql 存储引擎简介

    几个常用存储引擎的特点 下面我们重点介绍几种常用的存储引擎并对比各个存储引擎之间的区别和推荐使用方式. 特点 Myisam BDB Memory InnoDB Archive 存储限制 没有 没有 有 ...

  4. Flask入门之结构重组(瘦身)-第13讲笔记

    1. pip list Flask 0.10.1 Flask-Bootstrap 3.3.5.6 Flask-SQLAlchemy 2 Flask-Script 2.0.5 Flask-WTF 0.1 ...

  5. arcEngine开发之IMap、ILayer、IFeatureLayer和IFeatureClass关系

    刚开时学习 Engine 开发时,对于这几个接口之间的关系总是理不清,因此写下这篇文章做个总结. 是什么 在 engine 开发中,我觉得使用过程中应该将每个接口对应到 ArcMap 中的具体事物中, ...

  6. List集合学习总结

    1.List接口是Collection的子接口,用于定义线性表数据结构 ,可以将List理解为存放对象的数组,只不过其元素个数可以动态增加或减少. 2.List接口的两个常见的实现类为ArrayLis ...

  7. PAT1029:Median

    1029. Median (25) 时间限制 1000 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Given an incr ...

  8. Python接口测试之对MySQL/unittest框架/Requests 的操作

    单元测试支持测试自动化. 共享的安装程序和关闭代码测试. 聚合成集合,测试和报告框架从测试的独立性.单元测试模块提供可以很容易地支持这些素质的一组测试的类.关于unittest 测试框架建议可以到官方 ...

  9. Netty中如何写大型数据

    因为网络饱和的可能性,如何在异步框架中高效地写大块的数据是一个特殊的问题.由于写操作是非阻塞的,所以即使没有写出所有的数据,写操作也会在完成时返回并通知ChannelFuture.当这种情况发生时,如 ...

  10. C语言gets雨scanf函数的用法

    /*1.不同点: scanf不能接受空格.制表符Tab.回车等: 而gets能够接受空格.制表符Tab和回车等: 2.相同点:  字符串接受结束后自动加'\0'. 使用scanf("%s&q ...