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
Cross-service tracing capabilities with trace metadata extraction and injection for microservices architectures.
Retrieve linking metadata for correlating logs with traces across services.
/**
* Returns linking metadata for correlating logs with traces.
* @param {boolean} [omitSupportability] - Whether to skip supportability metric (defaults to false)
* @returns {object} Object containing trace.id, span.id, entity.name, entity.type, entity.guid, hostname
*/
function getLinkingMetadata(omitSupportability);Usage Examples:
const newrelic = require('newrelic');
// Basic linking metadata for logging
function logWithTraceContext(message, level = 'info') {
const metadata = newrelic.getLinkingMetadata();
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level: level,
message: message,
...metadata // Includes trace.id, span.id, entity.name, etc.
}));
}
// Usage
logWithTraceContext('Processing user order', 'info');
// Output: {"timestamp":"2023-10-01T12:00:00.000Z","level":"info","message":"Processing user order","trace.id":"abc123","span.id":"def456","entity.name":"my-service","entity.type":"SERVICE","entity.guid":"MTIzNDU2fEFQTXxBUFBMSUNBVElPTnw3ODkw","hostname":"server-1"}
// Structured logging with winston
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
winston.format.printf(info => {
const metadata = newrelic.getLinkingMetadata(true); // Skip supportability metric
return JSON.stringify({
...info,
...metadata
});
})
),
transports: [new winston.transports.Console()]
});
logger.info('Order processed successfully', { orderId: '12345' });Get the current trace and span identifiers for distributed tracing.
/**
* Returns the current trace and span identifiers.
* @returns {object} Object containing traceId and spanId
*/
function getTraceMetadata();Usage Examples:
// Basic trace metadata
function getCurrentTraceInfo() {
const metadata = newrelic.getTraceMetadata();
console.log(`Current trace ID: ${metadata.traceId}`);
console.log(`Current span ID: ${metadata.spanId}`);
return metadata;
}
// Add trace context to API responses
app.get('/api/users/:id', (req, res) => {
const user = getUserById(req.params.id);
const traceMetadata = newrelic.getTraceMetadata();
res.json({
user: user,
_meta: {
traceId: traceMetadata.traceId,
spanId: traceMetadata.spanId,
timestamp: new Date().toISOString()
}
});
});
// Custom correlation IDs
function generateCorrelationId() {
const traceMetadata = newrelic.getTraceMetadata();
return `${traceMetadata.traceId}-${Date.now()}`;
}interface LinkingMetadata {
/** Current trace identifier */
'trace.id': string;
/** Current span identifier */
'span.id': string;
/** Application name from New Relic configuration */
'entity.name': string;
/** Entity type, always "SERVICE" */
'entity.type': string;
/** New Relic entity GUID */
'entity.guid': string;
/** Hostname of the server */
hostname: string;
}
interface TraceMetadata {
/** Current distributed trace ID */
traceId: string;
/** Current span ID */
spanId: string;
}When using getTransaction(), the returned handle provides distributed tracing methods:
/**
* Accept and process incoming distributed trace headers from upstream services
* @param {string} transportType - Transport mechanism (e.g., 'HTTP', 'HTTPS', 'Queue')
* @param {object} headers - Headers object containing distributed trace information
*/
TransactionHandle.prototype.acceptDistributedTraceHeaders = function(transportType, headers);
/**
* Insert distributed trace headers for outgoing requests to downstream services
* @param {object} headers - Headers object to modify with distributed trace information
*/
TransactionHandle.prototype.insertDistributedTraceHeaders = function(headers);const axios = require('axios');
// Automatic header injection for outgoing requests
axios.interceptors.request.use((config) => {
const transaction = newrelic.getTransaction();
if (transaction) {
config.headers = config.headers || {};
transaction.insertDistributedTraceHeaders(config.headers);
}
return config;
});
// Manual header injection
async function callDownstreamService(data) {
const transaction = newrelic.getTransaction();
const headers = { 'Content-Type': 'application/json' };
if (transaction) {
transaction.insertDistributedTraceHeaders(headers);
}
const response = await axios.post('https://downstream-service/api/process', data, {
headers: headers
});
return response.data;
}// Express middleware to accept incoming distributed trace headers
app.use((req, res, next) => {
const transaction = newrelic.getTransaction();
if (transaction) {
transaction.acceptDistributedTraceHeaders('HTTP', req.headers);
}
next();
});
// Manual header processing
app.post('/api/webhook', (req, res) => {
const transaction = newrelic.getTransaction();
if (transaction) {
transaction.acceptDistributedTraceHeaders('HTTP', req.headers);
}
// Process webhook...
res.json({ status: 'processed' });
});// Producer - add trace headers to message
function publishMessage(queueName, messageData) {
const transaction = newrelic.getTransaction();
const messageHeaders = {};
if (transaction) {
transaction.insertDistributedTraceHeaders(messageHeaders);
}
return messageQueue.publish(queueName, {
data: messageData,
headers: messageHeaders,
timestamp: Date.now()
});
}
// Consumer - accept trace headers from message
function processMessage(message) {
return newrelic.startBackgroundTransaction('ProcessMessage', 'Queue', () => {
const transaction = newrelic.getTransaction();
if (transaction && message.headers) {
transaction.acceptDistributedTraceHeaders('Queue', message.headers);
}
// Process the message
return handleMessage(message.data);
});
}// Service A - initiating a distributed trace
async function processUserRequest(userData) {
// Start with web transaction (automatically creates trace)
const validation = await callValidationService(userData);
const enriched = await callEnrichmentService(userData, validation);
const stored = await callStorageService(enriched);
return stored;
}
async function callValidationService(userData) {
const transaction = newrelic.getTransaction();
const headers = { 'Content-Type': 'application/json' };
if (transaction) {
transaction.insertDistributedTraceHeaders(headers);
}
const response = await fetch('http://validation-service/validate', {
method: 'POST',
headers: headers,
body: JSON.stringify(userData)
});
return response.json();
}
// Service B - validation service
app.post('/validate', (req, res) => {
const transaction = newrelic.getTransaction();
if (transaction) {
transaction.acceptDistributedTraceHeaders('HTTP', req.headers);
}
const result = validateUserData(req.body);
res.json(result);
});// Add trace context to database operations for correlation
async function performDatabaseOperation(query, params) {
const traceMetadata = newrelic.getTraceMetadata();
// Add trace context to database session or query comments
const contextualQuery = `/* traceId: ${traceMetadata.traceId}, spanId: ${traceMetadata.spanId} */ ${query}`;
return await database.execute(contextualQuery, params);
}const winston = require('winston');
// Custom winston format that adds trace context
const traceFormat = winston.format((info) => {
const metadata = newrelic.getLinkingMetadata(true);
return { ...info, ...metadata };
});
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
traceFormat(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' })
]
});
// Usage - logs will automatically include trace context
logger.info('User order processed', {
orderId: '12345',
userId: 'user-789'
});// Create custom correlation IDs based on trace metadata
function createCorrelationId(prefix = 'corr') {
const traceMetadata = newrelic.getTraceMetadata();
const timestamp = Date.now();
if (traceMetadata.traceId) {
return `${prefix}-${traceMetadata.traceId}-${timestamp}`;
}
// Fallback for when no trace is active
return `${prefix}-${require('crypto').randomUUID()}-${timestamp}`;
}
// Add correlation ID to all responses
app.use((req, res, next) => {
const correlationId = createCorrelationId('req');
res.set('X-Correlation-ID', correlationId);
req.correlationId = correlationId;
next();
});
// Use correlation ID in logging
app.use((req, res, next) => {
const originalSend = res.send;
res.send = function(data) {
logger.info('Request completed', {
correlationId: req.correlationId,
method: req.method,
url: req.url,
statusCode: res.statusCode
});
return originalSend.call(this, data);
};
next();
});// When calling non-Node.js services, ensure trace context is propagated
async function callPythonService(data) {
const transaction = newrelic.getTransaction();
const headers = {
'Content-Type': 'application/json',
'X-Service-Context': 'node-service'
};
if (transaction) {
transaction.insertDistributedTraceHeaders(headers);
// Add additional context for non-New Relic instrumented services
const traceMetadata = newrelic.getTraceMetadata();
headers['X-Trace-ID'] = traceMetadata.traceId;
headers['X-Span-ID'] = traceMetadata.spanId;
}
return await fetch('http://python-service/process', {
method: 'POST',
headers: headers,
body: JSON.stringify(data)
});
}Distributed tracing requires:
distributed_tracing.enabled: true (default)