fix: harden workbench runtime session summary query (#1843)
This commit is contained in:
@@ -174,7 +174,7 @@ func (s *Server) withOtelInternalSpan(ctx context.Context, name string, attrs ma
|
||||
err := fn(ctx)
|
||||
errorCode := ""
|
||||
if err != nil {
|
||||
errorCode = otelFirst(queryErrorStage(err), sqlErrorCode(err), "query_failed")
|
||||
errorCode = otelFirst(sqlErrorCode(err), sqlCauseCode(err), queryErrorStage(err), "query_failed")
|
||||
}
|
||||
attributes := map[string]any{
|
||||
"hwlab.runtime.stage": name,
|
||||
@@ -185,10 +185,44 @@ func (s *Server) withOtelInternalSpan(ctx context.Context, name string, attrs ma
|
||||
attributes[key] = value
|
||||
}
|
||||
}
|
||||
addOtelErrorCauseAttributes(attributes, err)
|
||||
s.emitOtelSpanAsync(name, trace, spanID, trace.ServerSpanID, otelSpanKindInternal, startedAt, attributes, 0, errorCode)
|
||||
return err
|
||||
}
|
||||
|
||||
func addOtelErrorCauseAttributes(attributes map[string]any, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if stage := queryErrorStage(err); stage != "" {
|
||||
attributes["causeStage"] = stage
|
||||
}
|
||||
if code := sqlErrorCode(err); code != "" {
|
||||
attributes["causeCode"] = code
|
||||
return
|
||||
}
|
||||
if code := sqlCauseCode(err); code != "" {
|
||||
attributes["causeCode"] = code
|
||||
}
|
||||
}
|
||||
|
||||
func sqlCauseCode(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
message := strings.ToLower(err.Error())
|
||||
switch {
|
||||
case strings.Contains(message, "invalid input syntax for type json"):
|
||||
return "invalid_json_text"
|
||||
case strings.Contains(message, "cannot cast") && strings.Contains(message, "json"):
|
||||
return "invalid_json_cast"
|
||||
case strings.Contains(message, "context deadline exceeded"):
|
||||
return "query_timeout"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) emitOtelSpanAsync(name string, trace otelTraceContext, spanID string, parentSpanID string, kind int, startedAt time.Time, attrs map[string]any, httpStatus int, errorCode string) {
|
||||
go s.emitOtelSpan(context.Background(), name, trace, spanID, parentSpanID, kind, startedAt, attrs, httpStatus, errorCode)
|
||||
}
|
||||
|
||||
@@ -520,11 +520,11 @@ func (s *Server) querySessionSummaries(ctx context.Context, stage string, sqlTex
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var sessionID, ownerUserID, projectID, conversationID, threadID, status, lastTraceID, sourceEventID, providerProfile sql.NullString
|
||||
var sessionID, ownerUserID, projectID, conversationID, threadID, status, lastTraceID, sourceEventID, sessionJSON sql.NullString
|
||||
var projectedSeq, sourceSeq sql.NullInt64
|
||||
var terminal, sealed sql.NullBool
|
||||
var createdAt, updatedAt sql.NullTime
|
||||
if err := rows.Scan(&sessionID, &ownerUserID, &projectID, &conversationID, &threadID, &status, &lastTraceID, &projectedSeq, &sourceSeq, &sourceEventID, &terminal, &sealed, &createdAt, &updatedAt, &providerProfile); err != nil {
|
||||
if err := rows.Scan(&sessionID, &ownerUserID, &projectID, &conversationID, &threadID, &status, &lastTraceID, &projectedSeq, &sourceSeq, &sourceEventID, &terminal, &sealed, &createdAt, &updatedAt, &sessionJSON); err != nil {
|
||||
return err
|
||||
}
|
||||
if !sessionID.Valid || strings.TrimSpace(sessionID.String) == "" {
|
||||
@@ -549,9 +549,9 @@ func (s *Server) querySessionSummaries(ctx context.Context, stage string, sqlTex
|
||||
"valuesPrinted": false,
|
||||
"valuesRedacted": true,
|
||||
}
|
||||
if providerProfile.Valid && strings.TrimSpace(providerProfile.String) != "" {
|
||||
item["providerProfile"] = providerProfile.String
|
||||
item["sessionJson"] = map[string]any{"providerProfile": providerProfile.String, "valuesRedacted": true, "secretMaterialStored": false}
|
||||
if providerProfile := providerProfileFromSessionJSON(sessionJSON); providerProfile != "" {
|
||||
item["providerProfile"] = providerProfile
|
||||
item["sessionJson"] = map[string]any{"providerProfile": providerProfile, "valuesRedacted": true, "secretMaterialStored": false}
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
@@ -820,10 +820,27 @@ func sessionSummarySelectClause() string {
|
||||
"sealed",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"COALESCE(NULLIF(session_json::jsonb ->> 'providerProfile', ''), NULLIF(session_json::jsonb #>> '{sessionJson,providerProfile}', '')) AS provider_profile",
|
||||
"session_json",
|
||||
}, ", ")
|
||||
}
|
||||
|
||||
func providerProfileFromSessionJSON(raw sql.NullString) string {
|
||||
if !raw.Valid || strings.TrimSpace(raw.String) == "" {
|
||||
return ""
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal([]byte(raw.String), &payload); err != nil {
|
||||
return ""
|
||||
}
|
||||
if value := strings.TrimSpace(text(payload["providerProfile"])); value != "" {
|
||||
return value
|
||||
}
|
||||
if value := strings.TrimSpace(text(objectMap(payload["sessionJson"])["providerProfile"])); value != "" {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func boundedLimit(value int, fallback int, max int) int {
|
||||
if value <= 0 {
|
||||
return fallback
|
||||
|
||||
Reference in New Issue
Block a user