Follow these guidelines when implementing logging in Itential Platform Adapters, Integrations, and applications.
Before you begin
- Understand log formats and severity levels
- Know your Platform version (affects logging signature requirements)
- Review your organization's data security policies
Itential's logging security policy
Itential Platform automatically excludes Authentication, Authorization, and Accounting (AAA) sensitive data from logs, including Platform passwords, API keys, tokens, and credentials. However, Itential does not modify third-party system payloads.
Third-party payload data includes:
- API responses from external systems (ServiceNow, Ansible, Splunk)
- Device configurations from network equipment (Cisco, Juniper, Arista)
- Authentication tokens from external auth providers (Okta, Active Directory)
- Data returned from monitoring tools, ticketing systems, and cloud providers
Your responsibility
Filter sensitive data from third-party payloads before logging. Implement appropriate filtering in your workflows and custom code according to your organization's security policies.
Use structured logging
Requirements: Platform 2023.2 or 6.2+
Use the structured logging signature with a single object containing message, context, and error fields.
Do this:
log.info({
message: 'User authentication successful',
context: {
userId: user.id,
username: user.username,
method: 'ldap'
}
});
Not this:
log.info('User authentication successful', user);
Structured logging enables automated parsing and prevents accidental exposure of sensitive data.
Choose the correct log level
Select the appropriate severity level based on the event type and required response.
| Level | When to use | Example |
|---|---|---|
system |
Critical platform lifecycle events only | Itential Platform initialization complete, shutting down Itential Platform |
error |
Unrecoverable errors requiring immediate attention | Database connection lost, authentication service unavailable |
warn |
Recoverable issues requiring monitoring | API retry in progress, deprecated feature used |
info |
Normal operational messages | User logged in, workflow completed, service started |
debug |
Detailed diagnostic information for troubleshooting | Configuration loaded, API response received |
trace |
Step-by-step execution flow | Function entry/exit, variable values |
spam |
Extremely frequent events | Loop iterations, polling events |
Best practices:
- Use
infofor events you'd want in production dashboards - Use
debugonly during active troubleshooting - Reserve
errorfor situations requiring human intervention - Always include
errorobjects witherrorandwarnlevels
For more information, see Log Levels Reference.
Write clear messages
Write log messages that explain what happened and provide troubleshooting context.
**
:**
log.error({
message: 'Failed to update user profile in database',
context: {
userId: 'user123',
operation: 'updateEmail'
},
error: err
});
Not this:
log.error({ message: 'Update failed', error: err });
Filter sensitive data
Always create context objects containing only known-safe fields. Never log entire objects that may contain sensitive data.
Create safe context objects
Extract only the fields you need and explicitly define them in your context object.
Do this:
const logContext = {
userId: user.id,
username: user.username,
operation: 'updateProfile'
};
log.info({
message: 'User profile updated successfully',
context: logContext
});
Not this:
// Entire user object may contain email, address, or other PII
log.info({
message: 'User profile updated successfully',
context: user
});
Filter third-party payloads
Extract only metadata from third-party responses. Never log full response bodies.
Do this:
log.debug({
message: 'ServiceNow API response received',
context: {
endpoint: '/api/now/table/incident',
statusCode: response.status,
recordCount: response.data.result.length
}
});
Not this:
// response.data may contain PII or sensitive content
log.debug({
message: 'ServiceNow API response received',
context: {
endpoint: '/api/now/table/incident',
responseData: response.data
}
});
Sensitive data categories
Understand which data is automatically filtered by Itential and which data you must filter manually.
Platform data (handled by Itential):
- Platform passwords, API keys, tokens, secrets
- Platform authorization headers, cookies, sessions
- Platform JWT tokens, OAuth credentials
Third-party data (your responsibility):
- API response payloads from external systems
- Device configurations from network equipment
- Credentials or tokens in third-party responses
- Customer data from external integrations
Personally Identifiable Information:
- Email addresses, phone numbers, full names
- Social security numbers, addresses
- IP addresses (context-dependent)
Business & customer Data:
- Customer-specific business data
- Proprietary configurations
- Sensitive content from third-party systems
Log useful context
Include relevant context that helps diagnose issues without exposing sensitive data.
API calls
Log metadata about API requests and responses without including payloads or credentials.
log.info({
message: 'Outbound API request',
context: {
service: 'ServiceNow',
endpoint: '/api/now/table/incident',
method: 'POST',
correlationId: req.headers['x-correlation-id']
}
});
Include: HTTP method, endpoint (without sensitive query parameters), status codes, response times, correlation IDs, operation names, record counts
Exclude: Request/response bodies, authorization headers, API keys in URLs, full third-party payloads
Database operations
Log query metadata and execution details without exposing full result sets or credentials.
log.debug({
message: 'Database query executed',
context: {
collection: 'users',
operation: 'findOne',
query: { _id: userId },
resultFound: !!result,
duration: queryDuration
}
});
Include: Collection/table name, operation type, query parameters (if not sensitive), success/failure status, execution time
Exclude: Full result sets, connection strings with credentials, entire documents with PII
Workflows
Log workflow execution details that help track automation progress without exposing configuration or payload data.
log.info({
message: 'Workflow execution started',
context: {
workflowName: 'deploy_configuration',
workflowId: workflow.id,
triggeredBy: user.username,
targetDevices: deviceCount
}
});
Include: Workflow name and ID, initiating user (username only), count of affected resources, execution stage
Exclude: Complete workflow payloads, device configurations, credential data, full third-party responses
Authentication
Log authentication events for security auditing without exposing credentials or session tokens.
log.info({
message: 'User authentication successful',
context: {
userId: user.id,
username: user.username,
authMethod: 'ldap',
sourceIp: req.ip
}
});
Include: User identifier, authentication method, success/failure status, source IP (if relevant)
Exclude: Passwords, credentials, full authentication payloads, session tokens
Third-party integrations
Log integration operation metadata without exposing full configurations or sensitive data from external systems.
log.debug({
message: 'Device configuration retrieved',
context: {
deviceId: device.id,
deviceType: device.type,
configSize: config.length,
retrievalTime: duration
}
});
Include: Metadata about the operation (device ID, data size, timing, success/failure)
Exclude: Full configurations, credentials, proprietary settings, customer data
Handle errors
Always include error objects with error and warn level logs. Provide context about what operation failed.
try {
await database.updateUser(userId, updates);
} catch (err) {
log.error({
message: 'Failed to update user in database',
context: {
userId: userId,
operation: 'updateUser',
attemptedUpdates: Object.keys(updates)
},
error: err
});
throw err;
}
Best practices:
- Explain what the code was trying to do when it failed
- Include relevant identifiers (user ID, resource ID)
- Include the error object for stack traces
- Don't log the same error multiple times as it propagates
Use appropriate log frequency
Log summaries of batch operations rather than individual iterations. Excessive logging impacts performance and creates noise.
Do this:
log.info({
message: 'Processed device configurations',
context: {
deviceCount: devices.length,
successCount: results.filter(r => r.success).length,
failureCount: results.filter(r => !r.success).length,
duration: processingTime
}
});
Not this:
devices.forEach(device => {
log.debug({
message: 'Processing device',
context: { deviceId: device.id }
});
processDevice(device);
});
Best practices:
- Log summaries of batch operations, not individual iterations
- Use
traceorspamfor high-frequency events during development - Remove or reduce verbosity in production code
Common patterns
Use these code patterns as templates for implementing logging in your Adapters, Integrations, and applications.
Successful operation
log.info({
message: 'Configuration deployed successfully',
context: {
workflowId: workflow.id,
deviceCount: 5,
duration: executionTime
}
});
Recoverable warning
log.warn({
message: 'API request retry scheduled after timeout',
context: {
service: 'ServiceNow',
endpoint: '/api/now/table/incident',
retryAttempt: 2,
maxRetries: 3,
retryDelay: 5000
},
error: timeoutError
});
Unrecoverable error
log.error({
message: 'Database connection pool exhausted',
context: {
poolSize: config.db.poolSize,
activeConnections: pool.activeCount,
waitingRequests: pool.waitingCount
},
error: poolError
});
Debug information
log.debug({
message: 'External API response received',
context: {
service: 'ServiceNow',
endpoint: '/api/now/table/incident',
statusCode: response.status,
responseTime: responseTime,
recordCount: response.data.result.length
}
});
Third-party payload filtering
Do this - Log metadata only:
log.debug({
message: 'Network device configuration backup completed',
context: {
deviceId: device.id,
deviceType: 'cisco_ios',
configLines: configData.split('\n').length,
backupSize: Buffer.byteLength(configData),
duration: backupTime
}
});
Not this - Logging full configuration:
log.debug({
message: 'Network device configuration backup completed',
context: {
deviceId: device.id,
configData: configData // MAY CONTAIN PASSWORDS AND SENSITIVE SETTINGS
}
});
Automated validation
Requirements: Platform 2023.2 or 6.2+
Itential Platform automatically validates logging code during development. Validation checks:
- All log calls pass a single object as argument
- Object contains a
messageproperty contextproperty (if present) is an objectlog.errorandlog.warncalls include anerrorproperty
Code violating these rules triggers ESLint warnings.
Valid:
log.info({ message: 'Operation completed' });
log.error({
message: 'Operation failed',
context: { userId: '123' },
error: err
});
Invalid:
log.info('Operation completed'); // Not an object
log.error({ message: 'Failed' }); // Missing error property
log.info({ context: { userId: '123' }}); // Missing message
Migrate to structured logging
Requirements: Platform 2023.2 or 6.2+
Before migration:
log.warn('Service connection timed out', 'service-A', 5000);
After migration:
log.warn({
message: 'Service connection timed out',
context: {
serviceName: 'service-A',
timeoutMs: 5000
},
error: err
});
Migration steps:
- Identify the primary message (first argument)
- Create a
contextobject with named fields for additional data - Verify no sensitive data is included in context
- Add
errorobject if logging an error or warning
Multi-argument logging (Platform 2023.1 and earlier)
For Platform versions before 2023.2, use the multi-argument logging signature. Pass only safe, specific values as arguments.
log.info('User authentication successful', userId, authMethod);
Never pass entire objects:
// Don't pass entire objects - may contain sensitive data
log.info('User authenticated', user);
log.debug('Request received', req);
log.debug('API response', apiResponse);
Best practices:
- Only pass specific, known-safe values as arguments
- Avoid passing request, response, user, or configuration objects
- Manually extract safe fields before logging
- Filter third-party payloads before logging any data