apps/docs/content/guides/monitoring-and-debugging/log-drains.mdx
Log drains send all logs of the Supabase stack to one or more desired destinations. It is only available for customers on Pro, Team and Enterprise Plans. Log drains are available in the dashboard under Project Settings > Log Drains.
HTTP destinations receive logs as batched POST requests with a maximum of 250 events or 1-second intervals, whichever comes first.
Logs are delivered as a JSON array via HTTP POST. Both HTTP/1 and HTTP/2 are supported. Custom headers can be added to every request for authentication or routing.
Required configuration:
http:// or https://)HTTP/1 or HTTP/2Requests to custom endpoints are currently unsigned. Signed requests are coming in a future release.
</Admonition> <Accordion type="default"> <AccordionItem header="Edge Function walkthrough (uncompressed)" id="uncompressed">supabase functions new log-receiver
Update the function body to log the incoming payload:
import 'npm:@supabase/functions-js/edge-runtime.d.ts'
Deno.serve(async (req) => {
const data = await req.json()
console.log(`Received ${data.length} logs, first log:\n ${JSON.stringify(data[0])}`)
return new Response(JSON.stringify({ message: 'ok' }), {
headers: { 'Content-Type': 'application/json' },
})
})
Deploy it:
supabase functions deploy log-receiver --project-ref [PROJECT REF]
Deploying an Edge Function as a log drain target will create a feedback loop — each drain event generates a new Edge Function log, which triggers another drain event. The batching behavior limits how fast this escalates, but it will run continuously.
</Admonition>https://[PROJECT REF].supabase.co/functions/v1/log-receiver.Authorization: Bearer [PUBLISHABLE KEY].Gzip payloads can be decompressed using Node-compatible built-in APIs. See the Edge Function compression guide for more details.
import { gunzipSync } from 'node:zlib'
Deno.serve(async (req) => {
try {
const contentEncoding = req.headers.get('content-encoding')
if (contentEncoding !== 'gzip') {
return new Response('Request body is not gzip compressed', { status: 400 })
}
const compressedBody = await req.arrayBuffer()
const decompressedBody = gunzipSync(new Uint8Array(compressedBody))
const data = JSON.parse(new TextDecoder().decode(decompressedBody))
console.log(`Received: ${data.length} logs.`)
return new Response('ok', { headers: { 'Content-Type': 'text/plain' } })
} catch (error) {
console.error('Error:', error)
return new Response('Error processing request', { status: 500 })
}
})
Logs are sent to any OTLP-compatible endpoint using the OpenTelemetry Protocol over HTTP with Protocol Buffers encoding, following the OpenTelemetry Logs specification.
Required configuration:
/v1/logs)http/protobuf (the only supported protocol)Your OTLP endpoint must accept logs at the /v1/logs path with application/x-protobuf content type.
Compatible platforms include OpenTelemetry Collector, Grafana Cloud, New Relic, Honeycomb, Datadog (OTLP ingestion), Elastic, and any other OTLP-compatible observability tool.
<Accordion type="default"> <AccordionItem header="OpenTelemetry Collector example" id="otel-collector">Configure an OTLP HTTP receiver in your Collector config:
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
processors:
batch:
exporters:
logging:
loglevel: debug
service:
pipelines:
logs:
receivers: [otlp]
processors: [batch]
exporters: [logging]
Then create a log drain in Project Settings > Log Drains with the endpoint set to https://your-collector:4318/v1/logs.
Different OTLP platforms use different authentication methods. Add the appropriate header to your drain configuration:
API Key:
X-API-Key: your-api-key
Bearer Token:
Authorization: Bearer your-token
Basic Auth:
Authorization: Basic base64(username:password)
Logs are batched and sent to Datadog with Gzip compression. Each event's log source is mapped to the service field, and the source is set to Supabase. The payload message is a JSON string of the raw log event, prefixed with the event timestamp.
Required configuration:
Steps:
Grok parser — extract the timestamp into a date field:
%{date("yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZ"):date}
Grok parser — convert stringified JSON to structured JSON on the json field:
%{data::json}
Remapper — set the log level:
metadata.parsed.error_severity, metadata.level
Logs are formatted and sent to the Loki HTTP push API. The log source and product name are used as stream labels. The event_message and timestamp fields are dropped from events to avoid duplicate data. Events are batched with a maximum of 250 events per request.
Required configuration:
https://my-logs.grafana.net/loki/api/v1/push)Loki must be configured to accept structured metadata. Increase the default maximum number of structured metadata fields to at least 500 to accommodate large log event payloads across different Supabase products.
</Admonition>See the official Loki HTTP API documentation for more details on the push API format.
Logs are written as batched files to an existing S3 bucket that you own.
Required configuration:
The AWS account tied to the Access Key ID must have write permissions on the specified S3 bucket.
</Admonition>Logs are sent to Sentry's Logging product. All log event fields are attached as Sentry log attributes, which can be used for filtering and grouping. There are no cardinality limits on the number of attributes.
Required configuration:
{PROTOCOL}://{PUBLIC_KEY}@{HOST}/{PROJECT_ID}Steps:
Ingesting Supabase logs as Sentry errors is not supported. If you are self-hosting Sentry, Sentry Logs requires self-hosted version 25.9.0 or later.
</Admonition>Logs are sent to an Axiom dataset as JSON, with the timestamp adjusted for Axiom's ingestion format.
Required configuration:
Steps:
Logs are sent to Last9 using its OpenTelemetry-native ingestion endpoint. Credentials are obtained from the Last9 OTEL integration panel.
Required configuration:
Steps:
Logs are forwarded to a remote Syslog receiver using TCP or TLS, adhering to RFC 5424.
Required configuration:
Optional configuration:
[exampleSDID@32473 iut="3"])TLS-only options: