web框架
最后更新于:2022-04-02 04:58:22
Beego:开源的高性能 Go 语言 Web 框架。
* [https://github.com/astaxie/beego](https://github.com/astaxie/beego)
* [https://beego.me](https://beego.me/)
Buffalo:使用 Go 语言快速构建 Web 应用。
* [https://github.com/gobuffalo/buffalo](https://github.com/gobuffalo/buffalo)
* [https://gobuffalo.io](https://gobuffalo.io/)
Echo:简约的高性能 Go 语言 Web 框架。
* [https://github.com/labstack/echo](https://github.com/labstack/echo)
* [https://echo.labstack.com](https://echo.labstack.com/)
Gin:Go 语言编写的 Web 框架,以更好的性能实现类似 Martini 框架的 API。
* [https://github.com/gin-gonic/gin](https://github.com/gin-gonic/gin)
* [https://gin-gonic.github.io/gin](https://gin-gonic.github.io/gin)
Iris:全宇宙最快的 Go 语言 Web 框架。完备 MVC 支持,未来尽在掌握。
* [https://github.com/kataras/iris](https://github.com/kataras/iris)
* [https://iris-go.com](https://iris-go.com/)
Revel:Go 语言的高效、全栈 Web 框架。
* [https://github.com/revel/revel](https://github.com/revel/revel)
* [https://revel.github.io](https://revel.github.io/)
';
Docker
最后更新于:2022-04-02 04:58:20
[https://studygolang.com/articles/14953](https://studygolang.com/articles/14953) 这可能是把Docker的概念讲的最清楚的一篇文章
';
nsq
最后更新于:2022-04-02 04:58:17
[http://blog.csdn.net/shanhuhai5739](http://blog.csdn.net/shanhuhai5739) nsq源码分析
';
Caddy
最后更新于:2022-04-02 04:58:15
[https://lengzzz.com/note/caddy-http-2-web-server-guide-for-beginners](https://lengzzz.com/note/caddy-http-2-web-server-guide-for-beginners) Caddy - 方便够用的 HTTPS server 新手教程
';
k8s
最后更新于:2022-04-02 04:58:13
[http://url.cn/5rJfdZI](http://url.cn/5rJfdZI) 《k8s-1.13版本源码分析》-调度优选
《k8s-1.13版本源码分析》-调度优选,源码分析系列文章已经开源到github,地址如下:
github: [https://github.com/farmer-hutao/k8s-source-code-analysis](https://github.com/farmer-hutao/k8s-source-code-analysis)
gitbook: [https://farmer-hutao.github.io/k8s-source-code-analysis](https://farmer-hutao.github.io/k8s-source-code-analysis)
';
etcd
最后更新于:2022-04-02 04:58:10
Go开发的应用
最后更新于:2022-04-02 04:58:08
[etcd](etcd.md)
[k8s](k8s.md)
[Caddy](Caddy.md)
[nsq](nsq.md)
[Docker](Docker.md)
[web框架](web%E6%A1%86%E6%9E%B6.md)
';
为什么 Go 标准库中有些函数只有签名,没有函数体?
最后更新于:2022-04-02 04:58:06
如果你看过 Go 语言标准库,应该有见到过,有一些函数只有签名,没有函数体。你有没有感觉到很奇怪?这到底是怎么回事?我们自己可以这么做吗?本文就来解密它。
首先,函数肯定得有实现,没有函数体,一定是在其他某个地方。Go 中一般有两种形式。
### 函数签名使用Go,然后通过该包中的汇编文件来实现它
比如,在标准库`sync/atomic`包中的函数基本只有函数签名。比如:`atomic.StoreInt32`
~~~
// StoreInt32 atomically stores val into *addr.2func StoreInt32(addr *int32, val int32)3
~~~
它的函数实现在哪呢?其实只要稍微留意一下发现该目录下有一个文件:asm.s,它提供了具体的实现,即通过汇编来实现:
~~~
TEXT ·StoreInt32(SB),NOSPLIT,$02 JMP runtime∕internal∕atomic·Store(SB)
~~~
具体的实现,在`runtime∕internal`文件夹中,有兴趣你可以打开 asm\_amd64.s 看看。
很明显,这种方式一方面会是效率的考虑,另一方面,有一些代码只能汇编实现。
以上方式,你自己也可以尝试。比如实现一个`Sum(a, b int) int`。欢迎评论给出你的代码。
### 通过 //go:linkname 指令来实现
比如,在标准库`time`包中的`Sleep`函数:
~~~
// Sleep pauses the current goroutine for at least the duration d.2// A negative or zero duration causes Sleep to return immediately.3func Sleep(d Duration)4
~~~
它的实现在哪里呢?在 time 包中并没有找到相应的汇编文件。
按照 Go 源码的风格,这时候一般需要去`runtime`包中找。我们会找到 time.go,其中有一个函数:
~~~
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.2//go:linkname timeSleep time.Sleep3func timeSleep(ns int64) {4 ...5}
~~~
这就是我们要找的`time.Sleep`的实现。
如果你有认真跟着学习「每日一学」,对于`//go:linkname`应该不陌生,这里的关键就在于这个指令,它的格式是:
~~~
//go:linkname 函数名 包名.函数名
~~~
因此我们在遇到函数没有实现,但汇编又不存在时,可以通过尝试搜索:`go:linkname xxx xx.xxx`的形式来找,比如`time.Sleep`就可以通过`//go:linkname timeSleep time.Sleep`来查找具体实现在哪。
这里面要提示一点,使用`//go:linkname`,必须导入`unsafe`包,所以,有时候会见到:`import _ "unsafe"`这样的代码。
一般来说,我们自己的代码不会使用这样的方式,但你会写一个示例试试吗?欢迎评论给出你的代码。
另外,想想为什么`time.Sleep`的实现要这么搞?
';
一问一答(一)
最后更新于:2022-04-02 04:58:03
1、Wild Ones 提问: 你好,大牛,golang后端开发需要掌握哪些知识和技术?有没有一些开源项目推荐?
回答:
除了语言本身,看具体哪个领域。如果使用 Go 进行 Web 开发,普通工程师的话,至少需要掌握:HTTP协议、Linux、Web Server、数据库如MySQL、Redis缓存等。这是最基本的要求。其他比如高可用、分布式方案等,可以后续慢慢涉猎。
Go 的开源项目的话,可以根据上面的,设计不同方面。看你目的是什么。
2、暗香去 提问: 相对于java,Go拥有着超高并发能力,那么Go是如何解决IO等待问题的?
回答:
借此问题,普及一下 IO 的相关知识点。
IO 基本概念
Linux 的内核将所有外部设备都可以看做一个文件来操作(Unix 的设计原则,一切皆文件)。那么我们对外部设备的操作都可以看做对文件进行操作。我们对一个文件的读写,都通过调用内核提供的系统调用;内核给我们返回一个 file descriptor(fd,文件描述符)。对一个 socket 的读写也会有相应的描述符,称为socketfd(socket 描述符)。描述符就是一个数字(可以理解为一个索引),指向内核中一个结构体(文件路径,数据区,等一些属性)。应用程序对文件的读写就通过对描述符的读写完成。
一个基本的 IO,它会涉及到两个系统对象,一个是调用这个 IO 的进程对象,另一个就是系统内核(kernel)。
一般来说,服务器端的 I/O 主要有两种情况:一是来自网络的 I/O;二是对文件(设备)的I/O。
常见的 IO 模型
首先一个 IO 操作其实分成了两个步骤:发起 IO 请求(等待网络数据到达网卡并读取到内核缓冲区,数据准备好)和实际的 IO 操作(从内核缓冲区复制数据到进程空间)。
阻塞和非阻塞
阻塞 IO 和非阻塞 IO 的区别在于第一步:发起 IO 请求是否会被阻塞。如果阻塞直到完成,那么就是传统的阻塞 IO,如果不阻塞,那么就是非阻塞 IO。
同步和异步
同步 IO 和异步 IO 的区别就在于第二个步骤是否阻塞。如果实际的 IO 读写阻塞请求进程,那么就是同步IO。因此常说的阻塞 IO、非阻塞 IO、IO 复用、信号驱动 IO 都是同步 IO。如果不阻塞,而是操作系统帮你做完 IO 操作后再将结果返回给你(通知你),那么就是异步IO。
IO 多路复用
指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。
目前支持 I/O 多路复用的常用系统调用有 select,pselect,poll,epoll 等,I/O 多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但 select,pselect,poll,epoll本质上都是同步 I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步 I/O 则无需自己负责进行读写,异步 I/O 的实现会负责把数据从内核拷贝到用户空间。
5 种 IO 模型
《UNIX 网络编程》对 IO 模型进行了总结,分别是:
阻塞 IO、非阻塞 IO、IO 多路复用、信号驱动的 IO、异步 IO;前 4 种为同步 IO,只有异步 IO 模型是异步 IO。
回到问题
目前很多高性能的基础网络服务器都是采用的 C 语言开发的,比如:Nginx、Redis、memcached 等,它们都是基于”事件驱动 + 事件回调函数”的方式实现,也就是采用 epoll 等作为网络收发数据包的核心驱动。但不少人都认为“事件驱动 + 事件回调函数”的编程方法是“反人类”的;因为大多数人都更习惯线性的处理一件事情:做完第一件事情再做第二件事情,并不习惯在 N 件事情之间频繁的切换干活。为了解决程序员在开发服务器时需要自己的大脑不断的“上下文切换”的问题,Go 语言引入了一种用户态线程 goroutine 来取代编写异步的事件回调函数,从而重新回归到多线程并发模型的线性、同步的编程方式上。
在 Linux 上 Go 语言写的网络服务器也是采用的 epoll 作为最底层的数据收发驱动,Go 语言网络的底层实现中同样存在“上下文切换”的工作,只是这个切换工作由 runtime 的调度器来做了,减少了程序员的负担。
所以,IO 等待是必然,只是谁等的问题。Go 语言在遇到 IO 需要等待时,runtime 会进行调度,语言层面处理这个问题。Go 拥有超高并发能力的关键就在于用户态的 goroutine。
注:网络编程,涉及到太多知识点,咱们后面可以找时间慢慢聊。
3、 提问: go int32和uint32区别,和使用的场景,
回答:int32 是有符号类型;uint32 是无符号类型。有符号和无符号的概念知道吧。
一般的项目,直接使用 int 即可;有一些对内存要求很高的场景,希望每个变量都尽可能做到够用不超。比如,我们存一个数字,它不会是负数,同时最大值不会超过 1<<32 - 1,那么就可以使用 uint32。
在 32 位机器上,int 和 int32 占用空间和表示的范围是一样的。
3、想问一下,对Chan类型的变量,什么时候用select,什么时候用for range
回答:
多个chan用select,单个用for range
';
一问一答
最后更新于:2022-04-02 04:58:01
[一问一答(一)](%E4%B8%80%E9%97%AE%E4%B8%80%E7%AD%94%EF%BC%88%E4%B8%80%EF%BC%89.md)
[为什么 Go 标准库中有些函数只有签名,没有函数体?](%E4%B8%BA%E4%BB%80%E4%B9%88Go%E6%A0%87%E5%87%86%E5%BA%93%E4%B8%AD%E6%9C%89%E4%BA%9B%E5%87%BD%E6%95%B0%E5%8F%AA%E6%9C%89%E7%AD%BE%E5%90%8D%EF%BC%8C%E6%B2%A1%E6%9C%89%E5%87%BD%E6%95%B0%E4%BD%93%EF%BC%9F.md)
';
跨平台交叉编译
最后更新于:2022-04-02 04:57:59
**在 Mac、Linux、Windows 下Go交叉编译**
Go语言支持交叉编译,在一个平台上生成另一个平台的可执行程序,最近使用了一下,非常好用,这里备忘一下。
需要注意的是我发现golang在支持cgo的时候是没法交叉编译的
Mac 下编译 Linux 和 Windows 64位可执行程序
~~~
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
~~~
Linux 下编译 Mac 和 Windows 64位可执行程序
~~~
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
~~~
Windows 下编译 Mac 和 Linux 64位可执行程序
~~~
SET CGO_ENABLED=0
SET GOOS=darwin
SET GOARCH=amd64
go build main.go
SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build main.go
~~~
GOOS:目标平台的操作系统(darwin、freebsd、linux、windows)
GOARCH:目标平台的体系架构(386、amd64、arm)
交叉编译不支持 CGO 所以要禁用它
上面的命令编译 64 位可执行程序,你当然应该也会使用 386 编译 32 位可执行程序
很多博客都提到要先增加对其它平台的支持,但是我跳过那一步,上面所列的命令也都能成功,且得到我想要的结果,可见那一步应该是非必须的,或是我所使用的 Go 版本已默认支持所有平台。
##说明
windows下面 powershell不行,要cmd
';
Go编译
最后更新于:2022-04-02 04:57:57
[跨平台交叉编译](%E8%B7%A8%E5%B9%B3%E5%8F%B0%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91.md)
';
拼多多开放平台 SDK
最后更新于:2022-04-02 04:57:54
[https://github.com/liunian1004/pdd](https://github.com/liunian1004/pdd)
本项目为`go`语言实现的拼多多开放平台 SDK,调用方式简单粗暴。
`go get github.com/liunian1004/pdd`
~~~go
import github.com/liunian1004/pdd
p := pdd.NewPdd(&pdd.Config{
ClientId: "your client id",
ClientSecret: "your client secret",
RetryTimes: 3, // 设置接口调用失败重试次数
})
// 初始化多多客相关 API 调用
d := p.GetDDK()
// 或者
d := ddk.NewDDK(&pdd.Config{
ClientId: "your client id",
ClientSecret: "your client secret",
RetryTimes: 3, // 设置接口调用失败重试次数
})
// 获取主题列表
r, err := d.ThemeListGet(1, 20)
// 初始化商品 API
g := p.GetGoodsAPI()
~~~
## [](https://github.com/liunian1004/pdd#%E9%9D%9E%E5%BF%85%E9%A1%BB%E5%8F%82%E6%95%B0)非必须参数
通过自定义 Params 定制非必须参数,在函数的最后一个参数传入 Params 对象。
~~~go
d := NewDDK(p)
params := pdd.NewParams()
// 设置非必传参数
params.Set("custom_parameters", "test")
params.Set("generate_short_url", true)
s, err := d.RPPromUrlGenerate([]string{"pid"}, true, params)
~~~
## [](https://github.com/liunian1004/pdd#todo)Todo
* \[\] 实现 ddk 多多客 API 相关接口
* [x] `OrderListIncrementGet()`pdd.ddk.order.list.increment.get 最后更新时间段增量同步推广订单信息
* [x] `GoodsDetail()`pdd.ddk.goods.detail 多多进宝商品详情查询
* [x] `GoodsSearch()`pdd.ddk.goods.search 多多进宝商品查询
* [x] `GoodsPidQuery()`pdd.ddk.goods.pid.query 查询已经生成的推广位信息
* [x] `GoodsPidGenerate()`pdd.ddk.goods.pid.generate 创建多多进宝推广位
* [x] `GoodsPromotionUrlGenerate()`pdd.ddk.goods.promotion.url.generate 多多进宝推广链接生成
* [x] `TopGoodsListQuery()`pdd.ddk.top.goods.list.query 获取热销商品列表
* [x] `RPPromUrlGenerate()`pdd.ddk.rp.prom.url.generate 生成红包推广链接 (**需要对应权限**)
* [x] `CMSPromUrlGen()`pdd.ddk.cms.prom.url.generate 生成商城-频道推广链接
* [x] `ThemeListGet()`pdd.ddk.theme.list.get 多多进宝主题列表查询
* \[\] pdd.ddk.theme.goods.search 多多进宝主题商品查询
* \[\] pdd.ddk.theme.prom.url.generate 多多进宝主题推广链接生成
* \[\] pdd.ddk.app.new.bill.list.get 多多客拉新账单
* \[\] pdd.ddk.direct.goods.query 定向推广商品查询接口
* [x] `GoodsZsURLGen()`pdd.ddk.goods.zs.unit.url.gen 多多进宝转链接口
* \[\] pdd.ddk.zs.unit.goods.query 查询招商推广计划商品
* \[\] pdd.ddk.weapp.qrcode.url.gen 多多客生成单品推广小程序二维码 url
* \[\] pdd.ddk.goods.basic.info.get 获取商品基本信息接口
* \[\] pdd.ddk.goods.recommend.get 运营频道商品查询 API
* \[\] pdd.ddk.order.detail.get 查询订单详情
* \[\] pdd.ddk.mall.goods.list.get 查询店铺商品
* \[\] pdd.ddk.mall.url.gen 多多客生成店铺推广链接 API
* [x] `LotteryUrlGen()`pdd.ddk.lottery.url.gen 多多客生成转盘抽免单 url (**需要对应权限**)
* \[\] pdd.ddk.lottery.new.list.get 多多客查询转盘拉新订单列表
* \[\] pdd.ddk.resource.url.gen 生成多多进宝频道推广
* \[\] pdd.ddk.merchant.list.get 多多客查店铺列表接口
* 商品 API
* [x] `GoodsCatGet()`pdd.goods.cats.get 拼多多标准商品类目信息
* [x] `GoodsOptGet()`pdd.goods.opt.get 拼多多商品标签列表
## [](https://github.com/liunian1004/pdd#%E5%A4%9A%E5%A4%9A%E5%AE%A2%E5%B7%A5%E5%85%B7-api)多多客工具 API
提供给第三方开发者为多多进宝推广者提供第三方工具的 API。
* pdd.ddk.oauth.goods.pid.generate(多多进宝推广位创建接口)
* pdd.ddk.oauth.goods.pid.query(多多客已生成推广位信息查询)
* pdd.ddk.oauth.goods.prom.url.generate(生成多多进宝推广链接)
* pdd.ddk.oauth.order.list.increment.get(按照更新时间段增量同步推广订单信息)
* pdd.ddk.oauth.order.list.range.get(按照时间段获取多多进宝推广订单信息)
## [](https://github.com/liunian1004/pdd#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)注意事项
1. 多多客工具API需要多多客授权才能使用,授权服务URL为[http://jinbao.pinduoduo.com/open.html](http://jinbao.pinduoduo.com/open.html),根据接入指南提示组装合法的 URL,示例:[http://jinbao.pinduoduo.com/open.html?client\_id=89daeb71b2e546318fbb53cd04d0329c&redirect\_uri=www.pinduoduo.com&response\_type=code,之后引导多多客授权获取code即可。](http://jinbao.pinduoduo.com/open.html?client_id=89daeb71b2e546318fbb53cd04d0329c&redirect_uri=www.pinduoduo.com&response_type=code%EF%BC%8C%E4%B9%8B%E5%90%8E%E5%BC%95%E5%AF%BC%E5%A4%9A%E5%A4%9A%E5%AE%A2%E6%8E%88%E6%9D%83%E8%8E%B7%E5%8F%96code%E5%8D%B3%E5%8F%AF%E3%80%82)
2. code、access\_token、refresh\_token 失效时间与商家授权一致,分别为10分钟、24小时、30天,刷新token后分别延长access\_token和refresh\_token的失效时间,延长时间等同于授权时长,获取token教程详见:[http://open.pinduoduo.com/#/document](http://open.pinduoduo.com/#/document)
3. 调用多多客工具 API 时,必须入参 access\_token 方可正常调用
4. 多多客工具 API 可以通过创建多多客联盟应用获取权限
';
jasonlvhit/gocron
最后更新于:2022-04-02 04:57:52
**jasonlvhit/gocron**
安装:
~~~
go get -u github.com/jasonlvhit/gocron
~~~
每隔1秒执行一个任务,每隔4秒执行另一个任务:
~~~
package main
import (
"fmt"
"time"
"github.com/jasonlvhit/gocron"
)
func task() {
fmt.Println("I am runnning task.", time.Now())
}
func superWang() {
fmt.Println("I am runnning superWang.", time.Now())
}
func main() {
s := gocron.NewScheduler()
s.Every(1).Seconds().Do(task)
s.Every(4).Seconds().Do(superWang)
sc := s.Start() // keep the channel
go test(s, sc) // wait
<-sc // it will happens if the channel is closed
}
func test(s *gocron.Scheduler, sc chan bool) {
time.Sleep(8 * time.Second)
s.Remove(task) //remove task
time.Sleep(8 * time.Second)
s.Clear()
fmt.Println("All task removed")
close(sc) // close the channel
}
~~~
';
robfig/cron
最后更新于:2022-04-02 04:57:50
**robfig/cron**
安装:
~~~
go get -u github.com/robfig/cron
~~~
应用:
每分钟执行一次:
~~~
package main
import (
"log"
"github.com/robfig/cron"
)
func main() {
i := 0
c := cron.New()
spec := "0 */1 * * * *"
c.AddFunc(spec, func() {
i++
log.Println("execute per second", i)
})
c.Start()
select {}
}
~~~
其中注意select的用法:
golang 的 select 的功能和 select, poll, epoll 相似, 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。
每天上午9点到12点的第2和第10分钟执行:
~~~
package main
import (
"fmt"
"github.com/robfig/cron"
)
func main() {
spec := "2,10 9-12 * * *" // 每天上午9点到12点的第2和第10分钟执行
c := cron.New()
c.AddFunc(spec, myFunc)
c.Start()
select {}
}
func myFunc() {
fmt.Println("execute")
}
~~~
';
定时任务
最后更新于:2022-04-02 04:57:47
Linux下crontab
crontab 命令常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令。 该命令从标准输入设备读取指令,并将其存放于“crontab”文件中,以供之后读取和执行。 该词来源于希腊语chronos(χρόνος),原意是时间。
通常,crontab储存的指令被守护进程激活,crond常常在后台运行,每一分钟检查是否有预定的作业需要执行。这类作业一般称为cron jobs。
crontab文件包含送交cron守护进程的一系列作业和指令。每个用户可以拥有自己的crontab文件;同时,操作系统保存一个针对整个系统的crontab文件,该文件通常存放于/etc或者/etc之下的子目录中,而这个文件只能由系统管理员来修改。
crontab文件的每一行均遵守特定的格式,由空格或tab分隔为数个领域,每个领域可以放置单一或多个数值。
命令
安装crontab:
~~~
yum install crontabs
~~~
启动:
~~~
service crond start
~~~
关闭:
~~~
service crond stop
~~~
重启:
~~~
service crond restart
~~~
重载:
~~~
service crond reload
~~~
查看状态:
~~~
service crond status
~~~
编辑任务:
~~~
crontab -e
~~~
查看任务:
~~~
crontab -l
~~~
删除:
~~~
crontab -r
~~~
格式
分 时 日 月 星期 要运行的命令
操作符号
在一个区域里填写多个数值的方法:
逗号(’,’)分开的值,例如:“1,3,4,7,8”
连词符(’-‘)指定值的范围,例如:“1-6”,意思等同于“1,2,3,4,5,6”
星号(’*’)代表任何可能的值。例如,在“小时域”里的星号等于是“每一个小时”,等等
某些cron程序的扩展版本也支持斜线(’/’)操作符,用于表示跳过某些给定的数。例如,“*/3”在小时域中等于“0,3,6,9,12,15,18,21”等被3整除的数;
实例
每分钟执行一次命令:
~~~
* * * * * yourCommand
~~~
每小时的第2和第10分钟执行:
~~~
2,10 * * * * yourCommand
~~~
每天上午9点到12点的第2和第10分钟执行:
~~~
2,10 9-12 * * * yourCommand
~~~
每隔两天的上午9点到12点的第2和第10分钟执行:
~~~
2,10 9-12 */2 * * yourCommand
~~~
每周一上午9点到12点的第2和第10分钟执行:
~~~
2,10 9-12 * * 1 yourCommand
~~~
';
日志库
最后更新于:2022-04-02 04:57:45
# 一、golang日志库
## 1.1 golang日志库简介
golang标准库的日志框架非常简单,仅仅提供了print,panic和fatal三个函数。对于更精细的日志级别、日志文件分割,以及日志分发等方面,并没有提供支持。所以,催生了很多第三方的日志库。但是,在golang的世界里,没有一个日志库像slf4j那样在Java中具有绝对统治地位。在golang的世界,流行的日志框架包括logrus、zap、zerolog、seelog等。
logrus是目前Github上star数量最多的日志库,目前(2018.08,下同)star数量为8119,fork数为1031。logrus功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能。很多开源项目,如docker,prometheus等,都是用了logrus来记录其日志。
zap是Uber推出的一个快速、结构化的分级日志库。具有强大的ad-hoc分析功能,并且具有灵活的仪表盘。zap目前在GitHub上的star数量约为4.3k。
seelog提供了灵活的异步调度、格式化和过滤功能。目前在GitHub上的star数量也有约1.1k。
## 1.2 golang logrus的GitHub地址
logrus的GitHub地址 [https://github.com/sirupsen/logrus](https://github.com/sirupsen/logrus)
lfshook的GitHub地址 [https://github.com/rifflock/lfshook](https://github.com/rifflock/lfshook)
file-rotatelogs的GitHub地址 [https://github.com/lestrrat-go/file-rotatelogs](https://github.com/lestrrat-go/file-rotatelogs)
pkg/errors的GitHub地址 [https://github.com/pkg/errors](https://github.com/pkg/errors)
# 二、logrus特性
logrus具有以下特性:
1. 完全兼容golang标准库日志模块。logrus拥有六种日志级别:debug、info、warn、error、fatal和panic,这是golang标准库日志模块的API的超集。如果你的项目使用标准库日志模块,完全可以用最低的代价迁移到logrus上。
2. 可扩展的Hook机制。允许使用者通过hook方式,将日志分发到任意地方,如本地文件系统、标准输出、logstash、elasticsearch或者mq等,或者通过hook定义日志内容和格式等。
3. 可选的日志输出格式。**logrus内置了两种日志格式,JSONFormatter和TextFormatter。**如果这两个格式不满足需求,可以自己动手实现接口Formatter,来定义自己的日志格式。
4. Field机制。logrus鼓励通过Field机制进行精细化、结构化的日志记录,而不是通过冗长的消息来记录日志。
5. logrus是一个可插拔的、结构化的日志框架。
# 三、logrus的使用
## 3.1 第一个示例
最简单的使用logrus的示例如下:
~~~
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
}).Info("A walrus appears")
}
~~~
上面代码执行后,标准输出上输出如下:
~~~
time="2018-08-11T15:42:22+08:00" level=info msg="A walrus appears" animal=walrus
~~~
logrus与golang标准库日志模块完全兼容,因此,你可以使用`log “github.com/sirupsen/logrus”`替换所有日志导入。
## 3.2 第二个示例
logrus可以通过简单的配置,来定义输出、格式或者日志级别等。示例如下:
~~~
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func initLog() {
// 设置日志格式为json格式
log.SetFormatter(&log.JSONFormatter{})
// 设置将日志输出到标准输出(默认的输出为stderr,标准错误)
// 日志消息输出可以是任意的io.writer类型
log.SetOutput(os.Stdout)
// 设置日志级别为warn以上
log.SetLevel(log.WarnLevel)
}
func main() {
initLog()
log.WithFields(log.Fields{
"animal": "walrus",
"size": 10,
"country": "china",
}).Info("A group of walrus emerges from the ocean")
log.WithFields(log.Fields{
"omg": true,
"number": 122,
"country": "china",
}).Warn("The group's number increased tremendously!")
log.WithFields(log.Fields{
"omg": true,
"number": 100,
"country": "america",
}).Fatal("The ice breaks!")
}
~~~
上面代码执行后,标准输出上输出如下:
~~~
{"country":"china","level":"warning","msg":"The group's number increased tremendously!","number":122,"omg":true,"time":"2018-12-12T18:22:20+08:00"}
{"country":"america","level":"fatal","msg":"The ice breaks!","number":100,"omg":true,"time":"2018-12-12T18:22:20+08:00"}
exit status 1
~~~
## 3.3 Logger
logger是一种相对高级的用法。对于一个大型项目,往往需要一个全局的logrus实例,即`logger`对象,来记录项目所有的日志。示例如下:
~~~
package main
import (
"github.com/sirupsen/logrus"
"os"
)
// logrus提供了New()函数来创建一个logrus的实例。
// 项目中,可以创建任意数量的logrus实例。
var log = logrus.New()
func main() {
// 为当前logrus实例设置消息的输出,同样地,
// 可以设置logrus实例的输出到任意io.writer
log.Out = os.Stdout
// 为当前logrus实例设置消息输出格式为json格式。
// 同样地,也可以单独为某个logrus实例设置日志级别和hook,这里不详细叙述。
log.Formatter = &logrus.JSONFormatter{}
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
}
~~~
执行结果如下所示:
~~~
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the ocean","size":10,"time":"2018-12-12T18:33:38+08:00"}
~~~
## 3.4 Fields
前一章提到过,logrus不推荐使用冗长的消息来记录运行信息,它推荐使用`Fields`来进行精细化的、结构化的信息记录。
例如下面记录日志的方式:
~~~
log.Fatalf("Failed to send event %s to topic %s with key %d", event, topic, key)
````
在logrus中不太提倡,logrus鼓励使用以下方式替代之:
```go
log.WithFields(log.Fields{
"event": event,
"topic": topic,
"key": key,
}).Fatal("Failed to send event")
~~~
前面的`WithFields` API可以规范使用者按照其提倡的方式记录日志。但是,`WithFields`依然是可选的,因为某些场景下,使用者确实只需要记录一条简单的消息。
通常,在一个应用中,或者应用的一部分中,都有一些固定的`Field`。比如,在处理用户http请求时,在上下文中,所有的日志都会有`request_id`和`user_ip`。为了避免每次记录日志都要使用`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})`,我们可以创建一个`logrus.Entry`实例,为这个实例设置默认`Fields`,在上下文中使用这个`logrus.Entry`实例记录日志即可。
~~~
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request") # will log request_id and user_ip
requestLogger.Warn("something not great happened")
~~~
# 四、Hook
logrus最令人心动的功能,就是其可扩展的HOOK机制了。通过在初始化时为logrus添加hook,logrus可以实现各种扩展功能。
## 4.1 Hook接口
logrus的hook原理是:在每次写入日志时拦截,修改logrus.Entry。logrus的hook接口定义如下。
~~~
// logrus在记录Levels()返回的日志级别的消息时,会触发HOOK。
// 然后,按照Fire方法定义的内容,修改logrus.Entry。
type Hook interface {
Levels() []Level
Fire(*Entry) error
}
~~~
一个简单自定义的hook如下所示。`DefaultFieldHook类型的对象`会在所有级别的日志消息中加入默认字段`appName="myAppName"`。
~~~
type DefaultFieldHook struct {
}
func (hook *DefaultFieldHook) Fire(entry *log.Entry) error {
entry.Data["appName"] = "MyAppName"
return nil
}
func (hook *DefaultFieldHook) Levels() []log.Level {
return log.AllLevels
}
~~~
hook的使用也很简单,在初始化前调用`log.AddHook(hook)`添加相应的`hook`即可。
logrus官方仅仅内置了syslog的[hook](https://github.com/sirupsen/logrus/tree/master/hooks/syslog)。 但Github上有很多第三方的hook可供使用,文末将提供一些第三方HOOK的链接。
# 五、问题与解决方案
尽管logrus有诸多优点,但是为了灵活性和可扩展性,官方也削减了很多实用的功能,例如:
* 没有提供行号和文件名的支持
* 输出到本地文件系统时,没有提供日志分割功能
* 官方没有提供输出到ELK等日志处理中心的功能
但是,这些功能都可以通过自定义hook来实现。
## 5.1 记录文件名和行号
logrus一个很致命的问题,就是没有提供文件名和行号,这在大型项目中通过日志定位问题时有诸多不便。Github上的logrus的issue#63:[Log filename and line number](https://github.com/sirupsen/logrus/issues/63)创建于2014年,四年过去了仍是open状态。
网上给出的解决方案分位两类,一就是自己实现一个hook;二就是通过装饰器包装`logrus.Entry`。两种方案网上都有很多代码,但是大多无法正常工作。但总体来说,解决问题的思路都是对的:通过标准库的`runtime`模块获取运行时信息,并从中提取文件名、行号和调用函数名。
标准库`runtime`模块的`Caller(skip int)`函数可以返回当前goroutine调用栈中的文件名、行号、函数信息等,参数skip表示返回的栈帧的层次,0表示`runtime.Caller`的调用者。返回值包括响应栈帧层次的pc(程序计数器)、文件名和行号信息。为了提高效率,我们通过跟踪调用栈发现,从`runtime.Caller()`的调用者开始,到记录日志的生成代码之间,大概有8到11层左右,所以我们在hook中循环第8到11层调用栈,应该可以找到日志记录的生产代码。
![这里写图片描述](https://img-blog.csdn.net/20180814170801498?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzbHlrNjA2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
此外,`runtime.FuncForPC(pc uintptr) *Func`可以返回指定`pc`的函数信息。
所以,我们要实现的hook也是基于以上原理,使用`runtime.Caller()`依次循环调用栈的第7~11层,过滤掉`sirupsen`包内容,那么第一个非`siupsenr`包就认为是我们的生产代码了,并返回`pc`以便通过`runtime.FuncForPC()`获取函数名称。然后将文件名、行号和函数名组装为`source`字段塞到`logrus.Entry`中即可。
~~~
time="2018-08-11T19:10:15+08:00" level=warning msg="postgres_exporter is ready for scraping on 0.0.0.0:9295..." source="postgres_exporter/main.go:60:main()"
time="2018-08-11T19:10:17+08:00" level=error msg="!!!msb info not found" source="postgres/postgres_query.go:63:QueryPostgresInfo()"
time="2018-08-11T19:10:17+08:00" level=error msg="get postgres instances info failed, scrape metrics failed, error:msb env not found" source="collector/exporter.go:71:Scrape()"
~~~
## 5.2 日志本地文件分割
logrus本身不带日志本地文件分割功能,但是我们可以通过`file-rotatelogs`进行日志本地文件分割。 每次在我们写入日志的时候,logrus都会调用`file-rotatelogs`来判断日志是否要进行切分。关于本地日志文件分割的例子,网上很多,这里不再详细介绍,奉上代码:
~~~
import (
"github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
log "github.com/sirupsen/logrus"
"time"
)
func newLfsHook(logLevel *string, maxRemainCnt uint) log.Hook {
writer, err := rotatelogs.New(
logName+".%Y%m%d%H",
// WithLinkName 为最新的日志建立软连接,以方便随时找到当前日志文件
rotatelogs.WithLinkName(logName),
// WithRotationTime 设置日志分割的时间,这里设置为一小时分割一次
rotatelogs.WithRotationTime(time.Hour),
// WithMaxAge和WithRotationCount 二者只能设置一个,
// WithMaxAge 设置文件清理前的最长保存时间,
// WithRotationCount 设置文件清理前最多保存的个数。
//rotatelogs.WithMaxAge(time.Hour*24),
rotatelogs.WithRotationCount(maxRemainCnt),
)
if err != nil {
log.Errorf("config local file system for logger error: %v", err)
}
level, ok := logLevels[*logLevel]
if ok {
log.SetLevel(level)
} else {
log.SetLevel(log.WarnLevel)
}
lfsHook := lfshook.NewHook(lfshook.WriterMap{
log.DebugLevel: writer,
log.InfoLevel: writer,
log.WarnLevel: writer,
log.ErrorLevel: writer,
log.FatalLevel: writer,
log.PanicLevel: writer,
}, &log.TextFormatter{DisableColors: true})
return lfsHook
}
~~~
使用上述本地日志文件切割的效果如下:
![这里写图片描述](https://img-blog.csdn.net/20180814170847468?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzbHlrNjA2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
## 5.3 将日志发送到elasticsearch
将日志发送到elasticsearch,是很多日志监控系统的选择。将logrus日志发送到elasticsearch的原理是:在hook的每次fire调用时,使用golang的es客户端将日志信息写到elasticsearch。elasticsearch官方没有提供golang客户端,但是有很多第三方的go语言客户端可供使用,我们选择[elastic](https://github.com/olivere/elastic)。elastic提供了丰富的[文档](https://godoc.org/gopkg.in/olivere/elastic.v5),以及Java中的流式接口,使用起来非常方便。
~~~
client, err := elastic.NewClient(elastic.SetURL("http://localhost:9200"))
if err != nil {
log.Panic(err)
}
// Index a tweet (using JSON serialization)
tweet1 := Tweet{User: "olivere", Message: "Take Five", Retweets: 0}
put1, err := client.Index().
Index("twitter").
Type("tweet").
Id("1").
BodyJson(tweet1).
Do(context.Background())
~~~
考虑到logrus的Fields机制,可以实现如下数据格式:
~~~
msg := struct {
Host string
Timestamp string `json:"@timestamp"`
Message string
Data logrus.Fields
Level string
}
~~~
其中,`Host`记录产生日志主机信息,在创建hook时指定。对于其他数据,需要从`logrus.Entry`中取得。测试中,我们选择按照此原理实现的第三方HOOK:[elogrus](https://github.com/sohlich/elogrus)。其使用如下:
~~~
import (
"github.com/olivere/elastic"
"gopkg.in/sohlich/elogrus"
)
func initLog() {
client, err := elastic.NewClient(elastic.SetURL("http://localhost:9200"))
if err != nil {
log.Panic(err)
}
hook, err := elogrus.NewElasticHook(client, "localhost", log.DebugLevel, "mylog")
if err != nil {
log.Panic(err)
}
log.AddHook(hook)
}
~~~
从Elasticsearch查询得到日志存储,效果如下:
~~~
GET http://localhost:9200/mylog/_search
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
transfer-encoding: chunked
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2474,
"max_score": 1.0,
"hits": [
{
"_index": "mylog",
"_type": "log",
"_id": "AWUw13jWnMZReb-jHQup",
"_score": 1.0,
"_source": {
"Host": "localhost",
"@timestamp": "2018-08-13T01:12:32.212818666Z",
"Message": "!!!msb info not found",
"Data": {},
"Level": "ERROR"
}
},
{
"_index": "mylog",
"_type": "log",
"_id": "AWUw13jgnMZReb-jHQuq",
"_score": 1.0,
"_source": {
"Host": "localhost",
"@timestamp": "2018-08-13T01:12:32.223103348Z",
"Message": "get postgres instances info failed, scrape metrics failed, error:msb env not found",
"Data": {
"source": "collector/exporter.go:71:Scrape()"
},
"Level": "ERROR"
}
},
//...
{
"_index": "mylog",
"_type": "log",
"_id": "AWUw2f1enMZReb-jHQu_",
"_score": 1.0,
"_source": {
"Host": "localhost",
"@timestamp": "2018-08-13T01:15:17.212546892Z",
"Message": "!!!msb info not found",
"Data": {
"source": "collector/exporter.go:71:Scrape()"
},
"Level": "ERROR"
}
},
{
"_index": "mylog",
"_type": "log",
"_id": "AWUw2NhmnMZReb-jHQu1",
"_score": 1.0,
"_source": {
"Host": "localhost",
"@timestamp": "2018-08-13T01:14:02.21276903Z",
"Message": "!!!msb info not found",
"Data": {},
"Level": "ERROR"
}
}
]
}
}
Response code: 200 (OK); Time: 16ms; Content length: 3039 bytes
~~~
## 5.4 将日志发送到其他位置
将日志发送到日志中心,也是logrus所提倡的。虽然没有提供官方支持,但是目前Github上有很多第三方hook可供使用:
* [logrus\_amqp](https://github.com/vladoatanasov/logrus_amqp):Logrus hook for Activemq
* [logrus-logstash-hook](https://github.com/bshuster-repo/logrus-logstash-hook):Logstash hook for logrus
* [mgorus](https://github.com/weekface/mgorus):Mongodb Hooks for Logrus
* [logrus\_influxdb](https://github.com/abramovic/logrus_influxdb):InfluxDB Hook for Logrus
* [logrus-redis-hook](https://github.com/rogierlommers/logrus-redis-hook):Hook for Logrus which enables logging to RELK stack (Redis, Elasticsearch, Logstash and Kibana)
等等。对于上述第三方hook,我这里没有具体验证,大家可以根据需要自行尝试。
## 5.5 其他注意事项
### 5.5.1 Fatal处理
和很多日志框架一样,logrus的`Fatal`系列函数会执行`os.Exit(1)`。但是,logrus提供“可以注册一个或多个`fatal handler`函数`”`的接口`logrus.RegisterExitHandler(handler func(){})`,让logrus在执行`os.Exit(1)`之前进行相应的处理。`fatal handler`可以在系统异常时调用一些资源释放api等,让应用正确地关闭。
### 5.5.2 线程安全
**默认情况下,logrus的api都是线程安全的,其内部通过互斥锁来保护并发写**。互斥锁工作于调用hooks或者写日志的时候。如果不需要锁,可以调用`logger.SetNoLock()`来关闭之。可以关闭logrus互斥锁的情形包括:
* 没有设置hook,或者所有的hook都是线程安全的实现。
* 写日志到logger.Out已经是线程安全的了。例如,logger.Out已经被锁保护,或者写文件时,文件是以O\_APPEND方式打开的,并且每次写操作都小于4k。
尊重别人的劳动成果,原文网址:
[https://blog.csdn.net/wslyk606/article/details/81670713](https://blog.csdn.net/wslyk606/article/details/81670713)
';
tuotoo/qrcode识别二维码
最后更新于:2022-04-02 04:57:43
**tuotoo/qrcode识别二维码**
github地址:https://github.com/tuotoo/qrcode
获取:
~~~
go get github.com/tuotoo/qrcode
~~~
读取二维码图片:
~~~
package main
import (
"fmt"
"os"
"github.com/tuotoo/qrcode"
)
func main() {
fi, err := os.Open("qrcode.png")
if err != nil {
fmt.Println(err.Error())
return
}
defer fi.Close()
qrmatrix, err := qrcode.Decode(fi)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(qrmatrix.Content)
}
~~~
';
boombuler/barcode生成二维码
最后更新于:2022-04-02 04:57:40
**boombuler/barcode生成二维码**
github地址:https://github.com/boombuler/barcode
获取:
~~~
go get github.com/boombuler/barcode
~~~
生成二维码图片:
~~~
package main
import (
"image/png"
"os"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/qr"
)
func main() {
qrCode, _ := qr.Encode("http://blog.csdn.net/wangshubo1989", qr.M, qr.Auto)
qrCode, _ = barcode.Scale(qrCode, 256, 256)
file, _ := os.Create("qr2.png")
defer file.Close()
png.Encode(file, qrCode)
}
~~~
';
skip2/go-qrcode生成二维码
最后更新于:2022-04-02 04:57:38
**skip2/go-qrcode生成二维码**
github地址:https://github.com/skip2/go-qrcode
获取:
~~~
go get skip2/go-qrcode
~~~
生成二维码图片:
~~~
package main
import qrcode "github.com/skip2/go-qrcode"
import "fmt"
func main() {
err := qrcode.WriteFile("http://blog.csdn.net/wangshubo1989", qrcode.Medium, 256, "qr.png")
if err != nil {
fmt.Println("write error")
}
}
~~~
';