Node.js

Log from servers, workers and scripts, and capture crashes yourself.

What is different on a server

  • Nothing is captured automatically: no errors, requests or timings. You log what you need.
  • The SDK adds the platform and Node.js version to every log’s context.
  • It registers handlers for SIGINT and SIGTERM that send the last logs and exit. See below.

The SDK uses the global fetch, so it needs Node.js 18 or later. Install it with npm install apperio; both import and require work.

1. Create the logger

src/apperio.ts
import { Apperio } from 'apperio';

export const logger = new Apperio({
  apiKey: process.env.APPERIO_API_KEY ?? '',
  projectId: process.env.APPERIO_PROJECT_ID ?? '',
  environment: process.env.NODE_ENV ?? 'development',
  serviceName: 'api',
});

The constructor throws if either value is empty, so a missing environment variable stops the process at startup rather than losing logs quietly.

2. Capture crashes

src/crash-reporting.ts
import { logger } from './apperio';

process.on('uncaughtException', (error) => {
  logger.fatal('Uncaught exception', error);
  void logger.flush().finally(() => process.exit(1));
});

process.on('unhandledRejection', (reason) => {
  const error = reason instanceof Error ? reason : new Error(String(reason));
  logger.error('Unhandled rejection', error);
});

After an uncaught exception the process is in an unknown state, so this exits once the report is sent. Each error becomes an issue in Apperio, grouped by its name, message and stack.

3. Log what matters

Log failures with the error, and important events with enough data to find them later:

TypeScript
export async function handleRefund(orderId: string) {
  try {
    await processOrder(orderId);
    logger.info('Refund issued', { orderId });
  } catch (err) {
    const error = err instanceof Error ? err : new Error(String(err));
    logger.error('Refund failed', error, { orderId });
    throw error;
  }
}

In a web framework, log errors from its error handler, the one place every failed request passes through.

Scripts and jobs

The logger’s flush timer keeps Node.js running. At the end of a script, shut it down so the last logs go out and the process can exit:

scripts/nightly.ts
import { logger } from '../src/apperio';

try {
  await runJob();
  logger.info('Nightly job finished');
} catch (err) {
  logger.error('Nightly job failed', err instanceof Error ? err : new Error(String(err)));
  process.exitCode = 1;
} finally {
  await logger.shutdown();
}

SIGTERM and SIGINT

On either signal, the SDK’s handler calls logger.shutdown() and then process.exit(0).

Graceful shutdowns

The exit happens as soon as the SDK’s own logs are sent, even if your own handler is still closing the server or finishing requests, and the exit code is always 0. If your platform relies on a graceful shutdown, test it with the SDK in place. See Shutdown.