logs.ts137 lines · main
1import assert from 'node:assert'
2import { LogsService } from '@supabase/mcp-server-supabase/platform'
3import { stripIndent } from 'common-tags'
4
5import { WrappedResult } from './types'
6import { assertSelfHosted } from './util'
7import { PROJECT_ANALYTICS_URL } from '@/lib/constants/api'
8
9export type RetrieveAnalyticsDataOptions = {
10 name: string
11 projectRef: string
12 params: Record<string, string | undefined>
13}
14
15export type AnalyticsResult = {
16 result?: any[]
17 error?: {
18 message: string
19 }
20 [key: string]: any
21}
22
23/**
24 * Retrieves analytics data from Logflare.
25 *
26 * _Only call this from server-side self-hosted code._
27 */
28export async function retrieveAnalyticsData({
29 name,
30 projectRef,
31 params,
32}: RetrieveAnalyticsDataOptions): Promise<WrappedResult<AnalyticsResult>> {
33 assertSelfHosted()
34 assert(PROJECT_ANALYTICS_URL, 'PROJECT_ANALYTICS_URL is required')
35 assert(process.env.LOGFLARE_PRIVATE_ACCESS_TOKEN, 'LOGFLARE_PRIVATE_ACCESS_TOKEN is required')
36
37 const url = new URL(`${PROJECT_ANALYTICS_URL}endpoints/query/${name}`)
38 url.searchParams.set('project', projectRef)
39
40 // Add all other params
41 Object.entries(params).forEach(([key, value]) => {
42 if (value !== undefined) {
43 url.searchParams.set(key, value)
44 }
45 })
46
47 try {
48 const response = await fetch(url, {
49 method: 'GET',
50 headers: {
51 'x-api-key': process.env.LOGFLARE_PRIVATE_ACCESS_TOKEN,
52 'Content-Type': 'application/json',
53 Accept: 'application/json',
54 },
55 })
56
57 const result = await response.json()
58
59 if (!response.ok) {
60 const error = new Error(
61 result?.error?.message ?? `Failed to retrieve analytics data: ${response.statusText}`
62 )
63 return { data: undefined, error }
64 }
65
66 return { data: result, error: undefined }
67 } catch (error) {
68 if (error instanceof Error) {
69 return { data: undefined, error }
70 }
71 throw error
72 }
73}
74
75export function getLogQuery(service: LogsService, limit: number = 100): string {
76 assertSelfHosted()
77
78 switch (service) {
79 case 'api': {
80 return stripIndent`
81 select id, edge_logs.timestamp, event_message, request.method, request.path, request.search, response.status_code
82 from edge_logs
83 cross join unnest(metadata) as m
84 cross join unnest(m.request) as request
85 cross join unnest(m.response) as response
86 order by timestamp desc
87 limit ${limit};
88 `
89 }
90 case 'branch-action': {
91 throw new Error('Branching is only supported in the hosted Briven platform')
92 }
93 case 'postgres': {
94 return stripIndent`
95 select postgres_logs.timestamp, id, event_message, parsed.error_severity, parsed.detail, parsed.hint
96 from postgres_logs
97 cross join unnest(metadata) as m
98 cross join unnest(m.parsed) as parsed
99 order by timestamp desc
100 limit ${limit};
101 `
102 }
103 case 'edge-function': {
104 return stripIndent`
105 select id, function_edge_logs.timestamp, event_message
106 from function_edge_logs
107 order by timestamp desc
108 limit ${limit}
109 `
110 }
111 case 'auth': {
112 return stripIndent`
113 select id, auth_logs.timestamp, event_message, metadata.level, metadata.status, metadata.path, metadata.msg as msg, metadata.error from auth_logs
114 cross join unnest(metadata) as metadata
115 order by timestamp desc
116 limit ${limit};
117 `
118 }
119 case 'storage': {
120 return stripIndent`
121 select id, storage_logs.timestamp, event_message from storage_logs
122 order by timestamp desc
123 limit ${limit};
124 `
125 }
126 case 'realtime': {
127 return stripIndent`
128 select id, realtime_logs.timestamp, event_message from realtime_logs
129 order by timestamp desc
130 limit ${limit};
131 `
132 }
133 default: {
134 throw new Error(`Unsupported log service: ${service}`)
135 }
136 }
137}