Instrumentation.ts never executes on deployments

I have a Remix application deployed on Vercel.

I recently realized @sentry/remix wasn’t fully instrumented on the Remix server. I added an instrumentation.ts file and started simply with some logging:

import * as Sentry from '@sentry/remix';

console.log('VERCEL INSTRUMENTATION FILE');

export function register() {
  console.log('VERCEL INSTRUMENTATION FILE REGISTERED');

  Sentry.init({
    dsn: process.env.SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: 1.0
  });
}

However, I don’t see these logs appear in the Vercel logs, and I do not see the instrumentation file in any of the build artifacts under Sources (in either Source or Output).

I’m using the @vercel/remix package and the remix vite vercel preset. Not sure what I need to do to ensure the instrumentation file is included in the build artifacts. Couldn’t find any documentation for Other frameworks other than examples of the file structure.

Hi Atticus

instrumentation.ts is a Next.js feature

Sentry has docs here for how to instrument a Remix app. If you don’t have entry.server.tsx in your codebase, you can run npx remix reveal to make it appear

Ah, I had noticed there were a few parts of the docs that mentioned “Other Frameworks”, so I assumed it was vercel level.

The key detail Sentry calls out for its instrumentation is that it runs before the application runtime begins. The problem with adding it in entry.server.tsx is that the module has already been loaded by Vercel’s server entrypoint, and instrumentation is applied too late.

I could solve this if i can get an instrumentation file to belong as part of the build output, and instruct vercel import it before the application module is loaded, but I’m not sure if this is possible

This GH discussion has a few mentions of the problem as well Sentry Integration · remix-run/remix · Discussion #1522 · GitHub

In particular, issue is that the instrumentation has to run before Vercel’s server-index.mjs imports the remix build:

import {
  createRequestHandler as createRemixRequestHandler,
  createReadableStreamFromReadable,
  writeReadableStreamToWritable,
  installGlobals,
} from '@remix-run/node';
import * as build from './index.js';

installGlobals({
  nativeFetch:
    (parseInt(process.versions.node, 10) >= 20 &&
      process.env.VERCEL_REMIX_NATIVE_FETCH === '1') ||
    build.future.unstable_singleFetch,
});

const handleRequest = createRemixRequestHandler(
  build.default || build,
  process.env.NODE_ENV
);

function toWebHeaders(nodeHeaders) {
  const headers = new Headers();

  for (const key in nodeHeaders) {
    const header = nodeHeaders[key];
    // set-cookie is an array (maybe others)
    if (Array.isArray(header)) {
      for (const value of header) {
        headers.append(key, value);
      }
    } else {
      headers.append(key, header);
    }
  }

  return headers;
}

function toNodeHeaders(webHeaders) {
  return webHeaders.raw?.() || [...webHeaders].flat();
}

function createRemixRequest(req, res) {
  const host = req.headers['x-forwarded-host'] || req.headers.host;
  const protocol = req.headers['x-forwarded-proto'] || 'https';
  const url = new URL(req.url, `${protocol}://${host}`);

  // Abort action/loaders once we can no longer write a response
  const controller = new AbortController();
  res.on('close', () => controller.abort());

  const init = {
    method: req.method,
    headers: toWebHeaders(req.headers),
    signal: controller.signal,
  };

  if (req.method !== 'GET' && req.method !== 'HEAD') {
    init.body = createReadableStreamFromReadable(req);
    init.duplex = 'half';
  }

  return new Request(url.href, init);
}

async function sendRemixResponse(res, nodeResponse) {
  res.statusMessage = nodeResponse.statusText;
  res.writeHead(
    nodeResponse.status,
    nodeResponse.statusText,
    toNodeHeaders(nodeResponse.headers)
  );

  if (nodeResponse.body) {
    await writeReadableStreamToWritable(nodeResponse.body, res);
  } else {
    res.end();
  }
}

export default async (req, res) => {
  const request = createRemixRequest(req, res);
  const response = await handleRequest(request);
  await sendRemixResponse(res, response);
};

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.