Skip to content

Synthetic Activity Tool

scripts/generate_activity.py drives constant, randomised activity through the Salesforce REST API so a dev org has realistic data for EventLogFile ingestion and the streaming pipeline to chew on. It's a dev/test-only utility — it only ever touches the org named by SF_LOGIN_URL, and has no place in a production deployment.

What it generates

Covers the breadth of event categories a real org sees:

  • object CRUD (Account, Contact, Lead, Opportunity, Case, Task, Event) — API/RestApi ELF events, plus the underlying records product queries hit
  • SOQL queries — API ELF events
  • SOSL searches — Search ELF events
  • Bulk API 2.0 ingest and query jobs — BulkApi ELF events
  • composite / batch requests — API ELF events
  • sObject + global describe calls — API (metadata) ELF events
  • file upload + download (ContentVersion) — ContentTransfer / file-transfer ELF events
  • anonymous Apex via the Tooling API — ApexExecution ELF events
  • periodic re-authentication and token revocation — Login / Logout ELF events plus LoginEventStream

Capability-gated: a one-time discover() probe at startup checks which of these the org actually has, logs the active set, and silently skips the rest so the tool stays reliable in any org:

  • report runs, synchronous and async — Report / AsyncReportRun ELF events
  • dashboard reads — Dashboard ELF events
  • Campaign + CampaignMember, and Contract records — more object coverage

Actions are picked from a weighted menu each tick, so the traffic pattern looks organic rather than lock-step. URI, LightningPageView, and LightningInteraction stay out of reach — see Caveats.

Data pools

To keep the tenant looking real across long runs, account/company and person values are drawn from large committed pools rather than a handful of hard-coded names:

  • scripts/data/companies.csvname,industry,website,description (industry is a valid Salesforce picklist value; hundreds of fictional companies spanning regions and all standard industries).
  • scripts/data/people.csvfirst_name,last_name,title (hundreds of fictional people).

Records draw a base name from these pools and often get a short random suffix (a number or letter code) appended; emails get a random numeric tag — so repeat runs keep minting fresh-looking records instead of colliding. If the CSVs are missing, the script falls back to a small built-in pool, so it always runs. Creates also send Sforce-Duplicate-Rule-Header: allowSave=true, so Salesforce duplicate-alert rules never hard-fail a create.

Prerequisites

Same JWT-bearer credentials sf2loki itself uses — the script authenticates with the identical flow (see Salesforce setup). It reads these from a .env-style file or the environment:

  • SF_LOGIN_URL
  • SF_CLIENT_ID
  • SF_USERNAME
  • SF_PRIVATE_KEY_FILE
  • SF_API_VERSION (optional; also settable via --api-version)

.env.dev (gitignored, the same file the docker-compose setup uses) is a convenient place to keep these.

Usage

# continuous low-level noise (~6 ops/min) until Ctrl-C
python scripts/generate_activity.py --env-file .env.dev

# heavier burst for an hour
python scripts/generate_activity.py --env-file .env.dev --ops-per-min 30 --duration 3600

# delete everything this tool ever created
python scripts/generate_activity.py --env-file .env.dev --cleanup

Flags

Flag Default Description
--env-file PATH none Load KEY=VALUE env vars from this file (e.g. .env.dev).
--ops-per-min N 6 Approximate operations per minute.
--duration N 0 Run for N seconds then stop (0 = until Ctrl-C).
--relogin-every N 420 Re-authenticate every N seconds (produces Login events).
--api-version VER 60.0 (or SF_API_VERSION) Salesforce API version.
--cleanup off Delete all records this tool created, then exit.
--verbose / -v off Debug logging.

Cleanup marker

Every record the script creates gets [sf2loki-synthetic] stamped into its Description field. --cleanup walks each object type it can create (Task, Event, Case, Opportunity, Contact, Lead, Account — children first so parents delete cleanly) and deletes every marked record via the sObject Collections API, so a run can always be fully undone without hand-picking records.

Description is a Long Text Area, which SOQL cannot filter on (WHERE Description LIKE … raises INVALID_FIELD), so cleanup SELECTs the Description and matches the marker client-side. This is also why it only ever deletes records this tool created — real or sample records in the org (which don't carry the marker) are never touched.

Caveats

Dev/test tool only

This script performs real writes (and deletes, under --cleanup) against whatever org SF_LOGIN_URL points at. Never point it at a production org.

  • Cases are never closed. Closing a Case fires Salesforce's post-close Survey email flow, which would spam the org user's mailbox. The generator creates and works/escalates Cases but deliberately has no "close case" action.
  • UI-only ELF types can't be generated via API. URI, LightningPageView, and LightningInteraction EventLogFile types require a real browser UI session — there's no REST/Bulk API path that produces them, so this script can't cover them.