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

2022年12月11日 17:24

 

说明

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

一般设置方法

 
以下是设置gin日志的方法
 
#方法一
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属性实现
 
# 顶层模块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
评论(214) 阅读(3364)

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

2022年9月17日 20:03

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

示例代码

 
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()返回值不一样, 一次为空, 一次正常.
 
* 换一个电脑试试?
 
发现了在公司办公电脑出现,家里电脑不会出现。
 

解决办法

 
//使用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
评论(234) 阅读(2870)

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自定义监控项
略. 不是本文主题
 
 

下载安装

 
grafana
https://github.com/grafana/grafana

prometheus
https://github.com/prometheus/prometheus
 

gin支持prometheuse

 
1. 修改gin服务代码
 
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)
 
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
评论(10) 阅读(833)

golang实现枚举类型

2022年7月25日 10:50

描述

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

什么是枚举类型

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

示例

 
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
评论(9) 阅读(599)

基于ectd实现go的服务注册

2021年11月27日 18:42

 

描述

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

需求

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

创建etcd客户端

 

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

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

 
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
评论(78) 阅读(1398)

go和python解码msgpack不一致

2021年11月27日 18:37

 

描述

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

环境

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

python解码

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

错误提示

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

解决办法

 
msgpack.unpackb(xxx, raw=True)

#raw=True  解析为python字节类型
#raw=False 解析为python字符串, 默认使用utf-8编码
 

评论(775) 阅读(2816)

golang位移操作的一个小坑

2021年11月25日 16:08

问题描述

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

错误用法

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

正确用法

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

错误原因

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

评论(19) 阅读(907)

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, 进程可能会启动多个。需要加锁处理处理.
 
## 用法
 
* 配置文件查找顺序
 
$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
 
[program:test]
command = /your/program args
 
 
* 常用配置
 
[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
评论(41) 阅读(1021)

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

2021年11月14日 13:24

 

问题描述

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

环境

 
* go: 1.16
 
* nats-server
 

程序摘要

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

原因以及解决办法

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

Tags: nats go
评论(13) 阅读(1071)

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

2021年8月26日 17:22

 
 

介绍

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

为什么会认为是病毒?

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

解决办法

 
    在程序中,使用代码隐藏cmd窗口
 
 
import "github.com/lxn/win"

//隐藏cmd窗口
win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
 
    缺点:启动时会有很短暂的黑框闪现,不过问题不大
 
 

评论(63) 阅读(2504)