Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/docs/content/guides/telemetry/log-drains.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ All fields from the log event are attached as attributes to the Sentry log, whic

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.

## Axiom

Logs sent to a specified Axiom's dataset as JSON of a raw log event,
with timestamp modified to be parsed by ingestion endpoint.

To set up the Axiom log drain, you have to:

1. Create a dataset for ingestion in Axiom dashboard -> Datasets
2. Generate an Axiom API Token with permission to ingest into the created dataset (see [Axiom docs](https://axiom.co/docs/reference/tokens#create-basic-api-token))
3. Create log drain in [Supabase dashboard](/dashboard/project/_/settings/log-drains), providing:
- Name of the dataset
- API token
4. Watch for events in the Stream panel of Axiom dashboard

## Amazon S3

Logs are written to an existing S3 bucket that you own.
Expand Down
20 changes: 6 additions & 14 deletions apps/studio/.github/eslint-rule-baselines.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"react-hooks/exhaustive-deps": 216,
"import/no-anonymous-default-export": 57,
"@tanstack/query/exhaustive-deps": 14,
"@typescript-eslint/no-explicit-any": 1291
"@typescript-eslint/no-explicit-any": 1261
},
"ruleFiles": {
"react-hooks/exhaustive-deps": {
Expand Down Expand Up @@ -249,7 +249,6 @@
"components/grid/components/editor/TextEditor.tsx": 2,
"components/grid/components/formatter/ReferenceRecordPeek.tsx": 4,
"components/grid/components/grid/AddColumn.tsx": 2,
"components/grid/components/grid/Grid.tsx": 10,
"components/grid/components/grid/Grid.utils.tsx": 2,
"components/grid/components/grid/GridError.tsx": 4,
"components/grid/components/grid/SelectColumn.tsx": 3,
Expand Down Expand Up @@ -347,7 +346,6 @@
"components/interfaces/Home/ProjectList/ProjectList.tsx": 1,
"components/interfaces/Home/ProjectUsage.tsx": 1,
"components/interfaces/Home/ServiceStatus.tsx": 1,
"components/interfaces/HomeNew/ProjectUsageSection.tsx": 1,
"components/interfaces/Integrations/CronJobs/CreateCronJobSheet/CreateCronJobSheet.tsx": 1,
"components/interfaces/Integrations/CronJobs/CronJobTableCell.tsx": 2,
"components/interfaces/Integrations/CronJobs/PreviousRunsTab.tsx": 2,
Expand All @@ -369,6 +367,8 @@
"components/interfaces/JwtSecrets/jwt-settings.tsx": 5,
"components/interfaces/Linter/LinterDataGrid.tsx": 4,
"components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx": 2,
"components/interfaces/Observability/ObservabilityOverview.tsx": 1,
"components/interfaces/Observability/ServiceHealthCard.tsx": 1,
"components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx": 2,
"components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.utils.ts": 2,
"components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx": 2,
Expand Down Expand Up @@ -419,7 +419,7 @@
"components/interfaces/Reports/Reports.utils.tsx": 10,
"components/interfaces/Reports/SharedAPIReport/SharedAPIReport.tsx": 3,
"components/interfaces/Reports/v2/ReportChartUpsell.tsx": 1,
"components/interfaces/Reports/v2/ReportChartV2.tsx": 8,
"components/interfaces/Reports/v2/ReportChartV2.tsx": 1,
"components/interfaces/RoleImpersonationSelector/UserImpersonationSelector.tsx": 1,
"components/interfaces/SQLEditor/MonacoEditor.tsx": 2,
"components/interfaces/SQLEditor/MoveQueryModal.tsx": 2,
Expand Down Expand Up @@ -457,7 +457,7 @@
"components/interfaces/Settings/Logs/PreviewFilterPanel.tsx": 1,
"components/interfaces/Settings/Logs/PreviewFilterPanelWithUniversal.tsx": 2,
"components/interfaces/Settings/Logs/SidebarV2/SidebarItem.tsx": 1,
"components/interfaces/Sidebar.tsx": 3,
"components/interfaces/Sidebar.tsx": 2,
"components/interfaces/SignIn/SignInForm.tsx": 1,
"components/interfaces/SignIn/SignInWithCustom.tsx": 1,
"components/interfaces/SignIn/SignInWithGitHub.tsx": 1,
Expand Down Expand Up @@ -492,10 +492,8 @@
"components/interfaces/Storage/VectorBuckets/CreateVectorTableSheet.tsx": 3,
"components/interfaces/Storage/VectorBuckets/DeleteVectorTableModal.tsx": 1,
"components/interfaces/Storage/VectorBuckets/VectorBucketDetails/InitializeForeignSchemaDialog.tsx": 1,
"components/interfaces/Storage/useBucketPolicyCount.ts": 1,
"components/interfaces/Support/AttachmentUpload.tsx": 3,
"components/interfaces/Support/LinkSupportTicketForm.tsx": 6,
"components/interfaces/Support/SupportFormV2.tsx": 1,
"components/interfaces/TableGridEditor/SidePanelEditor/ActionBar.tsx": 1,
"components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnDefaultValue.tsx": 2,
"components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx": 5,
Expand Down Expand Up @@ -548,7 +546,6 @@
"components/layouts/DocsLayout/DocsLayout.tsx": 2,
"components/layouts/IntegrationsLayout/Integrations.utils.ts": 2,
"components/layouts/LogsLayout/LogsSidebarMenuV2.tsx": 1,
"components/layouts/ObservabilityLayout/ObservabilityMenu.tsx": 1,
"components/layouts/ProjectLayout/BuildingState.tsx": 1,
"components/layouts/ProjectLayout/LayoutHeader/BreadcrumbsView.tsx": 2,
"components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx": 3,
Expand All @@ -571,7 +568,7 @@
"components/ui/AIEditor/index.tsx": 2,
"components/ui/CardButton.tsx": 2,
"components/ui/Charts/AreaChart.tsx": 1,
"components/ui/Charts/BarChart.tsx": 2,
"components/ui/Charts/BarChart.tsx": 1,
"components/ui/Charts/ChartHandler.tsx": 4,
"components/ui/Charts/ChartHeader.tsx": 5,
"components/ui/Charts/Charts.constants.ts": 1,
Expand Down Expand Up @@ -620,10 +617,8 @@
"data/analytics/functions-combined-stats-query.ts": 1,
"data/analytics/functions-req-stats-query.ts": 1,
"data/analytics/functions-resource-usage-query.ts": 1,
"data/analytics/infra-monitoring-queries.ts": 1,
"data/analytics/infra-monitoring-query.ts": 1,
"data/analytics/project-daily-stats-queries.ts": 1,
"data/analytics/project-metrics-query.ts": 3,
"data/api-settings/create-and-expose-api-schema-mutation.ts": 1,
"data/auth/auth-overview-query.ts": 1,
"data/auth/session-access-token-query.ts": 1,
Expand Down Expand Up @@ -665,7 +660,6 @@
"data/organizations/organization-customer-profile-update-mutation.ts": 2,
"data/organizations/organization-payment-method-default-mutation.ts": 2,
"data/organizations/organization-update-mutation.ts": 1,
"data/platform/platform-status-query.ts": 1,
"data/profile/profile-identities-query.ts": 1,
"data/profile/profile-unlink-identity-mutation.ts": 2,
"data/projects/project-create-mutation.ts": 1,
Expand Down Expand Up @@ -694,7 +688,6 @@
"data/tables/tables-query.ts": 1,
"data/views/views-query.ts": 1,
"hooks/analytics/useDbQuery.tsx": 1,
"hooks/analytics/useFillTimeseriesSorted.ts": 3,
"hooks/analytics/useLogsPreview.tsx": 1,
"hooks/analytics/useLogsQuery.tsx": 1,
"hooks/analytics/useLogsUrlState.ts": 1,
Expand All @@ -704,7 +697,6 @@
"hooks/misc/withAuth.tsx": 1,
"hooks/ui/useClickedOutside.ts": 2,
"hooks/ui/useFlag.ts": 1,
"hooks/ui/useHotKey.ts": 1,
"hooks/useStaticEffectEvent.ts": 1,
"instrumentation-client.ts": 5,
"lib/ai/generate-assistant-response.ts": 2,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { IS_PLATFORM, useFlag, useParams } from 'common'
import { LogDrainData, useLogDrainsQuery } from 'data/log-drains/log-drains-query'
import { DOCS_URL } from 'lib/constants'
import { useTrack } from 'lib/telemetry/track'
import { TrashIcon } from 'lucide-react'
import Link from 'next/link'
import { ReactNode, useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { z } from 'zod'

import { IS_PLATFORM, useFlag, useParams } from 'common'
import { LogDrainData, useLogDrainsQuery } from 'data/log-drains/log-drains-query'
import { DOCS_URL } from 'lib/constants'
import {
Button,
cn,
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormItem_Shadcn_,
FormLabel_Shadcn_,
FormMessage_Shadcn_,
Form_Shadcn_,
Input_Shadcn_,
RadioGroupCard,
RadioGroupCardItem,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectLabel_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
Select_Shadcn_,
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetSection,
SheetTitle,
Switch,
cn,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { InfoTooltip } from 'ui-patterns/info-tooltip'
import { z } from 'zod'

import { urlRegex } from '../Auth/Auth.constants'
import { DATADOG_REGIONS, LOG_DRAIN_TYPES, LogDrainType } from './LogDrains.constants'

Expand Down Expand Up @@ -100,16 +100,18 @@ const formUnion = z.discriminatedUnion('type', [
.int({ message: 'Batch timeout must be an integer' })
.min(1, { message: 'Batch timeout must be a positive integer' }),
}),
z.object({
type: z.literal('axiom'),
}),
z.object({
type: z.literal('sentry'),
dsn: z
.string()
.min(1, { message: 'Sentry DSN is required' })
.refine((dsn) => dsn.startsWith('https://'), 'Sentry DSN must start with https://'),
}),
z.object({
type: z.literal('axiom'),
api_token: z.string().min(1, { message: 'API token is required' }),
dataset_name: z.string().min(1, { message: 'Dataset name is required' }),
}),
])

const formSchema = z
Expand Down Expand Up @@ -180,6 +182,7 @@ export function LogDrainDestinationSheetForm({

const sentryEnabled = useFlag('SentryLogDrain')
const s3Enabled = useFlag('S3logdrain')
const axiomEnabled = useFlag('axiomLogDrain')

const { ref } = useParams()
const { data: logDrains } = useLogDrainsQuery({
Expand Down Expand Up @@ -210,6 +213,8 @@ export function LogDrainDestinationSheetForm({
access_key_id: defaultConfig?.access_key_id || '',
secret_access_key: defaultConfig?.secret_access_key || '',
batch_timeout: defaultConfig?.batch_timeout ?? 3000,
dataset_name: defaultConfig?.dataset_name || '',
api_token: defaultConfig?.api_token || '',
},
})

Expand Down Expand Up @@ -331,11 +336,12 @@ export function LogDrainDestinationSheetForm({
{LOG_DRAIN_TYPES.find((t) => t.value === type)?.name}
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
{LOG_DRAIN_TYPES.filter(
(t) =>
(t.value !== 'sentry' || sentryEnabled) &&
(t.value !== 's3' || s3Enabled)
).map((type) => (
{LOG_DRAIN_TYPES.filter((t) => {
if (t.value === 'sentry') return sentryEnabled
if (t.value === 's3') return s3Enabled
if (t.value === 'axiom') return axiomEnabled
return true
}).map((type) => (
<SelectItem_Shadcn_
value={type.value}
key={type.value}
Expand Down Expand Up @@ -566,6 +572,26 @@ export function LogDrainDestinationSheetForm({
</p>
</div>
)}
{type === 'axiom' && (
<div className="grid gap-4 px-content">
<LogDrainFormItem
type="text"
value="dataset_name"
label="Dataset name"
placeholder="dataset"
formControl={form.control}
description="Name of the dataset in Axiom where the logs will be sent."
/>
<LogDrainFormItem
type="text"
value="api_token"
label="API Token"
placeholder="xaat-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
formControl={form.control}
description="Token allowing ingest access to the specified dataset"
/>
</div>
)}
<FormMessage_Shadcn_ />
</div>
</form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { components } from 'api-types'
import { Datadog, Grafana, Sentry } from 'icons'
import { Axiom } from 'icons'
import { BracesIcon, Cloud } from 'lucide-react'

const iconProps = {
Expand Down Expand Up @@ -43,6 +44,13 @@ export const LOG_DRAIN_TYPES = [
'Sentry is an application monitoring service that helps developers identify and debug performance issues and errors',
icon: <Sentry {...iconProps} fill="currentColor" strokeWidth={0} />,
},
{
value: 'axiom',
name: 'Axiom',
description:
'Axiom is a data platform designed to efficiently collect, store, and analyze event and telemetry data at massive scale.',
icon: <Axiom {...iconProps} fill="currentColor" strokeWidth={0} />,
},
] as const

export const LOG_DRAIN_SOURCE_VALUES = LOG_DRAIN_TYPES.map((source) => source.value)
Expand Down
33 changes: 20 additions & 13 deletions apps/studio/components/interfaces/LogDrains/LogDrains.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { MoreHorizontal, Pencil, TrashIcon } from 'lucide-react'
import React, { useState } from 'react'
import { toast } from 'sonner'

import { IS_PLATFORM, useFlag, useParams } from 'common'
import AlertError from 'components/ui/AlertError'
import { useDeleteLogDrainMutation } from 'data/log-drains/delete-log-drain-mutation'
import { LogDrainData, useLogDrainsQuery } from 'data/log-drains/log-drains-query'
import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
import { useTrack } from 'lib/telemetry/track'
import { MoreHorizontal, Pencil, TrashIcon } from 'lucide-react'
import React, { cloneElement, useState } from 'react'
import { toast } from 'sonner'
import {
Button,
Card,
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
Expand All @@ -22,9 +20,11 @@ import {
TableHead,
TableHeader,
TableRow,
cn,
} from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'

import { LOG_DRAIN_TYPES, LogDrainType } from './LogDrains.constants'
import { LogDrainsCard } from './LogDrainsCard'
import { LogDrainsEmpty } from './LogDrainsEmpty'
Expand Down Expand Up @@ -57,6 +57,7 @@ export function LogDrains({
)
const sentryEnabled = useFlag('SentryLogDrain')
const s3Enabled = useFlag('S3logdrain')
const axiomEnabled = useFlag('axiomLogDrain')
const hasLogDrains = !!logDrains?.length

const { mutate: deleteLogDrain } = useDeleteLogDrainMutation({
Expand Down Expand Up @@ -91,9 +92,12 @@ export function LogDrains({
return (
<>
<div className="grid lg:grid-cols-2 gap-4">
{LOG_DRAIN_TYPES.filter(
(t) => (t.value !== 'sentry' || sentryEnabled) && (t.value !== 's3' || s3Enabled)
).map((src) => (
{LOG_DRAIN_TYPES.filter((t) => {
if (t.value === 'sentry') return sentryEnabled
if (t.value === 's3') return s3Enabled
if (t.value === 'axiom') return axiomEnabled
return true
}).map((src) => (
<LogDrainsCard
key={src.value}
title={src.name}
Expand Down Expand Up @@ -145,13 +149,16 @@ export function LogDrains({
</TableCell>
<TableCell className="text-foreground-light">
<div className="flex items-center gap-2">
{React.cloneElement(
LOG_DRAIN_TYPES.find((t) => t.value === drain.type)
?.icon as React.ReactElement,
{ width: 16, height: 16 }
{LOG_DRAIN_TYPES.find((t) => t.value === drain.type)?.icon && (
<span className="text-foreground-light">
{cloneElement(LOG_DRAIN_TYPES.find((t) => t.value === drain.type)!.icon, {
height: 16,
width: 16,
})}
</span>
)}
<span className="truncate max-w-40">
{LOG_DRAIN_TYPES.find((t) => t.value === drain.type)?.name}
{LOG_DRAIN_TYPES.find((t) => t.value === drain.type)?.name ?? drain.type}
</span>
</div>
</TableCell>
Expand Down
Loading
Loading