全局gin对象如何在子模块中修改Logger

2022年12月11日 17:24

 

说明

 
gin是通过中间件写日志, 日志对象是中间件的闭包变量。在use加载中间件完成之后,无法修改日志对象。 该如何办呢?
 

一般设置方法

 
以下是设置gin日志的方法
 
1
2
3
4
5
6
7
8
9
#方法一
gin.DefaultWriter = xxx
gin.DefaultErrorWriter = xxx
app := gin.Default()
app.Use(xxx)
 
#方法二
app := gin.Default()
app.Use(gin.LoggerWithWriter(xxx))
 

当情况发生变化

 
1. app := gin.Default()被放在了顶层模块,并且加载了默认中间件
 
2. 程序真正的log在子模块中初始化
 
3. 在子模块中如何修改gin的日志?
 

解决办法

 
借助zapio.Writer对象,通过修改它的Log属性实现
 
1
2
3
4
5
6
7
8
9
10
11
# 顶层模块gin.go
gin.DefaultWriter =&zapio.Writer{Log:zap.L()}
gin.DefaultErrorWriter = gin.DefaultWriter
app := gin.Default()
app.use(xxx)
 
 
#子模块xxx/sim/http.go
if gl, ok := gin.DefaultWriter.(*zapio.Writer);ok{
    gl.Log = gs.log.Desugar()   #修改Log属性
}
 
 
 
 
 
 

Tags: gin
评论(227) 阅读(3972)

viper监控文件变化出现两次事件

2022年9月17日 20:03

 
问题描述
 
监控配置文件变化,如果内容发生了修改,需要及时加载. 使用viper监控文件变化发现了, 对文件修改一次,会出发两次事件.
 

示例代码

 
1
2
3
4
5
6
viper.OnConfigChange(func(e fsnotify.Event) {
    if e.Op & fsnotify.Write != 0 {
        fmt.Println("Config file changed:", e.Name)
    }
})
viper.WatchConfig()
 

推测原因

 
* 检查viper的源码是否存在bug?
 
未发现异常
 
* 两次变化viper读到的内容返回值是否不一样? 
 
发现viper.AllKeys()返回值不一样, 一次为空, 一次正常.
 
* 换一个电脑试试?
 
发现了在公司办公电脑出现,家里电脑不会出现。
 

解决办法

 
1
2
3
4
5
6
7
//使用AllKeys进行过滤
viper.OnConfigChange(func(e fsnotify.Event) {
    if e.Op & fsnotify.Write != 0 && viper.AllKeys()>0{
        fmt.Println("Config file changed:", e.Name)
    }
})
viper.WatchConfig()
 
 

总结

 
公司的办公电脑安装了文件加密软件。我估摸着是它搞了个什么鬼.
 
 
 
 
 

Tags: viper golang
评论(236) 阅读(4930)

gin支持prometheus

2022年8月03日 15:25

 

起因

 
gin使用了微服务架构, 如何将自己的服务都监控起来?
 

疑问列表

 
 
1. grafana是什么, 能做什么?
 
grafana是一个监控平台,支持不同数据源的可视化。也就是说支持从prometheuse,influxdb等数据源, 并将他们的数据形式可视化的图表.
 
2. prometheus能做什么?
prometheuse是一个监控系统,通过定时http pull采集数据. 并且支持http push.
 
3. prometheus和grafana是什么关系?
prometheuse是监控系统, 界面展示比较粗糙, grafana主要是可视化, 并且支持多种类型数据源。
 
4. gin如何与prometheuse结合?
见后面
 
5. gin如何与grafana?
grafana不支持直接接入gin服务。需要gin先接入prometheuse,再将prometheus接入grafana。
 
6. gin服务的metrics数据格式如何解析?
暂时未找到
 
7. gin自定义监控项
略. 不是本文主题
 
 

下载安装

 
1
2
3
4
5
grafana
https://github.com/grafana/grafana
 
prometheus
https://github.com/prometheus/prometheus
 

gin支持prometheuse

 
1. 修改gin服务代码
 
1
2
3
4
5
6
7
8
9
10
import (
    ...
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
    ...
)
 
 
//metrics, promhttp默认可以查看cpu,mem,线程等信息
router.GET("/metrics", gin.WrapH(promhttp.Handler()))
 
2.修改prometheuse配置(prometheuse.yml)
 
1
2
3
4
scrape_configs:
  - job_name: "prometheus-demo"
    static_configs:
      - targets: ["192.168.28.26:8011"]
 
3. 启动服务
 
a. 启动gin和prometheuse服务后, 访问localhost:9090
 
b. 在"Expression"输入框选择"go_goroutines", 点击"Execute"
 
c. 刚开始数据比较少,可以过一段时间后再刷新页面
 
净土大经科注2014-doc
此生必看的科学实验-水知道答案
印光大师十念法(胡小林主讲第1集)
 

Tags: go prometheus
评论(25) 阅读(1218)

golang实现枚举类型

2022年7月25日 10:50

描述

 
golang无枚举类型, 但可以借助自定义类型实现
 

什么是枚举类型

 
枚举类型是一种类型, 它的值只有有限个. 换而言之, 枚举类型=一个类型+N个离散值
 
go的实现, 可以自定义一个类型, 把它作为枚举类型, 然后使用该类型定义N个常量.
 
 

示例

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type FormatType int
 
const (
    FTByte FormatType = iota
    FTArray
    FTDefine
)
 
func (ft FormatType) String() string {
    switch ft{
    case FTByte:
        return "byte"
    case FTArray:
        return "array"
    case FTDefine:
        return "define"
    }
    return ""
}
 
 

Tags: golang
评论(34) 阅读(1026)

基于ectd实现go的服务注册

2021年11月27日 18:42

 

描述

 
    基于etcd实现go程序的服务注册,所看的学习资料均比较简单, 实际中要经过反复测试,自己写了一个先用着,有坑再填.
 

需求

 
1
2
3
4
5
1. 注册一个服务: 程序启动时
2. 注销服务: a. 程序异常退出时, 自动注销, 有5秒TTL延迟; b. 调用Stop()接口主动注销
3. 健壮性: a.ectd单节点重启, ectd本身支持; b. etcd全部重启后, 能够恢复正常
4. key的格式: 前缀/ip/pid
5. value: 字符串.(可以先转为json, 再转string)
 
 

创建etcd客户端

 
1
 
//不是重点, 摘要如下代码
 
cli, err := clientv3.New(clientv3.Config{
Endpoints:   ec.Endpoints,
DialTimeout: 5 * time.Second,
})
if err != nil {
log.Fatal(err)
}
 

创建Service对象, 包含服务注册

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package main
 
import (
    "context"
    "errors"
    "fmt"
    clientv3 "go.etcd.io/etcd/client/v3"
    "go.etcd.io/etcd/client/v3/clientv3util"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "net"
    "os"
    "strings"
    "sync"
    "test/etcd"
    "time"
)
 
 
var IP string
var PID int
 
 
func init(){
    //日志
    encoderConfig := zap.NewProductionEncoderConfig()
    encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
    encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
    encoder := zapcore.NewConsoleEncoder(encoderConfig)
 
    //创建
    var clevel zapcore.Level
    clevel.Set("debug")
    log := zap.New(zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), clevel))
    //设为全局
    zap.ReplaceGlobals(log)
 
    //
    IP, _ = GetLocalIP()
    PID = os.Getpid()
}
 
func GetLocalIP() (string,error){
    addrs, err := net.InterfaceAddrs()
    if err != nil {
        zap.S().Warn(err)
        return "", err
    }
    for _, addr := range addrs{
        if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback(){
            if ipnet.IP.To4() != nil {
                return ipnet.IP.String(), nil
            }
        }
    }
    return "", errors.New("unable to determine local ip")
}
 
 
func RegisterService(endpoints []string, key string, value string) *Service{
    s := &Service{
        Value: value,
        endpoints: endpoints,
    }
 
    s.TTL = 5
    s.Key = strings.Join([]string{key, IP, fmt.Sprintf("%d", PID)}, "/")
 
    s.init()
 
    return s
}
 
 
type Service struct{
    Key             string      //key 格式: /前缀/名称/ip/pid
    Value           string      //存放信息
    endpoints       []string    //etcd地址
    //心跳
    IsAlive         bool        //是否存活
    heartCancel     context.CancelFunc  //取消心跳
    //租约
    TTL             int64       //租约(秒)
    grant           *clientv3.LeaseGrantResponse    //租约
}
 
 
func (s *Service) init(){
    //启用etcd连接
    etcd.EnableEtcd(s.endpoints)
 
    //心跳
    go s.start()
}
 
func (s *Service) start(){
    defer func(){
        s.IsAlive = false
 
        //注销服务
        s.deleteService()
        //关闭续约(如果共用会误关?)
        etcd.Cli().Lease.Close()
    }()
 
    var aliveRsp <-chan *clientv3.LeaseKeepAliveResponse
    var err error
    var ctx context.Context
    for{
        if !s.IsAlive{
            // 注册服务
            s.registerService()
 
            // 续期信号chan
            aliveRsp, err = etcd.Cli().KeepAlive(context.TODO(), s.grant.ID)
            if err != nil{
                zap.S().Warn(err)
            }
 
            ctx, s.heartCancel = context.WithCancel(context.Background())
        }
 
        // 监听心跳信号
        select{
        case rsp := <- aliveRsp:
            //异常时无信号
            if rsp ==nil{
                s.IsAlive = false
                zap.S().Info("service missing signal")
                time.Sleep(time.Second*5)
                continue
            }
 
            s.IsAlive = true
            zap.S().Debugf("service alive %v", rsp.ID)
        case <- ctx.Done():
            zap.S().Info("service stopping")
            return
        }
    }
}
 
//停止
 
func (s *Service) Stop(){
    s.heartCancel()
}
 
[佛說大乘無量壽莊嚴清淨平等覺經pdf](http://www.sxjy360.top/page-download/)
[净土大经科注2014-doc](http://www.sxjy360.top/page-download/)
[此生必看的科学实验-水知道答案](http://www.sxjy360.top/page-download/)
[印光大师十念法(胡小林主讲第1集)](http://www.sxjy360.top/page-download/)
 

Tags: etcd
评论(85) 阅读(1553)

go和python解码msgpack不一致

2021年11月27日 18:37

 

描述

 
    有一个消息字典类型, 使用go进行编码, 传入消息中间件。同时有一个python程序监听, 监听到消息之后进行解码, 遇到了编码问题
 
 

环境

 
1
2
go使用:     github.com/vmihailenco/msgpack/v5 v5.3.4
python使用: msgpack==1.0.2
 
 

python解码

 
1
2
# go编码的对象是个字典,value可能包含了数组,并且数组类型复杂
msgpack.unpackb(xxx)
 
 

错误提示

 
1
UnicodeDecodeError: 'utf-8' codec can't decode byte 0bx0 in position 0: invalid start byte
 

解决办法

 
1
2
3
4
msgpack.unpackb(xxx, raw=True)
 
#raw=True  解析为python字节类型
#raw=False 解析为python字符串, 默认使用utf-8编码
 

评论(782) 阅读(3208)

golang位移操作的一个小坑

2021年11月25日 16:08

问题描述

 
    现有int16类型的整数,将它转为两个字节存放。在还原时,一不小心会就被坑了。
 
 

错误用法

 
1
length := int(keyBytes[cursor] + keyBytes[cursor+1] <<8)
 

正确用法

 
1
2
keyBytes := []byte{ 16, 1}
length := int(uint16(keyBytes[cursor]) + uint16(keyBytes[cursor+1]) <<8)
 

错误原因

 
字节byte位移时类型不会自动变化, 先要转换类型再位移。
 
 

评论(25) 阅读(1070)

go版本的supervisord

2021年11月14日 13:27

 

描述

 
python版本的supervisrod, 在linux、windows系统中离线安装都不怎么方便.  是否有go版本的呢?
 

github地址

 
[https://github.com/ochinchina/supervisord/](https://github.com/ochinchina/supervisord/)
 

踩坑记录

 
* 编译32版本, 有些报错, 需要修改源码
 
* 在ui界面快速点击start/stop, 进程可能会启动多个。需要加锁处理处理.
 
## 用法
 
* 配置文件查找顺序
 
1
2
3
4
5
6
$CWD/supervisord.conf
$CWD/etc/supervisord.conf
/etc/supervisord.conf
/etc/supervisor/supervisord.conf (since Supervisor 3.3.0)
../etc/supervisord.conf (Relative to the executable)
../supervisord.conf (Relative to the executable)
 
 
* 最简单配置supervisord.conf
 
1
2
[program:test]
command = /your/program args
 
 
* 常用配置
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[inet_http_server]          ;http接口
port=:9002
;username=xxxx
;password=xxxx
 
[supervisorctl]             ;ui界面
serverurl=http://127.0.0.1:9002
 
[supervisord]               ;日志
logfile=%(here)s/../log/supervisord.log
logfileMaxbytes=50MB        ;文件大小
logfileBackups=10           ;10个文件
loglevel=info               ;日志级别
 
[program:middleware1]       ;进程
command = %(here)s/../middleware1.exe   ;here指的是supervisord.conf的路径,而不是supervisord.exe的路径
autorestart = true
numprocs=1                  ;进程数量
 
[program:programname1]
command = %(here)s/../programname1.exe
autorestart = true
numprocs=1
depends_on=middleware1      ;依赖进程
 
 
[program:programname2]
command = %(here)s/../programname2.exe
autorestart = true
numprocs=1
depends_on=middleware1
 

Tags: supervisord go
评论(45) 阅读(1239)

nats批量publish最后一条疑似丢失

2021年11月14日 13:24

 

问题描述

 
    写了一个测试脚本,从数据读取一组数据,然后逐条publish到nats-server中, 确定每条都publish了
 
    有另外一个程序中subscribe订阅,在该程序发现最后一条一直没收到
 
 

环境

 
* go: 1.16
 
* nats-server
 

程序摘要

 
* 发布
 
1
2
3
4
5
for _, record := range records{
    ...
    gNatsConn.Publish(Topic, record)
    time.Sleep(time.Second*0.1)    //间隔越小出现的概率越大. 间隔1秒好像就没有
}
 
* 订阅
 
1
2
3
4
natsConn.Subscribe(topic, func(m *nats.Msg){
    fmt.Println(m.Data)
    ....
}
 

原因以及解决办法

 
    nats在发布消息时,有应该是用了缓存通道, 大小是1。最后一条数据有可能没被读走
 
    发布频率较高,最后需要flush一下
 
 
 

Tags: nats go
评论(12) 阅读(1211)

golang编译-ldflags -H windowsgui被认为是病毒

2021年8月26日 17:22

 
 

介绍

 
    使用walk为程序做了一个windows界面, cmd窗口需要隐藏。使用了 go build -ldflags="-H windowsgui"
 
    结果exe程序被杀毒软件认为是病毒
 

为什么会认为是病毒?

 
    隐藏cmd窗口, 双击exe,可以直接启动,并且看不到任何窗口。这不就是病毒的喜欢干的事吗.
 
    所以有些杀毒软件, 干脆通杀, 使用了 "-H windowsgui" 直接被认为是病毒
 
    我们这些正常的需求该咋办呢?
 

解决办法

 
    在程序中,使用代码隐藏cmd窗口
 
 
1
2
3
4
import "github.com/lxn/win"
 
//隐藏cmd窗口
win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
 
    缺点:启动时会有很短暂的黑框闪现,不过问题不大
 
 

评论(63) 阅读(2706)