0%

相比起 zap 日志库,logrus 性能会低一些,但在很多场景,并不需要太高性能,而 logrus 库的自定义能力较为强大,可以任意定义日志格式,较为方便。

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
package main

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

const logPath = "./log.txt"

var log = New()

func New() *Logger {
return &Logger{logger: logrus.New()}
}

type Logger struct {
ctx context.Context
logger *logrus.Logger
}

func (l *Logger) SetFormatter(formatter logrus.Formatter) {
l.logger.SetFormatter(formatter)
}

func (l *Logger) SetOutput(output io.Writer) {
l.logger.SetOutput(output)
}

func (l *Logger) SetLevel(level logrus.Level) {
l.logger.SetLevel(level)
}

func (l *Logger) WithContext(ctx context.Context) *Logger {
return &Logger{ctx: ctx, logger: l.logger}
}

func (l *Logger) Debug(format string, args ...any) {
l.log(logrus.DebugLevel, format, args...)
}

func (l *Logger) Info(format string, args ...any) {
l.log(logrus.InfoLevel, format, args...)
}

func (l *Logger) Warn(format string, args ...any) {
l.log(logrus.WarnLevel, format, args...)
}

func (l *Logger) Error(format string, args ...any) {
l.log(logrus.ErrorLevel, format, args...)
}

func (l *Logger) Fatal(format string, args ...any) {
l.log(logrus.FatalLevel, format, args...)
}

func (l *Logger) log(level logrus.Level, format string, args ...any) {
format = fmt.Sprintf("[%s] %s", GetCallerShort(3), format)
l.logger.WithContext(l.ctx).Logf(level, format, args...)
}

type Formatter struct{}

func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) {
pid := os.Getpid()
time := entry.Time.Format("2006-01-02T15:04:05.000000")
level := strings.ToUpper(entry.Level.String())
traceId := GetTraceIdFromCtx(entry.Context)

content := fmt.Sprintf("%s %s %d %s %s\n", time, level, pid, traceId, entry.Message)
return []byte(content), nil
}

// GetCallerShort 获取调用点文件名 + 行号
func GetCallerShort(skip int) string {
_, file, line, ok := runtime.Caller(skip + 1)
if !ok {
return ""
}
_, file = filepath.Split(file)
return fmt.Sprintf("%s:%d", file, line)
}

// CtxSetTraceId 在 ctx 中设置 trace id
func CtxSetTraceId(ctx context.Context) context.Context {
return context.WithValue(ctx, "trace_id", "id1")
}

// GetTraceIdFromCtx 打印日志时,从 ctx 中获取 trace id 打印
func GetTraceIdFromCtx(ctx context.Context) string {
if ctx == nil {
return "-"
}

val := ctx.Value("trace_id")
if traceId, ok := val.(string); ok {
return fmt.Sprintf("trace-%s", traceId)
} else {
return "-"
}
}

func init() {
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640)
if err != nil {
panic(err)
}

log.SetFormatter(&Formatter{})
log.SetOutput(io.MultiWriter(os.Stdout, f))
log.SetLevel(logrus.DebugLevel)
}

func main() {
ctx := context.Background()
ctx = CtxSetTraceId(ctx)
log.WithContext(ctx).Info("main failed: %s", "detail")
}

旅途终了,总感觉是要写点什么的。

八方旅人一代给我的感觉非常好,一个史诗、绚烂、浪漫的旅途。旅途——总是浪漫的。玩完之后的那种寂寥,感觉那八个人离我而去了,是非常不舍的。

不知是二代退步了,还是我本人心态有所变化,那种游戏通关后的寂寥感,是没了。

现在是 2023年,毕业后三年,初玩八方旅人一代大抵在我上大学那会儿,貌似时间间隔也不久。


阅读全文 »

ARP 格式

image-20230304013902868

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct arphdr
{
unsigned short int ar_hrd; /* Format of hardware address. */
unsigned short int ar_pro; /* Format of protocol address. */
unsigned char ar_hln; /* Length of hardware address. */
unsigned char ar_pln; /* Length of protocol address. */
unsigned short int ar_op; /* ARP opcode (command). */
};

struct ether_arp {
struct arphdr ea_hdr; /* fixed-size header */
uint8_t arp_sha[ETH_ALEN]; /* sender hardware address */
uint8_t arp_spa[4]; /* sender protocol address */
uint8_t arp_tha[ETH_ALEN]; /* target hardware address */
uint8_t arp_tpa[4]; /* target protocol address */
};
阅读全文 »

创建用户和数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 进入命令行
su -u postgre
psql

# 创建用户
create user vksir with password 'passwd';

# 创建数据库并赋予用户权限
create database db owner vksir;
grant all privileges on datebase db to vksir;

# 退出
\q

# 使用新用户登录数据库
psql -U vksir -d db
阅读全文 »

代码

loging\logging.go

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
package logging

import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)

const logPath = "./log.txt"

func SugaredLogger() *zap.SugaredLogger {
return zap.S()
}

func init() {
encoder := getEncoder()
writeSyncer := getWriteSyncer()
core := zapcore.NewCore(encoder, writeSyncer, zap.DebugLevel)
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))
zap.ReplaceGlobals(logger)
}

func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}

func getWriteSyncer() zapcore.WriteSyncer {
f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
panic(err)
}
return zapcore.NewMultiWriteSyncer(zapcore.AddSync(f), zapcore.AddSync(os.Stdout))
}
阅读全文 »

Gin 使用 validator 库做数据校验,如下可以实现自定义校验、及自定义校验错误信息。

其中,自定义错误信息无法用于多层嵌套结构体,可能可以通过反射做到,但感觉在性能上很捉急。

代码

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
package main

import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
"net/http"
"reflect"
"strings"
)

type Body struct {
Name string `json:"name" binding:"oneof=vk vksir" err:"one of vk,vksir"`
Age int `json:"age" binding:"BodyAgeValidate" err:"only 18"`
}

func BodyAgeValidate(f validator.FieldLevel) bool {
value := f.Field().Int()
if value != 18 {
return false
}
return true
}

func GetValidateErr(obj any, rawErr error) error {
validationErrs, ok := rawErr.(validator.ValidationErrors)
if !ok {
return rawErr
}
var errString []string
for _, validationErr := range validationErrs {
field, ok := reflect.TypeOf(obj).FieldByName(validationErr.Field())
if ok {
if e := field.Tag.Get("err"); e != "" {
errString = append(errString, fmt.Sprintf("%s: %s", validationErr.Namespace(), e))
continue
}
}
errString = append(errString, validationErr.Error())
}
return errors.New(strings.Join(errString, "\n"))
}

func ping(c *gin.Context) {
b := Body{}
if err := c.ShouldBindJSON(&b); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": GetValidateErr(b, err).Error()})
return
}
c.JSON(http.StatusOK, gin.H{})
}

func main() {
e := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("BodyAgeValidate", BodyAgeValidate)
}
e.GET("/ping", ping)
e.Run("127.0.0.1:8080")
}
阅读全文 »

习惯了使用 Unix 风格的终端,过去在 Windows 上,一直给 Clion 配置 Git Bash 作为终端:

1
"D:\Program Files\Git\bin\sh.exe"

但近来发现,在 Git Bash 中运行编译好的 C++ 程序时,有时候会没有回显。Cygwin Terminal 就没这问题,Clion 中配置 Cygwin Terminal 如下:

1
"D:\Dependencies\cygwin64\bin\sh.exe" -lic "cd ${OLDPWD-.}; bash"
阅读全文 »

其实可以将它视为动漫。

因其人物性格鲜明独特,画面精致,节奏紧凑……这么一说来其实更像是电影,但为何我觉得它更像动漫?

——星期三。

不像是电影亦或是电视剧中出现的人物,其性格——毒舌……说病娇不准确,有病无娇;说乖张不准确,并非疯狂,而是冷静沉着中满是怪异。经常会发出一些惊人之语,类似于“躺在停尸间冷冻柜里说,‘我感觉我快要好起来了。’”,”挖坟时深吸一口:‘Smell good.’”

Screenshot_20221212_224106_com.microsoft.emmx

阅读全文 »