Gin框架学习笔记(六)——gin中的日志使用

gin内置日志组件的使用

前言

在之前我们要使用Gin框架定义路由的时候我们一般会使用Default方法来实现,我们来看一下他的实现:


func Default(opts ...OptionFunc) *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine.With(opts...)
}

我们可以看到它注册了两个中间件Logger()Recovery(),而Logger就是我们今天的主角:gin框架自带的日志组件。

输出日志到文件中

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"os"
)

func main() {
	file, err := os.Create("ginlog")
	if err != nil {
		fmt.Println("Create file error! err:", err)
	}
	gin.DefaultWriter = io.MultiWriter(file)
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello World!",
		})
	})
	r.Run()
}

运行上面代码我们会发现,控制台不再会有相关日志的输出,而是打印到了ginlog文件中:

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

当然我们也可以选择既在控制台输出也在文件内输出:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"os"
)

func main() {
	file, err := os.Create("ginlog")
	if err != nil {
		fmt.Println("Create file error! err:", err)
	}
	gin.DefaultWriter = io.MultiWriter(file, os.Stdout)
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello World!",
		})
	})
	r.Run()
}

在这里插入图片描述
我们可以看到无论是日志文件ginlog和控制台,都实现了对日志的打印

定义日志中的路由格式

当我们运行Gin框架的时候,它会自动打印当前所有被定义的路由,比如下面这样的格式:

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)

而在Gin框架中它允许我们去自己定义路由的输出格式,我们可以自己去定义我们的路由格式:

func _Router_print_init() {
	gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string,
		nuHandlers int) {
		fmt.Printf("[三玖]: %v  %v   %v   %v  \n",
			httpMethod, absolutePath, handlerName, nuHandlers)
	}
}

输出的路由格式是这样的:

[三玖]: GET  /   main.main.func1   3

生产模式与开发模式

在我们程序其实是有两种模式的:

  • debug:开发模式
  • release:生产模式

如果我们希望控制台不在显示日志,可以将模式切换到release模式:

	gin.SetMode(gin.ReleaseMode)
	r := gin.Default()

在这里插入图片描述
我们可以看到控制台已不再输出日志信息了。

第三方包logrus日志包的使用

logrus包的安装与基本使用

logrus包的安装

logrus的安装很简单,只需要终端输入以下命令即可:

go get github.com/sirupsen/logrus

logrus包的基本使用

logrus常用方法:
	logrus.Debug("debug")
	logrus.Info("info")
	logrus.Warn("warn")
	logrus.Error("error")
	logrus.Println("println")

当我们运行该代码的时候会发现打印结果只有四行:
在这里插入图片描述
这主要是因为logrus默认的打印等级是info,在这个等级之下的不会打印,在我们生产环境下一般会要求不打印Warn以下的日志,我们可以对打印等级进行调整:

logrus.SetLevel(logrus.WarnLevel)

再次运行上面的代码,运行结果就会有所不同:
在这里插入图片描述
我们还可以查看当前的打印等级:

fmt.Println(logrus.GetLevel())

设置特定字段

如果我们希望某条日志记录的打印中添加某一条特定的字段,我们可以使用WithField方法:

log1 := logrus.WithField("key1", "value1")
log1.Info("hello world")

通常,在一个应用中、或者应用的一部分中,都有一些固定的Field。比如我们在处理用户http请求时,上下文中,所有的日志都会有request_id和user_ip为了避免每次记录日志都要使用log.WithFields(log.Fields{“request_id”: request_id, “user_ip”: user_ip}),我们可以创建一个logrus.Entry实例,为这个实例设置默认Fields,在上下文中使用这个logrus.Entry实例记录日志即可,这里我写了一个demo,仅做参考:

package main

import (
	"github.com/sirupsen/logrus"
)

type DefaultLogger struct {
	*logrus.Entry
	defaultFields logrus.Fields
}

func NewDefaultLogger() *DefaultLogger {
	logger := logrus.New()
	entry := logrus.NewEntry(logger)
	return &DefaultLogger{
		Entry:         entry,
		defaultFields: logrus.Fields{},
	}
}

func (l *DefaultLogger) WithFields(fields logrus.Fields) *logrus.Entry {
	allFields := make(logrus.Fields, len(fields))
	for k, v := range fields {
		allFields[k] = v
	}
	return l.Entry.WithFields(allFields)
}

func (l *DefaultLogger) WithDefaultField() {
	l.Entry = l.Entry.WithFields(l.defaultFields)
}

func (l *DefaultLogger) Info(msg string) {
	l.WithDefaultField()
	l.Entry.Info(msg)
}

func (l *DefaultLogger) AddDefaultField(key string, value interface{}) {
	l.defaultFields[key] = value
}

func main() {
	defaultLogger := NewDefaultLogger()
	defaultLogger.AddDefaultField("request_id", "123")
	defaultLogger.AddDefaultField("user_ip", "127.0.0.1")

	// 使用默认字段记录日志
	defaultLogger.Info("This is a log message with default fields")

	// 添加额外字段记录日志
	defaultLogger.WithFields(logrus.Fields{
		"additional_field": "abc",
	}).Info("This is a log message with additional field")

}

输出结果为:
在这里插入图片描述

设置显示样式

虽然日志的打印默认是txt格式的,但是我们也可以将格式修改为json格式的:

logrus.SetFormatter(&logrus.TextFormatter{})

将日志输入到文件

package main

import (
	"github.com/sirupsen/logrus"
	"os"
)

func main() {
	file, err := os.OpenFile("./logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		panic(err)
	}
	logrus.SetOutput(file)
	logrus.Error("error")
}

我们还可以让控制台和日志文件一起输出:

package main

import (
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/windows"
	"io"
	"os"
)

func main() {
	file, err := os.OpenFile("./logrus.log", os.O_CREATE|os.O_WRONLY|windows.O_APPEND, 0666)
	if err != nil {
		panic(err)
	}
	writers := []io.Writer{
		file,
		os.Stdout,
	}
	lod := io.MultiWriter(writers...)
	logrus.SetOutput(lod)
	logrus.Error("error")
	logrus.Info("info")
}

显示行号

logrus.SetReportCaller(true)

logus的Hook机制

在使用logrus这一第三方包的时候,我们可以基于Hook机制来为logrus添加一些拓展功能。

首先我们先定义Hook结构体:

type Hook struct {
	Levels() []logrus.Level  // 返回日志级别
	Fire(entry *logrus.Entry) error  // 日志处理
}

我们Hook结构体中一般会有两个成员:

  • Levels:Hook机制起作用的日志级别
  • Fire:对应的日志处理方式

这里我们举一个例子,如果我们希望将所有Error级别的日志单独拎出来,我们可以基于Hook机制来实现:

package main

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"os"
)

type Hook struct {
	Writer *os.File
}

func (MyHook *Hook) Fire(entry *logrus.Entry) error {
	line, err := entry.String()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
	}
	MyHook.Writer.Write([]byte(line))
	return nil
}

func (MyHook *Hook) Levels() []logrus.Level {
	return []logrus.Level{
		logrus.ErrorLevel,
	}
}

func main() {
	logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true, TimestampFormat: "2006-01-02 15:04:05", FullTimestamp: true})
	logrus.SetReportCaller(true)
	file, _ := os.OpenFile("./error.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
	hook := &Hook{Writer: file}
	logrus.AddHook(hook)
	logrus.Error("error")
}

日志分割

按时间分割

  • Write写法
package main

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type LogFormatter struct{}

// Format 格式详情
func (s *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	timestamp := time.Now().Local().Format("2006-01-02 15:04:05")
	var file string
	var len int
	if entry.Caller != nil {
		file = filepath.Base(entry.Caller.File)
		len = entry.Caller.Line
	}
	//fmt.Println(entry.Data)
	msg := fmt.Sprintf("[%s] %s [%s:%d] %s\n", strings.ToUpper(entry.Level.String()), timestamp, file, len, entry.Message)
	return []byte(msg), nil
}

type LogWriter struct {
	Writer   *os.File
	logPath  string
	fileDate string //判断是否需要切换日志文件
	fileName string //日志文件名
}

func (writer *LogWriter) Write(p []byte) (n int, err error) {
	if writer == nil {
		logrus.Error("writer is nil")
		return 0, nil
	}
	if writer.Writer == nil {
		logrus.Error("writer.Writer is nil")
		return 0, nil
	}
	timer := time.Now().Format("2006-01-02 04:12")

	//需要切换日志文件
	if writer.fileDate != timer {
		writer.fileDate = timer
		writer.Writer.Close()
		err = os.MkdirAll(writer.logPath, os.ModePerm)
		if err != nil {
			logrus.Error(err)
			return 0, nil
		}
		filename := fmt.Sprintf("%s/%s.log", writer.logPath, writer.fileDate)
		writer.Writer, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
		if err != nil {
			logrus.Error(err)
			return 0, nil
		}
	}
	return writer.Writer.Write(p)
}

func Initing(logPath string, fileName string) {
	fileDate := time.Now().Format("20060102")
	filepath := fmt.Sprintf("%s/%s", logPath, fileDate)
	err := os.MkdirAll(filepath, os.ModePerm)
	if err != nil {
		logrus.Error(err)
		return
	}

	filename := fmt.Sprintf("%s/%s.log", filepath, fileName)
	writer, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		logrus.Error(err)
		return
	}

	Logwriter := LogWriter{logPath: logPath, fileDate: fileDate, fileName: fileName, Writer: writer}
	logrus.SetOutput(os.Stdout)
	writers := []io.Writer{
		Logwriter.Writer,
		os.Stdout,
	}
	multiWriter := io.MultiWriter(writers...)
	logrus.SetOutput(multiWriter)
	logrus.SetReportCaller(true)
	logrus.SetFormatter(new(LogFormatter))
}

func main() {
	Initing("./", "fengxu")
	logrus.Warn("fengxu")
	logrus.Error("fengxu")
	logrus.Info("fengxu")

}
  • Hook写法
package main

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"os"
	"time"
)

type Hook struct {
	writer   *os.File
	logPath  string
	fileName string
	fileDate string
}

func (MyHook *Hook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (MyHook *Hook) Fire(entry *logrus.Entry) error {
	timer := time.Now().Format("2006-01-02")
	line, _ := entry.String()
	//需要切换日志文件
	if MyHook.fileDate != timer {
		MyHook.fileDate = timer
		MyHook.writer.Close()
		filepath := fmt.Sprintf("%s/%s", MyHook.logPath, MyHook.fileDate)
		err := os.MkdirAll(filepath, os.ModePerm)
		if err != nil {
			logrus.Error(err)
			return err
		}

		filename := fmt.Sprintf("%s/%s.log", filepath, MyHook.fileName)
		MyHook.writer, _ = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	}
	MyHook.writer.Write([]byte(line))
	return nil
}

func InitFile(logPath string, fileName string) {
	timer := time.Now().Format("2006-01-02")
	filepath := fmt.Sprintf("%s/%s", logPath, timer)
	err := os.MkdirAll(filepath, os.ModePerm)
	if err != nil {
		logrus.Error(err)
		return
	}

	filename := fmt.Sprintf("%s/%s.log", filepath, fileName)
	writer, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		logrus.Error(err)
		return
	}
	logrus.AddHook(&Hook{
		writer:   writer,
		logPath:  logPath,
		fileName: fileName,
		fileDate: timer,
	})
}

func main() {
	InitFile("./log", "fengxu")
	logrus.Error("test")

}

按日志等级分割

package main

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"os"
)

const (
	alllog   = "all"
	errorlog = "error"
	warnlog  = "warn"
)

type Hook struct {
	allLevel   *os.File
	errorLevel *os.File
	warnLevel  *os.File
}

func (MyHook *Hook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (MyHook *Hook) Fire(entry *logrus.Entry) error {
	line, _ := entry.String()
	switch entry.Level {
	case logrus.ErrorLevel:
		MyHook.errorLevel.Write([]byte(line))
	case logrus.WarnLevel:
		MyHook.warnLevel.Write([]byte(line))
	}
	MyHook.allLevel.Write([]byte(line))
	return nil
}

func InitLevel(logPath string) {
	err := os.MkdirAll(logPath, os.ModePerm)
	if err != nil {
		logrus.Error("创建目录失败")
		return
	}
	allFile, err := os.OpenFile((fmt.Sprintf("%s/%s", logPath, alllog)), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600)
	errFile, err := os.OpenFile((fmt.Sprintf("%s/%s", logPath, errorlog)), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600)
	warnFile, err := os.OpenFile((fmt.Sprintf("%s/%s", logPath, warnlog)), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600)
	logrus.AddHook(&Hook{allLevel: allFile, errorLevel: errFile, warnLevel: warnFile})
}

func main() {
	InitLevel("./log")
	logrus.SetReportCaller(true)
	logrus.Errorln("你好")
	logrus.Errorln("err")
	logrus.Warnln("warn")
	logrus.Infof("info")
	logrus.Println("print")

}

gin集成logrus

  • main函数(main.go)
package main

import (
	"gin/Logger/gin/gin_logrus/log"
	"gin/Logger/gin/gin_logrus/middleware"
	"github.com/gin-gonic/gin"
)

func main() {
	log.InitFile("./log", "fengxu")
	r := gin.New()
	r.Use(middleware.Logmiddleware())
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run(":8080")
}

  • log.go
package log

import (
	"bytes"
	"fmt"
	"github.com/sirupsen/logrus"
	"os"
	"time"
)

type Hook struct {
	writer   *os.File
	logPath  string
	fileName string
	fileDate string
}

func (MyHook *Hook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (MyHook *Hook) Fire(entry *logrus.Entry) error {
	timer := time.Now().Format("2006-01-02")
	line, _ := entry.String()
	//需要切换日志文件
	if MyHook.fileDate != timer {
		MyHook.fileDate = timer
		MyHook.writer.Close()
		filepath := fmt.Sprintf("%s/%s", MyHook.logPath, MyHook.fileDate)
		err := os.MkdirAll(filepath, os.ModePerm)
		if err != nil {
			logrus.Error(err)
			return err
		}

		filename := fmt.Sprintf("%s/%s.log", filepath, MyHook.fileName)
		MyHook.writer, _ = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	}
	MyHook.writer.Write([]byte(line))
	return nil
}

type LogFormat struct {
}

func (l *LogFormat) Format(entry *logrus.Entry) ([]byte, error) {
	var buff *bytes.Buffer
	if entry.Buffer != nil {
		buff = entry.Buffer
	} else {
		buff = &bytes.Buffer{}
	}
	_, _ = fmt.Fprintf(buff, "%s\n", entry.Message) //这里可以自己去设置输出格式
	return buff.Bytes(), nil
}

func InitFile(logPath string, fileName string) {
	logrus.SetFormatter(&LogFormat{})
	timer := time.Now().Format("2006-01-02")
	filepath := fmt.Sprintf("%s/%s", logPath, timer)
	err := os.MkdirAll(filepath, os.ModePerm)
	if err != nil {
		logrus.Error(err)
		return
	}

	filename := fmt.Sprintf("%s/%s.log", filepath, fileName)
	writer, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		logrus.Error(err)
		return
	}
	logrus.AddHook(&Hook{
		writer:   writer,
		logPath:  logPath,
		fileName: fileName,
		fileDate: timer,
	})
}

  • 中间件(lmiddleware.go)
package middleware

import (
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
	"time"
)

const ( //自定义状态码和方法的显示颜色
	status200 = 42
	status404 = 43
	status500 = 41
	methodGET = 44
)

func Logmiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		raw := c.Request.URL.RawQuery
		if raw != "" {
			path = path + "?" + raw
		}

		c.Next() //执行其他中间件

		//end := time.Now()
		//timesub := end.Sub(start)  //响应所需时间
		//ClientIp := c.ClientIP()  //客户端ip
		statuscode := c.Writer.Status()
		//var statusColor string  
		//switch c.Writer.Status() {
		//case 200:
		//	statusColor = fmt.Sprintf("\033[%dm%d\033[0m", status200, statuscode)
		//case 404:
		//	statusColor = fmt.Sprintf("\033[%dm%d\033[0m", status404, statuscode)
		//default:
		//	statusColor = fmt.Sprintf("\033[%dm%d\033[0m", status500, statuscode)
		//}
		//
		//var methodColor string
		//switch c.Request.Method {
		//case "GET":
		//	methodColor = fmt.Sprintf("\033[%dm%s\033[0m", methodGET, c.Request.Method)
		//}

		logrus.Infof("[GIN] %s  |%d  |%s  |%s",
			start.Format("2006-01-02 15:04:06"),
			statuscode,
			c.Request.Method,
			path,
		)

	}
}

项目结构:
在这里插入图片描述

结语

至此我们对Gin框架的简单学习就到此为止了,更多的学习大家可以前去查看Gin框架官方文档:
Gin框架官方文档

后面就要开始对Gorm的学习了,下篇见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/647758.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探秘SpringBoot默认线程池:了解其运行原理与工作方式(@Async和ThreadPoolTaskExecutor)

文章目录 文章导图Spring封装的几种线程池SpringBoot默认线程池TaskExecutionAutoConfiguration(SpringBoot 2.1后)主要作用优势使用场景如果没有它 2.1版本以后如何查看参数方式一:通过Async注解--采用ThreadPoolTaskExecutordetermineAsync…

LiveGBS流媒体平台GB/T28181用户手册-基础配置:信令服务配置、流媒体服务配置、白名单、黑名单、更多配置

LiveGBS流媒体平台GB/T28181用户手册-基础配置:信令服务配置、流媒体服务配置、白名单、黑名单、更多配置 1、基础配置1.1、信令服务配置1.2、白名单1.3、黑名单1.4、流媒体服务配置 2、搭建GB28181视频直播平台 1、基础配置 LiveGBS相关信令服务配置和流媒体服务配置都在这里…

React 中Redux结合React-Redux使用类组件版本(一)

一、Redux是什么? 1.Redux是一个专门用于状态管理的js库 2.它可以用在React、Angular、Vue的项目中,但基本与React配合使用。 3.作用:集中式管理React应用中多个组件共享的状态。 二、Redux 工作流程 三、Redux的三个核心概念 1.action 动…

在AndroidStudio创建虚拟手机DUB-AI20

1.DUB-AI20介绍 DUB-AL20是华为畅享9全网通机型。 华为畅享9采用基于Android 8.1定制的EMUI 8.2系统,最大的亮点是配置了1300万AI双摄、4000mAh大电池以及AI人脸识别功能,支持熄屏快拍、笑脸抓拍、声控拍照、手势拍照等特色的拍照功能,支持移…

搭建属于自己的 Git 仓库:GitLab

搭建属于自己的 Git 仓库:使用 GitLab 文章目录 搭建属于自己的 Git 仓库:使用 GitLab什么是 GitLab?准备工作安装 Docker使用Docker Compose 快速构建GitLab1、从docker compose快速搭建GitLab2、部署到服务器并访问3、浏览器访问 在现代软件…

Ant Design pro 6.0.0 搭建使用以及相关配置

一、背景 在选择一款比较合适的中台的情况下,挑选了有arco design、ant design pro、soybean、vue-pure-admin等中台系统,经过筛选就选择了ant design pro。之前使用过arco design 搭建通过组件库拼装过后台管理界面,官方文档也比较全&#…

数据库SQL语言实战(十)(最后一篇)

目录 前言 练习题 实验八 实验九 题目一 题目二 总结 前言 本篇练习题的重点有两个: 一、测试提交commit和回滚rollback的作用,了解锁等待、授权等知识。 二、学会复制表结构、学会插入数据,特别是学会如何避免重复插入,也就是如何避…

I2C SPI UART TCP/UDP AD/DA PWM大总结

I2C SPI UART TCP/UDP AD/DA PWM大总结 1. I2C总线描述1.1 基础协议内容1.1.1 通信时序1.1.2 一般通讯时序1.1.3 Burst模式 2. SPI总线2.1 基础协议内容 3. UART4. TCP/UDP5. AD/DA5.1 AD的原理5.2 DA的原理 6. PWM 1. I2C总线描述 I2C的特点:半双工,同步…

起保停电路工作原理

一、电路组成 起保停电路由电源保护设备(空气开关)、交流接触器、启动按钮、停止按钮和用电设备组成。 起保停电路的组成部分通常可分为四个部分: 保护部分:(空气开关)在电流或电压超出一定范围时自动切断…

计网期末复习指南:物理层(物理层的任务、香农公式、常用信道复用技术)

前言:本系列文章旨在通过TCP/IP协议簇自下而上的梳理大致的知识点,从计算机网络体系结构出发到应用层,每一个协议层通过一篇文章进行总结,本系列正在持续更新中... 计网期末复习指南(一):计算机…

SpringBoot学习小结之RocketMQ

文章目录 前言一、架构设计1.1 架构图1.2 消息1.3 工作流程 二、部署2.1 单机2.2 集群 三、Springboot Producter3.1 准备3.2 pom依赖、yml 配置3.3 普通消息3.4 顺序、批量、延迟消息3.5 事务消息 四、Springboot Consumer4.1 配置4.2 普通Push消费4.3 回复4.4 集群和广播4.5 …

兆原数通基于Apache SeaTunnel的探索实践

随着大数据技术的不断发展,数据同步工具在企业中的应用变得愈发重要。为了满足复杂多样的业务需求,找到一款高效、灵活的数据同步工具变得尤为关键。 在这篇文章中,我们将分享兆原数通研发经理李洪军对Apache SeaTunnel的选择、应用及经验。这…

蓝桥杯物联网竞赛_STM32L071KBU6_关于size of函数产生的BUG

首先现象是我在用LORA发送信息的时候,左边显示长度是8而右边接收到的数据长度却是4 我以为是OLED显示屏坏了,又或者是我想搞创新用了const char* 类型强制转换数据的原因,结果发现都不是 void Function_SendMsg( unsigned char* data){unsi…

【代码随想录】动态规划经典题

前言 更详细的在大佬的代码随想录 (programmercarl.com) 本系列仅是简洁版笔记,为了之后方便观看 做题步骤 含义公式初始化顺序检查 确定dp数组以及下标的含义递推公式dp数组如何初始化遍历顺序打印dp数组(看哪里有问题) 斐波那契数 c…

高性能推理框架漫谈

传统模型分布式推理框架 Tensorflow servingPytorch ServingTriton Server 大语言模型的推理框架 其中, VLLM 后端接入了Ray 框架, 作为调度请求的分发处理;除此之外,还包括Nvidia 最新推出的TensorRT-LLM, 增加了对…

若依 ruoyi-vue 用户账号前后端参数校验密码 手机号 邮箱

前端 <el-dialog :title"title" :visible.sync"open" width"800px" append-to-body><el-form ref"form" :model"form" :rules"rules" label-width"120px"><el-row><el-col :span…

IOT技术怎么落地?以宝马,施耐德为例

物联网技术 物联网&#xff08;IoT&#xff09;技术正逐渐成为数字化工厂转型的核心驱动力。本文将通过实际案例&#xff0c;探讨IoT技术如何促进制造业的数字化转型&#xff0c;提高生产效率&#xff0c;降低成本&#xff0c;并提升产品质量。 1. 物联网技术简介 物联网技术通…

记录一次Netty的WSS异常

概述 业务场景 应用通过 WSS 客户端连接三方接口。在高并发压测时&#xff0c;出现了请求服务器写入失败的异常&#xff0c;该异常是偶发&#xff0c;出现的概率不到千分之一&#xff0c;异常如下图所示。 问题概述 注意&#xff1a; 因为握手是通过 http 协议进行的。所以…

SpringBoot整合WebSocket实现聊天室

1.简单的实现了聊天室功能&#xff0c;注意页面刷新后聊天记录不会保存&#xff0c;后端没有做消息的持久化 2.后端用户的识别只简单使用Session用户的身份 0.依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-…

firewalld 防火墙

firewalld概述 Linux系统防火墙从CentOS7开始的默认防火墙工作在网络层&#xff0c;属于包过滤防火墙 Firewalld和iptables的关系 netfilter 位于Linux内核中的包过滤功能体系称为Linux防火墙的“内核态” firewalld Centos默认的管理防火墙规则的工具称为防火墙的“用…