一键导入
audit-log-reporting
指导实现审计日志上报功能。当开发人员需要在新模块中添加审计日志上报、创建审计服务层、或集成审计服务RPC客户端时调用此SKILL。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
指导实现审计日志上报功能。当开发人员需要在新模块中添加审计日志上报、创建审计服务层、或集成审计服务RPC客户端时调用此SKILL。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | audit-log-reporting |
| description | 指导实现审计日志上报功能。当开发人员需要在新模块中添加审计日志上报、创建审计服务层、或集成审计服务RPC客户端时调用此SKILL。 |
本SKILL提供在其他模块/仓库中实现审计日志上报功能的完整指导。审计日志用于记录用户对数据库实例、集群、保护对象等资源的操作行为。
framework_go-thrifts 库(包含 ncaudittype 和 ncauditsvc)service_resource-center_hrc-core 库在实现审计日志功能时,以下仓库可能提供参考和解决方案:
| 仓库 | 用途 | 参考内容 |
|---|---|---|
hrcm-app-dbalone | 参考实现 | service/audit/ 目录下的审计服务层实现 |
hrcm-app-dbcluster | 参考实现 | 集群相关的审计日志上报实现 |
backup-mgm-database | 参考实现 | 保护对象相关的审计日志上报实现 |
i18nlanguageresources | 国际化资源 | event/Audit_event.json 和 resource/Audit_resource.json |
framework_go-thrifts | Thrift类型定义 | ncaudittype 和 ncauditsvc 包 |
service_resource-center_hrc-core | 基础工具 | NewThriftClient 等工具函数 |
遇到问题时的排查建议:
framework_go-thrifts 中的类型定义是否有更新i18nlanguageresources 仓库中的资源码定义service_resource-center_hrc-core 中的客户端创建方式创建 service/audit/ 目录,包含以下文件:
注意:以下常量定义仅供参考格式,请根据实际业务需求定义符合自身业务场景的审计操作类型和资源码。
package audit
// AuditAction 定义审计操作类型常量
type AuditAction string
const (
AuditActionCreated AuditAction = "created"
AuditActionDeleted AuditAction = "deleted"
AuditActionAuthorized AuditAction = "authorized"
AuditActionAuthCancelled AuditAction = "auth_cancelled"
AuditActionFrozen AuditAction = "frozen"
AuditActionThawed AuditAction = "thawed"
AuditActionModified AuditAction = "modified"
)
// AuditResCode 定义审计资源码常量
type AuditResCode string
const (
AuditResDBInstanceCreated AuditResCode = "Audit.DBInstanceCreated"
AuditResDBInstanceDeleted AuditResCode = "Audit.DBInstanceDeleted"
AuditResDBInstanceAuthorized AuditResCode = "Audit.DBInstanceAuthorized"
AuditResDBInstanceAuthCancelled AuditResCode = "Audit.DBInstanceAuthCancelled"
AuditResDBInstanceFrozen AuditResCode = "Audit.DBInstanceFrozen"
AuditResDBInstanceThawed AuditResCode = "Audit.DBInstanceThawed"
AuditResDBInstanceModified AuditResCode = "Audit.DBInstanceModified"
)
// AuditActionToResCode 将审计Action映射到资源码
var AuditActionToResCode = map[AuditAction]AuditResCode{
AuditActionCreated: AuditResDBInstanceCreated,
AuditActionDeleted: AuditResDBInstanceDeleted,
AuditActionAuthorized: AuditResDBInstanceAuthorized,
AuditActionAuthCancelled: AuditResDBInstanceAuthCancelled,
AuditActionFrozen: AuditResDBInstanceFrozen,
AuditActionThawed: AuditResDBInstanceThawed,
}
package audit
import (
"context"
"time"
"github.com/anybackup-ai/Zeus/_git/framework_go-thrifts/ncaudittype"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// AuditUserInfo 审计用户信息
type AuditUserInfo struct {
UserId string
UserName string
LoginIp string
TenantId string
}
// AuditService 审计服务接口
type AuditService interface {
// ReportInstanceAudit 上报实例审计日志
// propertySet: 操作参数列表,可为nil或空数组
ReportInstanceAudit(c *gin.Context, action AuditAction, instanceName, resourceId string, propertySet []*ncaudittype.NcAuditProperty) error
}
// auditService 审计服务实现
type auditService struct {
auditSvcRpc IAuditSvcRpc
}
// IAuditSvcRpc 审计服务RPC接口(便于测试时mock)
type IAuditSvcRpc interface {
SaveAuditObject(ctx context.Context, auditObject *ncaudittype.NcAuditObject) error
SaveAuditLog(ctx context.Context, auditLog *ncaudittype.NcAuditLogContent) error
}
// NewAuditService 创建审计服务实例
func NewAuditService(rpc IAuditSvcRpc) AuditService {
return &auditService{
auditSvcRpc: rpc,
}
}
// getAuditUserInfo 从请求上下文获取用户信息
func (s *auditService) getAuditUserInfo(c *gin.Context) (*AuditUserInfo, error) {
// 根据项目实际情况获取用户信息
// 示例:
// userInfo, err := utils.GetUserInfo(c)
// return &AuditUserInfo{
// UserId: userInfo.UserId,
// UserName: userInfo.UserName,
// LoginIp: userInfo.LoginIp,
// TenantId: userInfo.TenantId,
// }, nil
return nil, nil
}
// buildObjectProperty 构建单个对象属性
func buildObjectProperty(nameResCode string, value string) *ncaudittype.NcAuditObjectProperty {
return &ncaudittype.NcAuditObjectProperty{
Name: &ncaudittype.NcLanguageText{
Text: nameResCode,
},
Value: []string{value},
}
}
// buildInstanceProperties 构建实例的对象属性列表
// 注意:只包含对象名称,对象类型由NcAuditObjectType_ProtectObject标识,无需额外属性
func buildInstanceProperties(instanceName string) []*ncaudittype.NcAuditObjectProperty {
return []*ncaudittype.NcAuditObjectProperty{
buildObjectProperty("Audit.ObjectName", instanceName),
}
}
// getCurrentTimeMillis 获取当前时间的毫秒级时间戳
func getCurrentTimeMillis() int64 {
return time.Now().UnixNano() / 1e6
}
// buildAuditProperty 构建审计属性(用于PropertySet)
func buildAuditProperty(nameResCode string, value string) *ncaudittype.NcAuditProperty {
return &ncaudittype.NcAuditProperty{
Name: &ncaudittype.NcLanguageText{
Text: nameResCode,
},
Value: []string{value},
}
}
// ReportInstanceAudit 上报实例审计日志
func (s *auditService) ReportInstanceAudit(c *gin.Context, action AuditAction, instanceName, resourceId string, propertySet []*ncaudittype.NcAuditProperty) error {
// 1. 获取用户信息
userInfo, err := s.getAuditUserInfo(c)
if err != nil {
logrus.Errorf("get user info failed: %v", err)
return err
}
// 2. 构建审计对象
objectProperties := buildInstanceProperties(instanceName)
auditObject := &ncaudittype.NcAuditObject{
ObjectId: resourceId,
UserId: userInfo.UserId,
TenantId: userInfo.TenantId,
ObjectName: instanceName,
ObjectType: ncaudittype.NcAuditObjectType_ProtectObject,
ObjectProperty: objectProperties,
}
// 3. 保存审计对象(失败不影响主流程)
ctx := c.Request.Context()
err = s.auditSvcRpc.SaveAuditObject(ctx, auditObject)
if err != nil {
logrus.Errorf("save audit object failed: %v", err)
}
// 4. 获取资源码
resCode, ok := AuditActionToResCode[action]
if !ok {
logrus.Warnf("unknown audit action: %s", action)
return nil
}
// 5. 构建审计日志
auditLog := &ncaudittype.NcAuditLogContent{
UserId: userInfo.UserId,
TriggerTime: getCurrentTimeMillis(),
IP: userInfo.LoginIp,
Description: &ncaudittype.NcLanguageText{
Text: string(resCode),
Params: map[string]string{
"userName": userInfo.UserName,
"time": time.Now().Format("2006-01-02 15:04:05"),
"instanceName": instanceName,
},
},
AuditObjectId: resourceId,
PropertySet: propertySet,
}
// 6. 保存审计日志
err = s.auditSvcRpc.SaveAuditLog(ctx, auditLog)
if err != nil {
logrus.Errorf("save audit log failed: %v", err)
return err
}
return nil
}
创建 ext-rpc/audit_svc.go:
package ext_rpc
import (
"context"
"time"
"github.com/anybackup-ai/Flora/_git/greed/rpc"
"github.com/anybackup-ai/Zeus/_git/framework_go-thrifts/ncauditsvc"
"github.com/anybackup-ai/Zeus/_git/framework_go-thrifts/ncaudittype"
hrc_core "github.com/anybackup-ai/Zeus/_git/service_resource-center_hrc-core"
hrc_util "github.com/anybackup-ai/Zeus/_git/service_resource-center_hrc-core/util"
"github.com/apache/thrift/lib/go/thrift"
"github.com/sirupsen/logrus"
)
// IAuditSvcService 审计服务RPC接口
type IAuditSvcService interface {
SaveAuditLog(ctx context.Context, auditLog *ncaudittype.NcAuditLogContent) error
SaveAuditObject(ctx context.Context, auditObject *ncaudittype.NcAuditObject) error
GetAuditObject(ctx context.Context, auditObjectId string) (*ncaudittype.NcAuditObject, error)
}
// AuditSvcService 审计服务实现
type AuditSvcService struct {
ThriftClient rpc.ThriftClient
TimeOut int
SvcName string
}
// NewAuditSvcService 创建审计服务客户端
func NewAuditSvcService(ip string, port int) IAuditSvcService {
auditService := &AuditSvcService{
TimeOut: 30,
SvcName: "AuditService",
}
client, err := hrc_util.NewThriftClient(ip, port)
if err != nil {
hrc_core.Logger.Errorf("new audit service thrift client failed")
return nil
}
auditService.ThriftClient = client
return auditService
}
// clientHolder 封装Thrift客户端
type clientHolder struct {
transport thrift.TTransport
client *ncauditsvc.NcAuditSvcClient
}
// createClient 创建Thrift客户端
func (a *AuditSvcService) createClient() (*clientHolder, error) {
transport, protocol, err := a.ThriftClient.CreateExternalRpcParam()
if err != nil {
logrus.Errorf("create external rpc param error: %s", err)
return nil, err
}
if err = transport.Open(); err != nil {
logrus.Errorf("open transport error: %s", err)
return nil, err
}
client := ncauditsvc.NewNcAuditSvcClientProtocol(transport, protocol, protocol)
return &clientHolder{
transport: transport,
client: client,
}, nil
}
// SaveAuditLog 保存审计日志
func (a *AuditSvcService) SaveAuditLog(ctx context.Context, auditLog *ncaudittype.NcAuditLogContent) error {
ctx, cancel := context.WithTimeout(ctx, time.Duration(a.TimeOut)*time.Second)
defer cancel()
holder, err := a.createClient()
if err != nil {
return err
}
defer func() {
_ = holder.transport.Close()
}()
err = holder.client.SaveAuditLog(ctx, auditLog)
if err != nil {
logrus.Error(err)
return err
}
return nil
}
// SaveAuditObject 保存审计对象
func (a *AuditSvcService) SaveAuditObject(ctx context.Context, auditObject *ncaudittype.NcAuditObject) error {
ctx, cancel := context.WithTimeout(ctx, time.Duration(a.TimeOut)*time.Second)
defer cancel()
holder, err := a.createClient()
if err != nil {
return err
}
defer func() {
_ = holder.transport.Close()
}()
err = holder.client.SaveAuditObject(ctx, auditObject)
if err != nil {
logrus.Error(err)
return err
}
return nil
}
// GetAuditObject 获取审计对象
func (a *AuditSvcService) GetAuditObject(ctx context.Context, auditObjectId string) (*ncaudittype.NcAuditObject, error) {
ctx, cancel := context.WithTimeout(ctx, time.Duration(a.TimeOut)*time.Second)
defer cancel()
holder, err := a.createClient()
if err != nil {
return nil, err
}
defer func() {
_ = holder.transport.Close()
}()
result, err := holder.client.GetAuditObject(ctx, auditObjectId)
if err != nil {
logrus.Error(err)
return nil, err
}
return result, nil
}
在 global/init.go 中添加:
var auditSvcRpc extrpc.IAuditSvcService
func GetAuditSvcRpc() extrpc.IAuditSvcService {
if auditSvcRpc != nil {
return auditSvcRpc
}
envConfig, err := hrc_core.GetEnvConfig()
if err != nil {
GetLog().Errorf("GetEnvConfig failed")
return nil
}
auditSvcRpc = extrpc.NewAuditSvcService(
hrc_core.GetSelfIp(),
envConfig.ServerIni.DefaultInt("AuditService::thrift", 9125),
)
return auditSvcRpc
}
func CreateInstance(c *gin.Context) {
// ... 业务逻辑 ...
// 异步上报审计日志(推荐)
go func() {
auditSvc := audit.NewAuditService(global.GetAuditSvcRpc())
err := auditSvc.ReportInstanceAudit(c, audit.AuditActionCreated, instanceName, resourceId, nil)
if err != nil {
logrus.Errorf("report audit log failed: %v", err)
}
}()
// ... 返回响应 ...
}
func CreateInstance(c *gin.Context) {
// ... 业务逻辑 ...
// 异步上报审计日志(带操作参数)
go func() {
// 构建操作参数
// 注意:直接上报原始编码值,不做转换,由前端或审计服务端处理国际化
propertySet := []*ncaudittype.NcAuditProperty{
audit.BuildAuditProperty("Audit.DBDBType", fmt.Sprintf("%d", req.Type)), // 数据库类型原始编码
audit.BuildAuditProperty("Audit.DBDBName", req.DbName), // 数据库名
}
auditSvc := audit.NewAuditService(global.GetAuditSvcRpc())
err := auditSvc.ReportInstanceAudit(c, audit.AuditActionCreated, instanceName, resourceId, propertySet)
if err != nil {
logrus.Errorf("report audit log failed: %v", err)
}
}()
// ... 返回响应 ...
}
重要原则:
注意:以下JSON格式仅供参考,请根据实际业务需求定义资源码和描述内容。
在 i18nlanguageresources 仓库的以下文件中添加:
zh_CN/event/Audit_event.json:
{
"Audit.DBInstanceCreated": {
"resId": "Audit.DBInstanceCreated",
"description": "用户[userName]于[time]创建了数据库实例[instanceName]"
}
}
en_US/event/Audit_event.json:
{
"Audit.DBInstanceCreated": {
"resId": "Audit.DBInstanceCreated",
"description": "User[userName]created database instance[instanceName]at[time]"
}
}
zh_CN/resource/Audit_resource.json:
{
"Audit.ObjectName": {
"resId": "Audit.ObjectName",
"description": "对象名称"
},
"Audit.ObjectType": {
"resId": "Audit.ObjectType",
"description": "对象类型"
},
"Audit.DBDBType": {
"resId": "Audit.DBDBType",
"description": "数据库类型"
},
"Audit.DBDBName": {
"resId": "Audit.DBDBName",
"description": "数据库名"
},
"Audit.DBAuthType": {
"resId": "Audit.DBAuthType",
"description": "授权类型"
},
"Audit.DBAuthMode": {
"resId": "Audit.DBAuthMode",
"description": "认证权限"
},
"Audit.DBClusterType": {
"resId": "Audit.DBClusterType",
"description": "集群类型"
},
"Audit.DBInstanceCount": {
"resId": "Audit.DBInstanceCount",
"description": "实例数量"
}
}
en_US/resource/Audit_resource.json:
{
"Audit.DBDBType": {
"resId": "Audit.DBDBType",
"description": "Database Type"
},
"Audit.DBDBName": {
"resId": "Audit.DBDBName",
"description": "Database Name"
},
"Audit.DBAuthType": {
"resId": "Audit.DBAuthType",
"description": "Authorization Type"
},
"Audit.DBAuthMode": {
"resId": "Audit.DBAuthMode",
"description": "Authentication Mode"
},
"Audit.DBClusterType": {
"resId": "Audit.DBClusterType",
"description": "Cluster Type"
},
"Audit.DBInstanceCount": {
"resId": "Audit.DBInstanceCount",
"description": "Instance Count"
}
}
ObjectProperty 字段,包含对象名称和类型Audit.DBxxx 格式(如 Audit.DBDBType、Audit.DBClusterType)Audit.DB2xxx 前缀(如 Audit.DB2InstanceCreated)NewAuditServiceWithRpc 注入mock RPC实现进行单元测试Q: 审计日志上报失败会影响主业务吗? A: 不应该。审计日志上报应该使用异步方式,且失败只记录日志,不返回错误给调用方。
Q: 如何支持新的操作类型?
A: 1) 在 audit_constants.go 中添加新的Action常量;2) 添加对应的资源码映射;3) 在i18n资源文件中添加描述。
Q: 对象属性中的资源码在哪里定义?
A: 对象属性名称(如 Audit.ObjectName、Audit.ObjectType)在 i18nlanguageresources 仓库的 resource/Audit_resource.json 文件中定义;审计日志描述(如 Audit.DBInstanceCreated)在 event/Audit_event.json 文件中定义。
Q: 审计日志描述没有成功国际化怎么办? A: 请检查以下几点:
i18nlanguageresources 仓库的 event/Audit_event.json 中已定义NcLanguageText 的 Params 字段正确填充了参数Params 字段Q: 如何为审计日志添加操作参数(PropertySet)? A: 参考以下步骤:
i18nlanguageresources 仓库的 resource/Audit_resource.json 中定义操作参数名称的资源码(如 Audit.DBDBType)buildAuditProperty 辅助函数构建 NcAuditProperty 对象ReportInstanceAudit 时传入 propertySet 参数Q: 操作参数中应该包含哪些信息? A: 建议包含:
当 DB2 备份配置创建或更新时,需要全量上报所有配置参数(约 70+ 个参数)到审计日志的 PropertySet 中,以便审计服务能够展示配置变更前后的参数对比。
向后兼容性设计:
ReportProtectObjectAuditWithGinAndPropertySet,保持 ReportProtectObjectAuditWithGin 不变ReportProtectObjectAudit 方法签名,添加 propertySet 参数ReportProtectObjectAuditWithGin分模块构建:
BuildDB2BackupConfigPropertySet 调用所有子构建函数参数结构体:
CommonBackupParam(24 个参数)DB2MultiSessionParam(2 个参数)DB2BufferParam(4 个参数)DB2PriorityParam(2 个参数)DB2BackupParam(35 个参数)SANGatewayInfo(7 个参数)DB2BackupScriptsParam(8 个参数)直接转换为字符串:
bool → fmt.Sprintf("%t", value)int32/int64 → fmt.Sprintf("%d", value)float64 → fmt.Sprintf("%f", value)string → 直接使用[]string → strings.Join(value, ",")敏感信息过滤:
AdgPrimaryPwd 字段(密码)资源码命名:
Audit.DB2xxx 前缀(如 Audit.DB2StoragePoolType、Audit.DB2BackupGran)资源文件位置:
i18nlanguageresources/zh_CN/resource/Audit_resource.jsoni18nlanguageresources/en_US/resource/Audit_resource.json修改 UpdateBackupConfig 方法:
// 构建 PropertySet(全量上报所有参数)
propertySet := audit.BuildDB2BackupConfigPropertySet(&req.DB2BackupConfig)
// 上报审计日志
err = utils.ReportProtectObjectAuditWithGinAndPropertySet(c, req.ObjectId, objectName,
"Audit.DB2ProtectConfigUpdated", map[string]string{
"userName": userInfo.UserName,
}, propertySet)
向后兼容性:通过添加新函数保持向后兼容,不影响现有代码 全量上报:所有参数(除密码外)都上报到 PropertySet 异步上报:使用 goroutine 异步上报,不影响主业务流程 敏感信息过滤:只过滤密码字段,不上报到审计日志
Q: 为什么操作参数要上报原始编码值而不是可读名称? A: 原因如下:
严禁包含: