Skip to content

Commit 4f4baba

Browse files
committed
feat: 增加请求情况统计及配套的部分修改
1 parent abd4484 commit 4f4baba

File tree

15 files changed

+567
-67
lines changed

15 files changed

+567
-67
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
- [x] 用户在服务下的密级评定
3333
- [x] redis存储token密级关联
3434
- [x] 支持均衡负载配置:多个上游,支持轮询、随机、权重等
35-
- [ ] 请求统计分析:按服务、路由、用户、时间等维度,以及脱敏次数
35+
- [x] 请求统计分析:按服务、路由、用户、时间等维度,以及脱敏次数
3636
- [x] 服务监控:服务的健康检查,服务的状态
37-
- [ ] 网关的配置管理权限
3837
- [x] 支持TLS配置,支持HTTPS
3938
- [x] 国密TLS支持(https)
39+
- [x] 增加特殊情况下不进行脱敏,如二次输入密码可查看明文等情况,需要后端返回的response时header中写入:`No-Masking: true`
40+
- [ ] 网关的配置管理权限
4041
- [ ] <font color='red'>*?</font> 支持分布式部署(暂时可以用keepalived实现高可用)
41-
- [x] 增加特殊情况下不进行脱敏,如二次输入密码可查看明文等情况,需要后端返回的response时header中写入:`No-Masking: true`

cmd/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import (
1919
var embedPage embed.FS
2020

2121
func main() {
22+
startApp()
23+
}
24+
25+
func startApp() {
2226
defer ants.Release()
2327
config.InitialLogger()
2428

conf/config.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ maxActive=100
2222

2323
[logger]
2424
level = "debug"
25+
maxSize = 10
26+
backups = 7
27+
maxAge = 30
28+
compress = true
29+
proxyTraceMaxSize = 1
30+
proxyTraceMaxAge = 30
31+
proxyBackups = 100
2532

2633
[task]
2734
checkHealth = true

internal/controller/LogController.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package controller
2+
3+
import (
4+
"github.com/gofiber/fiber/v2"
5+
logger "github.com/sirupsen/logrus"
6+
"security-gateway/internal/domain"
7+
"time"
8+
)
9+
10+
var LogController = &logController{}
11+
12+
type logController struct{}
13+
14+
func (c *logController) CountProxyTraceLog(ctx *fiber.Ctx) error {
15+
condition := new(domain.AccessLog)
16+
if err := ctx.QueryParser(condition); err != nil {
17+
return ctx.JSON(&CommonResponse{
18+
Code: ResponseCodeParamParseError,
19+
Msg: ResponseMsgParamParseError,
20+
})
21+
}
22+
23+
st := time.UnixMilli(condition.StartTime)
24+
et := time.UnixMilli(condition.EndTime)
25+
26+
count, err := domain.CountProxyTraceLogs(st, et, condition)
27+
if err != nil {
28+
logger.Error(err)
29+
return ctx.JSON(&CommonResponse{
30+
Code: ResponseCodeUnknownError,
31+
Msg: ResponseMsgUnknownError,
32+
})
33+
}
34+
35+
return ctx.JSON(&CommonResponse{
36+
Data: count,
37+
})
38+
}

internal/controller/index.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ func InitRouter() {
246246
serviceCert := apiV1.Group("/serviceCertificate")
247247
serviceCert.Post("/add", CertificateController.AddServiceCertificate)
248248
serviceCert.Post("/delete/:id", CertificateController.DeleteServiceCertificate)
249+
250+
// Log
251+
log := apiV1.Group("/log")
252+
log.Get("/count", LogController.CountProxyTraceLog)
249253
}
250254

251255
const (

internal/domain/logReader.go

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package domain
2+
3+
import (
4+
"bufio"
5+
"compress/gzip"
6+
logger "github.com/sirupsen/logrus"
7+
"io"
8+
"os"
9+
"path/filepath"
10+
"regexp"
11+
"security-gateway/pkg/config"
12+
"strconv"
13+
"strings"
14+
"time"
15+
)
16+
17+
type AccessLog struct {
18+
CustomIp string `json:"customIp,omitempty"`
19+
Domain string `json:"domain,omitempty"`
20+
MaskingLevel int `json:"maskingLevel,omitempty"`
21+
Path string `json:"path,omitempty"`
22+
Port uint16 `json:"port,omitempty"`
23+
TargetUrl string `json:"targetUrl,omitempty"`
24+
Username string `json:"username,omitempty"`
25+
StartTime int64 `json:"startTime,omitempty"`
26+
EndTime int64 `json:"endTime,omitempty"`
27+
}
28+
29+
var TraceLogPattern = regexp.MustCompile(`time="?([^"]+)"?\s+level=info\s+msg="requesting record"\s+customIp="?([^"]+)"?\s+domain="?([^"]*)"?\s+maskingLevel=(\d?)\s+path="?([^"]*)"?\s+port=(\d+)\s+target="?([^"]*)"?\s+username="?([^"]*)"?`)
30+
31+
func CountDefaultFileLogs(startTime, endTime time.Time, condition *AccessLog) (count int, earliestTime, latestTime time.Time, err error) {
32+
loggerDir := config.GetString("logger.dir")
33+
if loggerDir == "" {
34+
loggerDir = "logs"
35+
}
36+
p, _ := filepath.Abs(loggerDir)
37+
38+
lfPath := filepath.Join(p, "info.log")
39+
f, err := os.Open(lfPath)
40+
if err != nil {
41+
return 0, time.Time{}, time.Time{}, err
42+
}
43+
defer func(f *os.File) {
44+
_ = f.Close()
45+
}(f)
46+
47+
return CountLogsFromFile(f, startTime, endTime, condition)
48+
}
49+
50+
func CountLogsFromFile(f *os.File, startTime, endTime time.Time, condition *AccessLog) (count int, earliestTime, latestTime time.Time, err error) {
51+
return CountLogsFromReader(f, startTime, endTime, condition)
52+
}
53+
54+
func CountLogsFromGzFileName(gzFileName string, startTime, endTime time.Time, condition *AccessLog) (count int, earliestTime, latestTime time.Time, err error) {
55+
gzFileName = filepath.Base(gzFileName)
56+
var gz *os.File
57+
gz, err = os.Open(gzFileName)
58+
if err != nil {
59+
return
60+
}
61+
defer func(gz *os.File) {
62+
_ = gz.Close()
63+
}(gz)
64+
65+
return CountLogsFromGzFile(gz, startTime, endTime, condition)
66+
}
67+
func CountLogsFromGzFile(gzFile *os.File, startTime, endTime time.Time, condition *AccessLog) (count int, earliestTime, latestTime time.Time, err error) {
68+
gz, err := gzip.NewReader(gzFile)
69+
if err != nil {
70+
return 0, time.Time{}, time.Time{}, err
71+
}
72+
defer func(gz *gzip.Reader) {
73+
err = gz.Close()
74+
if err != nil {
75+
logger.Error(err)
76+
}
77+
}(gz)
78+
79+
return CountLogsFromReader(gz, startTime, endTime, condition)
80+
}
81+
82+
func CountLogsFromReader(r io.Reader, startTime, endTime time.Time, condition *AccessLog) (count int, earliestTime, latestTime time.Time, err error) {
83+
count = 0
84+
// 记录最早的时间和最晚的时间
85+
scanner := bufio.NewScanner(r)
86+
for scanner.Scan() {
87+
line := scanner.Text()
88+
matches := TraceLogPattern.FindStringSubmatch(line)
89+
if len(matches) > 0 {
90+
logTime, err := time.ParseInLocation("2006-01-02 15:04:05", matches[1], time.Local)
91+
if err != nil {
92+
continue
93+
}
94+
95+
if earliestTime.IsZero() || logTime.Before(earliestTime) {
96+
earliestTime = logTime
97+
}
98+
99+
if latestTime.IsZero() || logTime.After(latestTime) {
100+
latestTime = logTime
101+
}
102+
103+
if logTime.After(startTime) && logTime.Before(endTime) {
104+
if condition != nil {
105+
if condition.CustomIp != "" && condition.CustomIp != matches[2] {
106+
continue
107+
}
108+
if condition.Domain != "" && condition.Domain != matches[3] {
109+
continue
110+
}
111+
if condition.MaskingLevel != 0 {
112+
if matches[4] == "" || condition.MaskingLevel != int(matches[4][0]-48) {
113+
continue
114+
}
115+
}
116+
if condition.Path != "" && condition.Path != matches[5] {
117+
continue
118+
}
119+
if condition.Port != 0 && matches[6] != strconv.FormatInt(int64(condition.Port), 10) {
120+
continue
121+
}
122+
if condition.TargetUrl != "" && condition.TargetUrl != matches[7] {
123+
continue
124+
}
125+
if condition.Username != "" && condition.Username != matches[8] {
126+
continue
127+
}
128+
}
129+
count++
130+
}
131+
}
132+
}
133+
134+
if err = scanner.Err(); err != nil {
135+
return 0, time.Time{}, time.Time{}, err
136+
}
137+
138+
return
139+
}
140+
141+
func ProxyTraceLogPath() string {
142+
loggerDir := config.GetString("logger.dir")
143+
if loggerDir == "" {
144+
loggerDir = "logs"
145+
}
146+
p, _ := filepath.Abs(loggerDir)
147+
p = filepath.Join(p, "proxy_trace")
148+
return p
149+
}
150+
151+
func CurrentProxyTraceLogPath() string {
152+
return filepath.Join(ProxyTraceLogPath(), "info.log")
153+
}
154+
155+
// NextProxyTraceLogGzPath 获取下个代理跟踪日志文件的路径
156+
func NextProxyTraceLogGzPath(preFileName string) string {
157+
var latestTime time.Time
158+
159+
if !strings.HasSuffix(preFileName, ".log") {
160+
t, err := time.Parse("2006-01-02T15-04-05.999.log.gz", preFileName[5:len(preFileName)-7])
161+
if err != nil {
162+
logger.Error(err)
163+
return ""
164+
}
165+
latestTime = t
166+
}
167+
168+
dir := ProxyTraceLogPath()
169+
files, err := filepath.Glob(filepath.Join(dir, "*.log.gz"))
170+
if err != nil {
171+
logger.Error(err)
172+
return ""
173+
}
174+
if len(files) == 0 {
175+
return ""
176+
}
177+
// 找到最新的一个gz文件,文件命名规则:info-2024-07-01T08-58-56.164.log.gz
178+
var latestFile string
179+
for _, file := range files {
180+
fileName := filepath.Base(file)
181+
if !strings.HasPrefix(fileName, "info-") || !strings.HasSuffix(fileName, ".log.gz") {
182+
continue
183+
}
184+
t, err := time.Parse("2006-01-02T15-04-05.999.log.gz", fileName[5:len(fileName)-7])
185+
if err != nil {
186+
logger.Error(err)
187+
continue
188+
}
189+
if t.After(latestTime) {
190+
latestTime = t
191+
latestFile = file
192+
}
193+
}
194+
195+
return latestFile
196+
}
197+
198+
func CountProxyTraceLogs(startTime, endTime time.Time, condition *AccessLog) (count int, err error) {
199+
fileName := CurrentProxyTraceLogPath()
200+
var lf *os.File
201+
lf, err = os.Open(fileName)
202+
if err != nil {
203+
return 0, err
204+
}
205+
defer func(lf *os.File) {
206+
_ = lf.Close()
207+
}(lf)
208+
209+
var earliestTime, latestTime time.Time
210+
211+
count, earliestTime, latestTime, err = CountLogsFromFile(lf, startTime, endTime, condition)
212+
if err != nil {
213+
return
214+
}
215+
216+
for !earliestTime.IsZero() && !latestTime.IsZero() && earliestTime.After(startTime) {
217+
fileName = NextProxyTraceLogGzPath(filepath.Base(fileName))
218+
if fileName == "" {
219+
break
220+
}
221+
gzFileName := filepath.Base(fileName)
222+
223+
var c int
224+
c, earliestTime, latestTime, err = CountLogsFromGzFileName(gzFileName, startTime, endTime, condition)
225+
if err != nil {
226+
return
227+
}
228+
count += c
229+
}
230+
231+
return
232+
233+
}

0 commit comments

Comments
 (0)