[salesforce] Order EventLogFile queries by the cursor field (CreatedDate) - #19954
Conversation
…ate) The Apex, Login, and Logout EventLogFile queries sorted results by `LogDate` while tracking the collection cursor on `CreatedDate`. Those two fields are not correlated for EventLogFile records: Salesforce can create a log for an earlier `LogDate` period after one for a later period, so the last record in `LogDate` order frequently does not carry the maximum `CreatedDate`. Because the input watermarks the cursor from the last processed record, the stored `event_log_file.last_event_time` could be set below the newest `CreatedDate` already ingested, so the next poll re-collected data it had already fetched. Order these queries by `CreatedDate` (the cursor field), matching the already-correct SetupAuditTrail query, so the watermark only advances. The change is limited to the ORDER BY clause; the WHERE filter and cursor field are unchanged, so existing persisted cursors remain valid and no data is skipped on upgrade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
✅ Elastic Docs Style Checker (Vale)No issues found on modified lines! The Vale linter checks documentation changes against the Elastic Docs style guide. To use Vale locally or report issues, refer to Elastic style guide for Vale. |
There was a problem hiding this comment.
Pull request overview
Aligns Salesforce EventLogFile ordering with the CreatedDate cursor to prevent cursor regression and duplicate collection.
Changes:
- Orders Apex, Login, and Logout queries by
CreatedDate. - Updates system-test mock query matchers.
- Bumps the package to 1.8.1 with a changelog entry.
Repository review skills can be installed with /plugin install integration-skills@elastic-integration-skills.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
packages/salesforce/manifest.yml |
Bumps package version. |
packages/salesforce/changelog.yml |
Documents the cursor fix. |
packages/salesforce/data_stream/apex/agent/stream/salesforce.yml.hbs |
Corrects Apex query ordering. |
packages/salesforce/data_stream/login/agent/stream/salesforce.yml.hbs |
Corrects Login query ordering. |
packages/salesforce/data_stream/logout/agent/stream/salesforce.yml.hbs |
Corrects Logout query ordering. |
packages/salesforce/_dev/deploy/docker/files/config.yml |
Updates mock query expectations. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
🚀 Benchmarks reportTo see the full report comment with |
ishleenk17
left a comment
There was a problem hiding this comment.
1 question.
Otherwise looks good!
| query: | ||
| default: SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE {{#if initial_interval}}LogDate > [[ (formatTime (now.Add (parseDuration "-{{initial_interval}}")) "2006-01-02T15:04:05.000Z0700") ]] AND {{/if}}{{#if log_file_interval}}Interval = '{{log_file_interval}}' AND {{/if}}(EventType = 'ApexCallout' OR EventType = 'ApexExecution' OR EventType = 'ApexRestApi' OR EventType = 'ApexSoap' OR EventType = 'ApexTrigger' OR EventType = 'ExternalCustomApexCallout') ORDER BY LogDate ASC NULLS FIRST | ||
| value: SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE {{#if log_file_interval}}Interval = '{{log_file_interval}}' AND {{/if}}CreatedDate > [[ .cursor.event_log_file.last_event_time ]] AND (EventType = 'ApexCallout' OR EventType = 'ApexExecution' OR EventType = 'ApexRestApi' OR EventType = 'ApexSoap' OR EventType = 'ApexTrigger' OR EventType = 'ExternalCustomApexCallout') ORDER BY LogDate ASC NULLS FIRST | ||
| default: SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE {{#if initial_interval}}LogDate > [[ (formatTime (now.Add (parseDuration "-{{initial_interval}}")) "2006-01-02T15:04:05.000Z0700") ]] AND {{/if}}{{#if log_file_interval}}Interval = '{{log_file_interval}}' AND {{/if}}(EventType = 'ApexCallout' OR EventType = 'ApexExecution' OR EventType = 'ApexRestApi' OR EventType = 'ApexSoap' OR EventType = 'ApexTrigger' OR EventType = 'ExternalCustomApexCallout') ORDER BY CreatedDate ASC NULLS FIRST |
There was a problem hiding this comment.
@shmsr :
So the initial lookback window is bounded by LogDate, but once the cursor is established, all subsequent polls use CreatedDate.
Is that right ?
There was a problem hiding this comment.
Yes!
LogDate is the start of the interval the events occurred in (the hour/day the file covers). CreatedDate is when Salesforce generated the file. So, CreatedDate is now being used to find new files, including late ones as files are not always generated in LogDate order, and a later job can regenerate a file for an earlier LogDate.
So the first poll (no cursor) depends on LogDate for initial_interval. After that we cursor on CreatedDate.
|
Tick the box to add this pull request to the merge queue (same as
|
|
✅ All changelog entries have the correct PR link. |
💚 Build Succeeded
History
cc @shmsr |
|
Package salesforce - 1.8.1 containing this change is available at https://epr.elastic.co/package/salesforce/1.8.1/ |
What does this PR do?
Orders the Apex, Login, and Logout
EventLogFilequeries byCreatedDate(the cursor field) instead ofLogDate, in both thedefaultandvaluequeries.Background: two unrelated timestamps
Every
EventLogFilerecord has two datetime fields that mean different things:LogDate— the period the log covers (e.g. the start of the hour/day).CreatedDate— when Salesforce actually generated the file. This lagsLogDate, and the lag is variable (minutes to days), soCreatedDateorder does not followLogDateorder.These data streams track the collection cursor on
CreatedDate(cursor.field: CreatedDate) and resume withWHERE CreatedDate > <cursor>, but the queries sorted the results byLogDate:The bug, with an example
The input watermarks the cursor from the last record it processes in a page. When the page is ordered by
LogDate, that last record is not necessarily the one with the greatestCreatedDate.Consider two log files where the one covering the later period happened to be generated first:
2026-06-22T00:00:00Z2026-06-24T11:28:06Z2026-06-23T00:00:00Z2026-06-24T11:08:05ZORDER BY LogDate ASCreturns them as#1then#2. The input processes#1(sets watermark11:28:06), then#2(overwrites watermark with11:08:05). So the storedlast_event_timeends at2026-06-24T11:08:05Z— earlier than a record it already ingested (11:28:06).On the next poll:
…record #1 (
11:28:06) matches again and is re-collected. The cursor effectively lags behind the data and re-fetches already-ingested files each poll.This is not a contrived case — sorting real
EventLogFileresults byLogDateproduces multiple suchCreatedDateinversions per page whenever files are generated slightly out of period order (common for hourly logs).The fix
Order by the cursor field so the last record always carries the maximum
CreatedDate:With the example above,
ORDER BY CreatedDate ASCreturns#2then#1, so the watermark ends at2026-06-24T11:28:06Z(the true maximum) and the next poll (CreatedDate > 11:28:06) does not re-collect either file. This matches the already-correctSetupAuditTrailquery, which orders by its cursor field.Why is it important?
Prevents repeated re-collection of already-ingested
EventLogFiledata and keeps the collection cursor moving strictly forward.Compatibility / upgrade safety
The change is limited to the
ORDER BYclause. TheWHEREfilter andcursor.fieldare unchanged, so:CreatedDatevalues and are interpreted identically.Checklist
changelog.ymlfile.How to test this PR locally
elastic-package lintinpackages/salesforce.elastic-package test system -vfor theapex,login, andlogoutdata streams (the mock server query matchers in_dev/deploy/docker/files/config.ymlare updated to match the new ordering).