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)
|
err := fn(ctx)
|
||||||
errorCode := ""
|
errorCode := ""
|
||||||
if err != nil {
|
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{
|
attributes := map[string]any{
|
||||||
"hwlab.runtime.stage": name,
|
"hwlab.runtime.stage": name,
|
||||||
@@ -185,10 +185,44 @@ func (s *Server) withOtelInternalSpan(ctx context.Context, name string, attrs ma
|
|||||||
attributes[key] = value
|
attributes[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
addOtelErrorCauseAttributes(attributes, err)
|
||||||
s.emitOtelSpanAsync(name, trace, spanID, trace.ServerSpanID, otelSpanKindInternal, startedAt, attributes, 0, errorCode)
|
s.emitOtelSpanAsync(name, trace, spanID, trace.ServerSpanID, otelSpanKindInternal, startedAt, attributes, 0, errorCode)
|
||||||
return err
|
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) {
|
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)
|
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()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
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 projectedSeq, sourceSeq sql.NullInt64
|
||||||
var terminal, sealed sql.NullBool
|
var terminal, sealed sql.NullBool
|
||||||
var createdAt, updatedAt sql.NullTime
|
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
|
return err
|
||||||
}
|
}
|
||||||
if !sessionID.Valid || strings.TrimSpace(sessionID.String) == "" {
|
if !sessionID.Valid || strings.TrimSpace(sessionID.String) == "" {
|
||||||
@@ -549,9 +549,9 @@ func (s *Server) querySessionSummaries(ctx context.Context, stage string, sqlTex
|
|||||||
"valuesPrinted": false,
|
"valuesPrinted": false,
|
||||||
"valuesRedacted": true,
|
"valuesRedacted": true,
|
||||||
}
|
}
|
||||||
if providerProfile.Valid && strings.TrimSpace(providerProfile.String) != "" {
|
if providerProfile := providerProfileFromSessionJSON(sessionJSON); providerProfile != "" {
|
||||||
item["providerProfile"] = providerProfile.String
|
item["providerProfile"] = providerProfile
|
||||||
item["sessionJson"] = map[string]any{"providerProfile": providerProfile.String, "valuesRedacted": true, "secretMaterialStored": false}
|
item["sessionJson"] = map[string]any{"providerProfile": providerProfile, "valuesRedacted": true, "secretMaterialStored": false}
|
||||||
}
|
}
|
||||||
result = append(result, item)
|
result = append(result, item)
|
||||||
}
|
}
|
||||||
@@ -820,10 +820,27 @@ func sessionSummarySelectClause() string {
|
|||||||
"sealed",
|
"sealed",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_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 {
|
func boundedLimit(value int, fallback int, max int) int {
|
||||||
if value <= 0 {
|
if value <= 0 {
|
||||||
return fallback
|
return fallback
|
||||||
|
|||||||
Reference in New Issue
Block a user