This is an automated email from the ASF dual-hosted git repository.
fmariani pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
The following commit(s) were added to refs/heads/main by this push:
new 52db1e8 Smart log analyzer example
52db1e8 is described below
commit 52db1e817b5d34c01e085ae23f898a4c9ae7be55
Author: Croway <[email protected]>
AuthorDate: Wed Jan 28 13:14:18 2026 +0100
Smart log analyzer example
---
.gitignore | 1 +
smart-log-analyzer/README.adoc | 215 +++++++++++++
.../analyzer/application-dev.properties | 31 ++
.../analyzer/error-analyzer.camel.yaml | 74 +++++
.../caches/infinispan-events-config.json | 14 +
.../infinispan-events-to-process-config.json | 14 +
smart-log-analyzer/containers/docker-compose.yaml | 98 ++++++
.../containers/otel-collector-config.yaml | 32 ++
.../correlator/application-dev.properties | 28 ++
.../correlator/correlated-log-schema.json | 38 +++
.../correlator/correlated-trace-schema.json | 58 ++++
.../correlator/infinispan.camel.yaml | 104 +++++++
smart-log-analyzer/correlator/kafka-ca-cert.pem | 70 +++++
.../correlator/kaoto-datamapper-4a94acc3.xsl | 38 +++
.../correlator/kaoto-datamapper-8f5bb2dd.xsl | 64 ++++
.../correlator/logs-mapper.camel.yaml | 45 +++
.../correlator/otel-log-record-schema.json | 137 +++++++++
.../correlator/otel-logs-schema.json | 227 ++++++++++++++
.../correlator/otel-span-schema.json | 244 +++++++++++++++
.../correlator/otel-traces-schema.json | 334 +++++++++++++++++++++
.../correlator/traces-mapper.camel.yaml | 45 +++
.../first-iteration/analyzer.camel.yaml | 62 ++++
.../first-iteration/application.properties | 2 +
.../first-iteration/load-generator.camel.yaml | 48 +++
.../test/first-iteration.camel.it.yaml | 46 +++
.../first-iteration/test/jbang.properties | 3 +
.../first-iteration/test/payload-error.json | 1 +
.../first-iteration/test/payload-info.json | 1 +
smart-log-analyzer/log-generator/agent.properties | 6 +
.../log-generator/application-dev.properties | 3 +
.../log-generator/log-generator.camel.yaml | 87 ++++++
.../log-generator/opentelemetry-javaagent.jar | Bin 0 -> 24026362 bytes
.../ui-console/application-dev.properties | 31 ++
smart-log-analyzer/ui-console/index.html | 287 ++++++++++++++++++
.../ui-console/jms-file-storage.camel.yaml | 14 +
smart-log-analyzer/ui-console/rest-api.camel.yaml | 84 ++++++
36 files changed, 2586 insertions(+)
diff --git a/.gitignore b/.gitignore
index aeaba61..7bda6c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ target/
camel-runner.jar
*.i??
.citrus-jbang/
+.kaoto
\ No newline at end of file
diff --git a/smart-log-analyzer/README.adoc b/smart-log-analyzer/README.adoc
new file mode 100644
index 0000000..c96d25b
--- /dev/null
+++ b/smart-log-analyzer/README.adoc
@@ -0,0 +1,215 @@
+= Smart Log Analyzer
+
+An intelligent observability system that correlates OpenTelemetry logs and
traces, then uses LLM for automated root cause analysis.
+
+== Architecture
+
+[source,mermaid]
+----
+sequenceDiagram
+ participant App as Application
+ participant OTel as OTEL Collector
+ participant Kafka as Kafka
+ participant Correlator as Correlator
+ participant Events as Infinispan (events)
+ participant ToProcess as Infinispan (events-to-process)
+ participant JMS as JMS Queue
+ participant Analyzer as Analyzer
+ participant LLM as Ollama LLM
+ participant UIConsole as UI Console
+
+ App->>OTel: Send traces & logs (OTLP)
+ OTel->>Kafka: Export to otlp_spans & otlp_logs topics
+
+ Kafka->>Correlator: Consume spans
+ Kafka->>Correlator: Consume logs
+ Correlator->>Correlator: Transform via XSLT
+ Correlator->>Events: Store by traceId (TTL: 5min)
+
+ alt Error detected (severityText = ERROR)
+ Correlator->>ToProcess: Add traceId (TTL: 20s)
+ Note over ToProcess: Wait for related events
+ ToProcess-->>Correlator: Cache entry expired event
+ Correlator->>JMS: Send traceId to queue
+ JMS->>Analyzer: Consume traceId
+ Analyzer->>Events: Fetch all events for traceId
+ Analyzer->>LLM: Send events for analysis
+ LLM-->>Analyzer: Root cause analysis result
+ Analyzer->>JMS: Send analysis result
+ JMS->>UIConsole: Consume analysis result
+ UIConsole->>UIConsole: Save and display result
+ end
+----
+
+== First Iteration (Prototype)
+
+The `first-iteration/` folder contains a simpler prototype using in-memory
aggregation. Ideal for learning and local testing, but won't work in
distributed deployments. To run it:
+
+[source,bash]
+----
+# Terminal 1: Start Kafka
+camel infra run kafka
+
+# Terminal 2: Start the load generator
+cd first-iteration
+camel run load-generator.camel.yaml
+
+# Terminal 3: Start the analyzer (requires Ollama with granite4:3b)
+cd first-iteration
+camel run analyzer.camel.yaml --dep=org.apache.camel:camel-openai
+----
+
+=== Running the Test
+
+The first-iteration includes a Citrus integration test that uses
`--stub=openai` to mock the LLM endpoint.
+
+First, install Citrus as a JBang app (one-time setup):
+
+[source,bash]
+----
+jbang app install citrus@citrusframework/citrus
+----
+
+Then run the test:
+
+[source,bash]
+----
+cd first-iteration/test
+citrus run first-iteration.camel.it.yaml
+----
+
+The test starts Kafka via testcontainers, sends log events, and verifies the
aggregation triggers correctly.
+
+== Components
+
+=== Correlator
+
+The correlator is the central event processing component:
+
+* Consumes OTEL logs from `otlp_logs` Kafka topic
+* Consumes OTEL traces from `otlp_spans` Kafka topic
+* Transforms complex OTEL format to simplified JSON via XSLT
+* Correlates all events by traceId in Infinispan `events` cache (TTL: 5min)
+* Uses `events-to-process` cache for deferred error analysis:
+** When an ERROR log is detected, adds the traceId to this cache with a 20s TTL
+** Listens for cache expiration events to trigger analysis
+** This delay ensures all related spans and logs are collected before analysis
+* Sends expired traceIds to JMS queue for the Analyzer
+
+=== Analyzer
+
+The analyzer performs intelligent root cause analysis:
+
+* Listens on JMS `error-logs` queue for traceIds from the Correlator
+* Retrieves full trace context from Infinispan `events` cache
+* Sends events to Ollama LLM (granite4:3b) for intelligent analysis
+* Outputs analysis results to logs
+
+=== Log Generator
+
+A synthetic log generator for demonstration purposes that shows how to add
OpenTelemetry observability to Apache Camel applications. It simulates order
processing with intentional errors (30% failure rate).
+
+*Adding OpenTelemetry to your Camel application requires:*
+
+. *opentelemetry-javaagent.jar* - The OTEL Java agent for automatic
instrumentation. Download from
https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases[OpenTelemetry
releases].
+
+. *agent.properties* - Configures the OTEL agent:
++
+[source,properties]
+----
+otel.service.name=log-generator
+otel.traces.exporter=otlp
+otel.logs.exporter=otlp
+otel.metrics.exporter=none
+----
+
+. *application-dev.properties* - Enables Camel OpenTelemetry integration:
++
+[source,properties]
+----
+camel.opentelemetry2.enabled = true
+camel.jbang.dependencies=org.apache.camel:camel-opentelemetry2
+----
+
+Run with the javaagent flag pointing to the agent jar and its configuration
file. With this setup, Camel will automatically expose custom traces for route
executions and exchanges.
+
+=== UI Console
+
+The UI Console provides visualization and storage for analysis results:
+
+* Listens on JMS queue for analysis results from the Analyzer
+* Stores analysis results to files for persistence
+* Exposes REST API to display and access the results
+* Provides a user interface to view root cause analysis
+
+== Prerequisites
+
+* Docker & Docker Compose
+* https://www.jbang.dev/download/[JBang]
+* https://ollama.ai/[Ollama] with `granite4:3b` model:
++
+[source,bash]
+----
+ollama pull granite4:3b
+----
+
+== Running the Application
+
+=== 1. Start Infrastructure
+
+[source,bash]
+----
+cd containers
+docker-compose up
+----
+
+=== 2. Start Correlator
+
+[source,bash]
+----
+cd correlator
+camel run \
+ traces-mapper.camel.yaml \
+ logs-mapper.camel.yaml \
+ infinispan.camel.yaml \
+ kaoto-datamapper-4a94acc3.xsl \
+ kaoto-datamapper-8f5bb2dd.xsl
+----
+
+=== 3. Start Analyzer
+
+[source,bash]
+----
+cd analyzer
+camel run error-analyzer.camel.yaml
+----
+
+=== 4. Start Log Generator
+
+[source,bash]
+----
+cd log-generator
+jbang --javaagent=./opentelemetry-javaagent.jar \
+ -Dotel.javaagent.configuration-file=./agent.properties \
+ camel@apache/camel run log-generator.camel.yaml
+----
+
+=== 5. Start UI Console
+
+[source,bash]
+----
+cd ui-console
+camel run *
+----
+
+== Infrastructure Services
+
+[cols="1,1,2"]
+|===
+| Service | Port | Purpose
+
+| Kafka | 9092 | Message broker for OTEL data
+| Infinispan | 11222 | Event cache (admin:password)
+| OTEL Collector | 4317, 4318 | Telemetry receiver (gRPC, HTTP)
+| ActiveMQ Artemis | 61616, 8161 | JMS broker (artemis:artemis)
+|===
diff --git a/smart-log-analyzer/analyzer/application-dev.properties
b/smart-log-analyzer/analyzer/application-dev.properties
new file mode 100644
index 0000000..caef75f
--- /dev/null
+++ b/smart-log-analyzer/analyzer/application-dev.properties
@@ -0,0 +1,31 @@
+# Infinispan configuration
+camel.component.infinispan.hosts=localhost:11222
+camel.component.infinispan.username=admin
+camel.component.infinispan.password=password
+camel.component.infinispan.sasl-mechanism=DIGEST-MD5
+camel.component.infinispan.security-realm=default
+camel.component.infinispan.security-server-name=infinispan
+camel.component.infinispan.secure=true
+
+# Artemis JMS configuration
+camel.beans.artemisCF =
#class:org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory
+camel.beans.artemisCF.brokerURL = tcp://localhost:61616
+camel.beans.artemisCF.user = artemis
+camel.beans.artemisCF.password = artemis
+camel.beans.poolCF =
#class:org.messaginghub.pooled.jms.JmsPoolConnectionFactory
+camel.beans.poolCF.connectionFactory = #bean:artemisCF
+camel.beans.poolCF.maxSessionsPerConnection = 500
+camel.beans.poolCF.connectionIdleTimeout = 20000
+camel.component.jms.connection-factory = #bean:poolCF
+camel.jms.queue.error-logs=error-logs
+camel.jms.queue.analysis-result=analysis-result
+
+# Ollama configuration (OpenAI-compatible API)
+camel.component.openai.apiKey=ollama
+camel.component.openai.baseUrl=http://localhost:11434/v1
+camel.component.openai.model=granite4:3b
+
+# Camel configuration
+camel.main.name=analyzer
+
+camel.jbang.dependencies=org.apache.camel:camel-openai
diff --git a/smart-log-analyzer/analyzer/error-analyzer.camel.yaml
b/smart-log-analyzer/analyzer/error-analyzer.camel.yaml
new file mode 100644
index 0000000..406f570
--- /dev/null
+++ b/smart-log-analyzer/analyzer/error-analyzer.camel.yaml
@@ -0,0 +1,74 @@
+# Analyzes error logs by retrieving OpenTelemetry events from Infinispan
+# and sending them to an LLM for root cause analysis and recommendations
+- route:
+ id: error-log-analyzer
+ from:
+ uri: jms:{{camel.jms.queue.error-logs}}
+ steps:
+ - log:
+ loggingLevel: INFO
+ message: Received ERROR log for analysis
+ - setVariable:
+ name: traceId
+ simple: ${body}
+ - log:
+ loggingLevel: INFO
+ message: "Retrieving all events for traceId: ${variable.traceId}"
+ - setHeader:
+ name: CamelInfinispanKey
+ simple: otel-${variable.traceId}
+ - to:
+ uri: infinispan:events
+ parameters:
+ operation: GET
+ - choice:
+ otherwise:
+ steps:
+ - log:
+ loggingLevel: WARN
+ message: "No events found in Infinispan for traceId:
${variable.traceId}"
+ when:
+ - steps:
+ - setVariable:
+ name: recordCount
+ jq:
+ expression: length
+ resultType: java.lang.String
+ - log:
+ message: Sending ${variable.recordCount} records to LLM
for analysis
+ - setVariable:
+ name: eventsJson
+ simple: ${body}
+ - setBody:
+ simple: >
+ Analyze the following OpenTelemetry logs and traces for
+ traceId ${variable.traceId}.
+
+ Identify the root cause of the error, explain what
+ happened, and suggest possible fixes.
+
+
+ Events (sorted by timestamp):
+
+ ${variable.eventsJson}
+ - to:
+ uri: openai:chat-completion
+ parameters:
+ systemMessage: >
+ You are an expert DevOps engineer and log analyst.
+ Analyze OpenTelemetry logs and traces to identify
+ errors, their root causes, and provide actionable
+ recommendations. Be concise and focus on the error.
+ You want to build a tree of calls by associating each
+ span with its parent, where parentSpanId corresponds
+ to the parent caller’s spanId.
+ - log:
+ message: "LLM Analysis for traceId ${variable.traceId}:
${body}"
+ - setHeader:
+ name: traceId
+ simple: "${variable.traceId}"
+ - to:
+ uri: jms:{{camel.jms.queue.analysis-result}}
+ - log:
+ message: "Sent analysis result to queue for traceId:
${variable.traceId}"
+ simple: ${body} != null && ${body} != ''
diff --git a/smart-log-analyzer/containers/caches/infinispan-events-config.json
b/smart-log-analyzer/containers/caches/infinispan-events-config.json
new file mode 100644
index 0000000..da20578
--- /dev/null
+++ b/smart-log-analyzer/containers/caches/infinispan-events-config.json
@@ -0,0 +1,14 @@
+{
+ "events": {
+ "distributed-cache": {
+ "mode": "SYNC",
+ "statistics": true,
+ "encoding": {
+ "media-type": "text/plain"
+ },
+ "expiration": {
+ "lifespan": "600000ms"
+ }
+ }
+ }
+}
diff --git
a/smart-log-analyzer/containers/caches/infinispan-events-to-process-config.json
b/smart-log-analyzer/containers/caches/infinispan-events-to-process-config.json
new file mode 100644
index 0000000..d34d9a4
--- /dev/null
+++
b/smart-log-analyzer/containers/caches/infinispan-events-to-process-config.json
@@ -0,0 +1,14 @@
+{
+ "events-to-process": {
+ "distributed-cache": {
+ "mode": "SYNC",
+ "statistics": true,
+ "encoding": {
+ "media-type": "text/plain"
+ },
+ "expiration": {
+ "lifespan": "20000ms"
+ }
+ }
+ }
+}
diff --git a/smart-log-analyzer/containers/docker-compose.yaml
b/smart-log-analyzer/containers/docker-compose.yaml
new file mode 100644
index 0000000..afc84d9
--- /dev/null
+++ b/smart-log-analyzer/containers/docker-compose.yaml
@@ -0,0 +1,98 @@
+services:
+ kafka:
+ image: apache/kafka:latest
+ container_name: kafka
+ hostname: kafka
+ environment:
+ # KRaft mode configuration (no Zookeeper)
+ KAFKA_NODE_ID: 1
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ # Multiple listeners: PLAINTEXT for Docker network, PLAINTEXT_HOST for
localhost access
+ KAFKA_LISTENERS:
PLAINTEXT://0.0.0.0:19092,PLAINTEXT_HOST://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS:
PLAINTEXT://kafka:19092,PLAINTEXT_HOST://localhost:9092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:
PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_MIN_INSYNC_REPLICAS: 1
+ # Storage (ephemeral - no persistence)
+ KAFKA_LOG_DIRS: /tmp/kraft-combined-logs
+ ports:
+ - "9092:9092" # External access from localhost
+ networks:
+ - otel-network
+
+ infinispan:
+ image: quay.io/infinispan/server:16.0.5
+ container_name: infinispan
+ hostname: infinispan
+ environment:
+ USER: admin
+ PASS: password
+ ports:
+ - "11222:11222"
+ networks:
+ - otel-network
+ healthcheck:
+ test: ["CMD", "curl", "-u", "admin:password", "--digest",
"http://localhost:11222/rest/v2/caches"]
+ interval: 5s
+ timeout: 3s
+ retries: 8
+
+ infinispan-init:
+ image: curlimages/curl:latest
+ container_name: infinispan-init
+ depends_on:
+ infinispan:
+ condition: service_healthy
+ networks:
+ - otel-network
+ volumes:
+ - ./caches:/etc/caches:ro
+ command: >
+ sh -c "
+ sleep 2 &&
+ echo 'Creating caches...' &&
+ curl -f -u admin:password --digest -X POST
'http://infinispan:11222/rest/v2/caches/events' -H 'Content-Type:
application/json' -d @/etc/caches/infinispan-events-config.json &&
+ curl -f -u admin:password --digest -X POST
'http://infinispan:11222/rest/v2/caches/events-to-process' -H 'Content-Type:
application/json' -d @/etc/caches/infinispan-events-to-process-config.json &&
+ echo 'Caches created successfully'
+ "
+ deploy:
+ restart_policy:
+ condition: on-failure
+ max_attempts: 5
+
+ otel-collector:
+ image: otel/opentelemetry-collector-contrib:latest
+ container_name: otel-collector
+ command: ["--config=/etc/otel-collector-config.yaml"]
+ volumes:
+ - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
+ ports:
+ - "4317:4317" # OTLP gRPC receiver
+ - "4318:4318" # OTLP HTTP receiver
+ networks:
+ - otel-network
+ depends_on:
+ - kafka
+
+ artemis:
+ image: apache/activemq-artemis:latest
+ container_name: artemis
+ hostname: artemis
+ environment:
+ ARTEMIS_USER: artemis
+ ARTEMIS_PASSWORD: artemis
+ ports:
+ - "8161:8161" # Web console
+ - "61616:61616" # Core protocol
+ networks:
+ - otel-network
+
+networks:
+ otel-network:
+ driver: bridge
diff --git a/smart-log-analyzer/containers/otel-collector-config.yaml
b/smart-log-analyzer/containers/otel-collector-config.yaml
new file mode 100644
index 0000000..876bbf6
--- /dev/null
+++ b/smart-log-analyzer/containers/otel-collector-config.yaml
@@ -0,0 +1,32 @@
+exporters:
+ debug:
+ verbosity: detailed
+ kafka:
+ brokers:
+ - kafka:19092
+ logs:
+ encoding: otlp_json
+ traces:
+ encoding: otlp_json
+receivers:
+ otlp:
+ protocols:
+ grpc:
+ endpoint: 0.0.0.0:4317
+ http:
+ endpoint: 0.0.0.0:4318
+
+service:
+ pipelines:
+ logs:
+ receivers:
+ - otlp
+ exporters:
+ - debug
+ - kafka
+ traces:
+ receivers:
+ - otlp
+ exporters:
+ - debug
+ - kafka
diff --git a/smart-log-analyzer/correlator/application-dev.properties
b/smart-log-analyzer/correlator/application-dev.properties
new file mode 100644
index 0000000..3f126e6
--- /dev/null
+++ b/smart-log-analyzer/correlator/application-dev.properties
@@ -0,0 +1,28 @@
+# Kafka configuration
+camel.component.kafka.brokers=localhost:9092
+camel.kafka.topic.logs=otlp_logs
+camel.kafka.topic.spans=otlp_spans
+
+# Infinispan configuration
+camel.component.infinispan.hosts=localhost:11222
+camel.component.infinispan.username=admin
+camel.component.infinispan.password=password
+camel.component.infinispan.sasl-mechanism=DIGEST-MD5
+camel.component.infinispan.security-realm=default
+camel.component.infinispan.security-server-name=infinispan
+camel.component.infinispan.secure=true
+
+# Artemis JMS configuration
+camel.beans.artemisCF =
#class:org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory
+camel.beans.artemisCF.brokerURL = tcp://localhost:61616
+camel.beans.artemisCF.user = artemis
+camel.beans.artemisCF.password = artemis
+camel.beans.poolCF =
#class:org.messaginghub.pooled.jms.JmsPoolConnectionFactory
+camel.beans.poolCF.connectionFactory = #bean:artemisCF
+camel.beans.poolCF.maxSessionsPerConnection = 500
+camel.beans.poolCF.connectionIdleTimeout = 20000
+camel.component.jms.connection-factory = #bean:poolCF
+camel.jms.queue.error-logs=error-logs
+
+# Camel configuration
+camel.main.name=correlator
diff --git a/smart-log-analyzer/correlator/correlated-log-schema.json
b/smart-log-analyzer/correlator/correlated-log-schema.json
new file mode 100644
index 0000000..a49f953
--- /dev/null
+++ b/smart-log-analyzer/correlator/correlated-log-schema.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "correlated-log-entry",
+ "title": "Correlated Log Entry",
+ "description": "Simplified log entry for storage in Infinispan",
+ "type": "object",
+ "required": ["timeUnixNano", "severityText", "message", "traceId", "spanId"],
+ "properties": {
+ "timeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Time when the event occurred (nanoseconds since Unix
epoch)"
+ },
+ "observedTimeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Time when the event was observed (nanoseconds since Unix
epoch)"
+ },
+ "severityText": {
+ "type": "string",
+ "description": "Severity level (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)"
+ },
+ "message": {
+ "type": "string",
+ "description": "Combined message from body and attributes"
+ },
+ "traceId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{32}$",
+ "description": "Trace ID for correlation"
+ },
+ "spanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Span ID"
+ }
+ }
+}
diff --git a/smart-log-analyzer/correlator/correlated-trace-schema.json
b/smart-log-analyzer/correlator/correlated-trace-schema.json
new file mode 100644
index 0000000..7cdec55
--- /dev/null
+++ b/smart-log-analyzer/correlator/correlated-trace-schema.json
@@ -0,0 +1,58 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "correlated-trace-entry",
+ "title": "Correlated Trace Entry",
+ "description": "Simplified span entry for storage in Infinispan, designed
for LLM error analysis",
+ "type": "object",
+ "required": ["type", "timeUnixNano", "traceId", "spanId", "name", "status"],
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "span",
+ "description": "Entry type to distinguish from logs in combined timeline"
+ },
+ "timeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Start time of the span (nanoseconds since Unix epoch),
used for ordering with logs"
+ },
+ "durationNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Duration of the span in nanoseconds (endTimeUnixNano -
startTimeUnixNano)"
+ },
+ "traceId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{32}$",
+ "description": "Trace ID for correlation with logs"
+ },
+ "spanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Span ID"
+ },
+ "parentSpanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Parent span ID for understanding call hierarchy"
+ },
+ "name": {
+ "type": "string",
+ "description": "Operation name (e.g., timer, http.request)"
+ },
+ "kind": {
+ "type": "string",
+ "enum": ["UNSPECIFIED", "INTERNAL", "SERVER", "CLIENT", "PRODUCER",
"CONSUMER"],
+ "description": "Span kind indicating the type of operation"
+ },
+ "status": {
+ "type": "string",
+ "enum": ["UNSET", "OK", "ERROR"],
+ "description": "Span status"
+ },
+ "message": {
+ "type": "string",
+ "description": "Combined message from key attributes and error event
messages"
+ }
+ }
+}
diff --git a/smart-log-analyzer/correlator/infinispan.camel.yaml
b/smart-log-analyzer/correlator/infinispan.camel.yaml
new file mode 100644
index 0000000..c4e0cde
--- /dev/null
+++ b/smart-log-analyzer/correlator/infinispan.camel.yaml
@@ -0,0 +1,104 @@
+# Stores OpenTelemetry records in Infinispan cache, grouped by traceId
+# and marks ERROR events for deferred processing
+- route:
+ id: route-1200
+ from:
+ id: from-9847
+ uri: direct
+ parameters:
+ name: store
+ steps:
+ - log:
+ id: log-record
+ loggingLevel: DEBUG
+ message: "Processing record: ${body}"
+ - setVariable:
+ jq:
+ expression: .traceId
+ resultType: java.lang.String
+ name: traceId
+ - filter:
+ expression:
+ simple: ${variable.traceId} != null && ${variable.traceId} != ''
+ steps:
+ - claimCheck:
+ key: currentRecord
+ operation: Set
+ - setHeader:
+ name: CamelInfinispanKey
+ simple: otel-${variable.traceId}
+ - to:
+ uri: infinispan:events
+ parameters:
+ operation: GET
+ - choice:
+ otherwise:
+ steps:
+ - claimCheck:
+ key: currentRecord
+ operation: Get
+ - setBody:
+ jq:
+ expression: "[.]"
+ resultType: java.lang.String
+ when:
+ - steps:
+ - setVariable:
+ name: existingRecords
+ simple: ${body}
+ - claimCheck:
+ key: currentRecord
+ operation: Get
+ - setBody:
+ jq:
+ expression: (variable("existingRecords") |
fromjson) + [.] |
+ sort_by(.timeUnixNano)
+ resultType: java.lang.String
+ simple: ${body} != null && ${body} != ''
+ - setHeader:
+ name: CamelInfinispanKey
+ simple: otel-${variable.traceId}
+ - setHeader:
+ name: CamelInfinispanValue
+ simple: ${body}
+ - to:
+ uri: infinispan:events
+ parameters:
+ operation: PUT
+ - claimCheck:
+ key: currentRecord
+ operation: GetAndRemove
+ - log:
+ id: log-stored
+ message: "Stored record for traceId: ${variable.traceId}"
+ - choice:
+ when:
+ - steps:
+ - log:
+ message: "Adding ERROR log to events-to-process
cache for traceId:
+ ${variable.traceId}"
+ - setHeader:
+ name: CamelInfinispanKey
+ simple: ${variable.traceId}
+ - setHeader:
+ name: CamelInfinispanValue
+ simple: ${variable.traceId}
+ - to:
+ uri: infinispan:events-to-process
+ parameters:
+ operation: PUTIFABSENT
+ jq:
+ expression: .severityText == "ERROR"
+- route:
+ id: route-expired-events
+ from:
+ uri: infinispan:events-to-process
+ parameters:
+ eventTypes: CLIENT_CACHE_ENTRY_EXPIRED
+ steps:
+ - setBody:
+ simple: ${header.CamelInfinispanKey}
+ - log:
+ message: "Expired cache entry received, sending to JMS queue:
${body}"
+ - to:
+ uri: jms:{{camel.jms.queue.error-logs}}
diff --git a/smart-log-analyzer/correlator/kafka-ca-cert.pem
b/smart-log-analyzer/correlator/kafka-ca-cert.pem
new file mode 100644
index 0000000..3ea54c6
--- /dev/null
+++ b/smart-log-analyzer/correlator/kafka-ca-cert.pem
@@ -0,0 +1,70 @@
+-----BEGIN CERTIFICATE-----
+MIIHFTCCBP2gAwIBAgIUddBSUC646dJUTeT+vKy9E8WclvAwDQYJKoZIhvcNAQEN
+BQAwLTETMBEGA1UECgwKaW8uc3RyaW16aTEWMBQGA1UEAwwNY2x1c3Rlci1jYSB2
+MDAeFw0yNjAxMTYxNDI4NTZaFw0yNzAxMTYxNDI4NTZaMDMxEzARBgNVBAoMCmlv
+LnN0cmltemkxHDAaBgNVBAMME2NhbWVsLWNsdXN0ZXIta2Fma2EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCPWjgPFIDyK0i7bns4j28WZhD5xUgIol/0
+ns0awE6MZGFerQEOQXcbwrFmAn/LoTnAnlzcROOx096KJVNXj6JnaSBbGvrnCcYF
+2d6pBz7WxfHG9Pn/I6qMYTgc3XiJ4KLO+CZ/GKzo9fc6dl5kkciVS61thq26jkV1
+4aTA6oz9RComXdFjlzuSBoXULrSijWRYrGaT618kSYE2MBfJjOdAv08WoLE5UVf8
+F+bLEInJ7j1Z+/fmCxj0UB8j2UH3Wh/oAaQUBP1IGOPYfcfhiRzKgy7RWTGKMOOc
+pTmtHm5wRDvSm+1OYSZlFyMxtmmqK4ZstWf0OJhrORF2Kb6KJTTrAgMBAAGjggMl
+MIIDITCCAt0GA1UdEQSCAtQwggLQgj5jYW1lbC1jbHVzdGVyLWthZmthLWJyb2tl
+cnMuY2FtZWwtb3RlbC1pbmZyYS5zdmMuY2x1c3Rlci5sb2NhbIJYY2FtZWwtY2x1
+c3Rlci1tdWx0aXJvbGUtMC5jYW1lbC1jbHVzdGVyLWthZmthLWJyb2tlcnMuY2Ft
+ZWwtb3RlbC1pbmZyYS5zdmMuY2x1c3Rlci5sb2NhbIIwY2FtZWwtY2x1c3Rlci1r
+YWZrYS1icm9rZXJzLmNhbWVsLW90ZWwtaW5mcmEuc3Zjgh1jYW1lbC1jbHVzdGVy
+LWthZmthLWJvb3RzdHJhcIJUY2FtZWwtY2x1c3Rlci1rYWZrYS1ib290c3RyYXAt
+Y2FtZWwtb3RlbC1pbmZyYS5hcHBzLmNzYi1sYXN0LmZ1c2UuaW50ZWdyYXRpb24t
+cWUuY29tgjJjYW1lbC1jbHVzdGVyLWthZmthLWJvb3RzdHJhcC5jYW1lbC1vdGVs
+LWluZnJhLnN2Y4JKY2FtZWwtY2x1c3Rlci1tdWx0aXJvbGUtMC5jYW1lbC1jbHVz
+dGVyLWthZmthLWJyb2tlcnMuY2FtZWwtb3RlbC1pbmZyYS5zdmOCQGNhbWVsLWNs
+dXN0ZXIta2Fma2EtYm9vdHN0cmFwLmNhbWVsLW90ZWwtaW5mcmEuc3ZjLmNsdXN0
+ZXIubG9jYWyCLmNhbWVsLWNsdXN0ZXIta2Fma2EtYm9vdHN0cmFwLmNhbWVsLW90
+ZWwtaW5mcmGCLGNhbWVsLWNsdXN0ZXIta2Fma2EtYnJva2Vycy5jYW1lbC1vdGVs
+LWluZnJhghtjYW1lbC1jbHVzdGVyLWthZmthLWJyb2tlcnOCUGNhbWVsLWNsdXN0
+ZXItbXVsdGlyb2xlLTAtY2FtZWwtb3RlbC1pbmZyYS5hcHBzLmNzYi1sYXN0LmZ1
+c2UuaW50ZWdyYXRpb24tcWUuY29tMB0GA1UdDgQWBBQTlA+mmTCgeDSUKAwSpkIB
+TX+2+DAfBgNVHSMEGDAWgBTQeJU9RNTnrxXBxi8RaI3kQ6Y3RDANBgkqhkiG9w0B
+AQ0FAAOCAgEAIpbu83u9leM4dMqOLJWX1ZYAR31DxkhI+jMOe1JLxatD7UV/m/sh
+bdOaT1kV/ddynpuBxryXmrsm63kcCmUxYtI8bs/hLoxhXRAAy4CM9fezx7C5zVqW
+rqJ8Xfy8tTlDyHW9Ar3mxffQN3Sgnuvb+yqeW+OGj3nnXosrPL6cNZ2s19uRMjdl
+j5jfZu8tOT74UJvnQVuESfAxXvK0X/5IoTOo8vCayWFlvMnctus2VzbCCnNtFnlM
+R58qIbw+u1uESiQx2+YB8de/O9DhWLXjxqURV4Qb4OBhVuxVnACJkXnOdV70l+yN
+Am6R5p1mDkPqzkklA2Nl1xcjd1IFMqkgFBmP/JeLLI/3oH3nzVu6WvA8oxHuYF4C
+odojpPrnISwYYVCw0MkcYbGICbAPJHD2TZKFQ5QHolJgs+rn7c2e6vZj6nAspZU8
+4ebHXWf8jX7+T2eI2S7T8LmQV2lhIwJeKhvevrBQQtecqdQn+nFPRjsrwmeZ48Zw
+S3UL33dRhAmImny3vzgPDYCLCF0WiylmJfUT+sDTxfa1KW473f7S3KKLtx184vFO
+zjAUwHuSAmeO17I8Eyzdv0RcFx6YWKaVcGNvYzuFHtGQ3R0dYN0fPZ01sy53Nh42
+amfW9wQBD0MstgsxDWDfS4N6zwKSHAduAYsRHsSOwa06A+5hVdLworU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFLTCCAxWgAwIBAgIUARYnemLQ0i/Ml55sf+x1BmUme1gwDQYJKoZIhvcNAQEN
+BQAwLTETMBEGA1UECgwKaW8uc3RyaW16aTEWMBQGA1UEAwwNY2x1c3Rlci1jYSB2
+MDAeFw0yNjAxMTYxNDEzMzhaFw0yNzAxMTYxNDEzMzhaMC0xEzARBgNVBAoMCmlv
+LnN0cmltemkxFjAUBgNVBAMMDWNsdXN0ZXItY2EgdjAwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCzmMknBFpQuFkvgyxKOq97A0/elsyiplC6BSi818Z/
+0AKhYKf91EDUyfG2HhUsjrnpE3yv1B3DhE8mae4JKKeN+5M7/NJsJe5nfrkxQ2pL
+AQtOpxiD0J4EAl5TINQS8sU4dnllHCbiegepPO22+jsoLqt9l4wJmETpgmaVftCC
+/XbKrXng8wGr8xFYIUsJ/JgB1mn+w7oIgib3RQsk3nclHkn7IUs0mXxznzdj49nU
+nMCgtEg9CA9K8r/WQjij1AY3auaGAQN4i1uvzk2/mOjcTRjXdWe30asRlfvPoxZn
+ouIAJSokync5dP630jLWEXvGZSmqiXwAffCOpfdPyTazWhT+IQ/p/mk6dFZnQhIT
+tLEVJCHji3Y7JmuKEXgfKHuqI4CNX6VD/NSlTFSrShodNqbtKJp0ecIg+jX8tKvd
+u9dNgziCcCJAKOfTNO2gs3EZ2TPH6kxxOCeLCJ60QFifrN0trs63HRuY86CS3XNd
+10jpDHpOVSCWa09+g8Zk/jf8VyybzUtu1APxJMlhze8BrOx5mIQWBQds7EqCa7vs
+0PaptOYHJ8JMl69pLFzYDWRWdNVSc41GHMeLX5Hg9ENRjGq4eXJQ18SKw6vZMEtV
+PTG+mKGibA0kgKyf1ftSjC8urkfqYRVViHw/MPtjAWwer2QXXWHkjXklfh1OVhFZ
+BwIDAQABo0UwQzAdBgNVHQ4EFgQU0HiVPUTU568VwcYvEWiN5EOmN0QwEgYDVR0T
+AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIB
+ACkXOjCMFtEZL2BSMYi/rWbHAht2K67yj8kiT3Xru+tZ0grS6KNxb3xtpXqyxH/z
+j1jDW7vfW0vopTZRd0fZP7re+qHwVcEzaQfLisJ8IVmmMKOlhDXC0uD8zWH56g0/
+7gw9hDjJeNNsZLxkNd4e+rv9wJxVPm0DZ6h17rv3gMz5c24pqsv/5qO+HofE+Y46
+2jMS5t+ZzmpbYVoY0rzvkkACGjwcQ/Cr5WPEDIh1ndLXn7iIPnhNUwwLM1+97y00
+tGrEIXuL5r1p2ZZYT6KX/1yOVV6jPZXxbtD2mGWsVcmrWoaJfwkrAqaRjonPSQGB
+sEnLDYdyl0hkZivd7ti+UIDxy9hAD9DA2lM1+0MTG8a2u6slAZDBvkbLOeywK+g4
+WNcGLz0zxulN1x3rGL5WOPaf7lA3hSO9p9kDT7RXkawmw/mQLCl5tX4qSpyVvIZz
+6kdnxss+vsgb6qjqc8QMV+P6cXBWEaxcd2KexSmBBCg6FrjomFBMusw5tjcvdqwi
+jAGY1S8vodQDAbDMmidOgYdaR+hNdPPM+U1wlR0wPJx0BiVzJA+GUaVqsu+hCcw/
+c75SgDNerU7oO9bPxNeE+0lyUm813IhhAJWjkMgSGWyys53Gs6y28cBEFlA962Rs
+vSSSsYPkKrwL+sDvG8Ut+xQ4cshAFCyShOfV3MRwoEDL
+-----END CERTIFICATE-----
diff --git a/smart-log-analyzer/correlator/kaoto-datamapper-4a94acc3.xsl
b/smart-log-analyzer/correlator/kaoto-datamapper-4a94acc3.xsl
new file mode 100644
index 0000000..1e9e7f4
--- /dev/null
+++ b/smart-log-analyzer/correlator/kaoto-datamapper-4a94acc3.xsl
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This file is generated by Kaoto DataMapper. Do not edit. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions">
+ <xsl:output method="text" indent="yes"/>
+ <xsl:param name="logs"/>
+ <xsl:variable name="logs-x" select="json-to-xml($logs)"/>
+ <xsl:variable name="mapped-xml">
+ <map xmlns="http://www.w3.org/2005/xpath-functions">
+ <string key="timeUnixNano">
+ <xsl:value-of
select="$logs-x/fn:map/fn:string[@key='timeUnixNano']"/>
+ </string>
+ <string key="observedTimeUnixNano">
+ <xsl:value-of
select="$logs-x/fn:map/fn:string[@key='observedTimeUnixNano']"/>
+ </string>
+ <string key="severityText">
+ <xsl:value-of
select="$logs-x/fn:map/fn:string[@key='severityText']"/>
+ </string>
+ <string key="message">
+ <xsl:value-of
select="$logs-x/fn:map/fn:map[@key='body']/fn:string[@key='stringValue']"/>
+ <xsl:for-each
select="$logs-x/fn:map/fn:array[@key='attributes']/fn:map">
+ <xsl:value-of select="concat(' ',
fn:string[@key='key'], ': ',
fn:map[@key='value']/fn:string[@key='stringValue'])"/>
+ </xsl:for-each>
+ </string>
+ <string key="traceId">
+ <xsl:value-of
select="$logs-x/fn:map/fn:string[@key='traceId']"/>
+ </string>
+ <string key="spanId">
+ <xsl:value-of
select="$logs-x/fn:map/fn:string[@key='spanId']"/>
+ </string>
+ <string key="type">
+ <xsl:value-of select="'log'"/>
+ </string>
+ </map>
+ </xsl:variable>
+ <xsl:template match="/">
+ <xsl:value-of select="xml-to-json($mapped-xml)"/>
+ </xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/smart-log-analyzer/correlator/kaoto-datamapper-8f5bb2dd.xsl
b/smart-log-analyzer/correlator/kaoto-datamapper-8f5bb2dd.xsl
new file mode 100644
index 0000000..c818b32
--- /dev/null
+++ b/smart-log-analyzer/correlator/kaoto-datamapper-8f5bb2dd.xsl
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This file is generated by Kaoto DataMapper. Do not edit. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions">
+ <xsl:output method="text" indent="yes"/>
+ <xsl:param name="traces"/>
+ <xsl:variable name="traces-x" select="json-to-xml($traces)"/>
+ <xsl:variable name="mapped-xml">
+ <map xmlns="http://www.w3.org/2005/xpath-functions">
+ <string key="type">
+ <xsl:value-of select="'trace'"/>
+ </string>
+ <string key="timeUnixNano">
+ <xsl:value-of
select="$traces-x/fn:map/fn:string[@key='startTimeUnixNano']"/>
+ </string>
+ <string key="durationNano">
+ <xsl:value-of
select="xs:integer($traces-x/fn:map/fn:string[@key='endTimeUnixNano']) -
xs:integer($traces-x/fn:map/fn:string[@key='startTimeUnixNano'])"/>
+ </string>
+ <string key="traceId">
+ <xsl:value-of
select="$traces-x/fn:map/fn:string[@key='traceId']"/>
+ </string>
+ <string key="spanId">
+ <xsl:value-of
select="$traces-x/fn:map/fn:string[@key='spanId']"/>
+ </string>
+ <xsl:if test="$traces-x/fn:map/fn:string[@key='parentSpanId']">
+ <string key="parentSpanId">
+ <xsl:value-of
select="$traces-x/fn:map/fn:string[@key='parentSpanId']"/>
+ </string>
+ </xsl:if>
+ <string key="name">
+ <xsl:value-of
select="$traces-x/fn:map/fn:string[@key='name']"/>
+ </string>
+ <string key="kind">
+ <xsl:value-of
select="$traces-x/fn:map/fn:number[@key='kind']"/>
+ </string>
+ <string key="status">
+ <xsl:value-of
select="$traces-x/fn:map/fn:map[@key='status']/fn:number[@key='code']"/>
+ </string>
+ <string key="message">
+ <xsl:for-each
select="$traces-x/fn:map/fn:array[@key='attributes']/fn:map">
+ <xsl:choose>
+ <xsl:when
test="fn:map[@key='value']/fn:string[@key='stringValue']">
+ <xsl:value-of select="concat(' ',
fn:string[@key='key'], ': ',
fn:map[@key='value']/fn:string[@key='stringValue'])"/>
+ </xsl:when>
+ <xsl:when
test="fn:map[@key='value']/fn:string[@key='intValue']">
+ <xsl:value-of select="concat(' ',
fn:string[@key='key'], ': ', fn:map[@key='value']/fn:string[@key='intValue'])"/>
+ </xsl:when>
+ <xsl:when
test="fn:map[@key='value']/fn:number[@key='intValue']">
+ <xsl:value-of select="concat(' ',
fn:string[@key='key'], ': ', fn:map[@key='value']/fn:number[@key='intValue'])"/>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:for-each>
+ <xsl:for-each
select="$traces-x/fn:map/fn:array[@key='events']/fn:map">
+ <xsl:value-of select="concat(' [Event] ',
fn:string[@key='name'])"/>
+ <xsl:for-each select="fn:array[@key='attributes']/fn:map">
+ <xsl:value-of select="concat(' ',
fn:string[@key='key'], ': ',
fn:map[@key='value']/fn:string[@key='stringValue'])"/>
+ </xsl:for-each>
+ </xsl:for-each>
+ </string>
+ </map>
+ </xsl:variable>
+ <xsl:template match="/">
+ <xsl:value-of select="xml-to-json($mapped-xml)"/>
+ </xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/smart-log-analyzer/correlator/logs-mapper.camel.yaml
b/smart-log-analyzer/correlator/logs-mapper.camel.yaml
new file mode 100644
index 0000000..f4f85df
--- /dev/null
+++ b/smart-log-analyzer/correlator/logs-mapper.camel.yaml
@@ -0,0 +1,45 @@
+- route:
+ id: log-consumer
+ from:
+ uri: kafka:{{camel.kafka.topic.logs}}
+ parameters:
+ autoOffsetReset: earliest
+ groupId: correlator
+ steps:
+ - log:
+ loggingLevel: DEBUG
+ message: Received log from Kafka \n ${body}
+ - split:
+ id: split-logs
+ expression:
+ jsonpath:
+ expression: $.resourceLogs[*].scopeLogs[*].logRecords[*]
+ writeAsString: true
+ steps:
+ - setHeader:
+ id: set-logs-header
+ expression:
+ simple:
+ expression: ${body}
+ name: logs
+ - setBody:
+ id: set-dummy-xml
+ expression:
+ constant:
+ expression: <dummy/>
+ - step:
+ id: kaoto-datamapper-4a94acc3
+ steps:
+ - to:
+ id: kaoto-datamapper-xslt
+ uri: xslt-saxon:kaoto-datamapper-4a94acc3.xsl
+ parameters:
+ allowStAX: true
+ - log:
+ loggingLevel: DEBUG
+ message: "Mapped output: ${body}"
+ - to:
+ id: to-2266
+ uri: direct
+ parameters:
+ name: store
diff --git a/smart-log-analyzer/correlator/otel-log-record-schema.json
b/smart-log-analyzer/correlator/otel-log-record-schema.json
new file mode 100644
index 0000000..afc34fa
--- /dev/null
+++ b/smart-log-analyzer/correlator/otel-log-record-schema.json
@@ -0,0 +1,137 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://opentelemetry.io/schemas/logs/1.24.0/LogRecord",
+ "title": "OpenTelemetry Log Record",
+ "description": "JSON Schema for a single OpenTelemetry log record (after
split from OTLP batch)",
+ "type": "object",
+ "properties": {
+ "timeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Time when the event occurred (nanoseconds since Unix
epoch)"
+ },
+ "observedTimeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Time when the event was observed (nanoseconds since Unix
epoch)"
+ },
+ "severityNumber": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 24,
+ "description": "Numerical severity value (1-24)"
+ },
+ "severityText": {
+ "type": "string",
+ "description": "Severity text (e.g., TRACE, DEBUG, INFO, WARN, ERROR,
FATAL)"
+ },
+ "body": {
+ "$ref": "#/$defs/AnyValue",
+ "description": "The body of the log record"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Additional attributes for the log record",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ },
+ "droppedAttributesCount": {
+ "type": "integer",
+ "description": "Number of attributes that were dropped due to limits"
+ },
+ "flags": {
+ "type": "integer",
+ "description": "Flags for the log record"
+ },
+ "traceId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{32}$",
+ "description": "Trace ID (32 hex characters)"
+ },
+ "spanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Span ID (16 hex characters)"
+ }
+ },
+ "$defs": {
+ "KeyValue": {
+ "type": "object",
+ "description": "A key-value pair",
+ "required": ["key", "value"],
+ "properties": {
+ "key": {
+ "type": "string",
+ "description": "The attribute key"
+ },
+ "value": {
+ "$ref": "#/$defs/AnyValue",
+ "description": "The attribute value"
+ }
+ }
+ },
+ "AnyValue": {
+ "type": "object",
+ "description": "A value of any type",
+ "properties": {
+ "stringValue": {
+ "type": "string"
+ },
+ "boolValue": {
+ "type": "boolean"
+ },
+ "intValue": {
+ "type": "string",
+ "pattern": "^-?[0-9]+$"
+ },
+ "doubleValue": {
+ "type": "number"
+ },
+ "arrayValue": {
+ "$ref": "#/$defs/ArrayValue"
+ },
+ "kvlistValue": {
+ "$ref": "#/$defs/KeyValueList"
+ },
+ "bytesValue": {
+ "type": "string",
+ "contentEncoding": "base64"
+ }
+ },
+ "oneOf": [
+ { "required": ["stringValue"] },
+ { "required": ["boolValue"] },
+ { "required": ["intValue"] },
+ { "required": ["doubleValue"] },
+ { "required": ["arrayValue"] },
+ { "required": ["kvlistValue"] },
+ { "required": ["bytesValue"] }
+ ]
+ },
+ "ArrayValue": {
+ "type": "object",
+ "description": "An array of values",
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/AnyValue"
+ }
+ }
+ }
+ },
+ "KeyValueList": {
+ "type": "object",
+ "description": "A list of key-value pairs",
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/smart-log-analyzer/correlator/otel-logs-schema.json
b/smart-log-analyzer/correlator/otel-logs-schema.json
new file mode 100644
index 0000000..ffa7674
--- /dev/null
+++ b/smart-log-analyzer/correlator/otel-logs-schema.json
@@ -0,0 +1,227 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://opentelemetry.io/schemas/logs/1.24.0",
+ "title": "OpenTelemetry Log Data",
+ "description": "JSON Schema for OpenTelemetry Protocol (OTLP) log data",
+ "type": "object",
+ "required": ["resourceLogs"],
+ "properties": {
+ "resourceLogs": {
+ "type": "array",
+ "description": "Array of ResourceLogs messages",
+ "items": {
+ "$ref": "#/$defs/ResourceLogs"
+ }
+ }
+ },
+ "$defs": {
+ "ResourceLogs": {
+ "type": "object",
+ "description": "A collection of ScopeLogs from a Resource",
+ "properties": {
+ "resource": {
+ "$ref": "#/$defs/Resource"
+ },
+ "scopeLogs": {
+ "type": "array",
+ "description": "A list of ScopeLogs that originate from a resource",
+ "items": {
+ "$ref": "#/$defs/ScopeLogs"
+ }
+ },
+ "schemaUrl": {
+ "type": "string",
+ "format": "uri",
+ "description": "Schema URL for the resource"
+ }
+ }
+ },
+ "Resource": {
+ "type": "object",
+ "description": "Resource information",
+ "properties": {
+ "attributes": {
+ "type": "array",
+ "description": "Set of attributes that describe the resource",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ }
+ }
+ },
+ "ScopeLogs": {
+ "type": "object",
+ "description": "A collection of log records from an instrumentation
scope",
+ "properties": {
+ "scope": {
+ "$ref": "#/$defs/InstrumentationScope"
+ },
+ "logRecords": {
+ "type": "array",
+ "description": "A list of log records",
+ "items": {
+ "$ref": "#/$defs/LogRecord"
+ }
+ },
+ "schemaUrl": {
+ "type": "string",
+ "format": "uri",
+ "description": "Schema URL for the scope"
+ }
+ }
+ },
+ "InstrumentationScope": {
+ "type": "object",
+ "description": "Instrumentation scope information",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of the instrumentation scope"
+ },
+ "version": {
+ "type": "string",
+ "description": "Version of the instrumentation scope"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Attributes of the instrumentation scope",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ }
+ }
+ },
+ "LogRecord": {
+ "type": "object",
+ "description": "A log record",
+ "properties": {
+ "timeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Time when the event occurred (nanoseconds since Unix
epoch)"
+ },
+ "observedTimeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Time when the event was observed (nanoseconds since
Unix epoch)"
+ },
+ "severityNumber": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 24,
+ "description": "Numerical severity value (1-24)"
+ },
+ "severityText": {
+ "type": "string",
+ "description": "Severity text (e.g., TRACE, DEBUG, INFO, WARN,
ERROR, FATAL)"
+ },
+ "body": {
+ "$ref": "#/$defs/AnyValue",
+ "description": "The body of the log record"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Additional attributes for the log record",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ },
+ "droppedAttributesCount": {
+ "type": "integer",
+ "description": "Number of attributes that were dropped due to limits"
+ },
+ "flags": {
+ "type": "integer",
+ "description": "Flags for the log record"
+ },
+ "traceId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{32}$",
+ "description": "Trace ID (32 hex characters)"
+ },
+ "spanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Span ID (16 hex characters)"
+ }
+ }
+ },
+ "KeyValue": {
+ "type": "object",
+ "description": "A key-value pair",
+ "required": ["key", "value"],
+ "properties": {
+ "key": {
+ "type": "string",
+ "description": "The attribute key"
+ },
+ "value": {
+ "$ref": "#/$defs/AnyValue",
+ "description": "The attribute value"
+ }
+ }
+ },
+ "AnyValue": {
+ "type": "object",
+ "description": "A value of any type",
+ "properties": {
+ "stringValue": {
+ "type": "string"
+ },
+ "boolValue": {
+ "type": "boolean"
+ },
+ "intValue": {
+ "type": "string",
+ "pattern": "^-?[0-9]+$"
+ },
+ "doubleValue": {
+ "type": "number"
+ },
+ "arrayValue": {
+ "$ref": "#/$defs/ArrayValue"
+ },
+ "kvlistValue": {
+ "$ref": "#/$defs/KeyValueList"
+ },
+ "bytesValue": {
+ "type": "string",
+ "contentEncoding": "base64"
+ }
+ },
+ "oneOf": [
+ { "required": ["stringValue"] },
+ { "required": ["boolValue"] },
+ { "required": ["intValue"] },
+ { "required": ["doubleValue"] },
+ { "required": ["arrayValue"] },
+ { "required": ["kvlistValue"] },
+ { "required": ["bytesValue"] }
+ ]
+ },
+ "ArrayValue": {
+ "type": "object",
+ "description": "An array of values",
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/AnyValue"
+ }
+ }
+ }
+ },
+ "KeyValueList": {
+ "type": "object",
+ "description": "A list of key-value pairs",
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/smart-log-analyzer/correlator/otel-span-schema.json
b/smart-log-analyzer/correlator/otel-span-schema.json
new file mode 100644
index 0000000..9b12c69
--- /dev/null
+++ b/smart-log-analyzer/correlator/otel-span-schema.json
@@ -0,0 +1,244 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://opentelemetry.io/schemas/traces/1.24.0/Span",
+ "title": "OpenTelemetry Span",
+ "description": "JSON Schema for a single OpenTelemetry span (after split
from OTLP batch)",
+ "type": "object",
+ "properties": {
+ "traceId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{32}$",
+ "description": "Trace ID (32 hex characters)"
+ },
+ "spanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Span ID (16 hex characters)"
+ },
+ "traceState": {
+ "type": "string",
+ "description": "W3C trace state"
+ },
+ "parentSpanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Parent span ID (16 hex characters)"
+ },
+ "flags": {
+ "type": "integer",
+ "description": "Trace flags (W3C format)"
+ },
+ "name": {
+ "type": "string",
+ "description": "Name of the span"
+ },
+ "kind": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 5,
+ "description": "Span kind (0=UNSPECIFIED, 1=INTERNAL, 2=SERVER,
3=CLIENT, 4=PRODUCER, 5=CONSUMER)"
+ },
+ "startTimeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Start time (nanoseconds since Unix epoch)"
+ },
+ "endTimeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "End time (nanoseconds since Unix epoch)"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Attributes for the span",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ },
+ "droppedAttributesCount": {
+ "type": "integer",
+ "description": "Number of attributes that were dropped due to limits"
+ },
+ "events": {
+ "type": "array",
+ "description": "Timed events within the span",
+ "items": {
+ "$ref": "#/$defs/SpanEvent"
+ }
+ },
+ "droppedEventsCount": {
+ "type": "integer",
+ "description": "Number of events that were dropped due to limits"
+ },
+ "links": {
+ "type": "array",
+ "description": "Links to other spans",
+ "items": {
+ "$ref": "#/$defs/SpanLink"
+ }
+ },
+ "droppedLinksCount": {
+ "type": "integer",
+ "description": "Number of links that were dropped due to limits"
+ },
+ "status": {
+ "$ref": "#/$defs/Status"
+ }
+ },
+ "$defs": {
+ "SpanEvent": {
+ "type": "object",
+ "description": "A timed event within a span",
+ "properties": {
+ "timeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Time of the event (nanoseconds since Unix epoch)"
+ },
+ "name": {
+ "type": "string",
+ "description": "Name of the event"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Attributes for the event",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ },
+ "droppedAttributesCount": {
+ "type": "integer",
+ "description": "Number of attributes that were dropped due to limits"
+ }
+ }
+ },
+ "SpanLink": {
+ "type": "object",
+ "description": "A link to another span",
+ "properties": {
+ "traceId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{32}$",
+ "description": "Trace ID of the linked span"
+ },
+ "spanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Span ID of the linked span"
+ },
+ "traceState": {
+ "type": "string",
+ "description": "W3C trace state"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Attributes for the link",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ },
+ "droppedAttributesCount": {
+ "type": "integer",
+ "description": "Number of attributes that were dropped due to limits"
+ },
+ "flags": {
+ "type": "integer",
+ "description": "Trace flags"
+ }
+ }
+ },
+ "Status": {
+ "type": "object",
+ "description": "Status of the span",
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "Human-readable status message"
+ },
+ "code": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 2,
+ "description": "Status code (0=UNSET, 1=OK, 2=ERROR)"
+ }
+ }
+ },
+ "KeyValue": {
+ "type": "object",
+ "description": "A key-value pair",
+ "required": ["key", "value"],
+ "properties": {
+ "key": {
+ "type": "string",
+ "description": "The attribute key"
+ },
+ "value": {
+ "$ref": "#/$defs/AnyValue",
+ "description": "The attribute value"
+ }
+ }
+ },
+ "AnyValue": {
+ "type": "object",
+ "description": "A value of any type",
+ "properties": {
+ "stringValue": {
+ "type": "string"
+ },
+ "boolValue": {
+ "type": "boolean"
+ },
+ "intValue": {
+ "type": "string",
+ "pattern": "^-?[0-9]+$"
+ },
+ "doubleValue": {
+ "type": "number"
+ },
+ "arrayValue": {
+ "$ref": "#/$defs/ArrayValue"
+ },
+ "kvlistValue": {
+ "$ref": "#/$defs/KeyValueList"
+ },
+ "bytesValue": {
+ "type": "string",
+ "contentEncoding": "base64"
+ }
+ },
+ "oneOf": [
+ { "required": ["stringValue"] },
+ { "required": ["boolValue"] },
+ { "required": ["intValue"] },
+ { "required": ["doubleValue"] },
+ { "required": ["arrayValue"] },
+ { "required": ["kvlistValue"] },
+ { "required": ["bytesValue"] }
+ ]
+ },
+ "ArrayValue": {
+ "type": "object",
+ "description": "An array of values",
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/AnyValue"
+ }
+ }
+ }
+ },
+ "KeyValueList": {
+ "type": "object",
+ "description": "A list of key-value pairs",
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/smart-log-analyzer/correlator/otel-traces-schema.json
b/smart-log-analyzer/correlator/otel-traces-schema.json
new file mode 100644
index 0000000..6d05e12
--- /dev/null
+++ b/smart-log-analyzer/correlator/otel-traces-schema.json
@@ -0,0 +1,334 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://opentelemetry.io/schemas/traces/1.24.0",
+ "title": "OpenTelemetry Trace Data",
+ "description": "JSON Schema for OpenTelemetry Protocol (OTLP) trace data",
+ "type": "object",
+ "required": ["resourceSpans"],
+ "properties": {
+ "resourceSpans": {
+ "type": "array",
+ "description": "Array of ResourceSpans messages",
+ "items": {
+ "$ref": "#/$defs/ResourceSpans"
+ }
+ }
+ },
+ "$defs": {
+ "ResourceSpans": {
+ "type": "object",
+ "description": "A collection of ScopeSpans from a Resource",
+ "properties": {
+ "resource": {
+ "$ref": "#/$defs/Resource"
+ },
+ "scopeSpans": {
+ "type": "array",
+ "description": "A list of ScopeSpans that originate from a resource",
+ "items": {
+ "$ref": "#/$defs/ScopeSpans"
+ }
+ },
+ "schemaUrl": {
+ "type": "string",
+ "format": "uri",
+ "description": "Schema URL for the resource"
+ }
+ }
+ },
+ "Resource": {
+ "type": "object",
+ "description": "Resource information",
+ "properties": {
+ "attributes": {
+ "type": "array",
+ "description": "Set of attributes that describe the resource",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ }
+ }
+ },
+ "ScopeSpans": {
+ "type": "object",
+ "description": "A collection of spans from an instrumentation scope",
+ "properties": {
+ "scope": {
+ "$ref": "#/$defs/InstrumentationScope"
+ },
+ "spans": {
+ "type": "array",
+ "description": "A list of spans",
+ "items": {
+ "$ref": "#/$defs/Span"
+ }
+ },
+ "schemaUrl": {
+ "type": "string",
+ "format": "uri",
+ "description": "Schema URL for the scope"
+ }
+ }
+ },
+ "InstrumentationScope": {
+ "type": "object",
+ "description": "Instrumentation scope information",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of the instrumentation scope"
+ },
+ "version": {
+ "type": "string",
+ "description": "Version of the instrumentation scope"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Attributes of the instrumentation scope",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ }
+ }
+ },
+ "Span": {
+ "type": "object",
+ "description": "A span representing a trace segment",
+ "properties": {
+ "traceId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{32}$",
+ "description": "Trace ID (32 hex characters)"
+ },
+ "spanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Span ID (16 hex characters)"
+ },
+ "traceState": {
+ "type": "string",
+ "description": "W3C trace state"
+ },
+ "parentSpanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Parent span ID (16 hex characters)"
+ },
+ "flags": {
+ "type": "integer",
+ "description": "Trace flags (W3C format)"
+ },
+ "name": {
+ "type": "string",
+ "description": "Name of the span"
+ },
+ "kind": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 5,
+ "description": "Span kind (0=UNSPECIFIED, 1=INTERNAL, 2=SERVER,
3=CLIENT, 4=PRODUCER, 5=CONSUMER)"
+ },
+ "startTimeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Start time (nanoseconds since Unix epoch)"
+ },
+ "endTimeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "End time (nanoseconds since Unix epoch)"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Attributes for the span",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ },
+ "droppedAttributesCount": {
+ "type": "integer",
+ "description": "Number of attributes that were dropped due to limits"
+ },
+ "events": {
+ "type": "array",
+ "description": "Timed events within the span",
+ "items": {
+ "$ref": "#/$defs/SpanEvent"
+ }
+ },
+ "droppedEventsCount": {
+ "type": "integer",
+ "description": "Number of events that were dropped due to limits"
+ },
+ "links": {
+ "type": "array",
+ "description": "Links to other spans",
+ "items": {
+ "$ref": "#/$defs/SpanLink"
+ }
+ },
+ "droppedLinksCount": {
+ "type": "integer",
+ "description": "Number of links that were dropped due to limits"
+ },
+ "status": {
+ "$ref": "#/$defs/Status"
+ }
+ }
+ },
+ "SpanEvent": {
+ "type": "object",
+ "description": "A timed event within a span",
+ "properties": {
+ "timeUnixNano": {
+ "type": "string",
+ "pattern": "^[0-9]+$",
+ "description": "Time of the event (nanoseconds since Unix epoch)"
+ },
+ "name": {
+ "type": "string",
+ "description": "Name of the event"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Attributes for the event",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ },
+ "droppedAttributesCount": {
+ "type": "integer",
+ "description": "Number of attributes that were dropped due to limits"
+ }
+ }
+ },
+ "SpanLink": {
+ "type": "object",
+ "description": "A link to another span",
+ "properties": {
+ "traceId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{32}$",
+ "description": "Trace ID of the linked span"
+ },
+ "spanId": {
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{16}$",
+ "description": "Span ID of the linked span"
+ },
+ "traceState": {
+ "type": "string",
+ "description": "W3C trace state"
+ },
+ "attributes": {
+ "type": "array",
+ "description": "Attributes for the link",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ },
+ "droppedAttributesCount": {
+ "type": "integer",
+ "description": "Number of attributes that were dropped due to limits"
+ },
+ "flags": {
+ "type": "integer",
+ "description": "Trace flags"
+ }
+ }
+ },
+ "Status": {
+ "type": "object",
+ "description": "Status of the span",
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "Human-readable status message"
+ },
+ "code": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 2,
+ "description": "Status code (0=UNSET, 1=OK, 2=ERROR)"
+ }
+ }
+ },
+ "KeyValue": {
+ "type": "object",
+ "description": "A key-value pair",
+ "required": ["key", "value"],
+ "properties": {
+ "key": {
+ "type": "string",
+ "description": "The attribute key"
+ },
+ "value": {
+ "$ref": "#/$defs/AnyValue",
+ "description": "The attribute value"
+ }
+ }
+ },
+ "AnyValue": {
+ "type": "object",
+ "description": "A value of any type",
+ "properties": {
+ "stringValue": {
+ "type": "string"
+ },
+ "boolValue": {
+ "type": "boolean"
+ },
+ "intValue": {
+ "type": "string",
+ "pattern": "^-?[0-9]+$"
+ },
+ "doubleValue": {
+ "type": "number"
+ },
+ "arrayValue": {
+ "$ref": "#/$defs/ArrayValue"
+ },
+ "kvlistValue": {
+ "$ref": "#/$defs/KeyValueList"
+ },
+ "bytesValue": {
+ "type": "string",
+ "contentEncoding": "base64"
+ }
+ },
+ "oneOf": [
+ { "required": ["stringValue"] },
+ { "required": ["boolValue"] },
+ { "required": ["intValue"] },
+ { "required": ["doubleValue"] },
+ { "required": ["arrayValue"] },
+ { "required": ["kvlistValue"] },
+ { "required": ["bytesValue"] }
+ ]
+ },
+ "ArrayValue": {
+ "type": "object",
+ "description": "An array of values",
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/AnyValue"
+ }
+ }
+ }
+ },
+ "KeyValueList": {
+ "type": "object",
+ "description": "A list of key-value pairs",
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/KeyValue"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/smart-log-analyzer/correlator/traces-mapper.camel.yaml
b/smart-log-analyzer/correlator/traces-mapper.camel.yaml
new file mode 100644
index 0000000..c75721a
--- /dev/null
+++ b/smart-log-analyzer/correlator/traces-mapper.camel.yaml
@@ -0,0 +1,45 @@
+- route:
+ id: trace-consumer
+ from:
+ uri: kafka:{{camel.kafka.topic.spans}}
+ parameters:
+ autoOffsetReset: earliest
+ groupId: correlator
+ steps:
+ - log:
+ loggingLevel: DEBUG
+ message: Received trace from Kafka
+ - split:
+ id: split-spans
+ expression:
+ jsonpath:
+ expression: $.resourceSpans[*].scopeSpans[*].spans[*]
+ writeAsString: true
+ steps:
+ - setHeader:
+ id: set-traces-header
+ expression:
+ simple:
+ expression: ${body}
+ name: traces
+ - setBody:
+ id: set-dummy-xml-1
+ expression:
+ constant:
+ expression: <dummy/>
+ - step:
+ id: kaoto-datamapper-8f5bb2dd
+ steps:
+ - to:
+ id: kaoto-datamapper-xslt-1526
+ uri: xslt-saxon:kaoto-datamapper-8f5bb2dd.xsl
+ parameters:
+ allowStAX: true
+ - log:
+ loggingLevel: DEBUG
+ message: "Mapped output: ${body}"
+ - to:
+ id: to-22661
+ uri: direct
+ parameters:
+ name: store
diff --git a/smart-log-analyzer/first-iteration/analyzer.camel.yaml
b/smart-log-analyzer/first-iteration/analyzer.camel.yaml
new file mode 100644
index 0000000..01cc80d
--- /dev/null
+++ b/smart-log-analyzer/first-iteration/analyzer.camel.yaml
@@ -0,0 +1,62 @@
+# Consumes log events from Kafka, aggregates by traceId, and sends
+# to LLM for analysis when an ERROR level event is detected
+- route:
+ id: log-event-consumer
+ from:
+ uri: kafka:log-events
+ parameters:
+ autoOffsetReset: earliest
+ brokers: localhost:9092
+ groupId: log-analyzer
+ steps:
+ - log:
+ loggingLevel: DEBUG
+ message: "Received event from Kafka: ${body}"
+ - setVariable:
+ name: traceId
+ jq:
+ expression: .traceId
+ resultType: java.lang.String
+ - setVariable:
+ name: level
+ jq:
+ expression: .level
+ resultType: java.lang.String
+ - aggregate:
+ correlationExpression:
+ simple:
+ expression: ${variable.traceId}
+ aggregationStrategy: "#stringAggregationStrategy"
+ completionPredicate:
+ simple:
+ expression: ${variable.level} == 'ERROR'
+ completionTimeout: 30000
+ steps:
+ - log:
+ message: "Aggregation completed for traceId:
${variable.traceId} - sending to LLM"
+ - setVariable:
+ name: eventsJson
+ simple: ${body}
+ - setBody:
+ simple: >
+ Analyze the following log events for traceId
${variable.traceId}.
+
+ An ERROR was detected. Identify the root cause, explain
what
+ happened in the sequence of events, and suggest possible
fixes.
+
+ Events: ${variable.eventsJson}
+ - to:
+ uri: openai:chat-completion
+ parameters:
+ apiKey: not-needed
+ baseUrl: http://localhost:11434/v1
+ model: granite4:3b
+ systemMessage: >
+ Analyze log events to identify errors, their root
causes, and
+ provide actionable recommendations. Be concise and focus
+ on the error chain and sequence of events.
+ - log:
+ message: "LLM Analysis for traceId ${variable.traceId}:
${body}"
+- beans:
+ - name: stringAggregationStrategy
+ type: org.apache.camel.processor.aggregate.StringAggregationStrategy
\ No newline at end of file
diff --git a/smart-log-analyzer/first-iteration/application.properties
b/smart-log-analyzer/first-iteration/application.properties
new file mode 100644
index 0000000..caa88c5
--- /dev/null
+++ b/smart-log-analyzer/first-iteration/application.properties
@@ -0,0 +1,2 @@
+# Dependencies for the first-iteration analyzer
+camel.jbang.dependencies=org.apache.camel:camel-openai
diff --git a/smart-log-analyzer/first-iteration/load-generator.camel.yaml
b/smart-log-analyzer/first-iteration/load-generator.camel.yaml
new file mode 100644
index 0000000..3a8f248
--- /dev/null
+++ b/smart-log-analyzer/first-iteration/load-generator.camel.yaml
@@ -0,0 +1,48 @@
+- route:
+ id: event-generator
+ from:
+ uri: timer:eventGenerator
+ parameters:
+ period: 5000
+ steps:
+ # Generate a new traceId for each timer event
+ - setVariable:
+ name: traceId
+ simple: "${random(10000,99999)}-${date:now:yyyyMMddHHmmssSSS}"
+
+ # Send first INFO event
+ - setBody:
+ simple: |
+ {"traceId": "${variable.traceId}", "level": "INFO", "message":
"Application started processing request", "timestamp":
"${date:now:yyyy-MM-dd'T'HH:mm:ss.SSSZ}"}
+ - to:
+ uri: kafka:log-events
+ parameters:
+ brokers: localhost:9092
+
+ # Send second INFO event
+ - setBody:
+ simple: |
+ {"traceId": "${variable.traceId}", "level": "INFO", "message":
"Request validation completed successfully", "timestamp":
"${date:now:yyyy-MM-dd'T'HH:mm:ss.SSSZ}"}
+ - to:
+ uri: kafka:log-events
+ parameters:
+ brokers: localhost:9092
+
+ # 30% chance of generating an ERROR event
+ - choice:
+ when:
+ - simple: "${random(1,10)} <= 3"
+ steps:
+ - setBody:
+ simple: |
+ {"traceId": "${variable.traceId}", "level": "ERROR",
"message": "Unexpected error occurred during processing", "timestamp":
"${date:now:yyyy-MM-dd'T'HH:mm:ss.SSSZ}"}
+ - to:
+ uri: kafka:log-events
+ parameters:
+ brokers: localhost:9092
+ - log:
+ message: "ERROR event sent for traceId:
${variable.traceId}"
+ otherwise:
+ steps:
+ - log:
+ message: "No error generated for traceId:
${variable.traceId}"
\ No newline at end of file
diff --git
a/smart-log-analyzer/first-iteration/test/first-iteration.camel.it.yaml
b/smart-log-analyzer/first-iteration/test/first-iteration.camel.it.yaml
new file mode 100644
index 0000000..900b164
--- /dev/null
+++ b/smart-log-analyzer/first-iteration/test/first-iteration.camel.it.yaml
@@ -0,0 +1,46 @@
+name: first-iteration-test
+description: Integration test for the first-iteration log analyzer prototype
+variables:
+ - name: kafka.broker
+ value: localhost:9092
+ - name: kafka.topic
+ value: log-events
+actions:
+ - camel:
+ infra:
+ run:
+ service: kafka
+ - camel:
+ jbang:
+ run:
+ stub:
+ - "openai:*"
+ integration:
+ name: "log-analyzer"
+ file: "../analyzer.camel.yaml"
+ systemProperties:
+ file: "../application.properties"
+ - send:
+ endpoint: |
+ camel:kafka:${kafka.topic}?brokers=${kafka.broker}
+ message:
+ body:
+ resource:
+ file: "payload-info.json"
+ - send:
+ endpoint: |
+ camel:kafka:${kafka.topic}?brokers=${kafka.broker}
+ message:
+ body:
+ resource:
+ file: "payload-error.json"
+ - camel:
+ jbang:
+ verify:
+ integration: "log-analyzer"
+ logMessage: "Aggregation completed for traceId"
+ - camel:
+ jbang:
+ verify:
+ integration: "log-analyzer"
+ logMessage: "LLM Analysis for traceId test-12345"
diff --git a/smart-log-analyzer/first-iteration/test/jbang.properties
b/smart-log-analyzer/first-iteration/test/jbang.properties
new file mode 100644
index 0000000..8be759f
--- /dev/null
+++ b/smart-log-analyzer/first-iteration/test/jbang.properties
@@ -0,0 +1,3 @@
+run.deps=org.citrusframework:citrus-kafka:4.9.1,\
+ org.apache.camel:camel-test-infra-kafka:4.17.0,\
+ org.apache.camel:camel-kafka:4.17.0
diff --git a/smart-log-analyzer/first-iteration/test/payload-error.json
b/smart-log-analyzer/first-iteration/test/payload-error.json
new file mode 100644
index 0000000..2a7dd86
--- /dev/null
+++ b/smart-log-analyzer/first-iteration/test/payload-error.json
@@ -0,0 +1 @@
+{"traceId": "test-12345", "level": "ERROR", "message": "Unexpected error
occurred during processing", "timestamp": "2026-01-28T10:00:01.000+0000"}
diff --git a/smart-log-analyzer/first-iteration/test/payload-info.json
b/smart-log-analyzer/first-iteration/test/payload-info.json
new file mode 100644
index 0000000..c2604b1
--- /dev/null
+++ b/smart-log-analyzer/first-iteration/test/payload-info.json
@@ -0,0 +1 @@
+{"traceId": "test-12345", "level": "INFO", "message": "Application started
processing request", "timestamp": "2026-01-28T10:00:00.000+0000"}
diff --git a/smart-log-analyzer/log-generator/agent.properties
b/smart-log-analyzer/log-generator/agent.properties
new file mode 100644
index 0000000..6019ebc
--- /dev/null
+++ b/smart-log-analyzer/log-generator/agent.properties
@@ -0,0 +1,6 @@
+otel.javaagent.debug=false
+
+otel.service.name=log-generator
+otel.traces.exporter=otlp
+otel.metrics.exporter=none
+otel.logs.exporter=otlp
diff --git a/smart-log-analyzer/log-generator/application-dev.properties
b/smart-log-analyzer/log-generator/application-dev.properties
new file mode 100644
index 0000000..0950ceb
--- /dev/null
+++ b/smart-log-analyzer/log-generator/application-dev.properties
@@ -0,0 +1,3 @@
+camel.opentelemetry2.enabled = true
+
+camel.jbang.dependencies=org.apache.camel:camel-opentelemetry2
diff --git a/smart-log-analyzer/log-generator/log-generator.camel.yaml
b/smart-log-analyzer/log-generator/log-generator.camel.yaml
new file mode 100644
index 0000000..dcd316f
--- /dev/null
+++ b/smart-log-analyzer/log-generator/log-generator.camel.yaml
@@ -0,0 +1,87 @@
+# Simulates order processing with random success/failure outcomes (30% failure
rate)
+# to generate realistic log data for testing the error analyzer
+- route:
+ id: order-processor
+ from:
+ uri: timer:orderProcessor
+ parameters:
+ period: "{{timer.order.period:2000}}"
+ fixedRate: true
+ steps:
+
+ # Generate random order ID
+ - setVariable:
+ name: orderId
+ simple: "ORD-${random(10000,99999)}"
+
+ # Log: Order received
+ - log:
+ message: "Received new order ${variable.orderId} from customer
${random(1000,9999)}"
+ loggingLevel: INFO
+ logName: com.example.order.OrderService
+
+ # Log: Validation
+ - log:
+ message: "Validating order ${variable.orderId}: checking inventory
and pricing"
+ loggingLevel: INFO
+ logName: com.example.order.ValidationService
+
+ # Simulate random processing outcomes (30% failure rate)
+ - choice:
+ when:
+ - simple: "${random(0,100)} < 30"
+ steps:
+ # Simulate different error types
+ - choice:
+ when:
+ - simple: "${random(0,100)} < 33"
+ steps:
+ - log:
+ message: "Connecting to database for order
${variable.orderId}"
+ loggingLevel: INFO
+ logName: com.example.order.DatabaseService
+ - throwException:
+ message: "Database connection failed:
Connection refused to postgres:5432 while processing order ${variable.orderId}"
+ exceptionType: java.lang.RuntimeException
+ - simple: "${random(0,100)} < 66"
+ steps:
+ - log:
+ message: "Initiating payment for order
${variable.orderId}"
+ loggingLevel: INFO
+ logName: com.example.payment.PaymentGateway
+ - throwException:
+ message: "Network timeout while connecting to
payment gateway api.stripe.com for order ${variable.orderId}"
+ exceptionType: java.lang.RuntimeException
+ otherwise:
+ steps:
+ - log:
+ message: "Verifying user authentication for
order ${variable.orderId}"
+ loggingLevel: INFO
+ logName: com.example.auth.AuthService
+ - throwException:
+ message: "Authentication failed: Invalid JWT
token signature for order ${variable.orderId}"
+ exceptionType: java.lang.RuntimeException
+ otherwise:
+ steps:
+ - log:
+ message: "Payment processed successfully for order
${variable.orderId}"
+ loggingLevel: INFO
+ logName: com.example.payment.PaymentGateway
+ - log:
+ message: "Order ${variable.orderId} completed successfully"
+ loggingLevel: INFO
+ logName: com.example.order.OrderService
+
+# Periodic health check logging for system monitoring
+- route:
+ id: health-checker
+ from:
+ uri: timer:healthCheck
+ parameters:
+ period: 5000
+ fixedRate: true
+ steps:
+ - log:
+ message: "Health check: All systems operational"
+ loggingLevel: DEBUG
+ logName: com.example.health.HealthChecker
diff --git a/smart-log-analyzer/log-generator/opentelemetry-javaagent.jar
b/smart-log-analyzer/log-generator/opentelemetry-javaagent.jar
new file mode 100644
index 0000000..926195a
Binary files /dev/null and
b/smart-log-analyzer/log-generator/opentelemetry-javaagent.jar differ
diff --git a/smart-log-analyzer/ui-console/application-dev.properties
b/smart-log-analyzer/ui-console/application-dev.properties
new file mode 100644
index 0000000..7f32b42
--- /dev/null
+++ b/smart-log-analyzer/ui-console/application-dev.properties
@@ -0,0 +1,31 @@
+# Storage configuration
+analyzer.storage.root=/tmp/analyzer-store
+
+# JMS Queue configuration
+camel.jms.queue.analysis-result=analysis-result
+
+# Artemis JMS configuration
+camel.beans.artemisCF =
#class:org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory
+camel.beans.artemisCF.brokerURL = tcp://localhost:61616
+camel.beans.artemisCF.user = artemis
+camel.beans.artemisCF.password = artemis
+camel.beans.poolCF =
#class:org.messaginghub.pooled.jms.JmsPoolConnectionFactory
+camel.beans.poolCF.connectionFactory = #bean:artemisCF
+camel.beans.poolCF.maxSessionsPerConnection = 500
+camel.beans.poolCF.connectionIdleTimeout = 20000
+camel.component.jms.connection-factory = #bean:poolCF
+
+# REST configuration
+camel.rest.component=platform-http
+camel.rest.binding-mode=off
+camel.rest.port=8080
+camel.rest.host=0.0.0.0
+
+# Camel configuration
+camel.main.name=ui-console
+
+camel.server.enabled=true
+camel.server.staticEnabled=true
+
+# Dependencies
+camel.jbang.dependencies=org.apache.camel:camel-groovy
diff --git a/smart-log-analyzer/ui-console/index.html
b/smart-log-analyzer/ui-console/index.html
new file mode 100644
index 0000000..3474b82
--- /dev/null
+++ b/smart-log-analyzer/ui-console/index.html
@@ -0,0 +1,287 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Smart Log Analyzer - Console</title>
+ <link rel="stylesheet"
href="https://unpkg.com/[email protected]/css/carbon-components.min.css">
+ <link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=IBM+Plex+Sans:wght@400;600&display=swap">
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ font-family: 'IBM Plex Sans', 'Helvetica Neue', Arial, sans-serif;
+ }
+
+ .bx--header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 8000;
+ }
+
+ .camel-logo {
+ width: 32px;
+ height: 32px;
+ margin-right: 0.75rem;
+ vertical-align: middle;
+ }
+
+ .container {
+ display: flex;
+ height: 100vh;
+ padding-top: 48px;
+ }
+
+ .sidebar {
+ width: 320px;
+ background-color: #161616;
+ color: #f4f4f4;
+ padding: 1rem;
+ overflow-y: auto;
+ border-right: 1px solid #393939;
+ }
+
+ .main-content {
+ flex: 1;
+ padding: 2rem;
+ overflow-y: auto;
+ background-color: #f4f4f4;
+ }
+
+ .trace-list {
+ list-style: none;
+ padding: 0;
+ margin-top: 1rem;
+ }
+
+ .trace-item {
+ padding: 0.75rem 1rem;
+ margin-bottom: 0.5rem;
+ background-color: #262626;
+ cursor: pointer;
+ transition: background-color 0.11s;
+ border-left: 3px solid transparent;
+ }
+
+ .trace-item:hover {
+ background-color: #333333;
+ }
+
+ .trace-item.active {
+ background-color: #0f62fe;
+ border-left-color: #78a9ff;
+ }
+
+ .trace-id {
+ font-size: 0.875rem;
+ word-break: break-all;
+ font-family: 'IBM Plex Mono', monospace;
+ }
+
+ .content-body {
+ background-color: #ffffff;
+ padding: 1rem;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ font-family: 'IBM Plex Mono', monospace;
+ font-size: 0.875rem;
+ line-height: 1.6;
+ max-height: calc(100vh - 250px);
+ overflow-y: auto;
+ border: 1px solid #e0e0e0;
+ }
+
+ .empty-state {
+ text-align: center;
+ color: #525252;
+ padding: 4rem 2rem;
+ }
+
+ .empty-state-icon {
+ width: 80px;
+ height: 80px;
+ margin: 0 auto 1rem;
+ display: block;
+ }
+
+ .count-badge {
+ display: inline-block;
+ background-color: #0f62fe;
+ color: white;
+ padding: 0.25rem 0.75rem;
+ border-radius: 1rem;
+ font-size: 0.75rem;
+ margin-left: 0.5rem;
+ margin-bottom: 1rem;
+ }
+
+ .sidebar-header {
+ margin-bottom: 1rem;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid #393939;
+ }
+
+ .sidebar-title {
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+ }
+ </style>
+</head>
+<body class="bx--body">
+ <header class="bx--header" role="banner">
+ <a class="bx--header__name" href="javascript:void(0)">
+ <img
src="https://raw.githubusercontent.com/apache/camel/refs/heads/main/docs/img/logo-d.svg"
alt="Apache Camel" class="camel-logo">
+ <span class="bx--header__name--prefix">Smart Log</span>
+ Analyzer
+ </a>
+ </header>
+
+ <div class="container">
+ <div class="sidebar">
+ <div class="sidebar-header">
+ <div class="sidebar-title">Trace Analysis</div>
+ <div id="traceCount"></div>
+ </div>
+ <div class="bx--form-item">
+ <input
+ type="text"
+ class="bx--text-input bx--text-input--light"
+ id="searchBox"
+ placeholder="Search trace IDs...">
+ </div>
+ <ul class="trace-list" id="traceList"></ul>
+ </div>
+ <div class="main-content">
+ <div id="contentPanel"></div>
+ </div>
+ </div>
+
+ <script>
+ const API_BASE = '/api/traces';
+ let allTraces = [];
+ let selectedTraceId = null;
+
+ async function loadTraces() {
+ try {
+ const response = await fetch(API_BASE);
+ if (!response.ok) throw new Error('Failed to fetch traces');
+ const data = await response.json();
+ allTraces = data || [];
+ renderTraceList(allTraces);
+ } catch (error) {
+ console.error('Error loading traces:', error);
+ document.getElementById('traceList').innerHTML = `
+ <div class="bx--inline-notification
bx--inline-notification--error" role="alert" style="margin-top: 1rem;">
+ <div class="bx--inline-notification__details">
+ <div class="bx--inline-notification__text-wrapper">
+ <p
class="bx--inline-notification__subtitle">Failed to load traces. Make sure the
API server is running.</p>
+ </div>
+ </div>
+ </div>
+ `;
+ }
+ }
+
+ function renderTraceList(traces) {
+ const traceList = document.getElementById('traceList');
+ const traceCount = document.getElementById('traceCount');
+
+ traceCount.innerHTML = `<span class="count-badge">${traces.length}
trace${traces.length !== 1 ? 's' : ''}</span>`;
+
+ if (traces.length === 0) {
+ traceList.innerHTML = '<div style="padding: 1rem; color:
#525252; text-align: center;">No traces found</div>';
+ return;
+ }
+
+ traceList.innerHTML = traces.map(trace => `
+ <li class="trace-item ${trace.id === selectedTraceId ?
'active' : ''}"
+ onclick="loadTraceContent('${trace.id}')">
+ <div class="trace-id">${escapeHtml(trace.id)}</div>
+ </li>
+ `).join('');
+ }
+
+ async function loadTraceContent(traceId) {
+ selectedTraceId = traceId;
+
renderTraceList(filterTraces(document.getElementById('searchBox').value));
+
+ const contentPanel = document.getElementById('contentPanel');
+ contentPanel.innerHTML = `
+ <div class="bx--loading bx--loading--small">
+ <svg class="bx--loading__svg" viewBox="0 0 100 100">
+ <circle class="bx--loading__stroke" cx="50%" cy="50%"
r="44"></circle>
+ </svg>
+ </div>
+ `;
+
+ try {
+ const response = await fetch(`${API_BASE}/${traceId}`);
+ if (!response.ok) throw new Error('Trace not found');
+ const data = await response.json();
+
+ contentPanel.innerHTML = `
+ <div class="bx--tile" style="padding: 0;">
+ <div style="display: flex; justify-content:
space-between; align-items: center; padding: 1.5rem; border-bottom: 1px solid
#e0e0e0;">
+ <h3 class="bx--type-productive-heading-03">Trace:
${escapeHtml(data.id)}</h3>
+ <button class="bx--btn bx--btn--primary
bx--btn--sm" onclick="loadTraceContent('${escapeHtml(traceId)}')">
+ Refresh
+ </button>
+ </div>
+ <div style="padding: 1.5rem;">
+ <div
class="content-body">${escapeHtml(data.content)}</div>
+ </div>
+ </div>
+ `;
+ } catch (error) {
+ console.error('Error loading trace content:', error);
+ contentPanel.innerHTML = `
+ <div class="bx--inline-notification
bx--inline-notification--error" role="alert">
+ <div class="bx--inline-notification__details">
+ <div class="bx--inline-notification__text-wrapper">
+ <p
class="bx--inline-notification__title">Error</p>
+ <p
class="bx--inline-notification__subtitle">Failed to load trace content</p>
+ </div>
+ </div>
+ </div>
+ `;
+ }
+ }
+
+ function filterTraces(searchTerm) {
+ if (!searchTerm) return allTraces;
+ const term = searchTerm.toLowerCase();
+ return allTraces.filter(trace =>
+ trace.id.toLowerCase().includes(term)
+ );
+ }
+
+ function escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+ }
+
+ document.getElementById('searchBox').addEventListener('input', (e) => {
+ const filtered = filterTraces(e.target.value);
+ renderTraceList(filtered);
+ });
+
+ // Initial load
+ document.getElementById('contentPanel').innerHTML = `
+ <div class="empty-state">
+ <img
src="https://raw.githubusercontent.com/apache/camel/refs/heads/main/docs/img/logo-d.svg"
alt="Apache Camel" class="empty-state-icon">
+ <h3 class="bx--type-productive-heading-03"
style="margin-bottom: 0.5rem;">Welcome to Smart Log Analyzer</h3>
+ <p class="bx--type-body-short-01">Select a trace from the
sidebar to view its analysis</p>
+ </div>
+ `;
+
+ loadTraces();
+
+ // Auto-refresh every 15 seconds
+ setInterval(loadTraces, 15000);
+ </script>
+</body>
+</html>
diff --git a/smart-log-analyzer/ui-console/jms-file-storage.camel.yaml
b/smart-log-analyzer/ui-console/jms-file-storage.camel.yaml
new file mode 100644
index 0000000..b426959
--- /dev/null
+++ b/smart-log-analyzer/ui-console/jms-file-storage.camel.yaml
@@ -0,0 +1,14 @@
+- route:
+ id: jms-to-file-storage
+ from:
+ uri: jms:{{camel.jms.queue.analysis-result}}
+ steps:
+ - log:
+ message: "Received message from JMS queue: ${body}"
+ - setHeader:
+ name: CamelFileName
+ simple: ${header.traceId}.txt
+ - to:
+ uri: file:{{analyzer.storage.root}}
+ - log:
+ message: "Saved content for traceId: ${header.traceId}"
diff --git a/smart-log-analyzer/ui-console/rest-api.camel.yaml
b/smart-log-analyzer/ui-console/rest-api.camel.yaml
new file mode 100644
index 0000000..e166719
--- /dev/null
+++ b/smart-log-analyzer/ui-console/rest-api.camel.yaml
@@ -0,0 +1,84 @@
+- rest:
+ path: /api/traces
+ get:
+ - id: list-traces
+ produces: application/json
+ to: direct:list-traces
+ - id: get-trace-by-id
+ path: /{id}
+ produces: application/json
+ to: direct:get-trace
+
+- route:
+ id: route-list-traces
+ from:
+ uri: direct:list-traces
+ steps:
+ - setVariable:
+ name: storagePath
+ simple: "{{analyzer.storage.root}}"
+ - setBody:
+ groovy: |
+ import java.nio.file.*
+ try {
+ def path = Paths.get(exchange.getVariable('storagePath'))
+ if (!Files.exists(path)) {
+ return []
+ }
+ return Files.list(path)
+ .filter(Files::isRegularFile)
+ .filter(p -> p.getFileName().toString().endsWith('.txt'))
+ .map(p -> {
+ def filename = p.getFileName().toString()
+ def traceId = filename.substring(0,
filename.lastIndexOf('.txt'))
+ return [id: traceId]
+ })
+ .collect()
+ } catch (Exception e) {
+ return []
+ }
+ - marshal:
+ json:
+ library: Jackson
+ - setHeader:
+ name: Content-Type
+ constant: application/json
+
+- route:
+ id: route-get-trace
+ from:
+ uri: direct:get-trace
+ steps:
+ - setVariable:
+ name: traceId
+ simple: ${header.id}
+ - pollEnrich:
+ expression:
+ simple:
file:{{analyzer.storage.root}}?fileName=${variable.traceId}.txt&noop=true&idempotent=false
+ timeout: 1000
+ - choice:
+ when:
+ - simple: ${body} != null
+ steps:
+ - convertBodyTo:
+ type: String
+ - setVariable:
+ name: fileContent
+ simple: ${body}
+ - marshal:
+ json: {}
+ - setBody:
+ simple: '{"id":"${variable.traceId}","content":${body}}'
+ - setHeader:
+ name: Content-Type
+ constant: application/json
+ otherwise:
+ steps:
+ - setHeader:
+ name: CamelHttpResponseCode
+ constant: 404
+ - setBody:
+ constant: '{"error":"Trace not found"}'
+ - setHeader:
+ name: Content-Type
+ constant: application/json
\ No newline at end of file