fix
This commit is contained in:
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool
|
||||
*.out
|
||||
|
||||
# Dependency directories
|
||||
vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# IDEs
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Build artifacts
|
||||
ops-logs
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Config files with sensitive data
|
||||
*_local.yaml
|
||||
.env
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
35
cmd/main/main.go
Normal file
35
cmd/main/main.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/infra"
|
||||
"git.apinb.com/bsm-sdk/core/middleware"
|
||||
"git.apinb.com/ops/logs/internal/config"
|
||||
"git.apinb.com/ops/logs/internal/impl"
|
||||
"git.apinb.com/ops/logs/internal/ingest"
|
||||
"git.apinb.com/ops/logs/internal/routers"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var ServiceKey = "Logs"
|
||||
|
||||
func main() {
|
||||
config.New(ServiceKey)
|
||||
impl.NewImpl()
|
||||
|
||||
ingest.StartRefresher()
|
||||
ingest.StartSyslogUDP()
|
||||
ingest.StartTrapUDP()
|
||||
|
||||
app := gin.Default()
|
||||
middleware.Mode(app)
|
||||
app.Use(middleware.Cors())
|
||||
app.Use(gin.Recovery())
|
||||
app.HEAD("/", infra.Health)
|
||||
routers.Register(ServiceKey, app)
|
||||
|
||||
if err := app.Run(fmt.Sprintf(":%s", config.Spec.Port)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
26
etc/logs_dev.yaml
Normal file
26
etc/logs_dev.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
Service: logs
|
||||
Port: 12440
|
||||
|
||||
Databases:
|
||||
Driver: postgres
|
||||
Source:
|
||||
- host=8.137.107.29 user=postgres password=Weidong2023~! dbname=ops_dev port=19432 sslmode=disable TimeZone=Asia/Shanghai
|
||||
|
||||
# cache DB的选择请在后面直接带参数,不带会自动HASH计算选择DB库。
|
||||
Cache: redis://null:Weidong2023~!@8.137.107.29:19379/
|
||||
|
||||
MicroService:
|
||||
Enable: false
|
||||
Anonymous:
|
||||
- ops.ping.hello
|
||||
|
||||
Ingest:
|
||||
syslog_listen_addr: "0.0.0.0:5514"
|
||||
trap_listen_addr: "0.0.0.0:9162"
|
||||
rule_refresh_secs: 30
|
||||
|
||||
AlertForward:
|
||||
enabled: true
|
||||
base_url: https://ops2.apinb.com
|
||||
internal_key: "ops-alert"
|
||||
default_policy_id: 0
|
||||
18
etc/supervisor.ops-logs.conf
Normal file
18
etc/supervisor.ops-logs.conf
Normal file
@@ -0,0 +1,18 @@
|
||||
[program:ops-logs]
|
||||
command=/data/app/ops-logs
|
||||
directory=/data/app
|
||||
autostart=true
|
||||
autorestart=true
|
||||
user=root
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/data/app/logs/ops-logs.log
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
76
go.mod
Normal file
76
go.mod
Normal file
@@ -0,0 +1,76 @@
|
||||
module git.apinb.com/ops/logs
|
||||
|
||||
go 1.25.1
|
||||
|
||||
require (
|
||||
git.apinb.com/bsm-sdk/core v0.1.3
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/gosnmp/gosnmp v1.37.0
|
||||
gorm.io/gorm v1.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/allegro/bigcache/v3 v3.1.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/gin-contrib/cors v1.7.7 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/oklog/ulid/v2 v2.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.18.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.9 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.9 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.9 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.6.0 // indirect
|
||||
gorm.io/driver/postgres v1.6.0 // indirect
|
||||
)
|
||||
227
go.sum
Normal file
227
go.sum
Normal file
@@ -0,0 +1,227 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
git.apinb.com/bsm-sdk/core v0.1.3 h1:Kun3YS6lg0lvzjbyi61V3SFJMElyVuDruONDO1UErmk=
|
||||
git.apinb.com/bsm-sdk/core v0.1.3/go.mod h1:rCmMma8R2pvByImgoZDm2OPLdr+IUNr7LBPyayb8aN0=
|
||||
github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk=
|
||||
github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q=
|
||||
github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gosnmp/gosnmp v1.37.0 h1:/Tf8D3b9wrnNuf/SfbvO+44mPrjVphBhRtcGg22V07Y=
|
||||
github.com/gosnmp/gosnmp v1.37.0/go.mod h1:GDH9vNqpsD7f2HvZhKs5dlqSEcAS6s6Qp099oZRCR+M=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
||||
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.etcd.io/etcd/api/v3 v3.6.9 h1:UA7iKfEW1AzgihcBSGXci2kDGQiokSq41F9HMCI/RTI=
|
||||
go.etcd.io/etcd/api/v3 v3.6.9/go.mod h1:csEk/qTfxKL36NqJdU15Tgtl65A8dyEY2BYo7PRsIwk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.9 h1:T8nuk8Lz64C+Hzb0coBFLMSlVSQZBpAtFk46swdM1DA=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.9/go.mod h1:WEy3PpwbbEBVRdh1NVJYsuUe/8eyI21PNJRazeD8z/Y=
|
||||
go.etcd.io/etcd/client/v3 v3.6.9 h1:3X555hQXmhRr27O37wls53g68CpUiPOiHXrZfz2Al+o=
|
||||
go.etcd.io/etcd/client/v3 v3.6.9/go.mod h1:KO7H1HLYh1qaljuVZJQwBFk1lRce6pJzt+C81GEnrlM=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
|
||||
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
43
internal/config/config.go
Normal file
43
internal/config/config.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/conf"
|
||||
)
|
||||
|
||||
var Spec SrvConfig
|
||||
|
||||
type AlertForwardConf struct {
|
||||
BaseURL string `yaml:"base_url"`
|
||||
InternalKey string `yaml:"internal_key"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
DefaultPolicyID uint `yaml:"default_policy_id"`
|
||||
}
|
||||
|
||||
type IngestConf struct {
|
||||
SyslogListenAddr string `yaml:"syslog_listen_addr"`
|
||||
TrapListenAddr string `yaml:"trap_listen_addr"`
|
||||
RuleRefreshSecs int `yaml:"rule_refresh_secs"`
|
||||
}
|
||||
|
||||
type SrvConfig struct {
|
||||
conf.Base `yaml:",inline"`
|
||||
Databases *conf.DBConf `yaml:"Databases"`
|
||||
MicroService *conf.MicroServiceConf `yaml:"MicroService"`
|
||||
Rpc map[string]conf.RpcConf `yaml:"Rpc"`
|
||||
Gateway *conf.GatewayConf `yaml:"Gateway"`
|
||||
Apm *conf.ApmConf `yaml:"APM"`
|
||||
Etcd *conf.EtcdConf `yaml:"Etcd"`
|
||||
AlertForward *AlertForwardConf `yaml:"AlertForward"`
|
||||
Ingest IngestConf `yaml:"Ingest"`
|
||||
}
|
||||
|
||||
func New(srvKey string) {
|
||||
conf.New(srvKey, &Spec)
|
||||
Spec.Port = conf.CheckPort(Spec.Port)
|
||||
Spec.BindIP = conf.CheckIP(Spec.BindIP)
|
||||
Spec.Addr = net.JoinHostPort(Spec.BindIP, Spec.Port)
|
||||
conf.NotNil(Spec.Service, Spec.Cache)
|
||||
conf.PrintInfo(Spec.Addr)
|
||||
}
|
||||
32
internal/impl/impl.go
Normal file
32
internal/impl/impl.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package impl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/cache/redis"
|
||||
"git.apinb.com/bsm-sdk/core/logger"
|
||||
"git.apinb.com/bsm-sdk/core/with"
|
||||
"git.apinb.com/ops/logs/internal/config"
|
||||
"git.apinb.com/ops/logs/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
RedisService *redis.RedisClient
|
||||
DBService *gorm.DB
|
||||
)
|
||||
|
||||
func NewImpl() {
|
||||
RedisService = with.RedisCache(config.Spec.Cache)
|
||||
DBService = with.Databases(config.Spec.Databases, nil)
|
||||
logger.New(nil)
|
||||
|
||||
if DBService != nil {
|
||||
if err := DBService.AutoMigrate(models.GetAllModels()...); err != nil {
|
||||
panic(fmt.Sprintf("logs migrate: %v", err))
|
||||
}
|
||||
if err := models.InitData(); err != nil {
|
||||
panic(fmt.Sprintf("logs init data: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
64
internal/ingest/alert_forward.go
Normal file
64
internal/ingest/alert_forward.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/ops/logs/internal/config"
|
||||
)
|
||||
|
||||
// AlertReceiveBody 与 alert ReceiveRequest 对齐(含必填 raw_data)
|
||||
type AlertReceiveBody struct {
|
||||
AlertName string `json:"alert_name"`
|
||||
Summary string `json:"summary"`
|
||||
Description string `json:"description"`
|
||||
SeverityCode string `json:"severity_code"`
|
||||
Value string `json:"value"`
|
||||
Threshold string `json:"threshold"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Agent string `json:"agent"`
|
||||
PolicyID uint `json:"policy_id"`
|
||||
RawData json.RawMessage `json:"raw_data"`
|
||||
}
|
||||
|
||||
func forwardAlert(body AlertReceiveBody) error {
|
||||
cfg := config.Spec.AlertForward
|
||||
if cfg == nil || !cfg.Enabled || cfg.BaseURL == "" {
|
||||
return nil
|
||||
}
|
||||
if len(body.RawData) == 0 {
|
||||
return fmt.Errorf("raw_data 不能为空")
|
||||
}
|
||||
if body.AlertName == "" {
|
||||
body.AlertName = "日志告警"
|
||||
}
|
||||
if body.PolicyID == 0 && cfg.DefaultPolicyID > 0 {
|
||||
body.PolicyID = cfg.DefaultPolicyID
|
||||
}
|
||||
raw, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := cfg.BaseURL + "/Alert/v1/alerts/receive"
|
||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if cfg.InternalKey != "" {
|
||||
req.Header.Set("X-Internal-Key", cfg.InternalKey)
|
||||
}
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("alert returned HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
426
internal/ingest/engine.go
Normal file
426
internal/ingest/engine.go
Normal file
@@ -0,0 +1,426 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/ops/logs/internal/config"
|
||||
"git.apinb.com/ops/logs/internal/impl"
|
||||
"git.apinb.com/ops/logs/internal/models"
|
||||
"github.com/gosnmp/gosnmp"
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
trapDict []models.TrapDictionaryEntry
|
||||
syslogRules []models.SyslogRule
|
||||
trapRules []models.TrapRule
|
||||
shields []models.TrapShield
|
||||
}
|
||||
|
||||
var Global = &Engine{}
|
||||
|
||||
func (e *Engine) Refresh() error {
|
||||
var dict []models.TrapDictionaryEntry
|
||||
var syslog []models.SyslogRule
|
||||
var trap []models.TrapRule
|
||||
var shield []models.TrapShield
|
||||
|
||||
if err := impl.DBService.Where("enabled = ?", true).Find(&dict).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Slice(dict, func(i, j int) bool {
|
||||
return len(dict[i].OIDPrefix) > len(dict[j].OIDPrefix)
|
||||
})
|
||||
|
||||
if err := impl.DBService.Where("enabled = ?", true).Find(&syslog).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Slice(syslog, func(i, j int) bool { return syslog[i].Priority > syslog[j].Priority })
|
||||
|
||||
if err := impl.DBService.Where("enabled = ?", true).Find(&trap).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Slice(trap, func(i, j int) bool { return trap[i].Priority > trap[j].Priority })
|
||||
|
||||
if err := impl.DBService.Where("enabled = ?", true).Find(&shield).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
e.trapDict = dict
|
||||
e.syslogRules = syslog
|
||||
e.trapRules = trap
|
||||
e.shields = shield
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartRefresher() {
|
||||
interval := config.Spec.Ingest.RuleRefreshSecs
|
||||
if interval <= 0 {
|
||||
interval = 30
|
||||
}
|
||||
_ = Global.Refresh()
|
||||
go func() {
|
||||
t := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
defer t.Stop()
|
||||
for range t.C {
|
||||
_ = Global.Refresh()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func normOID(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
return strings.TrimPrefix(s, ".")
|
||||
}
|
||||
|
||||
func (e *Engine) HandleSyslog(addr *net.UDPAddr, payload []byte) {
|
||||
parsed := parseSyslogPayload(payload)
|
||||
device := parsed.Hostname
|
||||
if device == "" {
|
||||
device = addr.IP.String()
|
||||
}
|
||||
detailObj := map[string]interface{}{
|
||||
"priority": parsed.Priority,
|
||||
"hostname": parsed.Hostname,
|
||||
"tag": parsed.Tag,
|
||||
"message": parsed.Message,
|
||||
}
|
||||
detailBytes, _ := json.Marshal(detailObj)
|
||||
summary := formatSyslogSummary(parsed)
|
||||
sev := syslogPriorityToSeverity(parsed.Priority)
|
||||
|
||||
ev := models.LogEvent{
|
||||
SourceKind: "syslog",
|
||||
RemoteAddr: addr.String(),
|
||||
RawPayload: string(payload),
|
||||
NormalizedSummary: summary,
|
||||
NormalizedDetail: string(detailBytes),
|
||||
DeviceName: device,
|
||||
SeverityCode: sev,
|
||||
}
|
||||
|
||||
e.mu.RLock()
|
||||
rules := e.syslogRules
|
||||
e.mu.RUnlock()
|
||||
|
||||
var matched *models.SyslogRule
|
||||
for i := range rules {
|
||||
if syslogRuleMatches(&rules[i], device, parsed.Message, parsed.RawLine) {
|
||||
matched = &rules[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := impl.DBService.Create(&ev).Error; err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if matched == nil {
|
||||
return
|
||||
}
|
||||
labels := map[string]string{
|
||||
"source": "syslog",
|
||||
"device": device,
|
||||
"rule_id": strconv.FormatUint(uint64(matched.ID), 10),
|
||||
"rule_name": matched.Name,
|
||||
"remote_addr": addr.String(),
|
||||
}
|
||||
rawObj := map[string]interface{}{
|
||||
"source": "syslog",
|
||||
"received_at": time.Now().UTC().Format(time.RFC3339),
|
||||
"source_ip": addr.IP.String(),
|
||||
"rule_id": matched.ID,
|
||||
"log_entry_id": ev.ID,
|
||||
"raw_packet": string(payload),
|
||||
"parsed": detailObj,
|
||||
}
|
||||
rawBytes, mErr := json.Marshal(rawObj)
|
||||
if mErr != nil {
|
||||
return
|
||||
}
|
||||
body := AlertReceiveBody{
|
||||
AlertName: matched.AlertName,
|
||||
Summary: summary,
|
||||
Description: summary,
|
||||
SeverityCode: firstNonEmpty(matched.SeverityCode, sev),
|
||||
Value: parsed.Message,
|
||||
Labels: labels,
|
||||
Agent: "logs-syslog",
|
||||
PolicyID: matched.PolicyID,
|
||||
RawData: rawBytes,
|
||||
}
|
||||
if err := forwardAlert(body); err == nil {
|
||||
_ = impl.DBService.Model(&ev).Update("alert_sent", true).Error
|
||||
}
|
||||
}
|
||||
|
||||
func syslogRuleMatches(rule *models.SyslogRule, device, message, rawLine string) bool {
|
||||
if strings.TrimSpace(rule.DeviceNameContains) == "" && strings.TrimSpace(rule.KeywordRegex) == "" {
|
||||
return false
|
||||
}
|
||||
deviceName := strings.ToLower(device)
|
||||
contains := strings.ToLower(rule.DeviceNameContains)
|
||||
if contains != "" && !strings.Contains(deviceName, contains) {
|
||||
return false
|
||||
}
|
||||
if rule.KeywordRegex != "" {
|
||||
re, err := regexp.Compile(rule.KeywordRegex)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !re.MatchString(message) && !re.MatchString(rawLine) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func trapShielded(e *Engine, addr *net.UDPAddr, trapOID string, pkt *gosnmp.SnmpPacket) bool {
|
||||
ip := addr.IP
|
||||
fp := varbindFingerprint(pkt)
|
||||
now := time.Now()
|
||||
e.mu.RLock()
|
||||
shields := e.shields
|
||||
e.mu.RUnlock()
|
||||
for i := range shields {
|
||||
s := &shields[i]
|
||||
if !s.Enabled {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(s.SourceIPCIDR) == "" {
|
||||
continue
|
||||
}
|
||||
if !ipMatchesCIDR(ip, s.SourceIPCIDR) {
|
||||
continue
|
||||
}
|
||||
if p := strings.TrimSpace(s.OIDPrefix); p != "" && !strings.HasPrefix(normOID(trapOID), normOID(p)) {
|
||||
continue
|
||||
}
|
||||
if h := strings.TrimSpace(s.InterfaceHint); h != "" && !strings.Contains(fp, h) {
|
||||
continue
|
||||
}
|
||||
if !inTimeWindows(now, s.TimeWindowsJSON) {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func lookupTrapDict(e *Engine, trapOID string) *models.TrapDictionaryEntry {
|
||||
t := normOID(trapOID)
|
||||
e.mu.RLock()
|
||||
dict := e.trapDict
|
||||
e.mu.RUnlock()
|
||||
for i := range dict {
|
||||
if strings.HasPrefix(t, normOID(dict[i].OIDPrefix)) {
|
||||
return &dict[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) HandleTrap(addr *net.UDPAddr, pkt *gosnmp.SnmpPacket) {
|
||||
trapOID := extractTrapOID(pkt)
|
||||
if trapShielded(e, addr, trapOID, pkt) {
|
||||
return
|
||||
}
|
||||
|
||||
dict := lookupTrapDict(e, trapOID)
|
||||
fp := varbindFingerprint(pkt)
|
||||
vbJSON, _ := json.Marshal(trapVarbinds(pkt))
|
||||
|
||||
readable := buildTrapReadable(trapOID, dict, fp)
|
||||
detailObj := map[string]interface{}{
|
||||
"trap_oid": trapOID,
|
||||
"varbinds": trapVarbinds(pkt),
|
||||
"dict_title": "",
|
||||
"dict_description": "",
|
||||
"recovery": "",
|
||||
}
|
||||
sev := "warning"
|
||||
if dict != nil {
|
||||
detailObj["dict_title"] = dict.Title
|
||||
detailObj["dict_description"] = dict.Description
|
||||
detailObj["recovery"] = dict.RecoveryMessage
|
||||
if dict.SeverityCode != "" {
|
||||
sev = dict.SeverityCode
|
||||
}
|
||||
}
|
||||
detailBytes, _ := json.Marshal(detailObj)
|
||||
|
||||
ev := models.LogEvent{
|
||||
SourceKind: "snmp_trap",
|
||||
RemoteAddr: addr.String(),
|
||||
RawPayload: fp,
|
||||
NormalizedSummary: readable,
|
||||
NormalizedDetail: string(detailBytes),
|
||||
DeviceName: addr.IP.String(),
|
||||
SeverityCode: sev,
|
||||
TrapOID: trapOID,
|
||||
}
|
||||
if err := impl.DBService.Create(&ev).Error; err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
e.mu.RLock()
|
||||
rules := e.trapRules
|
||||
e.mu.RUnlock()
|
||||
|
||||
var matched *models.TrapRule
|
||||
for i := range rules {
|
||||
if trapRuleMatches(&rules[i], trapOID, fp) {
|
||||
matched = &rules[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matched == nil && dict != nil && strings.TrimSpace(dict.SeverityCode) != "" {
|
||||
matched = &models.TrapRule{
|
||||
AlertName: firstNonEmpty(dict.Title, "SNMP Trap"),
|
||||
SeverityCode: dict.SeverityCode,
|
||||
PolicyID: 0,
|
||||
}
|
||||
}
|
||||
if matched == nil {
|
||||
return
|
||||
}
|
||||
|
||||
desc := readable
|
||||
if dict != nil && dict.RecoveryMessage != "" {
|
||||
desc = readable + "\n恢复建议: " + dict.RecoveryMessage
|
||||
}
|
||||
labels := map[string]string{
|
||||
"source": "snmp_trap",
|
||||
"trap_oid": trapOID,
|
||||
"remote_addr": addr.String(),
|
||||
}
|
||||
if matched.ID != 0 {
|
||||
labels["rule_id"] = strconv.FormatUint(uint64(matched.ID), 10)
|
||||
labels["rule_name"] = matched.Name
|
||||
}
|
||||
resolved := map[string]interface{}{}
|
||||
if dict != nil {
|
||||
resolved["title"] = dict.Title
|
||||
resolved["description"] = dict.Description
|
||||
resolved["recovery"] = dict.RecoveryMessage
|
||||
}
|
||||
rawObj := map[string]interface{}{
|
||||
"source": "snmp_trap",
|
||||
"received_at": time.Now().UTC().Format(time.RFC3339),
|
||||
"source_ip": addr.IP.String(),
|
||||
"log_entry_id": ev.ID,
|
||||
"trap_oid": trapOID,
|
||||
"varbinds": trapVarbinds(pkt),
|
||||
"resolved": resolved,
|
||||
"pdu_summary": fp,
|
||||
}
|
||||
if matched.ID != 0 {
|
||||
rawObj["rule_id"] = matched.ID
|
||||
}
|
||||
rawBytes, mErr := json.Marshal(rawObj)
|
||||
if mErr != nil {
|
||||
return
|
||||
}
|
||||
body := AlertReceiveBody{
|
||||
AlertName: firstNonEmpty(matched.AlertName, "SNMP Trap"),
|
||||
Summary: readable,
|
||||
Description: desc,
|
||||
SeverityCode: firstNonEmpty(matched.SeverityCode, sev),
|
||||
Value: string(vbJSON),
|
||||
Labels: labels,
|
||||
Agent: "logs-trap",
|
||||
PolicyID: matched.PolicyID,
|
||||
RawData: rawBytes,
|
||||
}
|
||||
if err := forwardAlert(body); err == nil {
|
||||
_ = impl.DBService.Model(&ev).Update("alert_sent", true).Error
|
||||
}
|
||||
}
|
||||
|
||||
func extractTrapOID(pkt *gosnmp.SnmpPacket) string {
|
||||
const snmpTrapOID = "1.3.6.1.6.3.1.1.4.1.0"
|
||||
for _, v := range pkt.Variables {
|
||||
if v.Name == snmpTrapOID || strings.HasSuffix(v.Name, ".1.3.6.1.6.3.1.1.4.1.0") {
|
||||
return oidToString(v.Value)
|
||||
}
|
||||
}
|
||||
for _, v := range pkt.Variables {
|
||||
if strings.Contains(v.Name, "1.3.6.1.6.3.1.1.4.1") {
|
||||
return oidToString(v.Value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func oidToString(val interface{}) string {
|
||||
switch x := val.(type) {
|
||||
case string:
|
||||
return x
|
||||
case []byte:
|
||||
return string(x)
|
||||
default:
|
||||
return fmt.Sprintf("%v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func trapVarbinds(pkt *gosnmp.SnmpPacket) []map[string]string {
|
||||
out := make([]map[string]string, 0, len(pkt.Variables))
|
||||
for _, v := range pkt.Variables {
|
||||
out = append(out, map[string]string{
|
||||
"oid": v.Name,
|
||||
"type": fmt.Sprintf("%v", v.Type),
|
||||
"value": fmtVarbindValue(v),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func buildTrapReadable(trapOID string, dict *models.TrapDictionaryEntry, varbindSummary string) string {
|
||||
if dict != nil && dict.Title != "" {
|
||||
return dict.Title + " (" + trapOID + ")"
|
||||
}
|
||||
if trapOID != "" {
|
||||
return "Trap " + trapOID
|
||||
}
|
||||
return truncate(varbindSummary, 256)
|
||||
}
|
||||
|
||||
func trapRuleMatches(rule *models.TrapRule, trapOID, varbindFP string) bool {
|
||||
hasOID := strings.TrimSpace(rule.OIDPrefix) != ""
|
||||
hasRE := strings.TrimSpace(rule.VarbindMatchRegex) != ""
|
||||
if !hasOID && !hasRE {
|
||||
return false
|
||||
}
|
||||
if hasOID && !strings.HasPrefix(normOID(trapOID), normOID(rule.OIDPrefix)) {
|
||||
return false
|
||||
}
|
||||
if rule.VarbindMatchRegex != "" {
|
||||
re, err := regexp.Compile(rule.VarbindMatchRegex)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !re.MatchString(varbindFP) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func firstNonEmpty(a, b string) string {
|
||||
if strings.TrimSpace(a) != "" {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
114
internal/ingest/shield.go
Normal file
114
internal/ingest/shield.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gosnmp/gosnmp"
|
||||
)
|
||||
|
||||
type timeWindow struct {
|
||||
Days []int `json:"days"`
|
||||
Start string `json:"start"`
|
||||
End string `json:"end"`
|
||||
}
|
||||
|
||||
func ipMatchesCIDR(ip net.IP, cidr string) bool {
|
||||
cidr = strings.TrimSpace(cidr)
|
||||
if cidr == "" {
|
||||
return false
|
||||
}
|
||||
if !strings.Contains(cidr, "/") {
|
||||
p := net.ParseIP(cidr)
|
||||
return p != nil && p.Equal(ip)
|
||||
}
|
||||
_, network, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return network.Contains(ip)
|
||||
}
|
||||
|
||||
func inTimeWindows(now time.Time, jsonStr string) bool {
|
||||
s := strings.TrimSpace(jsonStr)
|
||||
if s == "" || s == "null" {
|
||||
return true
|
||||
}
|
||||
var windows []timeWindow
|
||||
if err := json.Unmarshal([]byte(s), &windows); err != nil || len(windows) == 0 {
|
||||
return true
|
||||
}
|
||||
tod := now.Hour()*60 + now.Minute()
|
||||
wd := int(now.Weekday())
|
||||
for _, w := range windows {
|
||||
if len(w.Days) > 0 {
|
||||
ok := false
|
||||
for _, d := range w.Days {
|
||||
if d == wd {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
start := parseHHMM(w.Start)
|
||||
end := parseHHMM(w.End)
|
||||
if start < 0 || end < 0 {
|
||||
continue
|
||||
}
|
||||
if start <= end {
|
||||
if tod >= start && tod <= end {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if tod >= start || tod <= end {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseHHMM(s string) int {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return -1
|
||||
}
|
||||
parts := strings.Split(s, ":")
|
||||
if len(parts) != 2 {
|
||||
return -1
|
||||
}
|
||||
h, err1 := strconv.Atoi(parts[0])
|
||||
m, err2 := strconv.Atoi(parts[1])
|
||||
if err1 != nil || err2 != nil || h < 0 || h > 23 || m < 0 || m > 59 {
|
||||
return -1
|
||||
}
|
||||
return h*60 + m
|
||||
}
|
||||
|
||||
func varbindFingerprint(pkt *gosnmp.SnmpPacket) string {
|
||||
var b strings.Builder
|
||||
for _, v := range pkt.Variables {
|
||||
b.WriteString(v.Name)
|
||||
b.WriteByte('=')
|
||||
b.WriteString(fmtVarbindValue(v))
|
||||
b.WriteByte(';')
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func fmtVarbindValue(v gosnmp.SnmpPDU) string {
|
||||
switch v.Type {
|
||||
case gosnmp.OctetString:
|
||||
if bb, ok := v.Value.([]byte); ok {
|
||||
return string(bb)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%v", v.Value)
|
||||
}
|
||||
109
internal/ingest/syslog_parse.go
Normal file
109
internal/ingest/syslog_parse.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var rePri = regexp.MustCompile(`^<(\d{1,3})>`)
|
||||
|
||||
type ParsedSyslog struct {
|
||||
Priority int
|
||||
Hostname string
|
||||
Tag string
|
||||
Message string
|
||||
RawLine string
|
||||
}
|
||||
|
||||
func parseSyslogPayload(payload []byte) ParsedSyslog {
|
||||
line := strings.TrimSpace(string(payload))
|
||||
p := ParsedSyslog{RawLine: line, Message: line}
|
||||
if line == "" {
|
||||
return p
|
||||
}
|
||||
rest := line
|
||||
if m := rePri.FindStringSubmatch(line); len(m) == 2 {
|
||||
if pri, err := strconv.Atoi(m[1]); err == nil {
|
||||
p.Priority = pri
|
||||
}
|
||||
rest = line[len(m[0]):]
|
||||
}
|
||||
rest = strings.TrimSpace(rest)
|
||||
fields := strings.SplitN(rest, " ", 6)
|
||||
if len(fields) >= 2 && len(fields[0]) == 1 && fields[0][0] >= '1' && fields[0][0] <= '9' {
|
||||
if len(fields) >= 4 {
|
||||
p.Hostname = fields[2]
|
||||
if len(fields) >= 6 {
|
||||
p.Message = fields[5]
|
||||
} else if len(fields) == 5 {
|
||||
p.Message = fields[4]
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
tokens := strings.SplitN(rest, " ", 3)
|
||||
if len(tokens) >= 2 {
|
||||
if len(tokens) >= 3 && isMonthAbbr(tokens[0]) {
|
||||
p.Hostname = tokens[2]
|
||||
if idx := strings.Index(rest, ": "); idx > 0 {
|
||||
p.Message = strings.TrimSpace(rest[idx+2:])
|
||||
}
|
||||
} else {
|
||||
p.Hostname = tokens[1]
|
||||
if len(tokens) >= 3 {
|
||||
tagMsg := tokens[2]
|
||||
if idx := strings.Index(tagMsg, ": "); idx > 0 {
|
||||
p.Tag = tagMsg[:idx]
|
||||
p.Message = strings.TrimSpace(tagMsg[idx+2:])
|
||||
} else {
|
||||
p.Message = tagMsg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func isMonthAbbr(s string) bool {
|
||||
if len(s) < 3 {
|
||||
return false
|
||||
}
|
||||
mons := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
|
||||
for _, m := range mons {
|
||||
if strings.HasPrefix(s, m) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func syslogPriorityToSeverity(pri int) string {
|
||||
sev := pri % 8
|
||||
switch sev {
|
||||
case 0, 1, 2:
|
||||
return "critical"
|
||||
case 3:
|
||||
return "major"
|
||||
case 4:
|
||||
return "warning"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
}
|
||||
|
||||
func formatSyslogSummary(p ParsedSyslog) string {
|
||||
host := p.Hostname
|
||||
if host == "" {
|
||||
host = "unknown-host"
|
||||
}
|
||||
return fmt.Sprintf("%s: %s", host, truncate(p.Message, 512))
|
||||
}
|
||||
|
||||
func truncate(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n] + "..."
|
||||
}
|
||||
32
internal/ingest/syslog_parse_test.go
Normal file
32
internal/ingest/syslog_parse_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseSyslogPayloadPri(t *testing.T) {
|
||||
p := parseSyslogPayload([]byte("<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8"))
|
||||
if p.Priority != 34 {
|
||||
t.Fatalf("priority=%d", p.Priority)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardAlertBodyIncludesRawData(t *testing.T) {
|
||||
raw := []byte(`{"source":"syslog","parsed":{}}`)
|
||||
b := AlertReceiveBody{
|
||||
AlertName: "a",
|
||||
RawData: raw,
|
||||
}
|
||||
data, err := json.Marshal(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(dec["raw_data"]) != string(raw) {
|
||||
t.Fatalf("raw_data %s", dec["raw_data"])
|
||||
}
|
||||
}
|
||||
41
internal/ingest/syslog_udp.go
Normal file
41
internal/ingest/syslog_udp.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"git.apinb.com/ops/logs/internal/config"
|
||||
)
|
||||
|
||||
func StartSyslogUDP() {
|
||||
addr := strings.TrimSpace(config.Spec.Ingest.SyslogListenAddr)
|
||||
if addr == "" {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
pc, err := net.ListenPacket("udp", addr)
|
||||
if err != nil {
|
||||
log.Printf("logs: syslog UDP listen %s: %v", addr, err)
|
||||
return
|
||||
}
|
||||
defer pc.Close()
|
||||
log.Printf("logs: syslog listening UDP %s", addr)
|
||||
buf := make([]byte, 65536)
|
||||
for {
|
||||
n, remote, err := pc.ReadFrom(buf)
|
||||
if err != nil {
|
||||
log.Printf("logs: syslog read: %v", err)
|
||||
continue
|
||||
}
|
||||
udpAddr, _ := remote.(*net.UDPAddr)
|
||||
if udpAddr == nil {
|
||||
continue
|
||||
}
|
||||
p := make([]byte, n)
|
||||
copy(p, buf[:n])
|
||||
a := *udpAddr
|
||||
Global.HandleSyslog(&a, p)
|
||||
}
|
||||
}()
|
||||
}
|
||||
32
internal/ingest/trap_udp.go
Normal file
32
internal/ingest/trap_udp.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"git.apinb.com/ops/logs/internal/config"
|
||||
"github.com/gosnmp/gosnmp"
|
||||
)
|
||||
|
||||
func StartTrapUDP() {
|
||||
addr := strings.TrimSpace(config.Spec.Ingest.TrapListenAddr)
|
||||
if addr == "" {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
tl := gosnmp.NewTrapListener()
|
||||
tl.OnNewTrap = func(pkt *gosnmp.SnmpPacket, u *net.UDPAddr) {
|
||||
if u == nil || pkt == nil {
|
||||
return
|
||||
}
|
||||
ua := *u
|
||||
Global.HandleTrap(&ua, pkt)
|
||||
}
|
||||
tl.Params = gosnmp.Default
|
||||
tl.Params.Logger = gosnmp.NewLogger(log.Default())
|
||||
if err := tl.Listen(addr); err != nil {
|
||||
log.Printf("logs: trap listener %s: %v", addr, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
297
internal/logic/controllers/crud.go
Normal file
297
internal/logic/controllers/crud.go
Normal file
@@ -0,0 +1,297 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/infra"
|
||||
"git.apinb.com/ops/logs/internal/impl"
|
||||
"git.apinb.com/ops/logs/internal/ingest"
|
||||
"git.apinb.com/ops/logs/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func parseID(ctx *gin.Context) (uint, error) {
|
||||
id64, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint(id64), nil
|
||||
}
|
||||
|
||||
func ListSyslogRules(ctx *gin.Context) {
|
||||
var rows []models.SyslogRule
|
||||
if err := impl.DBService.Order("priority desc, id asc").Find(&rows).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, gin.H{"items": rows})
|
||||
}
|
||||
|
||||
func CreateSyslogRule(ctx *gin.Context) {
|
||||
var row models.SyslogRule
|
||||
if err := ctx.ShouldBindJSON(&row); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row.ID = 0
|
||||
if err := impl.DBService.Create(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func UpdateSyslogRule(ctx *gin.Context) {
|
||||
id, err := parseID(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errors.New("invalid id"))
|
||||
return
|
||||
}
|
||||
var row models.SyslogRule
|
||||
if err := impl.DBService.First(&row, id).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&row); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row.ID = id
|
||||
if err := impl.DBService.Save(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func DeleteSyslogRule(ctx *gin.Context) {
|
||||
id, err := parseID(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errors.New("invalid id"))
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Delete(&models.SyslogRule{}, id).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, gin.H{"deleted": id})
|
||||
}
|
||||
|
||||
func ListTrapRules(ctx *gin.Context) {
|
||||
var rows []models.TrapRule
|
||||
if err := impl.DBService.Order("priority desc, id asc").Find(&rows).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, gin.H{"items": rows})
|
||||
}
|
||||
|
||||
func CreateTrapRule(ctx *gin.Context) {
|
||||
var row models.TrapRule
|
||||
if err := ctx.ShouldBindJSON(&row); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row.ID = 0
|
||||
if err := impl.DBService.Create(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func UpdateTrapRule(ctx *gin.Context) {
|
||||
id, err := parseID(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errors.New("invalid id"))
|
||||
return
|
||||
}
|
||||
var row models.TrapRule
|
||||
if err := impl.DBService.First(&row, id).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&row); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row.ID = id
|
||||
if err := impl.DBService.Save(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func DeleteTrapRule(ctx *gin.Context) {
|
||||
id, err := parseID(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errors.New("invalid id"))
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Delete(&models.TrapRule{}, id).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, gin.H{"deleted": id})
|
||||
}
|
||||
|
||||
func ListTrapDictionary(ctx *gin.Context) {
|
||||
var rows []models.TrapDictionaryEntry
|
||||
if err := impl.DBService.Order("id asc").Find(&rows).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, gin.H{"items": rows})
|
||||
}
|
||||
|
||||
func CreateTrapDictionary(ctx *gin.Context) {
|
||||
var row models.TrapDictionaryEntry
|
||||
if err := ctx.ShouldBindJSON(&row); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row.ID = 0
|
||||
if err := impl.DBService.Create(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func UpdateTrapDictionary(ctx *gin.Context) {
|
||||
id, err := parseID(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errors.New("invalid id"))
|
||||
return
|
||||
}
|
||||
var row models.TrapDictionaryEntry
|
||||
if err := impl.DBService.First(&row, id).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&row); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row.ID = id
|
||||
if err := impl.DBService.Save(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func DeleteTrapDictionary(ctx *gin.Context) {
|
||||
id, err := parseID(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errors.New("invalid id"))
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Delete(&models.TrapDictionaryEntry{}, id).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, gin.H{"deleted": id})
|
||||
}
|
||||
|
||||
func ListTrapShields(ctx *gin.Context) {
|
||||
var rows []models.TrapShield
|
||||
if err := impl.DBService.Order("id asc").Find(&rows).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, gin.H{"items": rows})
|
||||
}
|
||||
|
||||
func CreateTrapShield(ctx *gin.Context) {
|
||||
var row models.TrapShield
|
||||
if err := ctx.ShouldBindJSON(&row); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row.ID = 0
|
||||
if err := impl.DBService.Create(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func UpdateTrapShield(ctx *gin.Context) {
|
||||
id, err := parseID(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errors.New("invalid id"))
|
||||
return
|
||||
}
|
||||
var row models.TrapShield
|
||||
if err := impl.DBService.First(&row, id).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&row); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row.ID = id
|
||||
if err := impl.DBService.Save(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func DeleteTrapShield(ctx *gin.Context) {
|
||||
id, err := parseID(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errors.New("invalid id"))
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Delete(&models.TrapShield{}, id).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
_ = ingest.Global.Refresh()
|
||||
infra.Response.Success(ctx, gin.H{"deleted": id})
|
||||
}
|
||||
|
||||
func ListLogEvents(ctx *gin.Context) {
|
||||
kind := ctx.Query("source_kind")
|
||||
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(ctx.DefaultQuery("page_size", "50"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 || size > 500 {
|
||||
size = 50
|
||||
}
|
||||
offset := (page - 1) * size
|
||||
q := impl.DBService.Model(&models.LogEvent{})
|
||||
if kind != "" {
|
||||
q = q.Where("source_kind = ?", kind)
|
||||
}
|
||||
var total int64
|
||||
_ = q.Count(&total).Error
|
||||
var rows []models.LogEvent
|
||||
if err := q.Order("id desc").Offset(offset).Limit(size).Find(&rows).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"total": total, "page": page, "page_size": size, "items": rows})
|
||||
}
|
||||
10
internal/logic/ping/ping.go
Normal file
10
internal/logic/ping/ping.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package ping
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/infra"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Hello(ctx *gin.Context) {
|
||||
infra.Response.Success(ctx, gin.H{"service": "logs", "status": "ok"})
|
||||
}
|
||||
21
internal/models/log_event.go
Normal file
21
internal/models/log_event.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type LogEvent struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
SourceKind string `gorm:"size:16;index" json:"source_kind"`
|
||||
RemoteAddr string `gorm:"size:64" json:"remote_addr"`
|
||||
RawPayload string `gorm:"type:text" json:"raw_payload"`
|
||||
NormalizedSummary string `gorm:"type:text" json:"normalized_summary"`
|
||||
NormalizedDetail string `gorm:"type:text" json:"normalized_detail"`
|
||||
DeviceName string `gorm:"size:512;index" json:"device_name"`
|
||||
SeverityCode string `gorm:"size:32" json:"severity_code"`
|
||||
TrapOID string `gorm:"size:512;index" json:"trap_oid"`
|
||||
AlertSent bool `json:"alert_sent"`
|
||||
}
|
||||
|
||||
func (LogEvent) TableName() string {
|
||||
return "logs_events"
|
||||
}
|
||||
17
internal/models/query.go
Normal file
17
internal/models/query.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
// GetAllModels 数据库迁移用模型列表
|
||||
func GetAllModels() []interface{} {
|
||||
return []interface{}{
|
||||
&LogEvent{},
|
||||
&TrapDictionaryEntry{},
|
||||
&SyslogRule{},
|
||||
&TrapRule{},
|
||||
&TrapShield{},
|
||||
}
|
||||
}
|
||||
|
||||
// InitData 预留默认数据
|
||||
func InitData() error {
|
||||
return nil
|
||||
}
|
||||
21
internal/models/syslog_rule.go
Normal file
21
internal/models/syslog_rule.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type SyslogRule struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Name string `gorm:"size:256" json:"name"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
Priority int `gorm:"index" json:"priority"`
|
||||
DeviceNameContains string `gorm:"size:512" json:"device_name_contains"`
|
||||
KeywordRegex string `gorm:"size:512" json:"keyword_regex"`
|
||||
AlertName string `gorm:"size:256" json:"alert_name"`
|
||||
SeverityCode string `gorm:"size:32" json:"severity_code"`
|
||||
PolicyID uint `json:"policy_id"`
|
||||
}
|
||||
|
||||
func (SyslogRule) TableName() string {
|
||||
return "logs_syslog_rules"
|
||||
}
|
||||
19
internal/models/trap_dictionary.go
Normal file
19
internal/models/trap_dictionary.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type TrapDictionaryEntry struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
OIDPrefix string `gorm:"size:512;uniqueIndex" json:"oid_prefix"`
|
||||
Title string `gorm:"size:512" json:"title"`
|
||||
Description string `gorm:"type:text" json:"description"`
|
||||
SeverityCode string `gorm:"size:32" json:"severity_code"`
|
||||
RecoveryMessage string `gorm:"type:text" json:"recovery_message"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
}
|
||||
|
||||
func (TrapDictionaryEntry) TableName() string {
|
||||
return "logs_trap_dictionary"
|
||||
}
|
||||
21
internal/models/trap_rule.go
Normal file
21
internal/models/trap_rule.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type TrapRule struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Name string `gorm:"size:256" json:"name"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
Priority int `gorm:"index" json:"priority"`
|
||||
OIDPrefix string `gorm:"size:512" json:"oid_prefix"`
|
||||
VarbindMatchRegex string `gorm:"size:512" json:"varbind_match_regex"`
|
||||
AlertName string `gorm:"size:256" json:"alert_name"`
|
||||
SeverityCode string `gorm:"size:32" json:"severity_code"`
|
||||
PolicyID uint `json:"policy_id"`
|
||||
}
|
||||
|
||||
func (TrapRule) TableName() string {
|
||||
return "logs_trap_rules"
|
||||
}
|
||||
19
internal/models/trap_shield.go
Normal file
19
internal/models/trap_shield.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type TrapShield struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Name string `gorm:"size:256" json:"name"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
SourceIPCIDR string `gorm:"size:64" json:"source_ip_cidr"`
|
||||
OIDPrefix string `gorm:"size:512" json:"oid_prefix"`
|
||||
InterfaceHint string `gorm:"size:256" json:"interface_hint"`
|
||||
TimeWindowsJSON string `gorm:"type:text" json:"time_windows_json"`
|
||||
}
|
||||
|
||||
func (TrapShield) TableName() string {
|
||||
return "logs_trap_shields"
|
||||
}
|
||||
44
internal/routers/register.go
Normal file
44
internal/routers/register.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/middleware"
|
||||
"git.apinb.com/ops/logs/internal/logic/controllers"
|
||||
"git.apinb.com/ops/logs/internal/logic/ping"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Register(srvKey string, engine *gin.Engine) {
|
||||
v1 := fmt.Sprintf("/%s/%s", srvKey, "v1")
|
||||
anon := engine.Group(v1)
|
||||
{
|
||||
anon.GET("/ping/hello", ping.Hello)
|
||||
}
|
||||
|
||||
api := engine.Group(v1)
|
||||
api.Use(middleware.JwtAuth(true))
|
||||
{
|
||||
api.GET("/syslog-rules", controllers.ListSyslogRules)
|
||||
api.POST("/syslog-rules", controllers.CreateSyslogRule)
|
||||
api.PUT("/syslog-rules/:id", controllers.UpdateSyslogRule)
|
||||
api.DELETE("/syslog-rules/:id", controllers.DeleteSyslogRule)
|
||||
|
||||
api.GET("/trap-rules", controllers.ListTrapRules)
|
||||
api.POST("/trap-rules", controllers.CreateTrapRule)
|
||||
api.PUT("/trap-rules/:id", controllers.UpdateTrapRule)
|
||||
api.DELETE("/trap-rules/:id", controllers.DeleteTrapRule)
|
||||
|
||||
api.GET("/trap-dictionary", controllers.ListTrapDictionary)
|
||||
api.POST("/trap-dictionary", controllers.CreateTrapDictionary)
|
||||
api.PUT("/trap-dictionary/:id", controllers.UpdateTrapDictionary)
|
||||
api.DELETE("/trap-dictionary/:id", controllers.DeleteTrapDictionary)
|
||||
|
||||
api.GET("/trap-suppressions", controllers.ListTrapShields)
|
||||
api.POST("/trap-suppressions", controllers.CreateTrapShield)
|
||||
api.PUT("/trap-suppressions/:id", controllers.UpdateTrapShield)
|
||||
api.DELETE("/trap-suppressions/:id", controllers.DeleteTrapShield)
|
||||
|
||||
api.GET("/entries", controllers.ListLogEvents)
|
||||
}
|
||||
}
|
||||
3
scripts/pack.ps1
Normal file
3
scripts/pack.ps1
Normal file
@@ -0,0 +1,3 @@
|
||||
go env -w GOOS="linux"
|
||||
go build -o ops-logs ./cmd/main/main.go
|
||||
go env -w GOOS="windows"
|
||||
Reference in New Issue
Block a user