AppSignal Integration
Complete example of integrating AppSignal for application monitoring with Glasswork.
Recommended Setup
For most applications, the built-in CloudWatch integration provides excellent observability without additional dependencies. Use AppSignal when you need advanced APM features, team-focused error triage, or integrated uptime monitoring.
Installation
npm install @appsignal/nodejspnpm add @appsignal/nodejsyarn add @appsignal/nodejsAppSignal Initialization
AppSignal must be loaded before any other dependencies to enable automatic instrumentation (Prisma, HTTP clients, etc.). Create an appsignal.cjs file:
// appsignal.cjs
const { Appsignal } = require("@appsignal/nodejs");
new Appsignal({
active: process.env.APPSIGNAL_ACTIVE === "true",
name: process.env.APPSIGNAL_APP_NAME || "my-api",
pushApiKey: process.env.APPSIGNAL_PUSH_API_KEY,
environment: process.env.NODE_ENV || "development",
});Running Your Application
Use the --require flag to load AppSignal before your application:
# Development
node --require ./appsignal.cjs dist/server.js
# With TypeScript source maps
node --enable-source-maps --require ./appsignal.cjs dist/server.jsAdd to your package.json:
{
"scripts": {
"start": "node --enable-source-maps --require ./appsignal.cjs dist/server.js",
"dev": "tsx watch --require ./appsignal.cjs src/server.ts"
}
}For Lambda deployments with esbuild, use NODE_OPTIONS:
NODE_OPTIONS='--require ./appsignal.cjs' node dist/lambda.jsException Tracker for Glasswork
Create an adapter to integrate AppSignal with Glasswork's exception tracking:
// src/observability/appsignal-tracker.ts
import { Appsignal } from '@appsignal/nodejs';
import type { ExceptionTracker } from 'glasswork';
export function createAppSignalTracker(): ExceptionTracker {
const client = Appsignal.client;
return {
captureException(error: Error, context?: Record<string, unknown>) {
client?.sendError(error, span => {
if (context?.requestId) {
span.setTag('requestId', String(context.requestId));
}
if (context?.path) {
span.setTag('path', String(context.path));
}
if (context?.method) {
span.setTag('method', String(context.method));
}
if (context?.statusCode) {
span.setTag('statusCode', String(context.statusCode));
}
});
},
captureMessage(
message: string,
level: 'info' | 'warning' | 'error',
context?: Record<string, unknown>
) {
if (level === 'error') {
client?.sendError(new Error(message), span => {
span.setTag('level', level);
if (context) {
Object.entries(context).forEach(([key, value]) => {
span.setTag(key, String(value));
});
}
});
}
},
setUser(user: { id: string; email?: string }) {
// User context is set per-span - no-op at global level
},
setContext(key: string, data: Record<string, unknown>) {
// Context is set per-span - no-op at global level
},
};
}Bootstrap Configuration
// src/server.ts
import pino from 'pino';
import {
bootstrap,
isDevelopment,
lambdaPinoConfig,
createContextAwarePinoLogger,
} from 'glasswork';
import { createAppSignalTracker } from './observability/appsignal-tracker';
import { AppModule } from './app.module';
// Create Pino logger
const basePino = pino(lambdaPinoConfig);
// Create context-aware logger for services
export const logger = createContextAwarePinoLogger({
pino: basePino,
service: 'my-api',
});
// Create AppSignal tracker (uses globally initialized client)
const tracker = isDevelopment() ? undefined : createAppSignalTracker();
export const { app, start, stop } = await bootstrap(AppModule, {
logger: { pino: basePino },
...(tracker && {
exceptionTracking: {
tracker,
trackStatusCodes: (status) => status >= 500,
},
}),
openapi: {
enabled: true,
serveSpecs: isDevelopment(),
serveUI: isDevelopment(),
documentation: {
info: {
title: 'My API',
version: '1.0.0',
},
},
},
});Environment Variables
# .env.production
APPSIGNAL_ACTIVE=true
APPSIGNAL_APP_NAME=my-api
APPSIGNAL_PUSH_API_KEY=your-push-api-key-here
NODE_ENV=production
LOG_LEVEL=infoWhat You Get
With the --require setup, AppSignal automatically instruments:
- HTTP requests - Automatic request tracking and timing
- Prisma queries - Database query performance monitoring
- External HTTP calls - Outgoing request tracking
- Errors - Automatic error capture with stack traces
Plus Glasswork's exception tracking adds:
- Request ID correlation
- Custom error context
- Explicit tracking control via
{ track: true/false }
Service Usage
// src/modules/user/user.service.ts
import { NotFoundException, InternalServerErrorException } from 'glasswork';
import { logger } from '../../server';
import type { PrismaService } from '../prisma/prisma.service';
export class UserService {
constructor({ prisma }: { prisma: PrismaService }) {
this.prisma = prisma;
}
private prisma: PrismaService;
async findById(id: string) {
logger.info('Finding user', { userId: id });
// This Prisma query is automatically tracked by AppSignal!
const user = await this.prisma.user.findUnique({
where: { id },
});
if (!user) {
// Not tracked in AppSignal (404 by default)
throw new NotFoundException('User not found');
}
return user;
}
async create(data: CreateUserDto) {
logger.info('Creating user', { email: data.email });
try {
const user = await this.prisma.user.create({ data });
logger.info('User created', { userId: user.id });
return user;
} catch (error) {
// This 500 will be tracked in AppSignal
throw new InternalServerErrorException('Failed to create user');
}
}
}Dashboard Features
AppSignal dashboard provides:
- Errors - Grouped by type, with trends and frequency
- Performance - Request traces, percentiles, throughput
- Database - Query performance, slow query detection
- Alerts - Configurable notifications for error rates
- Deploy markers - Track errors by deployment
Alerts Configuration
Set up alerts in the AppSignal dashboard:
- Error Rate Alert - Trigger: >5 errors/minute
- Slow Response Alert - Trigger: P95 latency >1000ms
- Database Alert - Trigger: Slow queries >100ms
Learn More
- AppSignal Node.js Documentation - Official docs
- AppSignal Bundling with esbuild - For Lambda deployments
