Skip to Content

Audit Trail and Change Logging

Create detailed audit trail records when sensitive fields are modified, maintaining compliance and tracking changes.

Table: incident When: after
#audit #logging #compliance #tracking #after #update #change-history #security

Script Code

JavaScript
1(function executeRule(current, previous /*null when async*/) {
2
3  // Configuration: Define fields to audit
4  var auditedFields = [
5    'priority',
6    'state',
7    'assignment_group',
8    'assigned_to',
9    'close_code',
10    'resolved_by'
11  ];
12
13  // Configuration: Define critical tables that require full auditing
14  var criticalTables = ['incident', 'change_request', 'problem'];
15
16  // Check if any audited fields changed
17  var changedFields = [];
18  auditedFields.forEach(function(fieldName) {
19    if (current[fieldName].changes()) {
20      changedFields.push(fieldName);
21    }
22  });
23
24  // Only proceed if there are changes to audit
25  if (changedFields.length === 0) {
26    return;
27  }
28
29  // Create audit records for each changed field
30  changedFields.forEach(function(fieldName) {
31    var grAudit = new GlideRecord('u_audit_trail');  // Create this table for audit logging
32    grAudit.initialize();
33
34    // Record metadata
35    grAudit.u_table_name = current.getTableName();
36    grAudit.u_record_id = current.sys_id.toString();
37    grAudit.u_record_number = current.number.toString();
38    grAudit.u_field_name = fieldName;
39
40    // Get field label
41    var fieldLabel = current.getElement(fieldName).getLabel();
42    grAudit.u_field_label = fieldLabel;
43
44    // Record old and new values
45    var oldValue = previous ? previous.getValue(fieldName) : '';
46    var newValue = current.getValue(fieldName);
47
48    grAudit.u_old_value = oldValue;
49    grAudit.u_new_value = newValue;
50
51    // Get display values for reference fields
52    if (current.getElement(fieldName).getED().isReference()) {
53      grAudit.u_old_display_value = previous ? previous[fieldName].getDisplayValue() : '';
54      grAudit.u_new_display_value = current[fieldName].getDisplayValue();
55    }
56
57    // Record who made the change
58    grAudit.u_changed_by = gs.getUserID();
59    grAudit.u_changed_at = new GlideDateTime();
60
61    // Record IP address and session info
62    grAudit.u_ip_address = gs.getSession().getClientIP();
63    grAudit.u_session_id = gs.getSession().getSessionToken();
64
65    // Categorize the change type
66    if (fieldName === 'state') {
67      grAudit.u_change_type = 'Status Change';
68    } else if (fieldName === 'assignment_group' || fieldName === 'assigned_to') {
69      grAudit.u_change_type = 'Assignment Change';
70    } else if (fieldName === 'priority') {
71      grAudit.u_change_type = 'Priority Change';
72    } else {
73      grAudit.u_change_type = 'Field Update';
74    }
75
76    // Add business context
77    grAudit.u_reason = current.work_notes.toString() || 'No reason provided';
78
79    // Insert audit record
80    var auditId = grAudit.insert();
81
82    if (auditId) {
83      gs.info('Audit trail created for ' + current.number + ', field: ' + fieldLabel +
84              ', changed by: ' + gs.getUserName());
85    }
86  });
87
88  // Optional: Send alert for critical changes
89  if (changedFields.indexOf('priority') !== -1) {
90    var oldPriority = previous ? previous.priority.toString() : '';
91    var newPriority = current.priority.toString();
92
93    // Alert if priority increased to Critical
94    if (newPriority === '1' && oldPriority !== '1') {
95      gs.eventQueue('audit.critical_priority_change', current, gs.getUserID(), current.sys_id);
96      gs.warn('AUDIT: Priority changed to Critical for ' + current.number +
97              ' by ' + gs.getUserName());
98    }
99  }
100
101  // Optional: Add audit summary to work notes
102  var auditSummary = 'AUDIT: Fields changed - ' + changedFields.join(', ') +
103                     ' | Changed by: ' + gs.getUserName() +
104                     ' | Time: ' + new GlideDateTime().getDisplayValue();
105  current.comments = auditSummary;
106
107})(current, previous);

How to Use

1. Create a custom table u_audit_trail with fields: u_table_name, u_record_id, u_record_number, u_field_name, u_field_label, u_old_value, u_new_value, u_old_display_value, u_new_display_value, u_changed_by, u_changed_at, u_ip_address, u_session_id, u_change_type, u_reason 2. Create an after Business Rule on tables you want to audit 3. Check "Update" checkbox only 4. Customize auditedFields array for your requirements 5. Consider data retention policies for audit records 6. Test thoroughly to ensure no performance impact

Explore More Scripts

Browse our complete library of ServiceNow scripts

View All Scripts