This commit is contained in:
zxr
2026-03-30 15:26:16 +08:00
parent 5939a3d62d
commit 279021bf86
25 changed files with 1794 additions and 0 deletions

47
.gitignore vendored Normal file
View 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
View 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
View 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

View 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
View 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
View 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
View 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
View 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))
}
}
}

View 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
View 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
View 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)
}

View 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] + "..."
}

View 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"])
}
}

View 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)
}
}()
}

View 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)
}
}()
}

View 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})
}

View 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"})
}

View 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
View File

@@ -0,0 +1,17 @@
package models
// GetAllModels 数据库迁移用模型列表
func GetAllModels() []interface{} {
return []interface{}{
&LogEvent{},
&TrapDictionaryEntry{},
&SyslogRule{},
&TrapRule{},
&TrapShield{},
}
}
// InitData 预留默认数据
func InitData() error {
return nil
}

View 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"
}

View 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"
}

View 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"
}

View 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"
}

View 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
View File

@@ -0,0 +1,3 @@
go env -w GOOS="linux"
go build -o ops-logs ./cmd/main/main.go
go env -w GOOS="windows"