Skip to content

Commit a93ee65

Browse files
authored
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
1 parent 85c21a2 commit a93ee65

25 files changed

+1180
-32
lines changed

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
4848
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
4949
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
5050
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
51+
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
52+
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN
5153

5254
# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
5355
# causing regular expressions parsing errors when making a request. It's unclear

entrypoint.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
123123
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
124124
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"
125125

126+
# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
127+
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"
128+
129+
# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
130+
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"
131+
126132
# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
127133
# with their actual desired runtime value.
128134
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
131137
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
132138
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
133139
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
140+
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
141+
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
134142
done
135143
}
136144

packages/backend/.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
dist/
2-
!.env
2+
!.env
3+
# Sentry Config File
4+
.sentryclirc

packages/backend/package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
"scripts": {
88
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
99
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
10-
"build": "tsc",
11-
"test": "vitest --config ./vitest.config.ts"
10+
"build": "tsc && yarn sentry:sourcemaps",
11+
"test": "vitest --config ./vitest.config.ts",
12+
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
1213
},
1314
"devDependencies": {
1415
"@types/argparse": "^2.0.16",
@@ -23,6 +24,9 @@
2324
"dependencies": {
2425
"@gitbeaker/rest": "^40.5.1",
2526
"@octokit/rest": "^21.0.2",
27+
"@sentry/cli": "^2.42.2",
28+
"@sentry/node": "^9.3.0",
29+
"@sentry/profiling-node": "^9.3.0",
2630
"@sourcebot/crypto": "^0.1.0",
2731
"@sourcebot/db": "^0.1.0",
2832
"@sourcebot/error": "^0.1.0",
@@ -44,4 +48,4 @@
4448
"strip-json-comments": "^5.0.1",
4549
"winston": "^3.15.0"
4650
}
47-
}
51+
}

packages/backend/src/connectionManager.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
88
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
99
import { BackendError, BackendException } from "@sourcebot/error";
1010
import { captureEvent } from "./posthog.js";
11+
import * as Sentry from "@sentry/node";
1112

1213
interface IConnectionManager {
1314
scheduleConnectionSync: (connection: Connection) => Promise<void>;
@@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
9495
});
9596

9697
if (!connection) {
97-
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
98+
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
9899
message: `Connection ${job.data.connectionId} not found`,
99100
});
101+
Sentry.captureException(e);
102+
throw e;
100103
}
101104

102105
// Reset the syncStatusMetadata to an empty object at the start of the sync job
@@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
146149
})();
147150
} catch (err) {
148151
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
152+
Sentry.captureException(err);
153+
149154
if (err instanceof BackendException) {
150155
throw err;
151156
} else {

packages/backend/src/connectionUtils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as Sentry from "@sentry/node";
2+
13
type ValidResult<T> = {
24
type: 'valid';
35
data: T[];
@@ -39,6 +41,7 @@ export function processPromiseResults<T>(
3941
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
4042
const failedResult = results.find(result => result.status === 'rejected');
4143
if (failedResult) {
44+
Sentry.captureException(failedResult.reason);
4245
throw failedResult.reason;
4346
}
4447
}

packages/backend/src/environment.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import dotenv from 'dotenv';
2+
import * as Sentry from "@sentry/node";
23

34
export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
45
if (required && !env && !defaultValue) {
5-
throw new Error(`Missing required environment variable: ${env}`);
6+
const e = new Error(`Missing required environment variable: ${env}`);
7+
Sentry.captureException(e);
8+
throw e;
69
}
710

811
return env ?? defaultValue;
@@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);
3740

3841
export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
3942
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;
43+
44+
export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
45+
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;

packages/backend/src/gerrit.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import micromatch from "micromatch";
55
import { measure, fetchWithRetry } from './utils.js';
66
import { BackendError } from '@sourcebot/error';
77
import { BackendException } from '@sourcebot/error';
8+
import * as Sentry from "@sentry/node";
89

910
// https://gerrit-review.googlesource.com/Documentation/rest-api.html
1011
interface GerritProjects {
@@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
4041
const fetchFn = () => fetchAllProjects(url);
4142
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
4243
} catch (err) {
44+
Sentry.captureException(err);
4345
if (err instanceof BackendException) {
4446
throw err;
4547
}
@@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
5052
});
5153

5254
if (!projects) {
53-
throw new Error(`Failed to fetch projects from ${url}`);
55+
const e = new Error(`Failed to fetch projects from ${url}`);
56+
Sentry.captureException(e);
57+
throw e;
5458
}
5559

5660
// exclude "All-Projects" and "All-Users" projects
@@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
8993
response = await fetch(endpointWithParams);
9094
if (!response.ok) {
9195
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
92-
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
96+
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
9397
status: response.status,
9498
});
99+
Sentry.captureException(e);
100+
throw e;
95101
}
96102
} catch (err) {
103+
Sentry.captureException(err);
97104
if (err instanceof BackendException) {
98105
throw err;
99106
}

packages/backend/src/gitea.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import micromatch from 'micromatch';
77
import { PrismaClient } from '@sourcebot/db';
88
import { FALLBACK_GITEA_TOKEN } from './environment.js';
99
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
10+
import * as Sentry from "@sentry/node";
11+
1012
const logger = createLogger('Gitea');
1113

1214
export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
@@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
132134
data
133135
};
134136
} catch (e: any) {
137+
Sentry.captureException(e);
138+
135139
if (e?.status === 404) {
136140
logger.error(`User ${user} not found or no access`);
137141
return {
@@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
170174
data
171175
};
172176
} catch (e: any) {
177+
Sentry.captureException(e);
178+
173179
if (e?.status === 404) {
174180
logger.error(`Organization ${org} not found or no access`);
175181
return {
@@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
206212
data: [response.data]
207213
};
208214
} catch (e: any) {
215+
Sentry.captureException(e);
216+
209217
if (e?.status === 404) {
210218
logger.error(`Repository ${repo} not found or no access`);
211219
return {
@@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],
234242

235243
const totalCountString = result.headers.get('x-total-count');
236244
if (!totalCountString) {
237-
throw new Error("Header 'x-total-count' not found");
245+
const e = new Error("Header 'x-total-count' not found");
246+
Sentry.captureException(e);
247+
throw e;
238248
}
239249
const totalCount = parseInt(totalCountString);
240250

packages/backend/src/github.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
77
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
88
import { BackendException, BackendError } from "@sourcebot/error";
99
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
10+
import * as Sentry from "@sentry/node";
11+
1012
const logger = createLogger("GitHub");
1113

1214
export type OctokitRepository = {
@@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
5355
try {
5456
await octokit.rest.users.getAuthenticated();
5557
} catch (error) {
58+
Sentry.captureException(error);
59+
5660
if (isHttpError(error, 401)) {
57-
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
61+
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
5862
secretKey,
5963
});
64+
Sentry.captureException(e);
65+
throw e;
6066
}
6167

62-
throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
68+
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
6369
message: `Failed to authenticate with GitHub`,
6470
});
71+
Sentry.captureException(e);
72+
throw e;
6573
}
6674
}
6775

@@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
239247
data
240248
};
241249
} catch (error) {
250+
Sentry.captureException(error);
251+
242252
if (isHttpError(error, 404)) {
243253
logger.error(`User ${user} not found or no access`);
244254
return {
@@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
282292
data
283293
};
284294
} catch (error) {
295+
Sentry.captureException(error);
296+
285297
if (isHttpError(error, 404)) {
286298
logger.error(`Organization ${org} not found or no access`);
287299
return {
@@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
327339
};
328340

329341
} catch (error) {
342+
Sentry.captureException(error);
343+
330344
if (isHttpError(error, 404)) {
331345
logger.error(`Repository ${repo} not found or no access`);
332346
return {

0 commit comments

Comments
 (0)