Go之封装Http请求和日志
小伙伴们,你们好呀!我是老寇!跟我一起学习封装Http请求和日志
Http请求
封装Http请求,直接使用 net/http
就行,主要是有两点需要注意,Https如何关闭校验
和客户端上传文件
Https如何关闭校验
// 跳过TLS证书校验
client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},},
}
客户端上传文件
上传文件需遵循 RFC 1867 标准,因此,请求头 Content-Type
设为 multipart/form-data;boundary=xxx
http.go
import ("bytes""crypto/tls""errors""io""mime/multipart""net/http"
)func SendRequest(method, url string, param io.Reader, header map[string]string) (*http.Response, error) {request, err := http.NewRequest(method, url, param)if err != nil {return nil, errors.New("创建 Request 失败,错误信息:" + err.Error())}if header != nil {for k, v := range header {request.Header.Set(k, v)}}return sendHttpRequest(request)
}func GetFormFile(fileName string, buf []byte) (io.Reader, string, error) {body := &bytes.Buffer{}writer := multipart.NewWriter(body)part, err := writer.CreateFormFile("file", fileName)if err != nil {return nil, "", errors.New("创建 FormFile 失败,错误信息:" + err.Error())}_, err = io.Copy(part, bytes.NewReader(buf))if err != nil {return nil, "", errors.New("复制字节数组失败,错误信息:" + err.Error())}err = writer.Close()if err != nil {return nil, "", errors.New("关闭 Writer 失败,错误信息:" + err.Error())}return body, writer.FormDataContentType(), nil
}func SendRequestAndGetBody(method, url string, param io.Reader, header map[string]string) ([]byte, error) {response, err := SendRequest(method, url, param, header)if err != nil {return nil, err}body, err := io.ReadAll(response.Body)if err != nil {return nil, errors.New("读取响应失败,错误信息:" + err.Error())}defer response.Body.Close()return body, nil
}func sendHttpRequest(request *http.Request) (*http.Response, error) {client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},},}response, err := client.Do(request)if err != nil {return nil, errors.New("发送 HTTP 请求失败,错误信息:" + err.Error())}return response, nil
}
http_test.go
func Test_Http(t *testing.T) {// Get请求header = map[string]string{"Cookie": 'token=123',}_, _ = SendRequest(http.MethodGet, "https://localhost/api/test", nil ,header)// Post请求_, _ = SendRequest(http.MethodPost, "https://localhost/api/test", strings.NewReader("username=a&password=123") ,header)// Post上传文件【表单】// 这里便于演示,随便定义一个字节数组buf := []byte('123')formFile, contentType, err := core.GetFormFile("image.jpg", buf)if err != nil {fmt.println(err.Error())return}header = map[string]string{"Content-Type": contentType,}// 上传文件_, _ = SendRequest(http.MethodPost, "https://localhost/api/upload", formFile, header)
}
日志
日志对于一个项目来说是非常重要的,因此,很有必要根据业务进行挑选,go的常用日志包有glog
、logrus
和zap
维度 | glog (Google) | logrus (社区维护) | zap (Uber) |
---|---|---|---|
项目背景 | Kubernetes 等基础设施项目常用,轻量级 | 早期 Go 社区主流,已停止更新(仅维护) | 高性能工业级实践,活跃维护 |
性能 | 中等(基于标准库优化) | 较低(反射序列化,内存分配多) | 极高(预分配内存、零反射,每秒百万级日志) |
日志级别 | 4 级(Info/Warning/Error/Fatal) | 6 级(Trace/Debug/Info/Warn/Error/Fatal) | 7 级(Debug/Info/Warn/Error/Dpanic/Panic/Fatal) |
结构化日志 | ❌ 不支持(仅文本格式) | ✅ 支持(WithFields 动态类型) | ✅ 强类型(zap.String() ),SugaredLogger 兼容非结构化 |
API 风格 | 类似标准库(如 glog.Info("msg") ) | 链式调用(logrus.WithField().Info() ) | 显式类型(zap.Info("msg", zap.String("k","v")) ) |
日志轮转 | ❌ 需手动实现或第三方库 | ❌ 依赖插件(如 file-rotatelogs )2 | ✅ 原生支持(集成 lumberjack ) |
内存分配 | 较少 | 较多(反射+动态字段) | 极少(预分配+非反射) |
动态调级 | ❌ 不支持 | ✅ 需手动实现2 | ✅ 原生支持(AtomicLevel ) |
学习成本 | 低(类似标准库) | 低(类似 fmt ) | 中(结构化 API 稍复杂,SugaredLogger 简化) |
适用场景 | 轻量级工具、K8s 生态兼容 | 旧项目维护、插件依赖型应用 | 高并发服务、云原生、性能敏感场景 |
考虑到网关设备,为了节约内存空间和极致的性能,我们采用zap封装,zap不支持日志滚动,所以,我们使用第三方组件 timberjack增强,支持日志滚动,日志保留天数,日志级别
log.go
import ("errors""github.com/DeRuina/timberjack""go.uber.org/zap""go.uber.org/zap/zapcore""log""time"
)const (PROD = "prod"
)type LogConfig struct {// 日志级别Level string `yaml:"level"`// 环境Profile string `yaml:"profile"`// 日志格式Pattern string `yaml:"pattern"`// 日志文件路径FilePath string `yaml:"file-path"`// 日志最大容量【单位M】MaxSize int `yaml:"max-size"`// 日志最大数量MaxBackups int `yaml:"max-backups"`// 日志保留时间【单位天】MaxAge int `yaml:"max-age"`// 是否压缩Compress bool `yaml:"compress"`// 本地时间,默认值:false(使用UTC)LocalTime bool `yaml:"local-time"`// json格式JsonFormat bool `yaml:"json-format"`// 转换频率RotationInterval time.Duration `yaml:"rotation-interval"`// 转换时间【分钟】RotateAtMinutes []int `yaml:"rotate-at-minutes"`// 日志时间格式BackupTimeFormat string `yaml:"backup-time-format"`
}func (c *LogConfig) InitLogger() (*zap.Logger, error) {timberjackLogger := &timberjack.Logger{Filename: c.FilePath,MaxSize: c.MaxSize,MaxBackups: c.MaxBackups,MaxAge: c.MaxAge,Compress: c.Compress,LocalTime: c.LocalTime,RotationInterval: c.RotationInterval,RotateAtMinutes: c.RotateAtMinutes,BackupTimeFormat: c.BackupTimeFormat,}log.SetOutput(timberjackLogger)defer timberjackLogger.Close()writeSyncer := zapcore.AddSync(timberjackLogger)// 配置日志级别levelConfig := zap.NewAtomicLevel()level, err := zapcore.ParseLevel(c.Level)if err != nil {return nil, errors.New("日志级别不存在,请重新配置,错误信息:" + err.Error())}levelConfig.SetLevel(level)// 配置环境var encoderConfig zapcore.EncoderConfigswitch c.Profile {case PROD:encoderConfig = zap.NewProductionEncoderConfig()default:encoderConfig = zap.NewDevelopmentEncoderConfig()}// 设置时间格式encoderConfig.EncodeTime = c.customTimeEncodervar encoder zapcore.Encoder// Json格式if c.JsonFormat {encoder = zapcore.NewJSONEncoder(encoderConfig)} else {encoder = zapcore.NewConsoleEncoder(encoderConfig)}core := zapcore.NewCore(encoder, writeSyncer, levelConfig)return zap.New(core, zap.AddCaller()), nil
}func (c *LogConfig) customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {enc.AppendString(t.Format(c.Pattern))
}
application.yaml
log:# 级别level: error# 环境profile: prod# 日志格式pattern: 2006-01-02 15:04:05.000# 日志文件路径file-path: test.log# 日志最大容量【单位M】max-size: 500# 日志最大数量max-backups: 10# 日志保留时间【单位天】max-age: 30# 是否压缩compress: true# 本地时间,默认值:false(使用UTC)local-time: true# json格式,true为json格式日志json-format: false# 转换频率rotation-interval: 24h# 转换时间【分钟】rotate-at-minutes:- 0- 15- 30- 45# 日志时间格式backup-time-format: 2006-01-02-15-04-05
config.go
import ("errors""gopkg.in/yaml.v3""os"
)type SystemConfig struct {Log LogConfig `yaml:"log"`
}func GetSystemConfig(path string) (*SystemConfig, error) {data, err := os.ReadFile(path)if err != nil {return nil, errors.New("读取配置文件失败,错误信息:" + err.Error())}sysConfig := &SystemConfig{}err = yaml.Unmarshal(data, sysConfig)if err != nil {return nil, errors.New("配置文件反序列化失败,错误信息:" + err.Error())}return sysConfig, nil
}
log_test.go
import ("testing"
)func Test_Log(t *testing.T) {config, err := GetSystemConfig("application.yaml")if err != nil {t.Error(err.Error())return}logger, err := config.Log.InitLogger()if err != nil {t.Error(err.Error())return}logger.Info("信息")logger.Warn("警告")logger.Error("错误")t.Log("日志测试通过")
}
注意:部署到网关上面,日志无法打印,需要对日志增加权限和不能以sudo运行
# 增加日志权限
sudo chmod -R 7777 /home/a/app/logs
# 运行项目,不能以sudo运行项目,否则日志无法打印,这是我遇到的坑
nohup /home/a/app/log > /dev/null 2>&1 &
我是老寇,我们下次再见啦!