mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 04:15:30 +00:00
add sentry support to backend and webapp (#223)
* add sentry to web app * set sentry environemnt from env var * add sentry env replace logic in docker container * wip add backend sentry * add sentry to backend * move dns to env var * remove test exception
This commit is contained in:
parent
85c21a2519
commit
a93ee6527c
25 changed files with 1180 additions and 32 deletions
|
|
@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
|
|||
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
|
||||
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
|
||||
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
||||
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
||||
|
||||
# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
|
||||
# causing regular expressions parsing errors when making a request. It's unclear
|
||||
|
|
|
|||
|
|
@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
|
|||
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"
|
||||
|
||||
# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
||||
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"
|
||||
|
||||
# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
||||
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"
|
||||
|
||||
# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
|
||||
# with their actual desired runtime value.
|
||||
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
|
||||
|
|
@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
|
|||
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
|
||||
done
|
||||
}
|
||||
|
||||
|
|
|
|||
4
packages/backend/.gitignore
vendored
4
packages/backend/.gitignore
vendored
|
|
@ -1,2 +1,4 @@
|
|||
dist/
|
||||
!.env
|
||||
!.env
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@
|
|||
"scripts": {
|
||||
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
|
||||
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
|
||||
"build": "tsc",
|
||||
"test": "vitest --config ./vitest.config.ts"
|
||||
"build": "tsc && yarn sentry:sourcemaps",
|
||||
"test": "vitest --config ./vitest.config.ts",
|
||||
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/argparse": "^2.0.16",
|
||||
|
|
@ -23,6 +24,9 @@
|
|||
"dependencies": {
|
||||
"@gitbeaker/rest": "^40.5.1",
|
||||
"@octokit/rest": "^21.0.2",
|
||||
"@sentry/cli": "^2.42.2",
|
||||
"@sentry/node": "^9.3.0",
|
||||
"@sentry/profiling-node": "^9.3.0",
|
||||
"@sourcebot/crypto": "^0.1.0",
|
||||
"@sourcebot/db": "^0.1.0",
|
||||
"@sourcebot/error": "^0.1.0",
|
||||
|
|
@ -44,4 +48,4 @@
|
|||
"strip-json-comments": "^5.0.1",
|
||||
"winston": "^3.15.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
|
|||
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
|
||||
import { BackendError, BackendException } from "@sourcebot/error";
|
||||
import { captureEvent } from "./posthog.js";
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
interface IConnectionManager {
|
||||
scheduleConnectionSync: (connection: Connection) => Promise<void>;
|
||||
|
|
@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
|
|||
});
|
||||
|
||||
if (!connection) {
|
||||
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
|
||||
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
|
||||
message: `Connection ${job.data.connectionId} not found`,
|
||||
});
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Reset the syncStatusMetadata to an empty object at the start of the sync job
|
||||
|
|
@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
|
|||
})();
|
||||
} catch (err) {
|
||||
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
|
||||
Sentry.captureException(err);
|
||||
|
||||
if (err instanceof BackendException) {
|
||||
throw err;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import * as Sentry from "@sentry/node";
|
||||
|
||||
type ValidResult<T> = {
|
||||
type: 'valid';
|
||||
data: T[];
|
||||
|
|
@ -39,6 +41,7 @@ export function processPromiseResults<T>(
|
|||
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
|
||||
const failedResult = results.find(result => result.status === 'rejected');
|
||||
if (failedResult) {
|
||||
Sentry.captureException(failedResult.reason);
|
||||
throw failedResult.reason;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import dotenv from 'dotenv';
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
|
||||
if (required && !env && !defaultValue) {
|
||||
throw new Error(`Missing required environment variable: ${env}`);
|
||||
const e = new Error(`Missing required environment variable: ${env}`);
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return env ?? defaultValue;
|
||||
|
|
@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);
|
|||
|
||||
export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
|
||||
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;
|
||||
|
||||
export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
|
||||
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import micromatch from "micromatch";
|
|||
import { measure, fetchWithRetry } from './utils.js';
|
||||
import { BackendError } from '@sourcebot/error';
|
||||
import { BackendException } from '@sourcebot/error';
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
// https://gerrit-review.googlesource.com/Documentation/rest-api.html
|
||||
interface GerritProjects {
|
||||
|
|
@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
|
|||
const fetchFn = () => fetchAllProjects(url);
|
||||
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
if (err instanceof BackendException) {
|
||||
throw err;
|
||||
}
|
||||
|
|
@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
|
|||
});
|
||||
|
||||
if (!projects) {
|
||||
throw new Error(`Failed to fetch projects from ${url}`);
|
||||
const e = new Error(`Failed to fetch projects from ${url}`);
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// exclude "All-Projects" and "All-Users" projects
|
||||
|
|
@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
|
|||
response = await fetch(endpointWithParams);
|
||||
if (!response.ok) {
|
||||
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
|
||||
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
|
||||
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
|
||||
status: response.status,
|
||||
});
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
if (err instanceof BackendException) {
|
||||
throw err;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import micromatch from 'micromatch';
|
|||
import { PrismaClient } from '@sourcebot/db';
|
||||
import { FALLBACK_GITEA_TOKEN } from './environment.js';
|
||||
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
const logger = createLogger('Gitea');
|
||||
|
||||
export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
|
||||
|
|
@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
|
|||
data
|
||||
};
|
||||
} catch (e: any) {
|
||||
Sentry.captureException(e);
|
||||
|
||||
if (e?.status === 404) {
|
||||
logger.error(`User ${user} not found or no access`);
|
||||
return {
|
||||
|
|
@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
|
|||
data
|
||||
};
|
||||
} catch (e: any) {
|
||||
Sentry.captureException(e);
|
||||
|
||||
if (e?.status === 404) {
|
||||
logger.error(`Organization ${org} not found or no access`);
|
||||
return {
|
||||
|
|
@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
|
|||
data: [response.data]
|
||||
};
|
||||
} catch (e: any) {
|
||||
Sentry.captureException(e);
|
||||
|
||||
if (e?.status === 404) {
|
||||
logger.error(`Repository ${repo} not found or no access`);
|
||||
return {
|
||||
|
|
@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],
|
|||
|
||||
const totalCountString = result.headers.get('x-total-count');
|
||||
if (!totalCountString) {
|
||||
throw new Error("Header 'x-total-count' not found");
|
||||
const e = new Error("Header 'x-total-count' not found");
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
const totalCount = parseInt(totalCountString);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
|
|||
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
|
||||
import { BackendException, BackendError } from "@sourcebot/error";
|
||||
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
const logger = createLogger("GitHub");
|
||||
|
||||
export type OctokitRepository = {
|
||||
|
|
@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
|
|||
try {
|
||||
await octokit.rest.users.getAuthenticated();
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
|
||||
if (isHttpError(error, 401)) {
|
||||
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
|
||||
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
|
||||
secretKey,
|
||||
});
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
|
||||
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
|
||||
message: `Failed to authenticate with GitHub`,
|
||||
});
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
|
|||
data
|
||||
};
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
|
||||
if (isHttpError(error, 404)) {
|
||||
logger.error(`User ${user} not found or no access`);
|
||||
return {
|
||||
|
|
@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
|
|||
data
|
||||
};
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
|
||||
if (isHttpError(error, 404)) {
|
||||
logger.error(`Organization ${org} not found or no access`);
|
||||
return {
|
||||
|
|
@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
|
|||
};
|
||||
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
|
||||
if (isHttpError(error, 404)) {
|
||||
logger.error(`Repository ${repo} not found or no access`);
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
|
|||
import { PrismaClient } from "@sourcebot/db";
|
||||
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
|
||||
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
const logger = createLogger("GitLab");
|
||||
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";
|
||||
|
||||
|
|
@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
|
|||
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
|
||||
allRepos = allRepos.concat(_projects);
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
|
||||
throw e;
|
||||
}
|
||||
|
|
@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
|
|||
data
|
||||
};
|
||||
} catch (e: any) {
|
||||
Sentry.captureException(e);
|
||||
|
||||
const status = e?.cause?.response?.status;
|
||||
if (status === 404) {
|
||||
logger.error(`Group ${group} not found or no access`);
|
||||
|
|
@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
|
|||
data
|
||||
};
|
||||
} catch (e: any) {
|
||||
Sentry.captureException(e);
|
||||
|
||||
const status = e?.cause?.response?.status;
|
||||
if (status === 404) {
|
||||
logger.error(`User ${user} not found or no access`);
|
||||
|
|
@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
|
|||
data: [data]
|
||||
};
|
||||
} catch (e: any) {
|
||||
Sentry.captureException(e);
|
||||
|
||||
const status = e?.cause?.response?.status;
|
||||
|
||||
if (status === 404) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import "./instrument.js";
|
||||
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { ArgumentParser } from "argparse";
|
||||
import { existsSync } from 'fs';
|
||||
import { mkdir } from 'fs/promises';
|
||||
|
|
@ -48,6 +51,8 @@ main(prisma, context)
|
|||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
Sentry.captureException(e);
|
||||
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
})
|
||||
|
|
|
|||
8
packages/backend/src/instrument.ts
Normal file
8
packages/backend/src/instrument.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import * as Sentry from "@sentry/node";
|
||||
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_BACKEND_DSN,
|
||||
release: SOURCEBOT_VERSION,
|
||||
environment: SENTRY_ENVIRONMENT,
|
||||
});
|
||||
|
|
@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
|
|||
import { indexGitRepository } from "./zoekt.js";
|
||||
import os from 'os';
|
||||
import { PromClient } from './promClient.js';
|
||||
|
||||
import * as Sentry from "@sentry/node";
|
||||
interface IRepoManager {
|
||||
blockingPollLoop: () => void;
|
||||
dispose: () => void;
|
||||
|
|
@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
|
|||
});
|
||||
if (!existingRepo) {
|
||||
this.logger.error(`Repo ${repo.id} not found`);
|
||||
throw new Error(`Repo ${repo.id} not found`);
|
||||
const e = new Error(`Repo ${repo.id} not found`);
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;
|
||||
|
||||
|
|
@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
|
|||
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
|
||||
break;
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
|
||||
attempts++;
|
||||
this.promClient.repoIndexingReattemptsTotal.inc();
|
||||
if (attempts === maxAttempts) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { PrismaClient, Repo } from "@sourcebot/db";
|
|||
import { decrypt } from "@sourcebot/crypto";
|
||||
import { Token } from "@sourcebot/schemas/v3/shared.type";
|
||||
import { BackendException, BackendError } from "@sourcebot/error";
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
export const measure = async <T>(cb: () => Promise<T>) => {
|
||||
const start = Date.now();
|
||||
|
|
@ -22,9 +23,11 @@ export const marshalBool = (value?: boolean) => {
|
|||
|
||||
export const getTokenFromConfig = async (token: Token, orgId: number, db?: PrismaClient) => {
|
||||
if (!db) {
|
||||
throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
|
||||
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
|
||||
message: `No database connection provided.`,
|
||||
});
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const secretKey = token.secret;
|
||||
|
|
@ -38,9 +41,11 @@ export const getTokenFromConfig = async (token: Token, orgId: number, db?: Prism
|
|||
});
|
||||
|
||||
if (!secret) {
|
||||
throw new BackendException(BackendError.CONNECTION_SYNC_SECRET_DNE, {
|
||||
const e = new BackendException(BackendError.CONNECTION_SYNC_SECRET_DNE, {
|
||||
message: `Secret with key ${secretKey} not found for org ${orgId}`,
|
||||
});
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const decryptedSecret = decrypt(secret.iv, secret.encryptedValue);
|
||||
|
|
@ -104,6 +109,8 @@ export const fetchWithRetry = async <T>(
|
|||
try {
|
||||
return await fetchFn();
|
||||
} catch (e: any) {
|
||||
Sentry.captureException(e);
|
||||
|
||||
attempts++;
|
||||
if ((e.status === 403 || e.status === 429 || e.status === 443) && attempts < maxAttempts) {
|
||||
const computedWaitTime = 3000 * Math.pow(2, attempts - 1);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,12 @@
|
|||
"lib": ["ES2023"],
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true
|
||||
"inlineSources": true,
|
||||
|
||||
// Set `sourceRoot` to "/" to strip the build path prefix
|
||||
// from generated source code references.
|
||||
// This improves issue grouping in Sentry.
|
||||
"sourceRoot": "/"
|
||||
},
|
||||
"include": ["src/index.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
|
|
|||
4
packages/web/.gitignore
vendored
4
packages/web/.gitignore
vendored
|
|
@ -39,4 +39,6 @@ next-env.d.ts
|
|||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/nextjs
|
||||
|
||||
!.env
|
||||
!.env
|
||||
# Sentry Config File
|
||||
.env.sentry-build-plugin
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import {withSentryConfig} from "@sentry/nextjs";
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
|
|
@ -41,4 +42,39 @@ const nextConfig = {
|
|||
// } : {})
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
export default withSentryConfig(nextConfig, {
|
||||
// For all available options, see:
|
||||
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
|
||||
|
||||
org: "sourcebot",
|
||||
project: "webapp",
|
||||
|
||||
// Only print logs for uploading source maps in CI
|
||||
silent: !process.env.CI,
|
||||
|
||||
// For all available options, see:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
|
||||
// Automatically annotate React components to show their full name in breadcrumbs and session replay
|
||||
reactComponentAnnotation: {
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
|
||||
// This can increase your server load as well as your hosting bill.
|
||||
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
|
||||
// side errors will fail.
|
||||
tunnelRoute: "/monitoring",
|
||||
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
|
||||
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
|
||||
// See the following for more information:
|
||||
// https://docs.sentry.io/product/crons/
|
||||
// https://vercel.com/docs/cron-jobs
|
||||
automaticVercelMonitors: true,
|
||||
});
|
||||
|
|
@ -66,6 +66,7 @@
|
|||
"@replit/codemirror-lang-solidity": "^6.0.2",
|
||||
"@replit/codemirror-lang-svelte": "^6.0.0",
|
||||
"@replit/codemirror-vim": "^6.2.1",
|
||||
"@sentry/nextjs": "^9",
|
||||
"@shopify/lang-jsonc": "^1.0.0",
|
||||
"@sourcebot/crypto": "^0.1.0",
|
||||
"@sourcebot/db": "^0.1.0",
|
||||
|
|
|
|||
13
packages/web/sentry.client.config.ts
Normal file
13
packages/web/sentry.client.config.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// This file configures the initialization of Sentry on the client.
|
||||
// The config you add here will be used whenever a users loads a page in their browser.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
|
||||
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || 'unknown',
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
});
|
||||
14
packages/web/sentry.edge.config.ts
Normal file
14
packages/web/sentry.edge.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
|
||||
// The config you add here will be used whenever one of the edge features is loaded.
|
||||
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
|
||||
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || 'unknown',
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
});
|
||||
13
packages/web/sentry.server.config.ts
Normal file
13
packages/web/sentry.server.config.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// This file configures the initialization of Sentry on the server.
|
||||
// The config you add here will be used whenever the server handles a request.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
|
||||
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || 'unknown',
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
});
|
||||
23
packages/web/src/app/global-error.tsx
Normal file
23
packages/web/src/app/global-error.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"use client";
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import NextError from "next/error";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function GlobalError({ error }: { error: Error & { digest?: string } }) {
|
||||
useEffect(() => {
|
||||
Sentry.captureException(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
{/* `NextError` is the default Next.js error page component. Its type
|
||||
definition requires a `statusCode` prop. However, since the App Router
|
||||
does not expose status codes for errors, we simply pass 0 to render a
|
||||
generic error message. */}
|
||||
<NextError statusCode={0} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
13
packages/web/src/instrumentation.ts
Normal file
13
packages/web/src/instrumentation.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||
await import('../sentry.server.config');
|
||||
}
|
||||
|
||||
if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
await import('../sentry.edge.config');
|
||||
}
|
||||
}
|
||||
|
||||
export const onRequestError = Sentry.captureRequestError;
|
||||
Loading…
Reference in a new issue