Logging best practices

Follow these guidelines when implementing logging in Itential Platform adapters, applications, and integrations.

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

Platform does not filter data from external systems:

  • 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

You must 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.

Always create context objects containing only known-safe fields. Never log entire objects that may contain sensitive data.

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:

1log.info({
2 message: 'User authentication successful',
3 context: {
4 userId: user.id,
5 username: user.username,
6 method: 'ldap'
7 }
8});

Not this:

1// Multi-argument format (legacy)
2log.info('User authentication successful', user);

Structured logging enables automated parsing and prevents accidental exposure of sensitive data.

Migrate to structured logging

Requirements: Platform 2023.2 or 6.2+

Update existing multi-argument log calls to structured logging format.

Before migration:

1log.warn('Service connection timed out', 'service-A', 5000);

After migration:

1log.warn({
2 message: 'Service connection timed out',
3 context: {
4 serviceName: 'service-A',
5 timeoutMs: 5000
6 },
7 error: err
8});

Migration steps:

1

Set logger version

Set loggerVersion to 2 in your application’s pronghorn.json file.

2

Identify primary message

Find the main message (typically first argument).

3

Create context object

Create context object with named fields for additional data.

4

Verify no sensitive data

Ensure context contains only known-safe fields.

5

Add error object

Include error object for error and warn levels.

Choose the correct log level

Select appropriate severity level based on event type and required response.

LevelWhen to useExample
systemCritical platform lifecycle events onlyPlatform initialization complete, shutting down
errorUnrecoverable errors requiring immediate attentionDatabase connection lost, authentication service unavailable
warnRecoverable issues requiring monitoringAPI retry in progress, deprecated feature used
infoNormal operational messagesUser logged in, workflow completed, service started
debugDetailed diagnostic information for troubleshootingConfiguration loaded, API response received
traceStep-by-step execution flowFunction entry/exit, variable values
spamExtremely frequent eventsLoop iterations, polling events

Best practices for log levels

  • Use info for events you’d want in production dashboards
  • Use debug only during active troubleshooting
  • Reserve error for situations requiring human intervention
  • Always include error objects with error and warn levels
  • Never use trace or spam in production

Write clear messages

Write log messages that explain what happened and provide troubleshooting context.

Do this:

1log.error({
2 message: 'Failed to update user profile in database',
3 context: {
4 userId: 'user123',
5 operation: 'updateEmail'
6 },
7 error: err
8});

Not this:

1log.error({ message: 'Update failed', error: err });

Message guidelines

  • State what operation was attempted
  • Include relevant identifiers (user ID, resource ID)
  • Explain the outcome (success, failure, retry)
  • Provide context for troubleshooting
  • Avoid generic messages like “Error occurred” or “Processing complete”

Filter sensitive data

Always create context objects containing only known-safe fields.

Create safe context objects

Extract only the fields you need and explicitly define them.

Do this:

1const logContext = {
2 userId: user.id,
3 username: user.username,
4 operation: 'updateProfile'
5};
6
7log.info({
8 message: 'User profile updated successfully',
9 context: logContext
10});

Not this:

1// Entire user object may contain email, address, or other PII
2log.info({
3 message: 'User profile updated successfully',
4 context: user // NEVER DO THIS
5});

Filter third-party payloads

Extract only metadata from third-party responses. Never log full response bodies.

Do this:

1log.debug({
2 message: 'ServiceNow API response received',
3 context: {
4 endpoint: '/api/now/table/incident',
5 statusCode: response.status,
6 recordCount: response.data.result.length
7 }
8});

Not this:

1// response.data may contain PII or sensitive content
2log.debug({
3 message: 'ServiceNow API response received',
4 context: {
5 endpoint: '/api/now/table/incident',
6 responseData: response.data // NEVER DO THIS
7 }
8});

Sensitive data categories

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:

1log.info({
2 message: 'Outbound API request',
3 context: {
4 service: 'ServiceNow',
5 endpoint: '/api/now/table/incident',
6 method: 'POST',
7 correlationId: req.headers['x-correlation-id']
8 }
9});

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:

1log.debug({
2 message: 'Database query executed',
3 context: {
4 collection: 'users',
5 operation: 'findOne',
6 query: { _id: userId },
7 resultFound: !!result,
8 duration: queryDuration
9 }
10});

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:

1log.info({
2 message: 'Workflow execution started',
3 context: {
4 workflowName: 'deploy_configuration',
5 workflowId: workflow.id,
6 triggeredBy: user.username,
7 targetDevices: deviceCount
8 }
9});

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:

1log.info({
2 message: 'User authentication successful',
3 context: {
4 userId: user.id,
5 username: user.username,
6 authMethod: 'ldap',
7 sourceIp: req.ip
8 }
9});

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:

1log.debug({
2 message: 'Device configuration retrieved',
3 context: {
4 deviceId: device.id,
5 deviceType: device.type,
6 configSize: config.length,
7 retrievalTime: duration
8 }
9});

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.

1try {
2 await database.updateUser(userId, updates);
3} catch (err) {
4 log.error({
5 message: 'Failed to update user in database',
6 context: {
7 userId: userId,
8 operation: 'updateUser',
9 attemptedUpdates: Object.keys(updates)
10 },
11 error: err
12 });
13 throw err;
14}

Error handling 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 level (error vs warn)

Use appropriate log frequency

Log summaries of batch operations rather than individual iterations.

Do this:

1log.info({
2 message: 'Processed device configurations',
3 context: {
4 deviceCount: devices.length,
5 successCount: results.filter(r => r.success).length,
6 failureCount: results.filter(r => !r.success).length,
7 duration: processingTime
8 }
9});

Not this:

1devices.forEach(device => {
2 log.debug({
3 message: 'Processing device',
4 context: { deviceId: device.id }
5 });
6 processDevice(device);
7});

Frequency best practices

  • Log summaries of batch operations, not individual iterations
  • Use trace or spam for high-frequency events during development
  • Remove or reduce verbosity in production code
  • Consider performance impact of excessive logging

Common logging patterns

Successful operation

1log.info({
2 message: 'Configuration deployed successfully',
3 context: {
4 workflowId: workflow.id,
5 deviceCount: 5,
6 duration: executionTime
7 }
8});

Recoverable warning

1log.warn({
2 message: 'API request retry scheduled after timeout',
3 context: {
4 service: 'ServiceNow',
5 endpoint: '/api/now/table/incident',
6 retryAttempt: 2,
7 maxRetries: 3,
8 retryDelay: 5000
9 },
10 error: timeoutError
11});

Unrecoverable error

1log.error({
2 message: 'Database connection pool exhausted',
3 context: {
4 poolSize: config.db.poolSize,
5 activeConnections: pool.activeCount,
6 waitingRequests: pool.waitingCount
7 },
8 error: poolError
9});

Debug information

1log.debug({
2 message: 'External API response received',
3 context: {
4 service: 'ServiceNow',
5 endpoint: '/api/now/table/incident',
6 statusCode: response.status,
7 responseTime: responseTime,
8 recordCount: response.data.result.length
9 }
10});

Third-party payload filtering

Do this - Log metadata only:

1log.debug({
2 message: 'Network device configuration backup completed',
3 context: {
4 deviceId: device.id,
5 deviceType: 'cisco_ios',
6 configLines: configData.split('\n').length,
7 backupSize: Buffer.byteLength(configData),
8 duration: backupTime
9 }
10});

Not this - Logging full configuration:

1log.debug({
2 message: 'Network device configuration backup completed',
3 context: {
4 deviceId: device.id,
5 configData: configData // MAY CONTAIN PASSWORDS AND SENSITIVE SETTINGS
6 }
7});

Automated validation

Requirements: Platform 2023.2 or 6.2+

Platform automatically validates logging code during development.

Validation checks:

  • All log calls pass a single object as argument
  • Object contains a message property
  • context property (if present) is an object
  • log.error and log.warn calls include an error property

Code violating these rules triggers ESLint warnings.

Valid:

1log.info({ message: 'Operation completed' });
2
3log.error({
4 message: 'Operation failed',
5 context: { userId: '123' },
6 error: err
7});

Invalid:

1log.info('Operation completed'); // Not an object
2
3log.error({ message: 'Failed' }); // Missing error property
4
5log.info({ context: { userId: '123' }}); // Missing message

Multi-argument logging (Platform 2023.1 and earlier)

For Platform versions before 2023.2, use multi-argument logging signature.

Pass only safe, specific values as arguments:

1log.info('User authentication successful', userId, authMethod);

Never pass entire objects:

1// Don't pass entire objects - may contain sensitive data
2log.info('User authenticated', user); // WRONG
3log.debug('Request received', req); // WRONG
4log.debug('API response', apiResponse); // WRONG

Legacy logging 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

Performance considerations

Logging overhead

  • Console logging has higher performance impact than file logging
  • Structured JSON logging is more efficient than standard format
  • Excessive logging in hot paths impacts performance
  • Log level affects volume and performance

Production recommendations

  • Use info or warn log level
  • Disable debug logging
  • Never use trace or spam
  • Log summaries of batch operations
  • Monitor log volume and performance impact

Next steps