package recorder import ( "context" "encoding/json" "net/http" "time" "github.com/go-gost/core/recorder" xctx "github.com/go-gost/x/ctx" ) // MetadataRecorder wraps a Recorder and automatically appends metadata // to every Record call. type MetadataRecorder struct { recorder.Recorder Metadata any } func (r *MetadataRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error { if r.Recorder == nil { return nil } if r.Metadata == nil { return r.Recorder.Record(ctx, b, opts...) } return r.Recorder.Record(ctx, b, append(opts, recorder.MetadataRecordOption(r.Metadata))...) } const ( RecorderServiceHandler = "recorder.service.handler" RecorderServiceHandlerSerial = "recorder.service.handler.serial" RecorderServiceHandlerTunnel = "recorder.service.handler.tunnel" ) // HTTPRequestRecorderObject holds the recorded data of an HTTP request. type HTTPRequestRecorderObject struct { ContentLength int64 `json:"contentLength"` Header http.Header `json:"header"` Body []byte `json:"body"` } // HTTPResponseRecorderObject holds the recorded data of an HTTP response. type HTTPResponseRecorderObject struct { ContentLength int64 `json:"contentLength"` Header http.Header `json:"header"` Body []byte `json:"body"` } // HTTPRecorderObject holds the recorded HTTP request and response data // for a single HTTP transaction. type HTTPRecorderObject struct { Host string `json:"host"` Method string `json:"method"` Proto string `json:"proto"` Scheme string `json:"scheme"` URI string `json:"uri"` StatusCode int `json:"statusCode"` Request HTTPRequestRecorderObject `json:"request"` Response HTTPResponseRecorderObject `json:"response"` } // WebsocketRecorderObject holds the recorded data of a WebSocket frame. type WebsocketRecorderObject struct { From string `json:"from"` Fin bool `json:"fin"` Rsv1 bool `json:"rsv1"` Rsv2 bool `json:"rsv2"` Rsv3 bool `json:"rsv3"` OpCode int `json:"opcode"` Masked bool `json:"masked"` MaskKey uint32 `json:"maskKey"` Length int64 `json:"length"` Payload []byte `json:"payload"` } // TLSRecorderObject holds the recorded data of a TLS handshake. type TLSRecorderObject struct { ServerName string `json:"serverName"` CipherSuite string `json:"cipherSuite"` CompressionMethod uint8 `json:"compressionMethod"` Proto string `json:"proto"` Version string `json:"version"` ClientHello string `json:"clientHello"` ServerHello string `json:"serverHello"` } // DNSRecorderObject holds the recorded data of a DNS query and response. type DNSRecorderObject struct { ID int `json:"id"` Name string `json:"name"` Class string `json:"class"` Type string `json:"type"` Question string `json:"question"` Answer string `json:"answer"` Cached bool `json:"cached"` } // HandlerRecorderObject bundles traffic metadata recorded at the handler // level — addresses, protocols, transferred bytes, and optional sub-records // for HTTP, WebSocket, TLS, and DNS traffic. type HandlerRecorderObject struct { Node string `json:"node,omitempty"` Service string `json:"service"` Labels map[string]string `json:"labels,omitempty"` Network string `json:"network"` RemoteAddr string `json:"remote"` LocalAddr string `json:"local"` ClientAddr string `json:"client"` SrcAddr string `json:"src"` DstAddr string `json:"dst"` Host string `json:"host"` Proto string `json:"proto,omitempty"` ClientIP string `json:"clientIP"` ClientID string `json:"clientID,omitempty"` HTTP *HTTPRecorderObject `json:"http,omitempty"` Websocket *WebsocketRecorderObject `json:"websocket,omitempty"` TLS *TLSRecorderObject `json:"tls,omitempty"` DNS *DNSRecorderObject `json:"dns,omitempty"` Route string `json:"route,omitempty"` InputBytes uint64 `json:"inputBytes"` OutputBytes uint64 `json:"outputBytes"` Redirect string `json:"redirect,omitempty"` Err string `json:"err,omitempty"` SID string `json:"sid"` Duration time.Duration `json:"duration"` Time time.Time `json:"time"` } // Record serializes the HandlerRecorderObject as JSON and writes it to r. // It returns nil if p or r is nil or if p.Time is the zero value. func (p *HandlerRecorderObject) Record(ctx context.Context, r recorder.Recorder) error { if p == nil || r == nil || p.Time.IsZero() { return nil } if p.Labels == nil { p.Labels = xctx.LabelsFromContext(ctx) } data, err := json.Marshal(p) if err != nil { return err } return r.Record(ctx, data) }