log-drains.ts137 lines · main
1import { NextApiRequest, NextApiResponse } from 'next'
2
3import apiWrapper from '@/lib/api/apiWrapper'
4import { PROJECT_ANALYTICS_URL } from '@/lib/constants/api'
5
6export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
7
8async function handler(req: NextApiRequest, res: NextApiResponse) {
9 const { method } = req
10
11 const missingEnvVars = envVarsSet()
12
13 if (missingEnvVars !== true) {
14 return res
15 .status(500)
16 .json({ error: { message: `${missingEnvVars.join(', ')} env variables are not set` } })
17 }
18
19 const baseUrl = PROJECT_ANALYTICS_URL
20 if (!baseUrl) {
21 return res.status(500).json({ error: { message: `LOGFLARE_URL env variable is not set` } })
22 }
23
24 switch (method) {
25 case 'GET':
26 // list log drains
27 const url = new URL(baseUrl)
28 url.pathname = '/api/backends'
29 url.search = new URLSearchParams({
30 'metadata[type]': 'log-drain',
31 }).toString()
32 const upstream = await fetch(url, {
33 method: 'GET',
34 headers: {
35 Authorization: `Bearer ${process.env.LOGFLARE_PRIVATE_ACCESS_TOKEN}`,
36 'Content-Type': 'application/json',
37 Accept: 'application/json',
38 },
39 })
40
41 if (!upstream.ok) {
42 return res
43 .status(500)
44 .json({ error: { message: 'Failed to fetch log drains from upstream' } })
45 }
46
47 const resp = await upstream.json()
48
49 if (!Array.isArray(resp)) {
50 return res
51 .status(500)
52 .json({ error: { message: 'Unexpected response format from upstream' } })
53 }
54
55 return res.status(200).json(resp)
56 case 'POST':
57 // create the log drain
58 const postUrl = new URL(baseUrl)
59 postUrl.pathname = '/api/backends'
60 const postResult = await fetch(postUrl, {
61 body: JSON.stringify({
62 ...req.body,
63 config: req.body.config,
64 metadata: {
65 type: 'log-drain',
66 },
67 }),
68 method: 'POST',
69 headers: {
70 Authorization: `Bearer ${process.env.LOGFLARE_PRIVATE_ACCESS_TOKEN}`,
71 'Content-Type': 'application/json',
72 Accept: 'application/json',
73 },
74 }).then(async (r) => await r.json())
75
76 const sourcesGetUrl = new URL(baseUrl)
77 sourcesGetUrl.pathname = '/api/sources'
78 const sources = await fetch(sourcesGetUrl, {
79 method: 'GET',
80 headers: {
81 Authorization: `Bearer ${process.env.LOGFLARE_PRIVATE_ACCESS_TOKEN}`,
82 'Content-Type': 'application/json',
83 Accept: 'application/json',
84 },
85 }).then((r) => r.json())
86
87 const params = sources
88 .filter((source: { name: string; metadata: { type: string } }) =>
89 [
90 'cloudflare.logs.prod',
91 'deno-relay-logs',
92 'deno-subhosting-events',
93 'gotrue.logs.prod',
94 'pgbouncer.logs.prod',
95 'postgrest.logs.prod',
96 'postgres.logs',
97 'realtime.logs.prod',
98 'storage.logs.prod.2',
99 ].includes(source.name.toLocaleLowerCase())
100 )
101 .map((source: { name: string; id: number }) => {
102 return { backend_id: postResult.id, lql_string: `~".*?"`, source_id: source.id }
103 })
104 const rulesPostUrl = new URL(baseUrl)
105 rulesPostUrl.pathname = '/api/rules'
106 await Promise.all(
107 params.map((param: any) =>
108 fetch(rulesPostUrl, {
109 method: 'POST',
110 body: JSON.stringify(param),
111 headers: {
112 Authorization: `Bearer ${process.env.LOGFLARE_PRIVATE_ACCESS_TOKEN}`,
113 'Content-Type': 'application/json',
114 Accept: 'application/json',
115 },
116 })
117 )
118 )
119 return res.status(201).json(postResult)
120
121 default:
122 res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE'])
123 res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
124 }
125}
126
127const envVarsSet = () => {
128 const missingEnvVars = [
129 process.env.LOGFLARE_PRIVATE_ACCESS_TOKEN ? null : 'LOGFLARE_PRIVATE_ACCESS_TOKEN',
130 process.env.LOGFLARE_URL ? null : 'LOGFLARE_URL',
131 ].filter((v) => v)
132 if (missingEnvVars.length == 0) {
133 return true
134 } else {
135 return missingEnvVars
136 }
137}