Application Performance Monitoring (APM) agent for Node.js applications with transaction tracing, error tracking, custom metrics, and distributed tracing capabilities.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Comprehensive error tracking with custom attributes, error grouping, and expected error marking.
Record errors that have been handled by your application code.
/**
* Send errors to New Relic that you've already handled. Does not obey
* ignore_status_codes configuration.
* @param {Error|string} error - The error to be traced
* @param {object} [customAttributes] - Optional custom attributes for the error
* @param {boolean} [expected] - Whether the error is expected (defaults to false)
* @returns {false|undefined} Returns false when disabled/errored, undefined on success
*/
function noticeError(error, customAttributes, expected);Usage Examples:
const newrelic = require('newrelic');
// Basic error reporting
try {
performSomeTask();
} catch (err) {
newrelic.noticeError(err);
// Handle the error in your application
res.status(500).json({ error: 'Internal server error' });
}
// Error with custom attributes
try {
processPayment(paymentData);
} catch (err) {
newrelic.noticeError(err, {
userId: user.id,
paymentMethod: paymentData.method,
amount: paymentData.amount,
orderContext: 'checkout'
});
throw err; // Re-throw if needed
}
// Expected errors (tracked but don't affect error rate)
try {
validateUserInput(input);
} catch (validationError) {
newrelic.noticeError(validationError, {
inputType: 'userRegistration',
validationRule: validationError.rule
}, true); // Mark as expected
return res.status(400).json({
error: 'Validation failed',
details: validationError.message
});
}
// String errors (automatically converted to Error objects)
if (user.permissions < requiredLevel) {
newrelic.noticeError('Insufficient permissions', {
userId: user.id,
requiredLevel: requiredLevel,
userLevel: user.permissions
});
}Define custom logic for grouping similar errors together in New Relic's Errors Inbox.
/**
* Set a custom callback function to generate error group names for the Errors Inbox.
* The callback receives error metadata and must return a string.
* @param {Function} callback - Synchronous function to generate error.group.name attribute
*/
function setErrorGroupCallback(callback);Usage Examples:
// Basic error grouping by error type and HTTP status
newrelic.setErrorGroupCallback((metadata) => {
const errorType = metadata.error?.name || 'UnknownError';
const statusCode = metadata['http.statusCode'];
if (statusCode) {
return `${errorType}-${statusCode}`;
}
return errorType;
});
// Advanced error grouping with business context
newrelic.setErrorGroupCallback((metadata) => {
const error = metadata.error;
const customAttrs = metadata.customAttributes || {};
// Group validation errors by the field that failed
if (error?.name === 'ValidationError' && customAttrs.field) {
return `ValidationError-${customAttrs.field}`;
}
// Group payment errors by payment method
if (error?.message?.includes('payment') && customAttrs.paymentMethod) {
return `PaymentError-${customAttrs.paymentMethod}`;
}
// Group API errors by endpoint
if (metadata['request.uri'] && error?.name === 'APIError') {
const endpoint = metadata['request.uri'].split('?')[0]; // Remove query params
return `APIError-${endpoint}`;
}
// Default grouping
return error?.name || 'UnknownError';
});
// Error grouping with user context
newrelic.setErrorGroupCallback((metadata) => {
const error = metadata.error;
const isExpected = metadata['error.expected'];
const userTier = metadata.customAttributes?.userTier;
// Don't group expected errors with unexpected ones
const prefix = isExpected ? 'Expected' : 'Unexpected';
// Group by user tier for business insights
if (userTier) {
return `${prefix}-${error?.name}-${userTier}`;
}
return `${prefix}-${error?.name || 'UnknownError'}`;
});The error group callback receives a metadata object with the following structure:
interface ErrorMetadata {
/** Custom attributes passed to noticeError() */
customAttributes?: object;
/** HTTP request URI */
'request.uri'?: string;
/** HTTP status code */
'http.statusCode'?: string;
/** HTTP method */
'http.method'?: string;
/** The actual Error object */
error?: Error;
/** Whether the error was marked as expected */
'error.expected'?: boolean;
}When high security mode is enabled:
noticeError() are ignored and logged as debug messageapi.notice_error_enabled: false - Disables error recordingapi.custom_attributes_enabled: false - Disables custom attributes on errorsapp.use('/api', (req, res, next) => {
try {
next();
} catch (error) {
const errorContext = {
endpoint: req.path,
method: req.method,
userId: req.user?.id,
userAgent: req.get('User-Agent'),
ipAddress: req.ip,
requestId: req.headers['x-request-id']
};
newrelic.noticeError(error, errorContext);
res.status(500).json({
error: 'Internal server error',
requestId: errorContext.requestId
});
}
});async function performDatabaseOperation(query, params) {
try {
return await database.execute(query, params);
} catch (error) {
const errorContext = {
queryType: query.type,
tableCount: query.tables?.length || 0,
paramCount: params.length,
connectionPool: database.pool.name
};
// Mark timeout errors as expected if they're common
const isExpected = error.code === 'TIMEOUT' && query.type === 'SELECT';
newrelic.noticeError(error, errorContext, isExpected);
throw error;
}
}function validateUserData(userData, schema) {
try {
return schema.validate(userData);
} catch (validationError) {
// These are expected business logic errors
newrelic.noticeError(validationError, {
validationType: 'userRegistration',
failedField: validationError.field,
providedValue: validationError.value,
validationRule: validationError.rule
}, true); // Mark as expected
throw validationError;
}
}async function callExternalAPI(endpoint, data) {
try {
const response = await httpClient.post(endpoint, data);
return response.data;
} catch (error) {
const errorContext = {
externalService: 'payment-processor',
endpoint: endpoint,
httpStatusCode: error.response?.status,
retryAttempt: data.retryCount || 0,
requestSize: JSON.stringify(data).length
};
// 4xx errors are typically expected (bad requests)
const isExpected = error.response?.status >= 400 && error.response?.status < 500;
newrelic.noticeError(error, errorContext, isExpected);
throw error;
}
}// Set up comprehensive error grouping
newrelic.setErrorGroupCallback((metadata) => {
const error = metadata.error;
const customAttrs = metadata.customAttributes || {};
const uri = metadata['request.uri'] || '';
const statusCode = metadata['http.statusCode'];
// Payment processing errors
if (customAttrs.paymentMethod || uri.includes('/payment')) {
return `Payment-${customAttrs.paymentMethod || 'Unknown'}-${error?.name}`;
}
// User authentication errors
if (uri.includes('/auth') || error?.name === 'AuthenticationError') {
return `Auth-${error?.name}-${statusCode}`;
}
// Data validation errors grouped by entity type
if (error?.name === 'ValidationError' && customAttrs.entityType) {
return `Validation-${customAttrs.entityType}-${customAttrs.failedField || 'Unknown'}`;
}
// Third-party service errors
if (customAttrs.externalService) {
return `External-${customAttrs.externalService}-${error?.name}`;
}
// Database errors grouped by operation type
if (error?.name?.includes('Database') && customAttrs.queryType) {
return `Database-${customAttrs.queryType}-${error?.name}`;
}
// Default grouping by error name and status code
return `${error?.name || 'UnknownError'}${statusCode ? `-${statusCode}` : ''}`;
});// Monitor error rates and alert on thresholds
let errorCount = 0;
let totalRequests = 0;
app.use((req, res, next) => {
totalRequests++;
res.on('finish', () => {
if (res.statusCode >= 500) {
errorCount++;
// Calculate error rate every 100 requests
if (totalRequests % 100 === 0) {
const errorRate = errorCount / totalRequests;
newrelic.recordMetric('ErrorRate/Server', errorRate);
if (errorRate > 0.05) { // Alert if > 5% error rate
newrelic.recordCustomEvent('HighErrorRate', {
errorRate: errorRate,
errorCount: errorCount,
totalRequests: totalRequests,
alertLevel: 'critical'
});
}
}
}
});
next();
});// Proper async error handling with context preservation
async function processAsyncTask(taskData) {
const taskContext = {
taskId: taskData.id,
taskType: taskData.type,
userId: taskData.userId,
priority: taskData.priority
};
try {
const result = await performAsyncOperation(taskData);
return result;
} catch (error) {
// Preserve context when handling async errors
newrelic.noticeError(error, {
...taskContext,
errorOccurredAt: 'asyncTaskProcessing',
asyncContext: true
});
// Re-throw to maintain error flow
throw error;
}
}
// Handling unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
newrelic.noticeError(reason instanceof Error ? reason : new Error(String(reason)), {
source: 'unhandledRejection',
promiseLocation: promise.toString()
});
});