diff --git a/apps/api/package.json b/apps/api/package.json index 4bc7f35..f6e7086 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -10,6 +10,7 @@ "@boring.tools/logger": "workspace:*", "@boring.tools/schema": "workspace:*", "@hono/clerk-auth": "^2.0.0", + "@hono/sentry": "^1.2.0", "@hono/zod-openapi": "^0.16.2", "@scalar/hono-api-reference": "^0.5.149", "hono": "^4.6.3", diff --git a/apps/api/src/changelog/byId.ts b/apps/api/src/changelog/byId.ts index 4b48a7e..eed24b8 100644 --- a/apps/api/src/changelog/byId.ts +++ b/apps/api/src/changelog/byId.ts @@ -43,7 +43,6 @@ export const func = async ({ userId, id }: { userId: string; id: string }) => { if (!result) { throw new HTTPException(404, { message: 'Not found' }) } - return result } diff --git a/apps/api/src/changelog/index.ts b/apps/api/src/changelog/index.ts index ad3a117..ccfb1d6 100644 --- a/apps/api/src/changelog/index.ts +++ b/apps/api/src/changelog/index.ts @@ -1,8 +1,7 @@ -import { logger } from '@boring.tools/logger' import { OpenAPIHono } from '@hono/zod-openapi' -import { HTTPException } from 'hono/http-exception' import type { Variables } from '..' import { verifyAuthentication } from '../utils/authentication' +import { type ContextModule, captureSentry } from '../utils/sentry' import ById from './byId' import Create from './create' import Delete from './delete' @@ -11,7 +10,9 @@ import Update from './update' const app = new OpenAPIHono<{ Variables: Variables }>() -const changelog_logger = logger.child({ name: 'changelog' }) +const module: ContextModule = { + name: 'changelog', +} app.openapi(ById.route, async (c) => { const userId = verifyAuthentication(c) @@ -20,11 +21,14 @@ app.openapi(ById.route, async (c) => { const result = await ById.func({ userId, id }) return c.json(result, 200) } catch (error) { - changelog_logger.error(error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user: { + id: userId, + }, + }) } }) @@ -34,11 +38,14 @@ app.openapi(List.route, async (c) => { const result = await List.func({ userId }) return c.json(result, 200) } catch (error) { - changelog_logger.error(error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user: { + id: userId, + }, + }) } }) @@ -52,11 +59,14 @@ app.openapi(Create.route, async (c) => { }) return c.json(result, 201) } catch (error) { - changelog_logger.error(error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user: { + id: userId, + }, + }) } }) @@ -73,11 +83,14 @@ app.openapi(Delete.route, async (c) => { return c.json({ message: 'Changelog removed' }) } catch (error) { - changelog_logger.error(error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user: { + id: userId, + }, + }) } }) @@ -103,11 +116,14 @@ app.openapi(Update.route, async (c) => { return c.json(result) } catch (error) { - changelog_logger.error(error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user: { + id: userId, + }, + }) } }) diff --git a/apps/api/src/changelog/version/index.ts b/apps/api/src/changelog/version/index.ts index 19c3cf5..b73587d 100644 --- a/apps/api/src/changelog/version/index.ts +++ b/apps/api/src/changelog/version/index.ts @@ -1,8 +1,7 @@ -import { logger } from '@boring.tools/logger' import { OpenAPIHono } from '@hono/zod-openapi' -import { HTTPException } from 'hono/http-exception' import type { Variables } from '../..' import { verifyAuthentication } from '../../utils/authentication' +import { type ContextModule, captureSentry } from '../../utils/sentry' import { byId, byIdFunc } from './byId' import { create, createFunc } from './create' import { remove, removeFunc } from './delete' @@ -10,7 +9,10 @@ import { update, updateFunc } from './update' const app = new OpenAPIHono<{ Variables: Variables }>() -const version_logger = logger.child({ name: 'changelog_version' }) +const module: ContextModule = { + name: 'changelog', + sub_module: 'version', +} app.openapi(create, async (c) => { const userId = verifyAuthentication(c) @@ -24,11 +26,14 @@ app.openapi(create, async (c) => { return c.json(result, 201) } catch (error) { - version_logger.error(error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user: { + id: userId, + }, + }) } }) @@ -56,11 +61,14 @@ app.openapi(byId, async (c) => { 200, ) } catch (error) { - version_logger.error(error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user: { + id: userId, + }, + }) } }) @@ -81,17 +89,20 @@ app.openapi(update, async (c) => { return c.json(result) } catch (error) { - version_logger.error(error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user: { + id: userId, + }, + }) } }) app.openapi(remove, async (c) => { + const userId = verifyAuthentication(c) try { - const userId = verifyAuthentication(c) const id = c.req.param('id') const result = await removeFunc({ userId, id }) @@ -101,11 +112,14 @@ app.openapi(remove, async (c) => { return c.json({ message: 'Version removed' }) } catch (error) { - version_logger.error(error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user: { + id: userId, + }, + }) } }) diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index dad3a54..f1217b7 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,4 +1,5 @@ import type { UserOutput } from '@boring.tools/schema' +import { sentry } from '@hono/sentry' import { OpenAPIHono, type z } from '@hono/zod-openapi' import { apiReference } from '@scalar/hono-api-reference' import { cors } from 'hono/cors' @@ -18,6 +19,12 @@ export type Variables = { export const app = new OpenAPIHono<{ Variables: Variables }>() +app.use( + '*', + sentry({ + dsn: 'https://1d7428bbab0a305078cf4aa380721aa2@o4508167321354240.ingest.de.sentry.io/4508167323648080', + }), +) app.use('*', cors()) app.use('/v1/*', authentication) diff --git a/apps/api/src/user/index.ts b/apps/api/src/user/index.ts index 708b10f..fd5d4e6 100644 --- a/apps/api/src/user/index.ts +++ b/apps/api/src/user/index.ts @@ -1,23 +1,29 @@ import { logger } from '@boring.tools/logger' import { OpenAPIHono } from '@hono/zod-openapi' -import { HTTPException } from 'hono/http-exception' import { Webhook } from 'svix' import type { Variables } from '..' +import { type ContextModule, captureSentry } from '../utils/sentry' import get from './get' import webhook from './webhook' const app = new OpenAPIHono<{ Variables: Variables }>() +const module: ContextModule = { + name: 'user', +} + app.openapi(get.route, async (c) => { + const user = c.get('user') try { - const user = c.get('user') const result = await get.func({ user }) return c.json(result, 201) } catch (error) { - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + user, + }) } }) @@ -31,11 +37,11 @@ app.openapi(webhook.route, async (c) => { logger.info('Clerk Webhook', result) return c.json(result, 200) } catch (error) { - logger.error('Clert Webhook', error) - if (error instanceof HTTPException) { - return c.json({ message: error.message }, error.status) - } - return c.json({ message: 'An unexpected error occurred' }, 500) + return captureSentry({ + c, + error, + module, + }) } }) diff --git a/apps/api/src/utils/sentry.ts b/apps/api/src/utils/sentry.ts new file mode 100644 index 0000000..b71134e --- /dev/null +++ b/apps/api/src/utils/sentry.ts @@ -0,0 +1,37 @@ +import { logger } from '@boring.tools/logger' +import type { Context } from 'hono' +import { HTTPException } from 'hono/http-exception' + +export type ContextModule = { + name: string + sub_module?: string +} + +type ContextUser = { + id: string +} + +const route_logger = logger.child({ name: 'hono' }) + +export const captureSentry = ({ + c, + user, + module, + error, +}: { + c: Context + user?: ContextUser + module: ContextModule + error: unknown +}) => { + route_logger.error(error) + + c.get('sentry').setContext('module', module) + c.get('sentry').setContext('user', user) + c.get('sentry').captureException(error) + + if (error instanceof HTTPException) { + return c.json({ message: error.message }, error.status) + } + return c.json({ message: 'An unexpected error occurred' }, 500) +}