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") } } ~~~
';