Skip to content

add sentry support to backend and webapp #223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 2, 2025
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
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" |
Expand All @@ -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
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand Down Expand Up @@ -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;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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;
}
Expand All @@ -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`);
Expand Down Expand Up @@ -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`);
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -48,6 +51,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line number Diff line number Diff line change
@@ -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,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down
Loading