All files index.ts

100% Statements 1/1
100% Branches 0/0
100% Functions 0/0
100% Lines 1/1

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123                  1x                                                                                                                                                                                                                                  
import { config, define, t, type ConfigFromDefs } from '@devmoods/config';
import { type LoggerAdapter } from '@devmoods/observability';
import * as Sentry from '@sentry/node';
import { type ErrorRequestHandler } from 'express';
 
declare module '@devmoods/config' {
  interface Config extends ConfigFromDefs<typeof sentryConfig> {}
}
 
export const sentryConfig = define({
  SENTRY_DSN: t.string(),
  SENTRY_TRACES_SAMPLE_RATE: t.number(0.3),
  SENTRY_PROFILES_SAMPLE_RATE: t.number(0.3),
  SENTRY_EVENT_LOOP_THRESHOLD: t.number(750),
});
 
export type { Sentry };
 
export interface SentryOptions extends Sentry.NodeOptions {}
 
/* c8 ignore start */
export async function initSentry(options: SentryOptions = {}) {
  Sentry.init(await sentryOptions(options));
}
 
export async function closeSentry() {
  await Sentry.close(5000);
}
/* c8 ignore end */
 
/**
 * Safely imports native Sentry integrations if available.
 * Returns null if native modules are not available (e.g., on unsupported Node.js versions).
 */
async function getNativeIntegrations() {
  const integrations = [];
 
  /* v8 ignore next -- @preserve */
  try {
    const { nodeProfilingIntegration } = await import('@sentry/profiling-node');
    integrations.push(nodeProfilingIntegration());
  } catch {
    // Native profiling integration not available
  }
 
  /* v8 ignore next -- @preserve */
  try {
    const { eventLoopBlockIntegration } = await import('@sentry/node-native');
    integrations.push(
      eventLoopBlockIntegration({
        threshold: config.SENTRY_EVENT_LOOP_THRESHOLD,
      }),
    );
  } catch {
    // Native event loop integration not available
  }
 
  return integrations;
}
 
export async function sentryOptions(
  options: SentryOptions = {},
): Promise<SentryOptions> {
  const nativeIntegrations = await getNativeIntegrations();
 
  return {
    dsn: config.SENTRY_DSN,
    tracesSampleRate: config.SENTRY_TRACES_SAMPLE_RATE,
    profileSessionSampleRate: config.SENTRY_PROFILES_SAMPLE_RATE,
    integrations: nativeIntegrations,
    enableLogs: true,
    ...options,
  };
}
 
export function sentryErrorHandler(
  options: Sentry.NodeOptions = {},
): ErrorRequestHandler {
  return Sentry.expressErrorHandler({
    shouldHandleError() {
      return process.env.NODE_ENV === 'production';
    },
    ...options,
  }) as unknown as ErrorRequestHandler;
}
 
export class SentryLoggerAdapter implements LoggerAdapter {
  constructor() {}
 
  captureError(error: Error | null, attributes?: Record<string, any>) {
    /* v8 ignore next -- @preserve */
    if (error == null) {
      return;
    }
 
    /* v8 ignore next -- @preserve */
    if (Sentry.isInitialized()) {
      Sentry.captureException(error, {
        tags: attributes,
      });
    }
  }
 
  write(log: Record<string, any>) {
    const { message, ...attributes } = log;
 
    /* v8 ignore next -- @preserve */
    if (Sentry.isInitialized()) {
      const fn = log.level as
        | 'trace'
        | 'debug'
        | 'info'
        | 'warn'
        | 'error'
        | 'fatal';
 
      Sentry.logger[fn](message, attributes);
    }
 
    return true;
  }
}