Conversation
Introduce the new eventlens-spi contract layer with versioned plugin interfaces and baseline contract tests, so v3 plugin work can proceed with a stable API. Add phased v3 execution and learned-notes planning files to preserve reusable implementation guidance during delivery.
Implement complete plugin lifecycle management system with discovery, health tracking, and safe shutdown capabilities. Core Components: - PluginManager: Lifecycle orchestration with state machine (DISCOVERED → INITIALIZING → READY/FAILED → DEGRADED → STOPPED) - PluginDiscovery: ServiceLoader-based discovery from classpath and external JAR directory with parent-first classloading - PluginInstance: Immutable record tracking plugin state, health, and failure reasons with clean state transition methods Features: - Scheduled health checks with automatic READY ↔ DEGRADED transitions - Isolated failure handling - one plugin failure doesn't block others - SPI version compatibility checks enforced at registration time - Safe shutdown: streams first (unsubscribe), then sources (close) - Graceful handling of missing directories and invalid JARs - Daemon thread pool for health checks to avoid blocking shutdown Configuration: - Add plugins.directory (default: ./plugins) with env var support - Add plugins.health-check-interval-seconds (default: 30) - Environment variable interpolation via existing ConfigLoader API Models: - DatasourceListingModel for /api/v1/datasources endpoint - PluginListingModel for /api/v1/plugins endpoint Testing: - Comprehensive PluginManagerTest covering registration, failures, health transitions, and lifecycle management - All tests passing Dependencies: - Add eventlens-spi dependency to eventlens-core module Documentation: - Update v3_phase_2_plugin_manager with implementation summary - Capture Phase 2 learnings in v3_reusable_notes.md - Add eventlens-example.yaml with plugin configuration examples All Phase 2 requirements completed. Ready for Phase 3 integration.
Implemented the first Phase 3 extraction slice: PostgreSQL and Kafka now live in new plugin modules, and server startup has been rewired to use the plugin system instead of direct legacy module wiring. The main changes are in settings.gradle.kts, ServeCommand.java, PluginManager.java, plus the new modules under eventlens-source-postgres and eventlens-stream-kafka. I also added the Phase 3 learnings to v3_reusable_notes.md. Verification passed with ./gradlew.bat test and ./gradlew.bat check. There doesn’t appear to be a separate lint task in this repo, so check was the closest full verification pass. I also made the copied Testcontainers integration tests skip cleanly when Docker isn’t available, so local verification stays green.
Phase 4 is implemented. I added the new MySQL source plugin in eventlens-source-mysql, introduced config normalization via ConfigMigrator.java, extended the shared config model in EventLensConfig.java, and updated startup in ServeCommand.java to register plural datasources[] and streams[] while still preserving the v2 singular boot path. I also added config migration tests in ConfigLoaderTest.java, expanded validation coverage in ConfigValidatorTest.java, updated the example config in eventlens-example.yaml, and captured the new learnings in v3_reusable_notes.md. Verification passed with ./gradlew.bat test and ./gradlew.bat check.
Phase 5 is now implemented across the API and UI. On the backend, I added source-aware request resolution plus bounded query caching in SourceRegistry.java and QueryResultCache.java, then wired new endpoints and optional source / fields=metadata support through EventLensServer.java, AggregateRoutes.java, TimelineRoutes.java, DatasourceRoutes.java, and PluginRoutes.java. I also added cache config to EventLensConfig.java, updated startup wiring in ServeCommand.java, and added the SPI dependency in build.gradle.kts. On the UI side, the app now keeps the selected datasource in URL state, disables unhealthy sources, and exposes a hash-based plugin health page at /#/plugins. The main wiring is in App.tsx, client.ts, types.ts, SearchBar.tsx, Timeline.tsx, and StateViewer.tsx. I also updated the example config in eventlens-example.yaml and added Phase 5 learnings to v3_reusable_notes.md. Verification passed with ./gradlew.bat test and ./gradlew.bat check
Phase 6 is implemented in the repo. I added the new shared contract-test module eventlens-plugin-test, wired it into the build in settings.gradle.kts, and added reusable source/stream test kits plus canonical assertions. Then I added concrete contract tests for the built-ins in PostgresEventSourcePluginContractTest.java, MySqlEventSourcePluginContractTest.java, and KafkaStreamAdapterPluginContractTest.java. I also prepared SPI publishing in eventlens-spi/build.gradle.kts. For docs and release readiness, I added plugin-authoring.md, v3-ga-checklist.md, updated README.md and CONTRIBUTING.md, and captured Phase 6 learnings in v3_reusable_notes.md. Verification passed with ./gradlew.bat test and ./gradlew.bat check. One release item is still naturally external: actual publication of eventlens-spi to Maven Central or GitHub Packages is prepared in build config, but not performed from inside the repo.
added: External plugin JAR loading proof via PluginDiscoveryExternalJarTest.java. It compiles a tiny SPI-only plugin into a temporary JAR with a real META-INF/services entry and verifies discovery from a plugin directory. Cache hit-ratio measurement via QueryResultCacheBenchmarkTest.java. This shows repeated-query access clears the > 50% target without adding any runtime cache machinery beyond what we already shipped. Metadata-only payload reduction proof via TimelineMetadataPayloadBenchmarkTest.java. It verifies fields=metadata cuts serialized response size by more than 70% on a synthetic large-payload timeline. I also updated the release docs so those SHOULD items are now backed by repo-local evidence in v3-ga-checklist.md and noted the follow-through in v3_reusable_notes.md. On complexity/cost: this was worth doing because the added complexity lives almost entirely in tests and docs, not in the serving path. Runtime performance impact is effectively none, RAM impact is effectively none, and app complexity only increased slightly in the verification layer. That’s the sweet spot for these kinds of release-readiness checks. Verification passed with ./gradlew.bat test and ./gradlew.bat check.
Implemented the three v4-readiness backend E2E scenarios in V4ReadinessApiE2ETest.java: multi-source switching, plugin failure isolation, and metadata-to-full-payload round-trip. I added the needed test dependencies in build.gradle.kts. I also completed the lazy-load UI plumbing those tests depend on: metadata-only timeline responses now return payload: null in TimelineRoutes.java the timeline now loads metadata first in useTimeline.ts the app no longer eagerly fetches full transitions just to count events, and degraded sources are disabled in App.tsx StoredEvent.payload is nullable in types.ts the grouping feature is still there in Timeline.tsx, just based on metadata events instead of full transitions so lazy loading still works ./gradlew.bat test and ./gradlew.bat check both passed.
- Use a 3-column header grid so the title and demo pill stay visually centered - Move datasource into the workspace side panel; keep API/events/uptime on the right - Replace the wide workspace card with a right-edge collapsible dock (chevron-only when closed; compact content-height panel when open) - Restore full-width search; fix workspace panel visibility when collapsed (hidden vs flex) - Stabilize header uptime/events display (tabular nums, padded time, min-width) - Wire demo mode via VITE_EVENTLENS_DEMO + VITE_EVENTLENS_DEMO_ALLOW (local env)
Implemented the source-aware pass so the same datasource dropdown now drives anomalies and live stream too. The backend now resolves anomaly scans per selected source in AnomalyRoutes.java, and live WebSocket sessions are source-scoped in LiveTailWebSocket.java. I threaded datasource-to-stream bindings through EventLensServer.java, ServeCommand.java, and added optional streamId config support in EventLensConfig.java plus the example config in eventlens-example.yaml. On the UI side, App.tsx now passes selectedSource into both LiveStream.tsx and AnomalyPanel.tsx. LiveStream reconnects on source change with ?source=..., and shows a neutral “Live stream not available for this source” state when the backend sends NO_LIVE_STREAM. The anomaly client call is source-aware in client.ts, and I added an integration test for the new routing behavior in SourceAwarePanelsIntegrationTest.java. Verification passed with ./gradlew.bat test and ./gradlew.bat check.
…ent NPE Map.of() throws NullPointerException when any value is null. Before first health check or when stream-id is omitted, health, lastHealthCheck, and message fields can all be null. Switched to LinkedHashMap with Objects.toString() fallbacks to handle null health, null state, null message, and null lastHealthCheck.
When multiple datasources of the same type (e.g. two Postgres instances) were configured, registerDatasources() reused the same plugin singleton from classpath discovery. The second registration overwrote the first plugin's connection, causing both to point to the same database. Now always creates a new plugin instance via createBuiltinDatasource() for each configured datasource, using the discovered list only to verify type support.
- eventlens-api: upgrade micrometer 1.15.0->1.16.4, jackson-databind 2.21.1->2.21.2 (keep testcontainers/source deps from v3.0.0) - eventlens-core: upgrade jackson-databind/dataformat-yaml/datatype-jsr310 2.21.1->2.21.2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.