Skip to content

Commit

Permalink
feat: Add support for Sentry SDK v8
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilogorek committed May 20, 2024
1 parent 4580bb5 commit d2430c0
Show file tree
Hide file tree
Showing 23 changed files with 1,895 additions and 423 deletions.
96 changes: 86 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,47 @@ See [Showcase](#showcase) section for detailed screenshots of what is captured.
npm install @supabase/sentry-js-integration
```

## Usage
## Usage (Sentry SDK v8)

```js
import * as Sentry from "@sentry/browser";
import { SupabaseClient } from "@supabase/supabase-js";
import { supabaseIntegration } from "@supabase/sentry-js-integration";

Sentry.init({
dsn: SENTRY_DSN,
integrations: [
new supabaseIntegration(SupabaseClient, Sentry, {
tracing: true,
breadcrumbs: true,
errors: true,
}),
],
});
```

or

```js
import * as Sentry from "@sentry/browser";
import { createClient } from "@supabase/supabase-js";
import { supabaseIntegration } from "@supabase/sentry-js-integration";

const supabaseClient = createClient(SUPABASE_URL, SUPABASE_KEY);

Sentry.init({
dsn: SENTRY_DSN,
integrations: [
new supabaseIntegration(supabaseClient, Sentry, {
tracing: true,
breadcrumbs: true,
errors: true,
}),
],
});
```

## Usage (Sentry SDK v7)

```js
import * as Sentry from "@sentry/browser";
Expand Down Expand Up @@ -68,15 +108,15 @@ Sentry.init({

## Options

| Option | Description | Default |
| ------------- | ------------- | ------------- |
| `tracing` | Enable tracing instrumentation for database calls | **true** |
| `breadcrumbs` | Enable capturing breadcrumbs for database calls | **true** |
| `errors` | Enable capturing non-throwable database errors as Sentry exceptions | **false** |
| `operations` | Configures which methods should be instrumented for the features above | - |
| `sanitizeBody` | Allows for modifying captured body values that are passed to insert, upsert, and update operations, before assigned to a span, breadcrumb, or error | - |
| `shouldCreateSpan` | Decide whether a span should be created based on the query payload used to capture this data | - |
| `shouldCreateBreadcrumb` | Decide whether a breadcrumb should be created based on the query payload used to capture this data | - |
| Option | Description | Default |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| `tracing` | Enable tracing instrumentation for database calls | **true** |
| `breadcrumbs` | Enable capturing breadcrumbs for database calls | **true** |
| `errors` | Enable capturing non-throwable database errors as Sentry exceptions | **false** |
| `operations` | Configures which methods should be instrumented for the features above | - |
| `sanitizeBody` | Allows for modifying captured body values that are passed to insert, upsert, and update operations, before assigned to a span, breadcrumb, or error | - |
| `shouldCreateSpan` | Decide whether a span should be created based on the query payload used to capture this data | - |
| `shouldCreateBreadcrumb` | Decide whether a breadcrumb should be created based on the query payload used to capture this data | - |

See https://github.com/supabase-community/sentry-integration-js/blob/master/index.d.ts for detailed options types.

Expand Down Expand Up @@ -250,6 +290,42 @@ Afterward build your application (`npm run build`) and start it locally (`npm ru

</details>

## Developing

Run library unit tests:

```sh
npm install
npm run lint
npm run test
```

Run types tests for SDK v8:

```sh
cd tests-types/v8
npm install
npm run test
```

Run types tests for SDK v7:

```sh
cd tests-types/v7
npm install
npm run test
```

## Publishing

```sh
npm version <patch|minor|major>
git push
npm publish
```

Then bump release version + changelog in [https://github.com/supabase-community/sentry-integration-js/releases](https://github.com/supabase-community/sentry-integration-js/releases).

## Showcase

_(click to enlarge image)_
Expand Down
140 changes: 140 additions & 0 deletions common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
export function isPlainObject(wat) {
return Object.prototype.toString.call(wat) === "[object Object]";
}

export const DEFAULT_OPTIONS = {
tracing: true,
breadcrumbs: true,
errors: false,
operations: [...AVAILABLE_OPERATIONS],
shouldCreateSpan: undefined,
shouldCreateBreadcrumb: undefined,
sanitizeBody: undefined,
};

export function validateOption(availableOptions, key, value) {
if (!availableOptions.includes(key)) {
throw new Error(`Unknown option: ${key}`);
}

if (key === "operations") {
if (!Array.isArray(value)) {
throw new TypeError(`operations should be an array`);
}

for (const operation of value) {
if (!AVAILABLE_OPERATIONS.includes(operation)) {
throw new Error(`Unknown operation: ${operation}`);
}
}
}

if (key === "shouldCreateSpan" && typeof value !== "function") {
throw new TypeError(
"shouldCreateSpan should be a function that returns a boolean"
);
}

if (key === "shouldCreateBreadcrumb" && typeof value !== "function") {
throw new TypeError(
"shouldCreateBreadcrumb should be a function that returns a boolean"
);
}

if (key === "sanitizeBody" && typeof value !== "function") {
throw new TypeError(
"sanitizeBody should be a function that returns a valid data object"
);
}
}

export const AVAILABLE_OPERATIONS = [
"select",
"insert",
"upsert",
"update",
"delete",
];

export function extractOperation(method, headers = {}) {
switch (method) {
case "GET": {
return "select";
}
case "POST": {
if (headers["Prefer"]?.includes("resolution=")) {
return "upsert";
} else {
return "insert";
}
}
case "PATCH": {
return "update";
}
case "DELETE": {
return "delete";
}
}
}

export const FILTER_MAPPINGS = {
eq: "eq",
neq: "neq",
gt: "gt",
gte: "gte",
lt: "lt",
lte: "lte",
like: "like",
"like(all)": "likeAllOf",
"like(any)": "likeAnyOf",
ilike: "ilike",
"ilike(all)": "ilikeAllOf",
"ilike(any)": "ilikeAnyOf",
is: "is",
in: "in",
cs: "contains",
cd: "containedBy",
sr: "rangeGt",
nxl: "rangeGte",
sl: "rangeLt",
nxr: "rangeLte",
adj: "rangeAdjacent",
ov: "overlaps",
fts: "",
plfts: "plain",
phfts: "phrase",
wfts: "websearch",
not: "not",
};

export function translateFiltersIntoMethods(key, query) {
if (query === "" || query === "*") {
return `select(*)`;
}

if (key === "select") {
return `select(${query})`;
}

if (key === "or" || key.endsWith(".or")) {
return `${key}${query}`;
}

const [filter, ...value] = query.split(".");

let method;
// Handle optional `configPart` of the filter
if (filter.startsWith("fts")) {
method = "textSearch";
} else if (filter.startsWith("plfts")) {
method = "textSearch[plain]";
} else if (filter.startsWith("phfts")) {
method = "textSearch[phrase]";
} else if (filter.startsWith("wfts")) {
method = "textSearch[websearch]";
} else {
method = FILTER_MAPPINGS[filter] || "filter";
}

return `${method}(${key}, ${value.join(".")})`;
}
4 changes: 2 additions & 2 deletions example/index.html → example/index-v7.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<script type="importmap">
{
"imports": {
"@sentry/browser": "https://esm.sh/@sentry/browser@7.100.1",
"@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2.39.7",
"@sentry/browser": "https://esm.sh/@sentry/browser@7.115.0",
"@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2.43.2",
"@supabase/sentry-js-integration": "./integration.js"
}
}
Expand Down
68 changes: 68 additions & 0 deletions example/index-v8.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<script type="importmap">
{
"imports": {
"@sentry/browser": "https://esm.sh/@sentry/browser@8.2.1",
"@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2.43.2",
"@supabase/sentry-js-integration": "./integration.js"
}
}
</script>
<script type="module">
import * as Sentry from "@sentry/browser";
import { createClient } from "@supabase/supabase-js";
import { supabaseIntegration } from "@supabase/sentry-js-integration";

const supabaseClient = createClient(
"https://ktrpkblcwnulhtgpcrbp.supabase.co",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt0cnBrYmxjd251bGh0Z3BjcmJwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDY3ODk0OTAsImV4cCI6MjAyMjM2NTQ5MH0.CXEvu4tYFRXtwYvMFllhAwyu1NsyUmaqhgxfHFhzOHA"
);

Sentry.init({
dsn: "https://dsn@sentry.io/1337",
tracesSampleRate: 1.0,
integrations: [
Sentry.browserTracingIntegration(),
supabaseIntegration(supabaseClient, Sentry, {
tracing: true,
breadcrumbs: true,
errors: true,
}),
],
beforeSend(event) {
console.log("Captured exception:", event);
return null; // Do not send captured exception
},
beforeSendTransaction(event) {
console.log("Captured transaction:", event);
return null; // Do not send transaction
},
});

// Capture some trace and breadcrumbs via `tracing: true` and `breadcrumbs: true`
{
const { data, error } = await supabaseClient.from("jokes").select("*");
console.log("jokes response:");
console.log({ data });
console.log({ error });
}

// Capture broken calls as exceptions
{
const { data, error } = await supabaseClient
.from("unknown-table")
.select("*");
console.log("unknown-table response:");
console.log({ data });
console.log({ error });
}

Sentry.captureException(new Error("show me the money"));
</script>
</head>
<body>
<h1>Hello there! Open DevTools to see what's going on.</h1>
</body>
</html>
Loading

0 comments on commit d2430c0

Please sign in to comment.