Skip to content

Commit c8ee8cd

Browse files
AbhiPrasadjordienr
andauthored
feat(log-drains): Add Sentry Log Drain (supabase#39442)
* feat(log-drains): Add Sentry Log Drain * fix icon * fix type error * flag * fix dupped entry in schema --------- Co-authored-by: Jordi Enric <[email protected]>
1 parent a04ed40 commit c8ee8cd

File tree

6 files changed

+99
-15
lines changed

6 files changed

+99
-15
lines changed

apps/docs/content/guides/telemetry/log-drains.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The following table lists the supported destinations and the required setup conf
1717
| Generic HTTP endpoint | HTTP | URL <br /> HTTP Version <br/> Gzip <br /> Headers |
1818
| DataDog | HTTP | API Key <br /> Region |
1919
| Loki | HTTP | URL <br /> Headers |
20+
| Sentry | HTTP | DSN |
2021

2122
HTTP requests are batched with a max of 250 logs or 1 second intervals, whichever happens first. Logs are compressed via Gzip if the destination supports it.
2223

@@ -196,6 +197,20 @@ The `event_message` and `timestamp` fields will be dropped from the events to av
196197

197198
Loki must be configured to accept **structured metadata**, and it is advised to increase the default maximum number of structured metadata fields to at least 500 to accommodate large log event payloads of different products.
198199

200+
## Sentry
201+
202+
Logs are sent to Sentry as part of [Sentry's Logging Product](https://docs.sentry.io/product/explore/logs/). Ingesting Supabase logs as Sentry errors is currently not supported.
203+
204+
To setup the Sentry log drain, you need to do the following:
205+
206+
1. Grab your DSN from your [Sentry project settings](https://docs.sentry.io/concepts/key-terms/dsn-explainer/). It should be of the format `{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}{PATH}/{PROJECT_ID}`.
207+
2. Create log drain in [Supabase dashboard](/dashboard/project/_/settings/log-drains)
208+
3. Watch for events in the [Sentry Logs page](https://sentry.io/explore/logs/)
209+
210+
All fields from the log event are attached as attributes to the Sentry log, which can be used for filtering and grouping in the Sentry UI. There are no limits to cardinality or the number of attributes that can be attached to a log.
211+
212+
If you are self-hosting Sentry, Sentry Logs are only supported in self-hosted version [25.9.0](https://github.com/getsentry/self-hosted/releases/tag/25.9.0) and later.
213+
199214
## Pricing
200215

201216
For a detailed breakdown of how charges are calculated, refer to [Manage Log Drain usage](/docs/guides/platform/manage-your-usage/log-drains).

apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form'
55
import { toast } from 'sonner'
66
import { z } from 'zod'
77

8-
import { useParams } from 'common'
8+
import { useFlag, useParams } from 'common'
99
import { DocsButton } from 'components/ui/DocsButton'
1010
import { LogDrainData, useLogDrainsQuery } from 'data/log-drains/log-drains-query'
1111
import { DOCS_URL } from 'lib/constants'
@@ -80,6 +80,7 @@ const formUnion = z.discriminatedUnion('type', [
8080
}),
8181
z.object({
8282
type: z.literal('sentry'),
83+
dsn: z.string().min(1, { message: 'Sentry DSN is required' }),
8384
}),
8485
])
8586

@@ -156,6 +157,8 @@ export function LogDrainDestinationSheetForm({
156157
}
157158
const DEFAULT_HEADERS = mode === 'create' ? CREATE_DEFAULT_HEADERS : defaultConfig?.headers || {}
158159

160+
const sentryEnabled = useFlag('SentryLogDrain')
161+
159162
const { ref } = useParams()
160163
const { data: logDrains } = useLogDrainsQuery({
161164
ref,
@@ -164,6 +167,11 @@ export function LogDrainDestinationSheetForm({
164167
const defaultType = defaultValues?.type || 'webhook'
165168
const [newCustomHeader, setNewCustomHeader] = useState({ name: '', value: '' })
166169

170+
const baseValues = {
171+
name: defaultValues?.name || '',
172+
description: defaultValues?.description || '',
173+
}
174+
167175
const form = useForm<z.infer<typeof formSchema>>({
168176
resolver: zodResolver(formSchema),
169177
values: {
@@ -178,6 +186,7 @@ export function LogDrainDestinationSheetForm({
178186
region: defaultConfig?.region || '',
179187
username: defaultConfig?.username || '',
180188
password: defaultConfig?.password || '',
189+
dsn: defaultConfig?.dsn || '',
181190
},
182191
})
183192

@@ -274,7 +283,7 @@ export function LogDrainDestinationSheetForm({
274283
/>
275284
<LogDrainFormItem
276285
value="description"
277-
placeholder="My Destination"
286+
placeholder="Optional description"
278287
label="Description"
279288
formControl={form.control}
280289
/>
@@ -293,16 +302,18 @@ export function LogDrainDestinationSheetForm({
293302
{LOG_DRAIN_TYPES.find((t) => t.value === type)?.name}
294303
</SelectTrigger_Shadcn_>
295304
<SelectContent_Shadcn_>
296-
{LOG_DRAIN_TYPES.map((type) => (
297-
<SelectItem_Shadcn_
298-
value={type.value}
299-
key={type.value}
300-
id={type.value}
301-
className="text-left"
302-
>
303-
{type.name}
304-
</SelectItem_Shadcn_>
305-
))}
305+
{LOG_DRAIN_TYPES.filter((t) => t.value !== 'sentry' || sentryEnabled).map(
306+
(type) => (
307+
<SelectItem_Shadcn_
308+
value={type.value}
309+
key={type.value}
310+
id={type.value}
311+
className="text-left"
312+
>
313+
{type.name}
314+
</SelectItem_Shadcn_>
315+
)
316+
)}
306317
</SelectContent_Shadcn_>
307318
</Select_Shadcn_>
308319
</FormItemLayout>
@@ -456,6 +467,31 @@ export function LogDrainDestinationSheetForm({
456467
/>
457468
</div>
458469
)}
470+
{type === 'sentry' && (
471+
<div className="grid gap-4 px-content">
472+
<LogDrainFormItem
473+
type="text"
474+
value="dsn"
475+
label="DSN"
476+
placeholder="https://<project_id>@o<organization_id>.ingest.sentry.io/<project_id>"
477+
formControl={form.control}
478+
description={
479+
<>
480+
The DSN obtained from the Sentry dashboard. Read more about DSNs{' '}
481+
<a
482+
target="_blank"
483+
rel="noopener noreferrer"
484+
className="text-sm underline transition hover:text-foreground"
485+
href="https://docs.sentry.io/concepts/key-terms/dsn-explainer/"
486+
>
487+
here
488+
</a>
489+
.
490+
</>
491+
}
492+
/>
493+
</div>
494+
)}
459495
<FormMessage_Shadcn_ />
460496
</div>
461497
</form>

apps/studio/components/interfaces/LogDrains/LogDrains.constants.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { Datadog, Grafana, Sentry } from 'icons'
12
import { components } from 'api-types'
2-
import { Datadog, Grafana } from 'icons'
33
import { BracesIcon } from 'lucide-react'
44

55
const iconProps = {
@@ -28,6 +28,13 @@ export const LOG_DRAIN_TYPES = [
2828
'Loki is an open-source log aggregation system designed to store and query logs from multiple sources',
2929
icon: <Grafana {...iconProps} fill="currentColor" strokeWidth={0} />,
3030
},
31+
{
32+
value: 'sentry',
33+
name: 'Sentry',
34+
description:
35+
'Sentry is an application monitoring service that helps developers identify and debug performance issues and errors',
36+
icon: <Sentry {...iconProps} fill="currentColor" strokeWidth={0} />,
37+
},
3138
] as const
3239

3340
export const LOG_DRAIN_SOURCE_VALUES = LOG_DRAIN_TYPES.map((source) => source.value)

apps/studio/components/interfaces/LogDrains/LogDrains.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Link from 'next/link'
33
import { useState } from 'react'
44
import { toast } from 'sonner'
55

6-
import { useParams } from 'common'
6+
import { useFlag, useParams } from 'common'
77
import AlertError from 'components/ui/AlertError'
88
import CardButton from 'components/ui/CardButton'
99
import Panel from 'components/ui/Panel'
@@ -55,6 +55,8 @@ export function LogDrains({
5555
enabled: logDrainsEnabled,
5656
}
5757
)
58+
const sentryEnabled = useFlag('SentryLogDrain')
59+
5860
const { mutate: deleteLogDrain } = useDeleteLogDrainMutation({
5961
onSuccess: () => {
6062
setIsDeleteModalOpen(false)
@@ -91,7 +93,7 @@ export function LogDrains({
9193
if (!isLoading && logDrains?.length === 0) {
9294
return (
9395
<div className="grid lg:grid-cols-2 gap-3">
94-
{LOG_DRAIN_TYPES.map((src) => (
96+
{LOG_DRAIN_TYPES.filter((t) => t.value !== 'sentry' || sentryEnabled).map((src) => (
9597
<CardButton
9698
key={src.value}
9799
title={src.name}

packages/icons/src/icons/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export { default as Realtime } from './realtime';
1717
export { default as ReplaceCode } from './replace-code';
1818
export { default as Reports } from './reports';
1919
export { default as Settings } from './settings';
20+
export { default as Sentry } from './sentry';
2021
export { default as SqlEditor } from './sql-editor';
2122
export { default as Storage } from './storage';
2223
export { default as TableEditor } from './table-editor';

packages/icons/src/icons/sentry.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import createSupabaseIcon from '../createSupabaseIcon';
2+
3+
/**
4+
* @component @name Sentry
5+
* @description Supabase SVG icon component, renders SVG Element with children.
6+
*
7+
* @preview ![img](data:image/svg+xml;base64,PHN2ZyBzdHJva2U9ImN1cnJlbnRDb2xvciIgZmlsbD0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjAiIHJvbGU9ImltZyIgdmlld0JveD0iMCAwIDI0IDI0IiBoZWlnaHQ9IjFlbSIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTMuOTEgMi41MDVjLS44NzMtMS40NDgtMi45NzItMS40NDgtMy44NDQgMEw2LjkwNCA3LjkyYTE1LjQ3OCAxNS40NzggMCAwIDEgOC41MyAxMi44MTFoLTIuMjIxQTEzLjMwMSAxMy4zMDEgMCAwIDAgNS43ODQgOS44MTRsLTIuOTI2IDUuMDZhNy42NSA3LjY1IDAgMCAxIDQuNDM1IDUuODQ4SDIuMTk0YS4zNjUuMzY1IDAgMCAxLS4yOTgtLjUzNGwxLjQxMy0yLjQwMmE1LjE2IDUuMTYgMCAwIDAtMS42MTQtLjkxM0wuMjk2IDE5LjI3NWEyLjE4MiAyLjE4MiAwIDAgMCAuODEyIDIuOTk5IDIuMjQgMi4yNCAwIDAgMCAxLjA4Ni4yODhoNi45ODNhOS4zMjIgOS4zMjIgMCAwIDAtMy44NDUtOC4zMThsMS4xMS0xLjkyMmExMS40NyAxMS40NyAwIDAgMSA0Ljk1IDEwLjI0aDUuOTE1YTE3LjI0MiAxNy4yNDIgMCAwIDAtNy44ODUtMTUuMjhsMi4yNDQtMy44NDVhLjM3LjM3IDAgMCAxIC41MDQtLjEzYy4yNTUuMTQgOS43NSAxNi43MDggOS45MjggMTYuOWEuMzY1LjM2NSAwIDAgMS0uMzI3LjU0M2gtMi4yODdjLjAyOS42MTIuMDI5IDEuMjIzIDAgMS44MzFoMi4yOTdhMi4yMDYgMi4yMDYgMCAwIDAgMS45MjItMy4zMXoiPjwvcGF0aD48L3N2Zz4=)
8+
*
9+
* @param {Object} props - Supabase icons props and any valid SVG attribute
10+
* @returns {JSX.Element} JSX Element
11+
*
12+
*/
13+
const Sentry = createSupabaseIcon('Sentry', [
14+
[
15+
'path',
16+
{
17+
d: 'M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z',
18+
key: 'hopart',
19+
},
20+
],
21+
]);
22+
23+
export default Sentry;

0 commit comments

Comments
 (0)