mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 16:07:03 -05:00
Compare commits
2 Commits
ca6e95f4f8
...
0dfc5ff724
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dfc5ff724 | ||
|
|
177eb540a8 |
@@ -3520,6 +3520,60 @@ export type Database = {
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
request_spans: {
|
||||
Row: {
|
||||
created_at: string
|
||||
duration_ms: number | null
|
||||
end_time: string | null
|
||||
error_message: string | null
|
||||
error_stack: string | null
|
||||
error_type: string | null
|
||||
id: string
|
||||
kind: string
|
||||
name: string
|
||||
parent_span_id: string | null
|
||||
request_id: string | null
|
||||
span_id: string
|
||||
start_time: string
|
||||
status: string
|
||||
trace_id: string
|
||||
}
|
||||
Insert: {
|
||||
created_at?: string
|
||||
duration_ms?: number | null
|
||||
end_time?: string | null
|
||||
error_message?: string | null
|
||||
error_stack?: string | null
|
||||
error_type?: string | null
|
||||
id?: string
|
||||
kind: string
|
||||
name: string
|
||||
parent_span_id?: string | null
|
||||
request_id?: string | null
|
||||
span_id: string
|
||||
start_time: string
|
||||
status?: string
|
||||
trace_id: string
|
||||
}
|
||||
Update: {
|
||||
created_at?: string
|
||||
duration_ms?: number | null
|
||||
end_time?: string | null
|
||||
error_message?: string | null
|
||||
error_stack?: string | null
|
||||
error_type?: string | null
|
||||
id?: string
|
||||
kind?: string
|
||||
name?: string
|
||||
parent_span_id?: string | null
|
||||
request_id?: string | null
|
||||
span_id?: string
|
||||
start_time?: string
|
||||
status?: string
|
||||
trace_id?: string
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
review_deletions: {
|
||||
Row: {
|
||||
content: string | null
|
||||
@@ -5404,6 +5458,111 @@ export type Database = {
|
||||
},
|
||||
]
|
||||
}
|
||||
span_attributes: {
|
||||
Row: {
|
||||
created_at: string
|
||||
id: string
|
||||
key: string
|
||||
span_id: string
|
||||
value: string
|
||||
value_type: string
|
||||
}
|
||||
Insert: {
|
||||
created_at?: string
|
||||
id?: string
|
||||
key: string
|
||||
span_id: string
|
||||
value: string
|
||||
value_type?: string
|
||||
}
|
||||
Update: {
|
||||
created_at?: string
|
||||
id?: string
|
||||
key?: string
|
||||
span_id?: string
|
||||
value?: string
|
||||
value_type?: string
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "span_attributes_span_id_fkey"
|
||||
columns: ["span_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "request_spans"
|
||||
referencedColumns: ["span_id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
span_event_attributes: {
|
||||
Row: {
|
||||
created_at: string
|
||||
id: string
|
||||
key: string
|
||||
span_event_id: string
|
||||
value: string
|
||||
value_type: string
|
||||
}
|
||||
Insert: {
|
||||
created_at?: string
|
||||
id?: string
|
||||
key: string
|
||||
span_event_id: string
|
||||
value: string
|
||||
value_type?: string
|
||||
}
|
||||
Update: {
|
||||
created_at?: string
|
||||
id?: string
|
||||
key?: string
|
||||
span_event_id?: string
|
||||
value?: string
|
||||
value_type?: string
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "span_event_attributes_span_event_id_fkey"
|
||||
columns: ["span_event_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "span_events"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
span_events: {
|
||||
Row: {
|
||||
created_at: string
|
||||
id: string
|
||||
name: string
|
||||
sequence_order: number
|
||||
span_id: string
|
||||
timestamp: string
|
||||
}
|
||||
Insert: {
|
||||
created_at?: string
|
||||
id?: string
|
||||
name: string
|
||||
sequence_order: number
|
||||
span_id: string
|
||||
timestamp: string
|
||||
}
|
||||
Update: {
|
||||
created_at?: string
|
||||
id?: string
|
||||
name?: string
|
||||
sequence_order?: number
|
||||
span_id?: string
|
||||
timestamp?: string
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "span_events_span_id_fkey"
|
||||
columns: ["span_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "request_spans"
|
||||
referencedColumns: ["span_id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
submission_dependencies: {
|
||||
Row: {
|
||||
child_entity_type: string
|
||||
@@ -6439,6 +6598,34 @@ export type Database = {
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
span_hierarchy: {
|
||||
Row: {
|
||||
depth: number | null
|
||||
duration_ms: number | null
|
||||
kind: string | null
|
||||
name: string | null
|
||||
parent_span_id: string | null
|
||||
path: string[] | null
|
||||
span_id: string | null
|
||||
start_time: string | null
|
||||
status: string | null
|
||||
trace_id: string | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
trace_summary: {
|
||||
Row: {
|
||||
error_count: number | null
|
||||
span_count: number | null
|
||||
span_ids: string[] | null
|
||||
span_names: string[] | null
|
||||
total_duration_ms: number | null
|
||||
trace_end: string | null
|
||||
trace_id: string | null
|
||||
trace_start: string | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
}
|
||||
Functions: {
|
||||
anonymize_user_submissions: {
|
||||
@@ -6540,6 +6727,7 @@ export type Database = {
|
||||
}
|
||||
cleanup_old_page_views: { Args: never; Returns: undefined }
|
||||
cleanup_old_request_metadata: { Args: never; Returns: undefined }
|
||||
cleanup_old_spans: { Args: never; Returns: number }
|
||||
cleanup_old_submissions: {
|
||||
Args: { p_retention_days?: number }
|
||||
Returns: {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
endSpan,
|
||||
addSpanEvent,
|
||||
logSpan,
|
||||
logSpanToDatabase,
|
||||
extractSpanContextFromHeaders,
|
||||
type Span
|
||||
} from './logger.ts';
|
||||
@@ -188,6 +189,7 @@ export function wrapEdgeFunction(
|
||||
|
||||
endSpan(span, 'ok');
|
||||
logSpan(span);
|
||||
logSpanToDatabase(span, requestId);
|
||||
|
||||
// Clone response to add tracking headers
|
||||
const responseBody = await response.text();
|
||||
@@ -221,6 +223,7 @@ export function wrapEdgeFunction(
|
||||
logValidationError(error, requestId, name);
|
||||
endSpan(span, 'error', error);
|
||||
logSpan(span);
|
||||
logSpanToDatabase(span, requestId);
|
||||
|
||||
const duration = span.endTime ? span.duration : Date.now() - span.startTime;
|
||||
|
||||
@@ -291,6 +294,7 @@ export function wrapEdgeFunction(
|
||||
|
||||
endSpan(span, 'error', error);
|
||||
logSpan(span);
|
||||
logSpanToDatabase(span, requestId);
|
||||
|
||||
const duration = span.endTime ? span.duration : Date.now() - span.startTime;
|
||||
|
||||
@@ -333,6 +337,7 @@ export function wrapEdgeFunction(
|
||||
|
||||
endSpan(span, 'error', error);
|
||||
logSpan(span);
|
||||
logSpanToDatabase(span, requestId);
|
||||
|
||||
const duration = span.endTime ? span.duration : Date.now() - span.startTime;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Prevents sensitive data exposure and provides consistent log format
|
||||
*/
|
||||
|
||||
import { createClient } from 'jsr:@supabase/supabase-js@2';
|
||||
import { formatEdgeError } from './errorFormatter.ts';
|
||||
|
||||
type LogLevel = 'info' | 'warn' | 'error' | 'debug';
|
||||
@@ -178,7 +179,7 @@ export function injectSpanContextIntoHeaders(spanContext: SpanContext): Record<s
|
||||
}
|
||||
|
||||
/**
|
||||
* Log completed span
|
||||
* Log completed span (console only)
|
||||
*/
|
||||
export function logSpan(span: Span): void {
|
||||
const sanitizedAttributes = sanitizeContext(span.attributes);
|
||||
@@ -196,6 +197,159 @@ export function logSpan(span: Span): void {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist span to database (fire-and-forget)
|
||||
* Call this after logging the span to store it for monitoring/debugging
|
||||
*/
|
||||
export function logSpanToDatabase(span: Span, requestId?: string): void {
|
||||
// Fire-and-forget - don't await or block on this
|
||||
persistSpanToDatabase(span, requestId).catch((error) => {
|
||||
edgeLogger.error('Failed to persist span to database', {
|
||||
spanId: span.spanId,
|
||||
traceId: span.traceId,
|
||||
error: formatEdgeError(error),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to persist span to database
|
||||
*/
|
||||
async function persistSpanToDatabase(span: Span, requestId?: string): Promise<void> {
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL');
|
||||
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
|
||||
|
||||
if (!supabaseUrl || !supabaseServiceKey) {
|
||||
edgeLogger.warn('Skipping span persistence - Supabase credentials not configured');
|
||||
return;
|
||||
}
|
||||
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||
|
||||
// Insert span
|
||||
const { error: spanError } = await supabase
|
||||
.from('request_spans')
|
||||
.insert({
|
||||
span_id: span.spanId,
|
||||
trace_id: span.traceId,
|
||||
parent_span_id: span.parentSpanId,
|
||||
request_id: requestId,
|
||||
name: span.name,
|
||||
kind: span.kind,
|
||||
start_time: new Date(span.startTime).toISOString(),
|
||||
end_time: span.endTime ? new Date(span.endTime).toISOString() : null,
|
||||
duration_ms: span.duration,
|
||||
status: span.status,
|
||||
error_type: span.error?.type,
|
||||
error_message: span.error?.message,
|
||||
error_stack: span.error?.stack,
|
||||
});
|
||||
|
||||
if (spanError) {
|
||||
throw new Error(`Failed to insert span: ${spanError.message}`);
|
||||
}
|
||||
|
||||
// Insert attributes
|
||||
const attributeInserts = Object.entries(span.attributes).map(([key, value]) => {
|
||||
let valueType: 'string' | 'number' | 'boolean' = 'string';
|
||||
let valueStr: string;
|
||||
|
||||
if (typeof value === 'number') {
|
||||
valueType = 'number';
|
||||
valueStr = String(value);
|
||||
} else if (typeof value === 'boolean') {
|
||||
valueType = 'boolean';
|
||||
valueStr = String(value);
|
||||
} else {
|
||||
valueStr = String(value);
|
||||
}
|
||||
|
||||
return {
|
||||
span_id: span.spanId,
|
||||
key,
|
||||
value: valueStr,
|
||||
value_type: valueType,
|
||||
};
|
||||
});
|
||||
|
||||
if (attributeInserts.length > 0) {
|
||||
const { error: attrError } = await supabase
|
||||
.from('span_attributes')
|
||||
.insert(attributeInserts);
|
||||
|
||||
if (attrError) {
|
||||
edgeLogger.warn('Failed to insert span attributes', {
|
||||
spanId: span.spanId,
|
||||
error: attrError.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Insert events
|
||||
for (let i = 0; i < span.events.length; i++) {
|
||||
const event = span.events[i];
|
||||
|
||||
const { data: eventData, error: eventError } = await supabase
|
||||
.from('span_events')
|
||||
.insert({
|
||||
span_id: span.spanId,
|
||||
timestamp: new Date(event.timestamp).toISOString(),
|
||||
name: event.name,
|
||||
sequence_order: i,
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (eventError || !eventData) {
|
||||
edgeLogger.warn('Failed to insert span event', {
|
||||
spanId: span.spanId,
|
||||
eventName: event.name,
|
||||
error: eventError?.message,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Insert event attributes
|
||||
if (event.attributes) {
|
||||
const eventAttrInserts = Object.entries(event.attributes).map(([key, value]) => {
|
||||
let valueType: 'string' | 'number' | 'boolean' = 'string';
|
||||
let valueStr: string;
|
||||
|
||||
if (typeof value === 'number') {
|
||||
valueType = 'number';
|
||||
valueStr = String(value);
|
||||
} else if (typeof value === 'boolean') {
|
||||
valueType = 'boolean';
|
||||
valueStr = String(value);
|
||||
} else {
|
||||
valueStr = String(value);
|
||||
}
|
||||
|
||||
return {
|
||||
span_event_id: eventData.id,
|
||||
key,
|
||||
value: valueStr,
|
||||
value_type: valueType,
|
||||
};
|
||||
});
|
||||
|
||||
if (eventAttrInserts.length > 0) {
|
||||
const { error: eventAttrError } = await supabase
|
||||
.from('span_event_attributes')
|
||||
.insert(eventAttrInserts);
|
||||
|
||||
if (eventAttrError) {
|
||||
edgeLogger.warn('Failed to insert event attributes', {
|
||||
spanId: span.spanId,
|
||||
eventName: event.name,
|
||||
error: eventAttrError.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fields that should never be logged
|
||||
const SENSITIVE_FIELDS = [
|
||||
'password',
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
-- ============================================================================
|
||||
-- Phase 2: Span Storage Schema for Distributed Tracing
|
||||
-- ============================================================================
|
||||
-- Creates tables to store backend spans, attributes, and events for monitoring
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: request_spans
|
||||
-- ============================================================================
|
||||
-- Stores distributed tracing spans from edge functions
|
||||
CREATE TABLE IF NOT EXISTS public.request_spans (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
span_id text NOT NULL UNIQUE,
|
||||
trace_id text NOT NULL,
|
||||
parent_span_id text,
|
||||
request_id text,
|
||||
name text NOT NULL,
|
||||
kind text NOT NULL CHECK (kind IN ('SERVER', 'CLIENT', 'INTERNAL', 'DATABASE')),
|
||||
start_time timestamptz NOT NULL,
|
||||
end_time timestamptz,
|
||||
duration_ms integer,
|
||||
status text NOT NULL DEFAULT 'unset' CHECK (status IN ('ok', 'error', 'unset')),
|
||||
error_type text,
|
||||
error_message text,
|
||||
error_stack text,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Indexes for fast querying
|
||||
CREATE INDEX IF NOT EXISTS idx_request_spans_trace_id ON public.request_spans(trace_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_request_spans_parent_span_id ON public.request_spans(parent_span_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_request_spans_request_id ON public.request_spans(request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_request_spans_start_time ON public.request_spans(start_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_request_spans_name ON public.request_spans(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_request_spans_status ON public.request_spans(status) WHERE status = 'error';
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: span_attributes
|
||||
-- ============================================================================
|
||||
-- Stores key-value attributes for spans (relational, not JSONB)
|
||||
CREATE TABLE IF NOT EXISTS public.span_attributes (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
span_id text NOT NULL REFERENCES public.request_spans(span_id) ON DELETE CASCADE,
|
||||
key text NOT NULL,
|
||||
value text NOT NULL,
|
||||
value_type text NOT NULL DEFAULT 'string' CHECK (value_type IN ('string', 'number', 'boolean')),
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE(span_id, key)
|
||||
);
|
||||
|
||||
-- Index for fast attribute lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_span_attributes_span_id ON public.span_attributes(span_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_span_attributes_key ON public.span_attributes(key);
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: span_events
|
||||
-- ============================================================================
|
||||
-- Stores events that occurred during span execution
|
||||
CREATE TABLE IF NOT EXISTS public.span_events (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
span_id text NOT NULL REFERENCES public.request_spans(span_id) ON DELETE CASCADE,
|
||||
timestamp timestamptz NOT NULL,
|
||||
name text NOT NULL,
|
||||
sequence_order integer NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Index for fast event lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_span_events_span_id ON public.span_events(span_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_span_events_timestamp ON public.span_events(timestamp);
|
||||
|
||||
-- ============================================================================
|
||||
-- Table: span_event_attributes
|
||||
-- ============================================================================
|
||||
-- Stores attributes for span events
|
||||
CREATE TABLE IF NOT EXISTS public.span_event_attributes (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
span_event_id uuid NOT NULL REFERENCES public.span_events(id) ON DELETE CASCADE,
|
||||
key text NOT NULL,
|
||||
value text NOT NULL,
|
||||
value_type text NOT NULL DEFAULT 'string' CHECK (value_type IN ('string', 'number', 'boolean')),
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Index for fast event attribute lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_span_event_attributes_span_event_id ON public.span_event_attributes(span_event_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- View: trace_summary
|
||||
-- ============================================================================
|
||||
-- Aggregate view of trace statistics
|
||||
CREATE OR REPLACE VIEW public.trace_summary AS
|
||||
SELECT
|
||||
trace_id,
|
||||
COUNT(*) as span_count,
|
||||
MIN(start_time) as trace_start,
|
||||
MAX(end_time) as trace_end,
|
||||
SUM(duration_ms) as total_duration_ms,
|
||||
COUNT(*) FILTER (WHERE status = 'error') as error_count,
|
||||
ARRAY_AGG(DISTINCT name) as span_names,
|
||||
ARRAY_AGG(span_id ORDER BY start_time) as span_ids
|
||||
FROM public.request_spans
|
||||
GROUP BY trace_id;
|
||||
|
||||
-- ============================================================================
|
||||
-- View: span_hierarchy
|
||||
-- ============================================================================
|
||||
-- Recursive view showing parent-child span relationships
|
||||
CREATE OR REPLACE VIEW public.span_hierarchy AS
|
||||
WITH RECURSIVE span_tree AS (
|
||||
-- Root spans (no parent)
|
||||
SELECT
|
||||
span_id,
|
||||
parent_span_id,
|
||||
trace_id,
|
||||
name,
|
||||
kind,
|
||||
start_time,
|
||||
duration_ms,
|
||||
status,
|
||||
1 as depth,
|
||||
ARRAY[span_id] as path
|
||||
FROM public.request_spans
|
||||
WHERE parent_span_id IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Child spans
|
||||
SELECT
|
||||
rs.span_id,
|
||||
rs.parent_span_id,
|
||||
rs.trace_id,
|
||||
rs.name,
|
||||
rs.kind,
|
||||
rs.start_time,
|
||||
rs.duration_ms,
|
||||
rs.status,
|
||||
st.depth + 1,
|
||||
st.path || rs.span_id
|
||||
FROM public.request_spans rs
|
||||
INNER JOIN span_tree st ON rs.parent_span_id = st.span_id
|
||||
)
|
||||
SELECT * FROM span_tree;
|
||||
|
||||
-- ============================================================================
|
||||
-- RLS Policies
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE public.request_spans ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.span_attributes ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.span_events ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.span_event_attributes ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Moderators can view all spans
|
||||
CREATE POLICY "Moderators can view all spans"
|
||||
ON public.request_spans
|
||||
FOR SELECT
|
||||
USING (is_moderator(auth.uid()));
|
||||
|
||||
-- System can insert spans (service role)
|
||||
CREATE POLICY "System can insert spans"
|
||||
ON public.request_spans
|
||||
FOR INSERT
|
||||
WITH CHECK (true);
|
||||
|
||||
-- Moderators can view span attributes
|
||||
CREATE POLICY "Moderators can view span attributes"
|
||||
ON public.span_attributes
|
||||
FOR SELECT
|
||||
USING (is_moderator(auth.uid()));
|
||||
|
||||
-- System can insert span attributes
|
||||
CREATE POLICY "System can insert span attributes"
|
||||
ON public.span_attributes
|
||||
FOR INSERT
|
||||
WITH CHECK (true);
|
||||
|
||||
-- Moderators can view span events
|
||||
CREATE POLICY "Moderators can view span events"
|
||||
ON public.span_events
|
||||
FOR SELECT
|
||||
USING (is_moderator(auth.uid()));
|
||||
|
||||
-- System can insert span events
|
||||
CREATE POLICY "System can insert span events"
|
||||
ON public.span_events
|
||||
FOR INSERT
|
||||
WITH CHECK (true);
|
||||
|
||||
-- Moderators can view span event attributes
|
||||
CREATE POLICY "Moderators can view span event attributes"
|
||||
ON public.span_event_attributes
|
||||
FOR SELECT
|
||||
USING (is_moderator(auth.uid()));
|
||||
|
||||
-- System can insert span event attributes
|
||||
CREATE POLICY "System can insert span event attributes"
|
||||
ON public.span_event_attributes
|
||||
FOR INSERT
|
||||
WITH CHECK (true);
|
||||
|
||||
-- ============================================================================
|
||||
-- Data Retention: Auto-cleanup old spans (30 days)
|
||||
-- ============================================================================
|
||||
-- Note: This would be called by a scheduled job (pg_cron or similar)
|
||||
CREATE OR REPLACE FUNCTION public.cleanup_old_spans()
|
||||
RETURNS integer
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
deleted_count integer;
|
||||
BEGIN
|
||||
-- Delete spans older than 30 days
|
||||
DELETE FROM public.request_spans
|
||||
WHERE created_at < now() - interval '30 days';
|
||||
|
||||
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
||||
|
||||
RETURN deleted_count;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Grant execute permission to service role
|
||||
GRANT EXECUTE ON FUNCTION public.cleanup_old_spans() TO service_role;
|
||||
|
||||
COMMENT ON TABLE public.request_spans IS 'Stores distributed tracing spans from edge functions for monitoring and debugging';
|
||||
COMMENT ON TABLE public.span_attributes IS 'Key-value attributes for spans (e.g., http.method, user.id)';
|
||||
COMMENT ON TABLE public.span_events IS 'Events that occurred during span execution (e.g., authentication_start, handler_complete)';
|
||||
COMMENT ON TABLE public.span_event_attributes IS 'Attributes for span events providing additional context';
|
||||
COMMENT ON VIEW public.trace_summary IS 'Aggregate statistics for traces';
|
||||
COMMENT ON VIEW public.span_hierarchy IS 'Recursive view showing parent-child span relationships';
|
||||
@@ -0,0 +1,80 @@
|
||||
-- Fix security warnings from span storage schema
|
||||
|
||||
-- Drop and recreate views with SECURITY INVOKER
|
||||
DROP VIEW IF EXISTS public.trace_summary;
|
||||
DROP VIEW IF EXISTS public.span_hierarchy;
|
||||
|
||||
-- Recreate trace_summary with SECURITY INVOKER
|
||||
CREATE VIEW public.trace_summary
|
||||
WITH (security_invoker = true)
|
||||
AS
|
||||
SELECT
|
||||
trace_id,
|
||||
COUNT(*) as span_count,
|
||||
MIN(start_time) as trace_start,
|
||||
MAX(end_time) as trace_end,
|
||||
SUM(duration_ms) as total_duration_ms,
|
||||
COUNT(*) FILTER (WHERE status = 'error') as error_count,
|
||||
ARRAY_AGG(DISTINCT name) as span_names,
|
||||
ARRAY_AGG(span_id ORDER BY start_time) as span_ids
|
||||
FROM public.request_spans
|
||||
GROUP BY trace_id;
|
||||
|
||||
-- Recreate span_hierarchy with SECURITY INVOKER
|
||||
CREATE VIEW public.span_hierarchy
|
||||
WITH (security_invoker = true)
|
||||
AS
|
||||
WITH RECURSIVE span_tree AS (
|
||||
-- Root spans (no parent)
|
||||
SELECT
|
||||
span_id,
|
||||
parent_span_id,
|
||||
trace_id,
|
||||
name,
|
||||
kind,
|
||||
start_time,
|
||||
duration_ms,
|
||||
status,
|
||||
1 as depth,
|
||||
ARRAY[span_id] as path
|
||||
FROM public.request_spans
|
||||
WHERE parent_span_id IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Child spans
|
||||
SELECT
|
||||
rs.span_id,
|
||||
rs.parent_span_id,
|
||||
rs.trace_id,
|
||||
rs.name,
|
||||
rs.kind,
|
||||
rs.start_time,
|
||||
rs.duration_ms,
|
||||
rs.status,
|
||||
st.depth + 1,
|
||||
st.path || rs.span_id
|
||||
FROM public.request_spans rs
|
||||
INNER JOIN span_tree st ON rs.parent_span_id = st.span_id
|
||||
)
|
||||
SELECT * FROM span_tree;
|
||||
|
||||
-- Recreate function with explicit search_path
|
||||
CREATE OR REPLACE FUNCTION public.cleanup_old_spans()
|
||||
RETURNS integer
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
deleted_count integer;
|
||||
BEGIN
|
||||
-- Delete spans older than 30 days
|
||||
DELETE FROM public.request_spans
|
||||
WHERE created_at < now() - interval '30 days';
|
||||
|
||||
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
||||
|
||||
RETURN deleted_count;
|
||||
END;
|
||||
$$;
|
||||
Reference in New Issue
Block a user