Skip to main content

⚙️ Implementation Details

Dive into the codebase: modules, key classes, and how core features work.

Repository Structure​

apexium-log/
├── apexium # CLI wrapper (bash) — setup wizard, stack control
├── frontend/ # React/TypeScript frontend (Vite, git submodule)
├── src/main/kotlin/com/observability/sfdc/
│ ├── SfdcApplication.kt # Spring Boot entry point
│ ├── config/ # Spring configuration
│ │ ├── MinioConfig.kt # MinIO (S3) client setup
│ │ ├── OpenApiConfig.kt # Swagger/OpenAPI documentation
│ │ ├── RedisConfig.kt # Redis cache configuration
│ │ └── SecurityConfig.kt # CORS, API key auth
│ ├── controller/ # REST endpoints
│ │ ├── AdminController.kt # Cache management
│ │ ├── SalesforceLogController.kt # Logs, trace flags, trace jobs
│ │ ├── SalesforceMetadataController.kt # Classes, triggers, reports, debug levels
│ │ └── SalesforceUserController.kt # User retrieval
│ ├── service/ # Business logic interfaces
│ │ ├── ApexLogService.kt
│ │ ├── TraceFlagService.kt
│ │ ├── ApexClassMetadataService.kt
│ │ ├── ApexTriggerMetadataService.kt
│ │ ├── DebugLevelMetadataService.kt
│ │ ├── MetadataDetailService.kt
│ │ ├── MetadataHistoryService.kt
│ │ ├── ReportMetadataService.kt
│ │ ├── UserService.kt
│ │ ├── AuthService.kt
│ │ ├── SalesforceBaseService.kt
│ │ └── impl/ # Concrete implementations
│ │ └── OrgContextService.kt
│ ├── repository/ # JPA repositories
│ ├── domain/ # Entity classes
│ ├── dto/ # Data transfer objects
│ ├── exception/ # Custom exceptions
│ └── util/ # Utility helpers
├── docker-compose.yml # Infrastructure orchestration
├── pom.xml # Maven build (backend)
└── .env # Environment config (generated, git-ignored)

Key Packages & Classes​

ClassPackagePurpose
SfdcApplicationrootSpring Boot entry point
SalesforceLogControllercontrollerLogs CRUD, trace flags, trace jobs endpoints
SalesforceMetadataControllercontrollerClasses, triggers, reports, debug levels endpoints
SalesforceUserControllercontrollerUser retrieval endpoints
AdminControllercontrollerCache flush endpoint
ApexLogServiceserviceLog query, download, delete from Salesforce + local storage
TraceFlagServiceserviceTraceFlag CRUD via Salesforce Tooling API
TraceJobServiceservice.implManaged trace jobs with sliding window auto-renewal
MetadataHistoryServiceserviceChange timeline and body diff comparison
MetadataDetailServiceserviceDeep details with coverage and test classes
ReportMetadataServiceserviceReport metadata, describe, SOQL conversion
OrgContextServiceservice.implMulti-org context and Redis cache management
SalesforceBaseServiceserviceBase class for Salesforce API communication
AuthServiceserviceSalesforce OAuth 2.0 token management
MinioConfigconfigMinIO client and bucket setup
RedisConfigconfigRedis cache manager configuration
SecurityConfigconfigCORS origins and API key filter

Trace Job Sliding Window​

The core innovation — solving Salesforce's 24-hour Trace Flag limit:

1. User creates TraceJob via API
↓
2. TraceJobService creates TraceFlag in Salesforce (expires in 24h)
↓
3. Background scheduler monitors active jobs
↓
4. Before expiry (e.g., at 23h) → deletes old flag → creates new flag
↓
5. Sliding window repeats until user cancels
↓
6. User cancels → flag deleted → job marked completed

Key class: TraceJobService in service/impl/

// Simplified flow
fun createJob(request: FrontendTraceFlagRequest): TraceJob {
val traceFlag = traceFlagService.createTraceFlag(request)
return traceJobRepository.save(TraceJob(...))
}

fun renewJob(job: TraceJob) {
traceFlagService.deleteTraceFlag(job.activeTraceFlagId)
val newFlag = traceFlagService.createTraceFlag(job.toRequest())
job.activeTraceFlagId = newFlag.id
traceJobRepository.save(job)
}

Metadata Change Tracking​

Scheduled polling detects changes in Apex classes and triggers:

Every 6 hours (configurable):
↓
1. Fetch all ApexClass/ApexTrigger from Salesforce Tooling API
↓
2. Compare with local database records
↓
3. If body changed → store snapshot in MinIO, record history entry
↓
4. History includes: who changed, when, old body reference
↓
5. Diff endpoint compares current body vs any historical snapshot

Storage flow:

  • Current body → PostgreSQL body_text column
  • Historical snapshots → MinIO (sfdc-bucket/classes/{id}/{timestamp})
  • Diff → fetch both bodies, compute line-by-line diff

Log Polling & Storage​

Every 1 minute:
↓
1. Query Salesforce Tooling API for new ApexLog records
↓
2. Filter out already-processed logs (by Salesforce ID)
↓
3. Store metadata in PostgreSQL (author, class, timestamp, size)
↓
4. Fetch full log body → compress → store in MinIO
↓
5. Body available via /api/sfdc/logs/{id}/body or /download

Retention: Logs older than 15 days are cleaned up daily at 6 AM.

CLI (apexium)​

Bash script that orchestrates the entire stack:

CommandAction
./apexium startRuns setup wizard (if no .env), starts Docker Compose, waits for health
./apexium initForce re-runs wizard, overwrites Salesforce credentials
./apexium stopdocker compose down
./apexium restartStop then start
./apexium logsdocker compose logs -f app
./apexium statusHealth check + container status

Setup wizard flow:

  1. Check prerequisites (Docker, Docker Compose, Java 21+)
  2. Prompt for Salesforce Instance URL, Consumer Key, Consumer Secret
  3. Write Salesforce config to .env
  4. Fill infrastructure defaults (PostgreSQL, Redis, MinIO, pgAdmin)
  5. Generate unique APEXIUM_API_KEY (openssl/rand)
  6. Run docker compose up -d --build
  7. Wait for backend health check

Configuration​

Environment Variables​

VariableDefaultPurpose
SALESFORCE_INSTANCE_ORG—Salesforce login URL
SALESFORCE_CLIENT_ID—Connected App consumer key
SALESFORCE_CLIENT_SECRET—Connected App consumer secret
SALESFORCE_GRANT_TYPEclient_credentialsOAuth grant type
SALESFORCE_API_VERSIONv61.0Salesforce API version
DB_HOSTlocalhostPostgreSQL host
DB_PORT5432PostgreSQL port
DB_NAMEsfdc_logsDatabase name
DB_USERpostgresDatabase user
DB_PASSWORDpostgresDatabase password
REDIS_HOSTlocalhostRedis host
REDIS_PORT6379Redis port
MINIO_URLhttp://localhost:9000MinIO endpoint
MINIO_ACCESS_KEYminioadminMinIO access key
MINIO_SECRET_KEYminioadminMinIO secret key
MINIO_BUCKET_NAMEsfdc-bucketStorage bucket
APEXIUM_API_KEYauto-generatedBackend API key
APEXIUM_CORS_ALLOWED_ORIGINShttp://localhost:5173CORS origins

Scheduling Configuration​

PropertyValueDescription
log.poll.ratePT1MLog polling interval (ISO 8601)
metadata.poll.ratePT6HMetadata polling interval
metadata.poll.batch-size200Records per metadata batch
log.retention.days15Days to retain logs
log.retention.cron0 0 6 * * ?Cleanup schedule (6 AM daily)

Testing​

  • Unit tests — JUnit 5 + MockK in src/test/kotlin/
  • Integration tests — PostgreSQL testcontainers + mocked Salesforce API
  • E2E tests — against a real Salesforce sandbox (manual or CI on-demand)
  • Frontend tests — Vitest + React Testing Library in frontend/