From 62dd71b860f2c6b05f79055815dbb685123a77fa Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:30:56 +0200 Subject: [PATCH] fix: stamp environment on agent_events rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The agent_events table has an `environment` column and AgentEventsController filters on it, but the INSERT never populated it — every row got the column default ('default'). Result: Timeline on the Application Runtime page was empty whenever the user's selected env was anything other than 'default'. Thread env through the write path: - AgentEventRepository.insert + AgentEventService.recordEvent gain an `environment` param; delete the no-env query overload (unused). - ClickHouseAgentEventRepository.insert writes the column (falls back to 'default' on null to match column DEFAULT). - All 5 callers source env from the agent registry (AgentInfo.environmentId) or the registration request body; AgentLifecycleMonitor, deregister, command ack, event ingestion, register/re-register. - Integration test updated for the new signatures. Pre-existing rows in deployed CH will still report environment='default'. New events from this build forward will carry the correct env. Backfill (UPDATE ... FROM apps) is left as a manual DB step if historical timeline is needed for non-default envs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../app/agent/AgentLifecycleMonitor.java | 3 +- .../controller/AgentCommandController.java | 3 +- .../AgentRegistrationController.java | 7 ++-- .../controller/EventIngestionController.java | 3 +- .../ClickHouseAgentEventRepository.java | 12 +++---- .../ClickHouseAgentEventRepositoryIT.java | 34 +++++++++---------- .../core/agent/AgentEventRepository.java | 4 +-- .../server/core/agent/AgentEventService.java | 10 ++---- 8 files changed, 35 insertions(+), 41 deletions(-) diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/agent/AgentLifecycleMonitor.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/agent/AgentLifecycleMonitor.java index 4abb88da..6610fa43 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/agent/AgentLifecycleMonitor.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/agent/AgentLifecycleMonitor.java @@ -55,7 +55,8 @@ public class AgentLifecycleMonitor { if (before != null && before != agent.state()) { String eventType = mapTransitionEvent(before, agent.state()); if (eventType != null) { - agentEventService.recordEvent(agent.instanceId(), agent.applicationId(), eventType, + agentEventService.recordEvent(agent.instanceId(), agent.applicationId(), + agent.environmentId(), eventType, agent.displayName() + " " + before + " -> " + agent.state()); serverMetrics.recordAgentTransition(eventType); } diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentCommandController.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentCommandController.java index 5f219f32..4d944bb3 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentCommandController.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentCommandController.java @@ -223,7 +223,8 @@ public class AgentCommandController { if (body != null && body.status() != null) { AgentInfo agent = registryService.findById(id); String application = agent != null ? agent.applicationId() : "unknown"; - agentEventService.recordEvent(id, application, "COMMAND_" + body.status(), + String environment = agent != null ? agent.environmentId() : null; + agentEventService.recordEvent(id, application, environment, "COMMAND_" + body.status(), "Command " + commandId + ": " + body.message()); log.debug("Command {} ack from agent {}: {} - {}", commandId, id, body.status(), body.message()); } diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java index 164f2274..c94ec9eb 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java @@ -143,12 +143,12 @@ public class AgentRegistrationController { if (reRegistration) { log.info("Agent re-registered: {} (application={}, routes={}, capabilities={})", request.instanceId(), application, routeIds.size(), capabilities.keySet()); - agentEventService.recordEvent(request.instanceId(), application, "RE_REGISTERED", + agentEventService.recordEvent(request.instanceId(), application, environmentId, "RE_REGISTERED", "Agent re-registered with " + routeIds.size() + " routes"); } else { log.info("Agent registered: {} (application={}, routes={})", request.instanceId(), application, routeIds.size()); - agentEventService.recordEvent(request.instanceId(), application, "REGISTERED", + agentEventService.recordEvent(request.instanceId(), application, environmentId, "REGISTERED", "Agent registered: " + request.instanceId()); } @@ -315,8 +315,9 @@ public class AgentRegistrationController { return ResponseEntity.notFound().build(); } String applicationId = agent.applicationId(); + String environment = agent.environmentId(); registryService.deregister(id); - agentEventService.recordEvent(id, applicationId, "DEREGISTERED", "Agent deregistered"); + agentEventService.recordEvent(id, applicationId, environment, "DEREGISTERED", "Agent deregistered"); auditService.log(id, "agent_deregister", AuditCategory.AGENT, id, null, AuditResult.SUCCESS, httpRequest); return ResponseEntity.ok().build(); } diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/EventIngestionController.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/EventIngestionController.java index 87de2d17..42b7ee3f 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/EventIngestionController.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/EventIngestionController.java @@ -71,9 +71,10 @@ public class EventIngestionController { AgentInfo agent = registryService.findById(instanceId); String applicationId = agent != null ? agent.applicationId() : ""; + String environment = agent != null ? agent.environmentId() : null; for (AgentEvent event : events) { - agentEventService.recordEvent(instanceId, applicationId, + agentEventService.recordEvent(instanceId, applicationId, environment, event.getEventType(), event.getDetails() != null ? event.getDetails().toString() : null); diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseAgentEventRepository.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseAgentEventRepository.java index 41bbc2ab..3b45b211 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseAgentEventRepository.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseAgentEventRepository.java @@ -18,7 +18,7 @@ import java.util.List; public class ClickHouseAgentEventRepository implements AgentEventRepository { private static final String INSERT_SQL = - "INSERT INTO agent_events (tenant_id, instance_id, application_id, event_type, detail) VALUES (?, ?, ?, ?, ?)"; + "INSERT INTO agent_events (tenant_id, instance_id, application_id, environment, event_type, detail) VALUES (?, ?, ?, ?, ?, ?)"; private static final String SELECT_BASE = "SELECT 0 AS id, instance_id, application_id, event_type, detail, timestamp FROM agent_events WHERE tenant_id = ?"; @@ -32,13 +32,9 @@ public class ClickHouseAgentEventRepository implements AgentEventRepository { } @Override - public void insert(String instanceId, String applicationId, String eventType, String detail) { - jdbc.update(INSERT_SQL, tenantId, instanceId, applicationId, eventType, detail); - } - - @Override - public List query(String applicationId, String instanceId, Instant from, Instant to, int limit) { - return query(applicationId, instanceId, null, from, to, limit); + public void insert(String instanceId, String applicationId, String environment, String eventType, String detail) { + jdbc.update(INSERT_SQL, tenantId, instanceId, applicationId, + environment != null ? environment : "default", eventType, detail); } @Override diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/storage/ClickHouseAgentEventRepositoryIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/storage/ClickHouseAgentEventRepositoryIT.java index f4b898de..dc0e8b50 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/storage/ClickHouseAgentEventRepositoryIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/storage/ClickHouseAgentEventRepositoryIT.java @@ -49,15 +49,15 @@ class ClickHouseAgentEventRepositoryIT { */ private void insertAt(String instanceId, String applicationId, String eventType, String detail, Instant ts) { jdbc.update( - "INSERT INTO agent_events (tenant_id, instance_id, application_id, event_type, detail, timestamp) VALUES (?, ?, ?, ?, ?, ?)", - "default", instanceId, applicationId, eventType, detail, Timestamp.from(ts)); + "INSERT INTO agent_events (tenant_id, instance_id, application_id, environment, event_type, detail, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?)", + "default", instanceId, applicationId, "default", eventType, detail, Timestamp.from(ts)); } // ── Tests ───────────────────────────────────────────────────────────────── @Test void insert_writesEvent() { - repo.insert("agent-1", "app-a", "CONNECTED", "agent came online"); + repo.insert("agent-1", "app-a", "default", "CONNECTED", "agent came online"); Long count = jdbc.queryForObject( "SELECT count() FROM agent_events WHERE instance_id = 'agent-1'", @@ -67,10 +67,10 @@ class ClickHouseAgentEventRepositoryIT { @Test void query_byAppId_filtersCorrectly() { - repo.insert("agent-1", "app-x", "CONNECTED", ""); - repo.insert("agent-2", "app-y", "DISCONNECTED", ""); + repo.insert("agent-1", "app-x", "default", "CONNECTED", ""); + repo.insert("agent-2", "app-y", "default", "DISCONNECTED", ""); - List results = repo.query("app-x", null, null, null, 100); + List results = repo.query("app-x", null, null, null, null, 100); assertThat(results).hasSize(1); assertThat(results.get(0).applicationId()).isEqualTo("app-x"); @@ -79,10 +79,10 @@ class ClickHouseAgentEventRepositoryIT { @Test void query_byAgentId_filtersCorrectly() { - repo.insert("agent-alpha", "app-shared", "CONNECTED", ""); - repo.insert("agent-beta", "app-shared", "CONNECTED", ""); + repo.insert("agent-alpha", "app-shared", "default", "CONNECTED", ""); + repo.insert("agent-beta", "app-shared", "default", "CONNECTED", ""); - List results = repo.query(null, "agent-alpha", null, null, 100); + List results = repo.query(null, "agent-alpha", null, null, null, 100); assertThat(results).hasSize(1); assertThat(results.get(0).instanceId()).isEqualTo("agent-alpha"); @@ -99,7 +99,7 @@ class ClickHouseAgentEventRepositoryIT { insertAt("agent-1", "app-a", "DISCONNECTED", "late", t3); // Query [t2, t3) — should return only the middle event - List results = repo.query(null, null, t2, t3, 100); + List results = repo.query(null, null, null, t2, t3, 100); assertThat(results).hasSize(1); assertThat(results.get(0).eventType()).isEqualTo("HEARTBEAT"); @@ -112,16 +112,16 @@ class ClickHouseAgentEventRepositoryIT { insertAt("agent-1", "app-a", "HEARTBEAT", "beat-" + i, base.plusSeconds(i)); } - List results = repo.query(null, null, null, null, 3); + List results = repo.query(null, null, null, null, null, 3); assertThat(results).hasSize(3); } @Test void query_returnsZeroId() { - repo.insert("agent-1", "app-a", "CONNECTED", ""); + repo.insert("agent-1", "app-a", "default", "CONNECTED", ""); - List results = repo.query(null, null, null, null, 10); + List results = repo.query(null, null, null, null, null, 10); assertThat(results).hasSize(1); assertThat(results.get(0).id()).isEqualTo(0L); @@ -129,10 +129,10 @@ class ClickHouseAgentEventRepositoryIT { @Test void query_noFilters_returnsAllEvents() { - repo.insert("agent-1", "app-a", "CONNECTED", ""); - repo.insert("agent-2", "app-b", "DISCONNECTED", ""); + repo.insert("agent-1", "app-a", "default", "CONNECTED", ""); + repo.insert("agent-2", "app-b", "default", "DISCONNECTED", ""); - List results = repo.query(null, null, null, null, 100); + List results = repo.query(null, null, null, null, null, 100); assertThat(results).hasSize(2); } @@ -147,7 +147,7 @@ class ClickHouseAgentEventRepositoryIT { insertAt("agent-1", "app-a", "SECOND", "", t2); insertAt("agent-1", "app-a", "THIRD", "", t3); - List results = repo.query(null, null, null, null, 100); + List results = repo.query(null, null, null, null, null, 100); assertThat(results.get(0).eventType()).isEqualTo("THIRD"); assertThat(results.get(1).eventType()).isEqualTo("SECOND"); diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventRepository.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventRepository.java index 24aa8465..9838582b 100644 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventRepository.java +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventRepository.java @@ -5,9 +5,7 @@ import java.util.List; public interface AgentEventRepository { - void insert(String instanceId, String applicationId, String eventType, String detail); - - List query(String applicationId, String instanceId, Instant from, Instant to, int limit); + void insert(String instanceId, String applicationId, String environment, String eventType, String detail); List query(String applicationId, String instanceId, String environment, Instant from, Instant to, int limit); } diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventService.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventService.java index 91fe90d7..d6f00210 100644 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventService.java +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventService.java @@ -16,13 +16,9 @@ public class AgentEventService { this.repository = repository; } - public void recordEvent(String instanceId, String applicationId, String eventType, String detail) { - log.debug("Recording agent event: instance={}, app={}, type={}", instanceId, applicationId, eventType); - repository.insert(instanceId, applicationId, eventType, detail); - } - - public List queryEvents(String applicationId, String instanceId, Instant from, Instant to, int limit) { - return repository.query(applicationId, instanceId, from, to, limit); + public void recordEvent(String instanceId, String applicationId, String environment, String eventType, String detail) { + log.debug("Recording agent event: instance={}, app={}, env={}, type={}", instanceId, applicationId, environment, eventType); + repository.insert(instanceId, applicationId, environment, eventType, detail); } public List queryEvents(String applicationId, String instanceId, String environment, Instant from, Instant to, int limit) {