| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- package audit
- import (
- "context"
- "database/sql"
- "encoding/json"
- "time"
- "udeServer/internal/log"
- udeModel "udeServer/internal/model"
- )
- // SpanRecorder records type=2 interface-log entries for the outbound remote
- // HTTP calls to the agent program (外挂程序): /path/lists (ListDirChildren),
- // /api/fuseMount and /api/fuseUnMount. Ported from sdf-serve's
- // SpanRecorder.DoSimple: the entry links to the inbound request (type=1,
- // produced by LogMiddleware) through parent_span_id, so one trace shows the
- // whole local-request → remote-call chain.
- type SpanRecorder struct {
- LogModel udeModel.TSystemLogModel
- ConfigModel udeModel.TConfigModel
- Service string
- }
- // NewSpanRecorder builds the shared recorder; models and service name come
- // from ServiceContext at startup.
- func NewSpanRecorder(logModel udeModel.TSystemLogModel, configModel udeModel.TConfigModel, service string) *SpanRecorder {
- return &SpanRecorder{
- LogModel: logModel,
- ConfigModel: configModel,
- Service: service,
- }
- }
- // DoSimple wraps a remote-call closure and records one type=2 entry:
- // - ctx must carry the inbound trace/span (it does: the middleware injected
- // it before the handler ran);
- // - a fresh child span id is generated for this remote call;
- // - reqBody is the outbound payload digest/uploaded before the call runs
- // (for GET calls the caller passes a {method,url} description so the
- // digest still identifies the outbound request);
- // - remoteURL is stored in uri_path for display;
- // - fn performs the call and returns (result, code, err): code is the
- // remote HTTP status (0 when the call failed at transport level).
- func DoSimple[T any](sr *SpanRecorder, ctx context.Context, methodName, remoteURL string, reqBody []byte, fn func() (T, int64, error)) (T, error) {
- // 1.获取上下文的 traceId、父 SpanId(即入站请求的 SpanId)
- ctx, traceID, parentSpanID, _, _, appKey := EnsureTraceAndSpanID(ctx)
- // 2.生成新的 SpanId 并写回上下文,保持调用链可继续嵌套
- spanID := NewUUID()
- nextCtx := context.WithValue(ctx, SpanKey, spanID)
- // 3.请求体摘要与原文上传(MinIO 未就绪时降级为仅摘要)
- client, logConfig := GetGlobalConfig(sr.ConfigModel)
- reqDigestStr, reqUrl, reqErr := UploadAndDigest(reqBody, traceID, spanID, "[Device-Request]", client, "application/json")
- if reqErr != nil {
- log.RecordError(nextCtx, reqErr)
- }
- // 4.执行闭包并计时
- start := time.Now()
- result, code, callErr := fn()
- duration := time.Since(start).Milliseconds()
- // 5.响应体摘要与原文上传
- var respBody []byte
- if b, mErr := json.Marshal(result); mErr == nil {
- respBody = b
- }
- respDigestStr, respUrl, respErr := UploadAndDigest(respBody, traceID, spanID, "[Device-Response]", client, "application/json")
- if respErr != nil {
- log.RecordError(nextCtx, respErr)
- }
- // 6.构造 type=2 日志对象(UriPath 存远程完整 URL,便于定位四个外挂接口调用)
- if len(remoteURL) > 255 { // uri_path varchar(255),截断防插入失败
- remoteURL = remoteURL[:255]
- }
- entry := &udeModel.TSystemLog{
- Iden: NewUUID(),
- TraceId: traceID,
- SpanId: spanID,
- ParentSpanId: sql.NullString{String: parentSpanID, Valid: parentSpanID != ""}, //顶级链路父SpanId为""时填写null
- Type: InterfaceLog,
- ServiceName: sr.Service,
- UriPath: sql.NullString{String: remoteURL, Valid: remoteURL != ""},
- Method: methodName,
- Level: ChooseLevelByInterfaceCode(code, callErr),
- AppKey: sql.NullString{String: appKey, Valid: true},
- RequestBodyDigest: reqDigestStr,
- RequestBodyFileKey: reqUrl,
- ResponseCode: code,
- ResponseBodyDigest: respDigestStr,
- ResponseBodyFileKey: respUrl,
- DurationMs: duration,
- AuditStatus: UnAudited,
- }
- // 7.填充 ErrMsg
- if callErr != nil {
- entry.ErrorMessage = sql.NullString{String: callErr.Error(), Valid: true}
- } else {
- entry.ErrorMessage = sql.NullString{Valid: false}
- }
- // 8.填充 Hmac 值;密钥为空时用空串计算,保持列非空
- macBuffer := make([]byte, 0, 32)
- if mac, hErr := CalculateHMAC(entry, logConfig.HmacSecret, macBuffer); hErr == nil {
- entry.HMac = mac
- } else {
- log.RecordError(nextCtx, hErr)
- }
- entry.CreatedAt = time.Now()
- // 9.按配置库中 log_record_level 过滤后异步入库
- if SaveToDBByLogLevel(logConfig.LogRecordLevel, entry.Level) {
- go AsyncInsertLog(sr.LogModel, entry)
- }
- return result, callErr
- }
- // ChooseLevelByInterfaceCode maps a remote-call outcome to a log level:
- // call error or agent 5xx → ERROR; 2xx → INFO; other (4xx/3xx/0) → WARN.
- func ChooseLevelByInterfaceCode(code int64, callErr error) string {
- switch {
- case callErr != nil, code >= 500:
- return "ERROR"
- case code >= 200 && code < 300:
- return "INFO"
- default:
- return "WARN"
- }
- }
- // BuildRemoteGetRequest renders the outbound GET request as JSON so the
- // digest identifies the actual remote request (GET has no body).
- func BuildRemoteGetRequest(method, fullURL string) []byte {
- b, err := json.Marshal(map[string]string{"method": method, "url": fullURL})
- if err != nil {
- return nil
- }
- return b
- }
|