Skip to content

Commit

Permalink
feat: setup optional OpenTelemetry (#585)
Browse files Browse the repository at this point in the history
## What does this PR do?

Open Telemetry setup with No-op when not enabled.
fixes #507 

## Type of change

<!-- Please mark the relevant points by using [x] -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Chore (refactoring code, technical debt, workflow improvements)
- [ ] Enhancement (small improvements)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## Checklist

<!-- We're starting to get more and more contributions. Please help us making this efficient for all of us and go through this checklist. Please tick off what you did  -->

### Required

- [ ] Read [Contributing Guide](https://github.com/un/inbox/blob/main/CONTRIBUTING.md)
- [ ] Self-reviewed my own code
- [ ] Tested my code in a local environment
- [ ] Commented on my code in hard-to-understand areas
- [ ] Checked for warnings, there are none
- [ ] Removed all `console.logs`
- [ ] Merged the latest changes from main onto my branch with `git pull origin main`
- [ ] My changes don't cause any responsiveness issues

### Appreciated

- [ ] If a UI change was made: Added a screen recording or screenshots to this PR
- [ ] Updated the UnInbox Docs if changes were necessary
  • Loading branch information
BlankParticle authored Jul 21, 2024
1 parent b9813d4 commit 471e56f
Show file tree
Hide file tree
Showing 44 changed files with 1,000 additions and 419 deletions.
8 changes: 7 additions & 1 deletion .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,10 @@ TURNSTILE_SECRET_KEY=

########################## WORKER APP VARIABLES ################################
WORKER_URL=http://localhost:3400
WORKER_ACCESS_KEY=secretsecretsecretsecretsecretsecret
WORKER_ACCESS_KEY=secretsecretsecretsecretsecretsecret

######################### OpenTelemetry Variables ################################
OTEL_ENABLED=false
OTEL_EXPORTER_TRACES_ENDPOINT=
OTEL_EXPORTER_METRICS_ENDPOINT=
OTEL_EXPORTER_LOGS_ENDPOINT=
7 changes: 3 additions & 4 deletions apps/mail-bridge/app.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import './tracing';
import { env } from './env';
import { db } from '@u22n/database';
import { trpcMailBridgeRouter } from './trpc';
import { eventApi } from './postal-routes/events';
import { inboundApi } from './postal-routes/inbound';
import { signatureMiddleware } from './postal-routes/signature-middleware';
import { otel } from '@u22n/otel/hono';
import { opentelemetry } from '@u22n/otel/hono';
import type { Ctx, TRPCContext } from './ctx';
import {
createHonoApp,
Expand All @@ -21,9 +20,9 @@ const processCleanup: Array<() => Promise<void>> = [];

if (env.MAILBRIDGE_MODE === 'dual' || env.MAILBRIDGE_MODE === 'handler') {
const app = createHonoApp<Ctx>();
app.use(otel());
app.use(opentelemetry('mail-bridge/hono'));

setupRouteLogger(app, process.env.NODE_ENV === 'development');
setupRouteLogger(app, env.NODE_ENV === 'development');

setupHealthReporting(app, {
service: `Mail Bridge [${env.MAILBRIDGE_MODE === 'handler' ? 'Handler' : 'Dual'}]`
Expand Down
5 changes: 1 addition & 4 deletions apps/mail-bridge/ctx.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import type { db } from '@u22n/database';
import type { Otel } from '@u22n/otel/hono';
import type { env } from './env';
import type { Context } from '@u22n/hono/helpers';
import type { HonoContext } from '@u22n/hono';

export type Ctx = HonoContext<{
otel: Otel;
}>;
export type Ctx = HonoContext;

export type TRPCContext = {
auth: boolean;
Expand Down
9 changes: 5 additions & 4 deletions apps/mail-bridge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
"name": "@u22n/mail-bridge",
"private": true,
"type": "module",
"version": "0.0.0-dev",
"scripts": {
"dev": "tsx watch --clear-screen=false app.ts",
"start": "node .output/app.js",
"dev": "tsx watch --clear-screen=false --import ./tracing.ts app.ts",
"start": "node --import ./.output/tracing.js .output/app.js",
"build": "tsup",
"check": "tsc --noEmit"
},
Expand All @@ -15,8 +16,8 @@
},
"dependencies": {
"@t3-oss/env-core": "^0.10.1",
"@trpc/client": "11.0.0-rc.413",
"@trpc/server": "11.0.0-rc.413",
"@trpc/client": "11.0.0-rc.466",
"@trpc/server": "11.0.0-rc.466",
"@u22n/database": "workspace:*",
"@u22n/hono": "workspace:^",
"@u22n/mailtools": "^0.1.2",
Expand Down
40 changes: 23 additions & 17 deletions apps/mail-bridge/postal-routes/signature-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ import { env } from '../env';
import { validatePostalWebhookSignature } from '../utils/validatePostalWebhookSignature';
import type { Ctx } from '../ctx';
import { createMiddleware } from '@u22n/hono/helpers';
import { getTracer } from '@u22n/otel/helpers';
import { flatten } from '@u22n/otel/exports';

const middlewareTracer = getTracer('mail-bridge/hono/middleware');

export const signatureMiddleware = createMiddleware<Ctx>(async (c, next) =>
c
.get('otel')
.tracer.startActiveSpan('Postal Signature Middleware', async (span) => {
middlewareTracer.startActiveSpan(
'Postal Signature Middleware',
async (span) => {
if (c.req.method !== 'POST') {
span.recordException(`Method not allowed, ${c.req.method}`);
span.end();
span?.recordException(new Error(`Method not allowed, ${c.req.method}`));
return c.json({ message: 'Method not allowed' }, 405);
}
const body = await c.req.json().catch(() => ({}));
const signature = c.req.header('x-postal-signature');
if (!signature) {
span.recordException(`Missing signature`);
span.end();
span?.recordException(new Error('Missing signature'));
return c.json({ message: 'Missing signature' }, 401);
}
const publicKeys = env.MAILBRIDGE_POSTAL_SERVERS.map(
Expand All @@ -28,17 +30,21 @@ export const signatureMiddleware = createMiddleware<Ctx>(async (c, next) =>
publicKeys
);
if (!valid) {
span.recordException('Invalid signature');
span.setAttributes({
'signature.valid': false,
'signature.availableKeys': publicKeys,
'signature.signature': signature
});
span.end();
span?.setAttributes(
flatten({
'req.signature.meta': {
valid: false,
body: body,
signature: signature
}
})
);
span?.recordException(new Error('Invalid signature'));
return c.json({ message: 'Invalid signature' }, 401);
}
span.setAttribute('signature.valid', true);
span.end();
span?.setAttribute('req.signature.meta.valid', true);

await next();
})
}
)
);
26 changes: 12 additions & 14 deletions apps/mail-bridge/queue/mail-processor/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ import { tipTapExtensions } from '@u22n/tiptap/extensions';
import { sendRealtimeNotification } from '../../utils/realtime';
import { simpleParser, type EmailAddress } from 'mailparser';
import { env } from '../../env';
import { trace } from '@u22n/otel/exports';
import { discord } from '@u22n/utils/discord';
import mime from 'mime';
import { getTracer } from '@u22n/otel/helpers';
import { logger } from '@u22n/otel/logger';

const { host, username, password, port } = new URL(
env.DB_REDIS_CONNECTION_STRING
);

const tracer = trace.getTracer('mail-processor');
const tracer = getTracer('mail-bridge/queue/mail-processor');

export const worker = new Worker<
{
Expand All @@ -48,9 +49,9 @@ export const worker = new Worker<
>(
'mail-processor',
(job) =>
tracer.startActiveSpan('mail-processor', async (span) => {
tracer.startActiveSpan('Mail Processor', async (span) => {
try {
span.setAttributes({
span?.setAttributes({
'job.id': job.id,
'job.data': JSON.stringify(job.data)
});
Expand All @@ -64,7 +65,7 @@ export const worker = new Worker<
...params
});

span.addEvent('Resolved org and mailserver', {
span?.addEvent('mail-processor.resolved_org_mailserver', {
orgId,
orgPublicId,
forwardedEmailAddress: forwardedEmailAddress || '<null>'
Expand All @@ -88,7 +89,7 @@ export const worker = new Worker<
throw new Error('No message ID found in the email');

if (parsedEmail.from.value.length > 1) {
span.addEvent(
logger.warn(
'Multiple from addresses detected in a message, only using first email address'
);
}
Expand Down Expand Up @@ -141,7 +142,7 @@ export const worker = new Worker<
});

if (alreadyProcessedMessageWithThisId) {
span.addEvent('Message already processed');
logger.warn('Message already processed');
return;
}

Expand Down Expand Up @@ -196,7 +197,7 @@ export const worker = new Worker<
!messageFromPlatformObject ||
!messageFromPlatformObject[0]
) {
span.setAttributes({
span?.setAttributes({
'message.toObject': JSON.stringify(messageToPlatformObject),
'message.fromObject': JSON.stringify(messageFromPlatformObject)
});
Expand Down Expand Up @@ -260,7 +261,7 @@ export const worker = new Worker<

// if theres no email identity ids, then we assume that this email has no destination, so we need to send the bounce message
if (!emailIdentityIds.length) {
span.setAttributes({
span?.setAttributes({
'message.addressIds': JSON.stringify(messageAddressIds)
});
throw new Error('No email identity ids found');
Expand Down Expand Up @@ -783,14 +784,11 @@ export const worker = new Worker<
convoEntryId: +insertNewConvoEntry.insertId
});
} catch (e) {
span.recordException(e as Error);
span.setStatus({ code: 2 });
console.error(e, 'Error processing email');
span?.recordException(e as Error);
console.error('Error processing email');
await discord.info(`Mailbridge Queue Error\n${(e as Error).message}`);
// Throw the error to be caught by the worker, and moving to failed jobs
throw e;
} finally {
span.end();
}
}),
{
Expand Down
6 changes: 4 additions & 2 deletions apps/mail-bridge/tracing.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
import { setupTracer } from '@u22n/otel/otel';
setupTracer({ name: `@u22n/mail-bridge`, version: `0.0.0-dev` });
import { setupOpentelemetry } from '@u22n/otel';
import { name, version } from './package.json';

setupOpentelemetry({ name, version });
30 changes: 19 additions & 11 deletions apps/mail-bridge/trpc/trpc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { TRPCError, initTRPC } from '@trpc/server';
import superjson from 'superjson';
import type { TRPCContext } from '../ctx';
import { getTracer } from '@u22n/otel/helpers';
import { flatten } from '@u22n/otel/exports';

export const trpcContext = initTRPC
.context<TRPCContext>()
Expand All @@ -13,18 +15,24 @@ const isServiceAuthenticated = trpcContext.middleware(({ next, ctx }) => {
return next({ ctx });
});

const trpcTracer = getTracer('mail-bridge/trpc');
export const publicProcedure = trpcContext.procedure.use(
async ({ ctx, type, path, next }) =>
ctx.context
.get('otel')
.tracer.startActiveSpan(`TRPC ${type} ${path}`, async (span) => {
const result = await next();
span.setAttributes({
'trpc.ok': result.ok
});
span.end();
return result;
})
async ({ type, path, next }) =>
trpcTracer.startActiveSpan(`TRPC ${type} ${path}`, async (span) => {
const result = await next();
if (span) {
span.setAttributes(
flatten({
trpc: {
type: type,
path: path,
ok: result.ok
}
})
);
}
return result;
})
);
export const protectedProcedure = publicProcedure.use(isServiceAuthenticated);
export const router = trpcContext.router;
Expand Down
6 changes: 1 addition & 5 deletions apps/mail-bridge/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { defineConfig } from 'tsup';

export default defineConfig({
entry: ['app.ts'],
entry: ['app.ts', './tracing.ts'],
outDir: '.output',
format: 'esm',
target: 'esnext',
clean: true,
bundle: true,
treeshake: true,
noExternal: [/^@u22n\/.*/],
external: ['thream-stream'],
cjsInterop: true,
shims: true,
minify: false,
splitting: false,
banner: {
js: [
`import { createRequire } from 'module';`,
Expand Down
5 changes: 2 additions & 3 deletions apps/platform/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import './tracing';
import type { Ctx, TrpcContext } from './ctx';
import { env } from './env';
import {
Expand All @@ -16,12 +15,12 @@ import { realtimeApi } from './routes/realtime';
import { trpcPlatformRouter } from './trpc';
import { db } from '@u22n/database';
import { authMiddleware, serviceMiddleware } from './middlewares';
import { otel } from '@u22n/otel/hono';
import { opentelemetry } from '@u22n/otel/hono';
import { servicesApi } from './routes/services';

const app = createHonoApp<Ctx>();

app.use(otel());
app.use(opentelemetry('platform/hono'));

setupRouteLogger(app, env.NODE_ENV === 'development');
setupCors(app, { origin: [env.WEBAPP_URL], exposeHeaders: ['Location'] });
Expand Down
2 changes: 0 additions & 2 deletions apps/platform/ctx.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import type { DBType } from '@u22n/database';
import type { HonoContext } from '@u22n/hono';
import type { Otel } from '@u22n/otel/hono';
import type { Context } from '@u22n/hono/helpers';
import type { DatabaseSession } from 'lucia';

export type Ctx = HonoContext<{
account: AccountContext;
otel: Otel;
}>;

export type OrgContext = {
Expand Down
Loading

0 comments on commit 471e56f

Please sign in to comment.