mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Merge pull request #13166 from supabase/feat/local-logs
feat: initial local logflare integration
This commit is contained in:
8 files changed
+131
-68
No files matched your search
@@ -78,3 +78,10 @@ SUPABASE_PUBLIC_URL=http://localhost:8000
|
||||
|
||||
# Enable webp support
|
||||
IMGPROXY_ENABLE_WEBP_DETECTION=true
|
||||
|
||||
# Configure Logflare API Calls
|
||||
LOGFLARE_API_KEY=api-key
|
||||
LOGFLARE_URL=http://localhost:54329
|
||||
|
||||
# Enable logging and observability in the UI
|
||||
NEXT_PUBLIC_ENABLE_LOGS=true
|
||||
+3
-1
@@ -9,9 +9,11 @@ SUPABASE_PUBLIC_URL=http://localhost:8000
|
||||
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
|
||||
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q
|
||||
SENTRY_IGNORE_API_RESOLUTION_ERROR=1
|
||||
|
||||
LOGFLARE_URL=http://localhost:54329
|
||||
LOGFLARE_API_KEY=api-key
|
||||
# TODO: evaluate these variables at runtime instead
|
||||
# https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables
|
||||
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_GOTRUE_URL=$SUPABASE_PUBLIC_URL/auth/v1
|
||||
NEXT_PUBLIC_HCAPTCHA_SITE_KEY=10000000-ffff-ffff-ffff-000000000001
|
||||
NEXT_PUBLIC_ENABLE_LOGS=true
|
||||
@@ -1,4 +1,5 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { DatetimeHelper, FilterTableSet, LogTemplate } from '.'
|
||||
|
||||
export const LOGS_EXPLORER_DOCS_URL =
|
||||
@@ -665,3 +666,5 @@ export const TIER_QUERY_LIMITS: {
|
||||
TEAM: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false },
|
||||
ENTERPRISE: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false },
|
||||
}
|
||||
|
||||
export const SHOW_O11Y = IS_PLATFORM ? true : process.env.NEXT_PUBLIC_ENABLE_LOGS == 'true'
|
||||
@@ -14,6 +14,7 @@ import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { checkPermissions } from 'hooks'
|
||||
import { useProfileQuery } from 'data/profile/profile-query'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
|
||||
export interface LogsQueryPanelProps {
|
||||
templates?: LogTemplate[]
|
||||
@@ -130,7 +131,7 @@ const LogsQueryPanel = ({
|
||||
<Button type="default" onClick={onClear}>
|
||||
Clear query
|
||||
</Button>
|
||||
{onSave && (
|
||||
{IS_PLATFORM && onSave && (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
import { Project } from 'types'
|
||||
import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types'
|
||||
import { ProductMenuGroup, ProductMenuGroupItem } from 'components/ui/ProductMenu/ProductMenu.types'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
|
||||
export const generateLogsMenu = (project?: Project): ProductMenuGroup[] => {
|
||||
const ref = project?.ref ?? 'default'
|
||||
|
||||
return [
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
{
|
||||
title: 'Logs Explorer',
|
||||
items: [
|
||||
{ key: 'explorer', name: 'Query', root: true },
|
||||
{ key: 'saved', name: 'Saved Queries' },
|
||||
{ key: 'recent', name: 'Recent Queries' },
|
||||
{ key: 'templates', name: 'Templates' },
|
||||
].map(({ key, name, root }) => ({
|
||||
name,
|
||||
key,
|
||||
url: `/project/${ref}/logs/explorer${root ? '' : '/' + key}`,
|
||||
{
|
||||
title: 'Logs Explorer',
|
||||
items: (
|
||||
[
|
||||
{ key: 'explorer', name: 'Query', root: true },
|
||||
IS_PLATFORM ? { key: 'saved', name: 'Saved Queries' } : null,
|
||||
{ key: 'recent', name: 'Recent Queries' },
|
||||
{ key: 'templates', name: 'Templates' },
|
||||
].filter((item) => item) as { name: string; key: string; root: boolean }[]
|
||||
).map(({ key, name, root }) => ({
|
||||
name,
|
||||
key,
|
||||
url: `/project/${ref}/logs/explorer${root ? '' : '/' + key}`,
|
||||
items: [],
|
||||
})),
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
name: IS_PLATFORM ? 'API Edge Network' : 'Kong API',
|
||||
key: 'edge-logs',
|
||||
url: `/project/${ref}/logs/edge-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Postgres',
|
||||
key: 'postgres-logs',
|
||||
url: `/project/${ref}/logs/postgres-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'PostgREST',
|
||||
key: 'postgrest-logs',
|
||||
url: `/project/${ref}/logs/postgrest-logs`,
|
||||
items: [],
|
||||
},
|
||||
IS_PLATFORM
|
||||
? {
|
||||
name: 'PgBouncer',
|
||||
key: 'pgbouncer-logs',
|
||||
url: `/project/${ref}/logs/pgbouncer-logs`,
|
||||
items: [],
|
||||
})),
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
name: 'API Edge Network',
|
||||
key: 'edge-logs',
|
||||
url: `/project/${ref}/logs/edge-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Postgres',
|
||||
key: 'postgres-logs',
|
||||
url: `/project/${ref}/logs/postgres-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'PostgREST',
|
||||
key: 'postgrest-logs',
|
||||
url: `/project/${ref}/logs/postgrest-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'PgBouncer',
|
||||
key: 'pgbouncer-logs',
|
||||
url: `/project/${ref}/logs/pgbouncer-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Auth',
|
||||
key: 'auth-logs',
|
||||
url: `/project/${ref}/logs/auth-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Storage',
|
||||
key: 'storage-logs',
|
||||
url: `/project/${ref}/logs/storage-logs`,
|
||||
items: [],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Realtime',
|
||||
key: 'realtime-logs',
|
||||
url: `/project/${ref}/logs/realtime-logs`,
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
}
|
||||
: null,
|
||||
,
|
||||
{
|
||||
name: 'Auth',
|
||||
key: 'auth-logs',
|
||||
url: `/project/${ref}/logs/auth-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Storage',
|
||||
key: 'storage-logs',
|
||||
url: `/project/${ref}/logs/storage-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Realtime',
|
||||
key: 'realtime-logs',
|
||||
url: `/project/${ref}/logs/realtime-logs`,
|
||||
items: [],
|
||||
},
|
||||
].filter((item) => item) as ProductMenuGroupItem[],
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import SVG from 'react-inlinesvg'
|
||||
import { ProjectBase } from 'types'
|
||||
import { Route } from 'components/ui/ui.types'
|
||||
import { BASE_PATH, IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
|
||||
import { SHOW_O11Y } from 'components/interfaces/Settings/Logs'
|
||||
|
||||
export const generateToolRoutes = (ref?: string, project?: ProjectBase): Route[] => {
|
||||
const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP
|
||||
@@ -139,7 +140,7 @@ export const generateOtherRoutes = (ref?: string, project?: ProjectBase): Route[
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(IS_PLATFORM
|
||||
...(SHOW_O11Y
|
||||
? [
|
||||
{
|
||||
key: 'logs',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const PUBLIC_URL = new URL(process.env.SUPABASE_PUBLIC_URL || 'http://localhost:8000')
|
||||
|
||||
// Use LOGFLARE_URL until analytics/v1/ routing is supported
|
||||
export const PROJECT_ANALYTICS_URL = `${process.env.LOGFLARE_URL}/api/`
|
||||
|
||||
export const PROJECT_REST_URL = `${PUBLIC_URL.origin}/rest/v1/`
|
||||
export const PROJECT_ENDPOINT = PUBLIC_URL.host
|
||||
export const PROJECT_ENDPOINT_PROTOCOL = PUBLIC_URL.protocol.replace(':', '')
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import apiWrapper from 'lib/api/apiWrapper'
|
||||
import { PROJECT_ANALYTICS_URL } from 'pages/api/constants'
|
||||
import { get } from 'lib/common/fetch'
|
||||
|
||||
export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { method } = req
|
||||
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
const missingEnvVars = [
|
||||
process.env.LOGFLARE_API_KEY ? null : 'LOGFLARE_API_KEY',
|
||||
process.env.LOGFLARE_URL ? null : 'LOGFLARE_URL',
|
||||
].filter((v) => v)
|
||||
if (missingEnvVars.length == 0) {
|
||||
const result = await proxyRequest(req)
|
||||
return res.status(200).json(result)
|
||||
} else {
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: { message: `${missingEnvVars.join(', ')} env variables are not set` } })
|
||||
}
|
||||
|
||||
default:
|
||||
res.setHeader('Allow', ['GET'])
|
||||
res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
|
||||
}
|
||||
}
|
||||
|
||||
const proxyRequest = async (req: NextApiRequest) => {
|
||||
const { name, ...toForward } = req.query
|
||||
const payload = { ...toForward, project_tier: 'ENTERPRISE' }
|
||||
const search = '?' + new URLSearchParams(payload as any).toString()
|
||||
const apiKey = process.env.LOGFLARE_API_KEY
|
||||
const url = `${PROJECT_ANALYTICS_URL}endpoints/query/name/${name}${search}`
|
||||
const result = await get(url, {
|
||||
headers: {
|
||||
'x-api-key': apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
return result
|
||||
}
|
||||
Reference in new issue
Block a user