You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by sh...@apache.org on 2026/06/17 06:01:23 UTC

(unomi) 01/01: UNOMI-873: Add request tracing and web-servlets extraction

This is an automated email from the ASF dual-hosted git repository.

sergehuber pushed a commit to branch UNOMI-873-tracing
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit df5d50a0de187d6f1d808894b79e34851591308c
Author: Serge Huber <sh...@apache.org>
AuthorDate: Wed Jun 17 08:01:05 2026 +0200

    UNOMI-873: Add request tracing and web-servlets extraction
    
    Introduce tracing-api/tracing-impl for the Explain feature, wire TracerService
    across services, evaluators, actions, and REST endpoints, and extract legacy
    HTTP servlets into the web-servlets module while preserving master review fixes.
---
 api/pom.xml                                        |   6 +
 .../java/org/apache/unomi/api/ContextResponse.java |  11 +
 bom/artifacts/pom.xml                              |  15 ++
 extensions/geonames/services/pom.xml               |   8 +-
 extensions/groovy-actions/services/pom.xml         |  13 +
 .../groovy/actions/GroovyActionDispatcher.java     |  62 +++--
 extensions/json-schema/services/pom.xml            |  13 +
 .../unomi/schema/impl/SchemaServiceImpl.java       |  15 ++
 .../resources/OSGI-INF/blueprint/blueprint.xml     |   2 +
 extensions/router/router-core/pom.xml              |   8 +-
 itests/pom.xml                                     |   8 +-
 kar/src/main/feature/feature.xml                   |   5 +
 persistence-spi/pom.xml                            |   7 +
 .../spi/conditions/ConditionContextHelper.java     |  87 +++++--
 .../impl/ConditionEvaluatorDispatcherImpl.java     |  50 +++-
 plugins/baseplugin/pom.xml                         |   6 +
 .../actions/AllEventToProfilePropertiesAction.java |  76 ++++--
 .../baseplugin/actions/CopyPropertiesAction.java   | 117 ++++++---
 .../actions/EvaluateProfileAgeAction.java          |  80 +++++-
 .../actions/EvaluateProfileSegmentsAction.java     |  90 ++++++-
 .../actions/EvaluateVisitPropertiesAction.java     | 100 ++++++--
 .../actions/EventToProfilePropertyAction.java      |  51 +++-
 .../actions/IncrementPropertyAction.java           |  84 ++++--
 .../actions/MergeProfilesOnPropertyAction.java     |  61 +++++
 .../baseplugin/actions/ModifyConsentAction.java    |  74 ++++--
 .../baseplugin/actions/SendEventAction.java        |  66 ++++-
 .../actions/SetEventOccurenceCountAction.java      | 174 +++++++------
 .../baseplugin/actions/SetPropertyAction.java      |  86 +++++--
 .../baseplugin/actions/UpdatePropertiesAction.java | 119 +++++----
 .../conditions/BooleanConditionEvaluator.java      |  76 ++++--
 .../conditions/IdsConditionEvaluator.java          |  45 +++-
 .../conditions/NotConditionEvaluator.java          |  38 ++-
 .../conditions/PropertyConditionEvaluator.java     |  41 +++
 .../resources/OSGI-INF/blueprint/blueprint.xml     |  47 +++-
 pom.xml                                            |   2 +
 rest/pom.xml                                       |   7 +
 .../unomi/rest/endpoints/ContextJsonEndpoint.java  |  40 ++-
 .../rest/endpoints/EventsCollectorEndpoint.java    |  33 ++-
 .../unomi/rest/models/EventCollectorResponse.java  |  32 ++-
 services/pom.xml                                   |  13 +
 .../actions/impl/ActionExecutorDispatcherImpl.java | 131 +++++++---
 .../impl/definitions/DefinitionsServiceImpl.java   |  23 ++
 .../services/impl/events/EventServiceImpl.java     |  57 +++++
 .../services/impl/goals/GoalsServiceImpl.java      | 190 +++++++++++---
 .../services/impl/rules/RulesServiceImpl.java      |  58 ++++-
 .../services/impl/segments/SegmentServiceImpl.java | 189 ++++++++++----
 .../resources/OSGI-INF/blueprint/blueprint.xml     |  27 +-
 tracing/pom.xml                                    |  36 +++
 tracing/tracing-api/pom.xml                        |  68 +++++
 .../apache/unomi/tracing/api/RequestTracer.java    |  84 ++++++
 .../org/apache/unomi/tracing/api/TraceNode.java    | 109 ++++++++
 .../apache/unomi/tracing/api/TracerService.java    |  52 ++++
 .../services => tracing/tracing-impl}/pom.xml      |  72 ++----
 .../unomi/tracing/impl/DefaultRequestTracer.java   | 146 +++++++++++
 .../unomi/tracing/impl/DefaultTracerService.java   |  69 +++++
 .../unomi/tracing/impl/LoggingRequestTracer.java   | 160 ++++++++++++
 .../tracing/impl/DefaultTracerServiceTest.java     | 281 +++++++++++++++++++++
 wab/pom.xml                                        | 110 --------
 {wab => web-servlets}/pom.xml                      |  29 +--
 .../apache/unomi/web/servlets}/ClientServlet.java  |  14 +-
 .../apache/unomi/web/servlets}/ContextServlet.java |  15 +-
 .../web/servlets}/EventsCollectorServlet.java      |  14 +-
 .../HttpServletRequestForwardWrapper.java          |   2 +-
 .../org/apache/unomi/web/servlets}/HttpUtils.java  |   2 +-
 .../org/apache/unomi/web/servlets}/WebConfig.java  |   2 +-
 .../src/main/resources/org.apache.unomi.web.cfg    |   0
 66 files changed, 3093 insertions(+), 715 deletions(-)

diff --git a/api/pom.xml b/api/pom.xml
index b1933fbed..93e1603ee 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -43,6 +43,12 @@
     </dependencyManagement>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>jakarta.xml.bind</groupId>
             <artifactId>jakarta.xml.bind-api</artifactId>
diff --git a/api/src/main/java/org/apache/unomi/api/ContextResponse.java b/api/src/main/java/org/apache/unomi/api/ContextResponse.java
index 8c158ec76..de2335390 100644
--- a/api/src/main/java/org/apache/unomi/api/ContextResponse.java
+++ b/api/src/main/java/org/apache/unomi/api/ContextResponse.java
@@ -19,6 +19,7 @@ package org.apache.unomi.api;
 
 import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.services.RulesService;
+import org.apache.unomi.tracing.api.TraceNode;
 
 import java.io.Serializable;
 import java.util.*;
@@ -59,6 +60,8 @@ public class ContextResponse implements Serializable {
 
     private Map<String, Consent> consents = new LinkedHashMap<>();
 
+    private TraceNode requestTracing;
+
     /**
      * Retrieves the profile identifier associated with the profile of the user on behalf of which the client performed the context request.
      *
@@ -285,4 +288,12 @@ public class ContextResponse implements Serializable {
     public void setConsents(Map<String, Consent> consents) {
         this.consents = consents;
     }
+
+    public TraceNode getRequestTracing() {
+        return requestTracing;
+    }
+
+    public void setRequestTracing(TraceNode requestTracing) {
+        this.requestTracing = requestTracing;
+    }
 }
diff --git a/bom/artifacts/pom.xml b/bom/artifacts/pom.xml
index 5a739b101..07c372c3f 100644
--- a/bom/artifacts/pom.xml
+++ b/bom/artifacts/pom.xml
@@ -245,6 +245,21 @@
                 <artifactId>graphql-providers-sample</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.unomi</groupId>
+                <artifactId>unomi-tracing-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.unomi</groupId>
+                <artifactId>unomi-tracing-impl</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.unomi</groupId>
+                <artifactId>unomi-web-servlets</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.unomi</groupId>
                 <artifactId>unomi-plugins-base</artifactId>
diff --git a/extensions/geonames/services/pom.xml b/extensions/geonames/services/pom.xml
index c83b1299a..a44f3131c 100644
--- a/extensions/geonames/services/pom.xml
+++ b/extensions/geonames/services/pom.xml
@@ -37,7 +37,13 @@
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
-        </dependencies>
+            <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-impl</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
     </dependencyManagement>
 
     <dependencies>
diff --git a/extensions/groovy-actions/services/pom.xml b/extensions/groovy-actions/services/pom.xml
index 751baf13e..d4070f3e6 100644
--- a/extensions/groovy-actions/services/pom.xml
+++ b/extensions/groovy-actions/services/pom.xml
@@ -46,6 +46,19 @@
             <artifactId>unomi-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-impl</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.unomi</groupId>
             <artifactId>unomi-metrics</artifactId>
diff --git a/extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/GroovyActionDispatcher.java b/extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/GroovyActionDispatcher.java
index 093a91d6f..17932568a 100644
--- a/extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/GroovyActionDispatcher.java
+++ b/extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/GroovyActionDispatcher.java
@@ -21,15 +21,20 @@ import org.apache.unomi.api.Event;
 import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.actions.ActionDispatcher;
 import org.apache.unomi.api.services.DefinitionsService;
+import org.apache.unomi.api.services.EventService;
 import org.apache.unomi.groovy.actions.services.GroovyActionsService;
 import org.apache.unomi.metrics.MetricAdapter;
 import org.apache.unomi.metrics.MetricsService;
 import org.apache.unomi.services.actions.ActionExecutorDispatcher;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.HashMap;
+
 /**
  * High-performance ActionDispatcher for pre-compiled Groovy scripts.
  * Executes scripts without GroovyShell overhead using isolated instances.
@@ -46,6 +51,7 @@ public class GroovyActionDispatcher implements ActionDispatcher {
     private GroovyActionsService groovyActionsService;
     private DefinitionsService definitionsService;
     private ActionExecutorDispatcher actionExecutorDispatcher;
+    private TracerService tracerService;
 
     @Reference
     public void setMetricsService(MetricsService metricsService) {
@@ -67,32 +73,54 @@ public class GroovyActionDispatcher implements ActionDispatcher {
         this.actionExecutorDispatcher = actionExecutorDispatcher;
     }
 
+    @Reference
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     public String getPrefix() {
         return GROOVY_PREFIX;
     }
 
     public Integer execute(Action action, Event event, String actionName) {
-        Class<? extends Script> scriptClass = groovyActionsService.getCompiledScript(actionName);
-        if (scriptClass == null) {
-            LOGGER.warn("Couldn't find a Groovy action with name {}, action will not execute!", actionName);
-            return 0;
+        RequestTracer tracer = tracerService.getCurrentTracer();
+        if (!tracer.isEnabled()) {
+            tracer.setEnabled(true);
         }
-        
+
+        tracer.startOperation("groovy-action", "Executing Groovy action", new HashMap<String, Object>() {{
+            put("action.name", actionName);
+            put("action.type", action.getActionTypeId());
+            put("event.type", event.getEventType());
+        }});
+
         try {
-            Script script = scriptClass.getDeclaredConstructor().newInstance();
-            setScriptVariables(script, action, event);
-            
-            return new MetricAdapter<Integer>(metricsService, this.getClass().getName() + ".action.groovy." + actionName) {
-                @Override
-                public Integer execute(Object... args) throws Exception {
-                    return (Integer) script.invokeMethod("execute", null);
-                }
-            }.runWithTimer();
+            Class<? extends Script> scriptClass = groovyActionsService.getCompiledScript(actionName);
+            if (scriptClass == null) {
+                LOGGER.warn("Couldn't find a Groovy action with name {}, action will not execute!", actionName);
+                tracer.trace("Action not found", null);
+                return EventService.NO_CHANGE;
+            }
             
-        } catch (Exception e) {
-            LOGGER.error("Error executing Groovy action with key={}", actionName, e);
+            try {
+                Script script = scriptClass.getDeclaredConstructor().newInstance();
+                setScriptVariables(script, action, event);
+                
+                return new MetricAdapter<Integer>(metricsService, this.getClass().getName() + ".action.groovy." + actionName) {
+                    @Override
+                    public Integer execute(Object... args) throws Exception {
+                        return (Integer) script.invokeMethod("execute", null);
+                    }
+                }.runWithTimer();
+                
+            } catch (Exception e) {
+                LOGGER.error("Error executing Groovy action with key={}", actionName, e);
+                tracer.trace("Error executing action", e);
+                return EventService.NO_CHANGE;
+            }
+        } finally {
+            tracer.endOperation(null, "Completed Groovy action execution");
         }
-        return 0;
     }
     
     /**
diff --git a/extensions/json-schema/services/pom.xml b/extensions/json-schema/services/pom.xml
index 710a7d1a0..65c96a388 100644
--- a/extensions/json-schema/services/pom.xml
+++ b/extensions/json-schema/services/pom.xml
@@ -52,6 +52,19 @@
             <artifactId>unomi-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-impl</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.unomi</groupId>
             <artifactId>unomi-persistence-spi</artifactId>
diff --git a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java
index 5a2cd0926..5a7068ac1 100644
--- a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java
+++ b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java
@@ -40,6 +40,8 @@ import org.apache.unomi.schema.api.SchemaService;
 import org.apache.unomi.schema.api.ValidationError;
 import org.apache.unomi.schema.api.ValidationException;
 import org.apache.unomi.schema.keyword.ScopeKeyword;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.apache.unomi.services.common.cache.AbstractMultiTypeCachingService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -78,6 +80,7 @@ public class SchemaServiceImpl extends AbstractMultiTypeCachingService implement
     private Integer jsonSchemaRefreshInterval = 1000;
 
     private ScopeService scopeService;
+    private TracerService tracerService;
 
     // Map to store tenant-specific JsonSchemaFactory instances
     private final ConcurrentMap<String, JsonSchemaFactory> tenantJsonSchemaFactories = new ConcurrentHashMap<>();
@@ -375,6 +378,14 @@ public class SchemaServiceImpl extends AbstractMultiTypeCachingService implement
                 }
             }
 
+            // Add validation info to trace if tracing is enabled
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.addValidationInfo(validationMessages, jsonSchema.getCurrentUri().toString());
+                }
+            }
+
             return validationMessages != null ?
                     validationMessages.stream()
                             .map(validationMessage -> new ValidationError(validationMessage.getMessage()))
@@ -594,6 +605,10 @@ public class SchemaServiceImpl extends AbstractMultiTypeCachingService implement
         this.jsonSchemaRefreshInterval = jsonSchemaRefreshInterval;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     /**
      * Refreshes schema extensions and factories with the provided schemas map.
      * This method encapsulates the common logic needed after schema changes.
diff --git a/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index d73eca09c..b89e3d003 100644
--- a/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -27,6 +27,7 @@
     <reference id="scopeService" interface="org.apache.unomi.api.services.ScopeService"/>
     <reference id="tenantService" interface="org.apache.unomi.api.tenants.TenantService"/>
     <reference id="contextManager" interface="org.apache.unomi.api.services.ExecutionContextManager"/>
+    <reference id="tracerService" interface="org.apache.unomi.tracing.api.TracerService"/>
     <reference id="schedulerService" interface="org.apache.unomi.api.services.SchedulerService" timeout="2000"/>
     <reference id="cacheService" interface="org.apache.unomi.api.services.cache.MultiTypeCacheService" />
 
@@ -44,6 +45,7 @@
         <property name="scopeService" ref="scopeService"/>
         <property name="tenantService" ref="tenantService"/>
         <property name="contextManager" ref="contextManager"/>
+        <property name="tracerService" ref="tracerService"/>
         <property name="jsonSchemaRefreshInterval" value="${json.schema.refresh.interval}"/>
         <property name="schedulerService" ref="schedulerService"/>
         <property name="bundleContext" ref="blueprintBundleContext" />
diff --git a/extensions/router/router-core/pom.xml b/extensions/router/router-core/pom.xml
index c28a7e1b0..0ec3cfc1b 100644
--- a/extensions/router/router-core/pom.xml
+++ b/extensions/router/router-core/pom.xml
@@ -36,7 +36,13 @@
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
-        </dependencies>
+            <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-impl</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
     </dependencyManagement>
 
     <dependencies>
diff --git a/itests/pom.xml b/itests/pom.xml
index 55b19b39d..3e8c79ee0 100644
--- a/itests/pom.xml
+++ b/itests/pom.xml
@@ -54,7 +54,13 @@
                 <artifactId>awaitility</artifactId>
                 <version>3.1.6</version>
             </dependency>
-        </dependencies>
+            <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-api</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
     </dependencyManagement>
 
     <dependencies>
diff --git a/kar/src/main/feature/feature.xml b/kar/src/main/feature/feature.xml
index 2a764410f..9d59b3022 100644
--- a/kar/src/main/feature/feature.xml
+++ b/kar/src/main/feature/feature.xml
@@ -68,6 +68,8 @@
         <bundle>mvn:org.apache.unomi/unomi-lifecycle-watcher/${project.version}</bundle>
         <bundle>mvn:org.apache.unomi/unomi-api/${project.version}</bundle>
         <bundle>mvn:org.apache.unomi/unomi-common/${project.version}</bundle>
+        <bundle>mvn:org.apache.unomi/unomi-tracing-api/${project.version}</bundle>
+        <bundle>mvn:org.apache.unomi/unomi-tracing-impl/${project.version}</bundle>
         <bundle>mvn:org.apache.unomi/unomi-scripting/${project.version}</bundle>
         <bundle>mvn:org.apache.unomi/unomi-metrics/${project.version}</bundle>
         <bundle>mvn:org.apache.unomi/unomi-persistence-spi/${project.version}</bundle>
@@ -130,10 +132,13 @@
         <feature>unomi-cxs-privacy-extension-services</feature>
         <configfile finalname="/etc/org.apache.unomi.schema.cfg">mvn:org.apache.unomi/unomi-json-schema-services/${project.version}/cfg/schemacfg</configfile>
         <configfile finalname="/etc/org.apache.unomi.rest.authentication.cfg">mvn:org.apache.unomi/unomi-rest/${project.version}/cfg/restauth</configfile>
+        <!-- Legacy servlet forwarders (HTTP Whiteboard) and their configuration -->
+        <configfile finalname="/etc/org.apache.unomi.web.cfg">mvn:org.apache.unomi/unomi-web-servlets/${project.version}/cfg/unomicfg</configfile>
         <bundle start="false">mvn:org.apache.unomi/unomi-json-schema-services/${project.version}</bundle>
         <bundle start="false">mvn:org.apache.unomi/unomi-rest/${project.version}</bundle>
         <bundle start="false">mvn:org.apache.unomi/unomi-json-schema-rest/${project.version}</bundle>
         <bundle start="false">mvn:org.apache.unomi/cxs-lists-extension-actions/${project.version}</bundle>
+        <bundle start="false">mvn:org.apache.unomi/unomi-web-servlets/${project.version}</bundle>
     </feature>
 
     <feature name="unomi-cxs-lists-extension" description="Apache Unomi :: Extensions :: Lists" version="${project.version}">
diff --git a/persistence-spi/pom.xml b/persistence-spi/pom.xml
index a50c555af..54cb4608c 100644
--- a/persistence-spi/pom.xml
+++ b/persistence-spi/pom.xml
@@ -47,6 +47,13 @@
             <artifactId>unomi-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.unomi</groupId>
             <artifactId>unomi-scripting</artifactId>
diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ConditionContextHelper.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ConditionContextHelper.java
index d8c974af7..8e0b08313 100644
--- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ConditionContextHelper.java
+++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ConditionContextHelper.java
@@ -23,6 +23,8 @@ import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.conditions.ConditionType;
 import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.scripting.ScriptExecutor;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -114,7 +116,7 @@ public class ConditionContextHelper {
     }
 
     public static Condition getContextualCondition(Condition condition, Map<String, Object> context, ScriptExecutor scriptExecutor) {
-        return getContextualCondition(condition, context, scriptExecutor, null);
+        return getContextualCondition(condition, context, scriptExecutor, null, null);
     }
 
     /**
@@ -125,13 +127,24 @@ public class ConditionContextHelper {
      * @param context context map for parameter resolution
      * @param scriptExecutor executor for script expressions
      * @param definitionsService optional service for parameter type information
+     * @param tracerService optional tracer service for validation warnings
      * @return resolved condition with all parameter references resolved
      */
+
     public static Condition getContextualCondition(
         Condition condition,
         Map<String, Object> context,
         ScriptExecutor scriptExecutor,
         DefinitionsService definitionsService) {
+        return getContextualCondition(condition, context, scriptExecutor, definitionsService, null);
+    }
+
+    public static Condition getContextualCondition(
+        Condition condition,
+        Map<String, Object> context,
+        ScriptExecutor scriptExecutor,
+        DefinitionsService definitionsService,
+        TracerService tracerService) {
 
         // Debug logging
 
@@ -166,7 +179,7 @@ public class ConditionContextHelper {
 
         Object rawValues = parseParameterWithValidation(
             context, condition.getParameterValues(), scriptExecutor,
-            parameterDefs, condition.getConditionTypeId());
+            parameterDefs, tracerService, condition.getConditionTypeId());
 
         if (rawValues == null || rawValues == RESOLUTION_ERROR) {
             return null;
@@ -181,7 +194,7 @@ public class ConditionContextHelper {
 
     @SuppressWarnings("unchecked")
     private static Object parseParameter(Map<String, Object> context, Object value, ScriptExecutor scriptExecutor) {
-        return parseParameterWithValidation(context, value, scriptExecutor, null, null);
+        return parseParameterWithValidation(context, value, scriptExecutor, null, null, null);
     }
 
     @SuppressWarnings("unchecked")
@@ -190,10 +203,12 @@ public class ConditionContextHelper {
         Object value,
         ScriptExecutor scriptExecutor,
         Map<String, Parameter> parameterDefs,
+        TracerService tracerService,
         String conditionTypeId) {
 
         return parseParameterWithValidationRecursive(
-            context, value, scriptExecutor, parameterDefs, conditionTypeId, new HashSet<>(), 0);
+            context, value, scriptExecutor, parameterDefs,
+            tracerService, conditionTypeId, new HashSet<>(), 0);
     }
 
     /**
@@ -204,10 +219,11 @@ public class ConditionContextHelper {
      * @param value the value to resolve
      * @param scriptExecutor executor for script expressions
      * @param parameterDefs optional parameter definitions for validation
+     * @param tracerService optional tracer service for warnings
      * @param conditionTypeId condition type ID for context
      * @param resolutionChain set of parameter keys/scripts already being resolved (for cycle detection)
      * @param depth current resolution depth
-     * @return resolved value, or {@code RESOLUTION_ERROR} sentinel if cycle detected or max depth exceeded
+     * @return resolved value, or null if cycle detected or max depth exceeded
      */
     @SuppressWarnings("unchecked")
     private static Object parseParameterWithValidationRecursive(
@@ -215,6 +231,7 @@ public class ConditionContextHelper {
         Object value,
         ScriptExecutor scriptExecutor,
         Map<String, Parameter> parameterDefs,
+        TracerService tracerService,
         String conditionTypeId,
         Set<String> resolutionChain,
         int depth) {
@@ -226,6 +243,16 @@ public class ConditionContextHelper {
                 "Possible infinite chain or very deep nesting. Resolution chain: %s",
                 MAX_RESOLUTION_DEPTH, resolutionChain);
             LOGGER.error(message);
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    Map<String, Object> traceContext = new HashMap<>();
+                    traceContext.put("maxDepth", MAX_RESOLUTION_DEPTH);
+                    traceContext.put("resolutionChain", new ArrayList<>(resolutionChain));
+                    traceContext.put("conditionTypeId", conditionTypeId);
+                    tracer.trace("Parameter resolution depth exceeded", traceContext);
+                }
+            }
             return RESOLUTION_ERROR;
         }
 
@@ -239,6 +266,16 @@ public class ConditionContextHelper {
                     "Circular parameter reference detected: %s. Resolution chain: %s",
                     referenceKey, resolutionChain);
                 LOGGER.warn(message);
+                if (tracerService != null) {
+                    RequestTracer tracer = tracerService.getCurrentTracer();
+                    if (tracer != null && tracer.isEnabled()) {
+                        Map<String, Object> traceContext = new HashMap<>();
+                        traceContext.put("circularReference", referenceKey);
+                        traceContext.put("resolutionChain", new ArrayList<>(resolutionChain));
+                        traceContext.put("conditionTypeId", conditionTypeId);
+                        tracer.trace("Circular parameter reference detected", traceContext);
+                    }
+                }
                 // Return a special marker to indicate cycle (we'll check for this in Map processing)
                 return RESOLUTION_ERROR;
             }
@@ -268,8 +305,10 @@ public class ConditionContextHelper {
                 // If resolved value is itself a parameter reference, continue resolving
                 if (resolvedValue != null && isParameterReference(resolvedValue)) {
                     Object furtherResolved = parseParameterWithValidationRecursive(
-                        context, resolvedValue, scriptExecutor, parameterDefs, conditionTypeId, resolutionChain, depth + 1);
-                    // Propagate RESOLUTION_ERROR if cycle/max-depth was detected, or resolved value otherwise
+                        context, resolvedValue, scriptExecutor, parameterDefs,
+                        tracerService, conditionTypeId, resolutionChain, depth + 1);
+                    // If further resolution returns null due to cycle or max depth, propagate it
+                    // But if it's just a missing parameter, return null (not a cycle)
                     return furtherResolved;
                 }
 
@@ -287,17 +326,18 @@ public class ConditionContextHelper {
                 Parameter paramDef = parameterDefs != null ? parameterDefs.get(paramName) : null;
 
                 Object parameter = parseParameterWithValidationRecursive(
-                    context, paramValue, scriptExecutor, parameterDefs, conditionTypeId, resolutionChain, depth);
+                    context, paramValue, scriptExecutor, parameterDefs,
+                    tracerService, conditionTypeId, resolutionChain, depth);
 
-                // If resolution returned an error marker, propagate failure
+                // If resolution returned an error marker, return null for entire map
                 if (parameter == RESOLUTION_ERROR) {
-                    return RESOLUTION_ERROR;
+                    return null;
                 }
 
                 // Validate type if parameter definition is available and value is not null
                 if (parameter != null && paramDef != null && paramDef.getType() != null) {
                     validateParameterType(paramName, parameter, paramDef,
-                        conditionTypeId);
+                        conditionTypeId, tracerService);
                 }
 
                 // Always put the value, even if null (missing parameter is valid)
@@ -308,10 +348,11 @@ public class ConditionContextHelper {
             List<Object> values = new ArrayList<Object>();
             for (Object o : ((List<?>) value)) {
                 Object parameter = parseParameterWithValidationRecursive(
-                    context, o, scriptExecutor, parameterDefs, conditionTypeId, resolutionChain, depth);
-                // If resolution returned an error marker, propagate failure (consistent with Map behavior)
+                    context, o, scriptExecutor, parameterDefs,
+                    tracerService, conditionTypeId, resolutionChain, depth);
+                // If resolution returned an error marker, skip this element
                 if (parameter == RESOLUTION_ERROR) {
-                    return RESOLUTION_ERROR;
+                    continue;
                 }
                 // Add the value, even if null (missing parameter is valid in lists)
                 values.add(parameter);
@@ -323,18 +364,20 @@ public class ConditionContextHelper {
 
     /**
      * Validates that a resolved parameter value matches the expected type.
-     * Logs warnings if type mismatch is detected.
+     * Logs warnings via tracer if type mismatch is detected.
      *
      * @param paramName parameter name
      * @param resolvedValue resolved parameter value
      * @param paramDef parameter definition
      * @param conditionTypeId condition type ID for context
+     * @param tracerService optional tracer service
      */
     private static void validateParameterType(
         String paramName,
         Object resolvedValue,
         Parameter paramDef,
-        String conditionTypeId) {
+        String conditionTypeId,
+        TracerService tracerService) {
 
         if (resolvedValue == null || paramDef == null || paramDef.getType() == null) {
             return;
@@ -350,6 +393,18 @@ public class ConditionContextHelper {
 
             LOGGER.warn(message + " (value: {})", resolvedValue);
 
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    Map<String, Object> traceContext = new HashMap<>();
+                    traceContext.put("parameter", paramName);
+                    traceContext.put("expectedType", expectedType);
+                    traceContext.put("actualType", actualType);
+                    traceContext.put("value", resolvedValue);
+                    traceContext.put("conditionTypeId", conditionTypeId);
+                    tracer.trace("Parameter type mismatch detected", traceContext);
+                }
+            }
         }
     }
 
diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/impl/ConditionEvaluatorDispatcherImpl.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/impl/ConditionEvaluatorDispatcherImpl.java
index d63719c80..8c5b34cd0 100644
--- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/impl/ConditionEvaluatorDispatcherImpl.java
+++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/impl/ConditionEvaluatorDispatcherImpl.java
@@ -28,6 +28,8 @@ import org.apache.unomi.persistence.spi.conditions.ConditionContextHelper;
 import org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluator;
 import org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluatorDispatcher;
 import org.apache.unomi.scripting.ScriptExecutor;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.annotations.ReferenceCardinality;
@@ -52,6 +54,7 @@ public class ConditionEvaluatorDispatcherImpl
 
     private MetricsService metricsService;
     private ScriptExecutor scriptExecutor;
+    private TracerService tracerService;
     private DefinitionsService definitionsService;
 
     public ConditionEvaluatorDispatcherImpl() {
@@ -85,6 +88,11 @@ public class ConditionEvaluatorDispatcherImpl
         evaluators.remove((String) props.get("conditionEvaluatorId"));
     }
 
+    @Reference
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     @Override
     public void addEvaluator(String name, ConditionEvaluator evaluator) {
         evaluators.put(name, evaluator);
@@ -106,6 +114,13 @@ public class ConditionEvaluatorDispatcherImpl
             throw new UnsupportedOperationException("Null condition passed for item : " + item);
         }
 
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("condition-evaluation",
+                "Evaluating condition: " + condition.getConditionTypeId(), condition);
+        }
+
         try {
             // Resolve condition type if needed - try to resolve first if definitionsService is available
             if (condition.getConditionType() == null) {
@@ -120,6 +135,9 @@ public class ConditionEvaluatorDispatcherImpl
                 // If still null after attempting resolution (or definitionsService was null), return false gracefully
                 if (condition.getConditionType() == null) {
                     LOGGER.warn("Condition type is null for condition typeID={}, returning false gracefully", condition.getConditionTypeId());
+                    if (tracer != null) {
+                        tracer.endOperation(false, "Condition type not resolved");
+                    }
                     return false;
                 }
             }
@@ -132,6 +150,9 @@ public class ConditionEvaluatorDispatcherImpl
                 if (resolved == null) {
                     LOGGER.warn("Could not resolve effective condition for typeID={} on item={} (cycle or max depth), returning false",
                         condition.getConditionTypeId(), item != null ? item.getItemId() : "null");
+                    if (tracer != null) {
+                        tracer.endOperation(false, "Could not resolve effective condition");
+                    }
                     return false;
                 }
                 effectiveCondition = resolved;
@@ -145,14 +166,18 @@ public class ConditionEvaluatorDispatcherImpl
             if (effectiveCondition.getConditionType() == null) {
                 LOGGER.debug("Effective condition type is null for condition typeID={}, returning false gracefully",
                     effectiveCondition.getConditionTypeId());
+                if (tracer != null) {
+                    tracer.endOperation(false, "Effective condition type not resolved");
+                }
                 return false;
             }
 
             String conditionEvaluatorKey = effectiveCondition.getConditionType().getConditionEvaluator();
             if (conditionEvaluatorKey == null) {
-                // resolveEffectiveCondition() already resolved to the root parent above,
-                // so a null evaluator key here means the chain genuinely has no evaluator.
                 LOGGER.warn("No evaluator defined for condition type: {}", effectiveCondition.getConditionTypeId());
+                if (tracer != null) {
+                    tracer.endOperation(false, "No evaluator defined for condition type");
+                }
                 return false;
             }
 
@@ -161,8 +186,11 @@ public class ConditionEvaluatorDispatcherImpl
                 final ConditionEvaluatorDispatcher dispatcher = this;
                 try {
                     // Use effective condition for evaluation
-                    Condition contextualCondition = ConditionContextHelper.getContextualCondition(effectiveCondition, context, scriptExecutor);
+                    Condition contextualCondition = ConditionContextHelper.getContextualCondition(effectiveCondition, context, scriptExecutor, definitionsService, tracerService);
                     if (contextualCondition == null) {
+                        if (tracer != null) {
+                            tracer.endOperation(false, "Contextual condition is null");
+                        }
                         return false;
                     }
                     final Condition finalContextualCondition = contextualCondition;
@@ -172,18 +200,30 @@ public class ConditionEvaluatorDispatcherImpl
                             return evaluator.eval(finalContextualCondition, item, context, dispatcher);
                         }
                     }.runWithTimer();
+
+                    if (tracer != null) {
+                        tracer.endOperation(result, "Condition evaluation completed");
+                    }
                     return result;
                 } catch (Exception e) {
                     LOGGER.error("Error executing condition evaluator with key={}", conditionEvaluatorKey, e);
+                    if (tracer != null) {
+                        tracer.endOperation(false, "Error during condition evaluation: " + e.getMessage());
+                    }
                     return false;
                 }
             } else {
                 LOGGER.error("Couldn't find evaluator with key={}", conditionEvaluatorKey);
+                if (tracer != null) {
+                    tracer.endOperation(false, "No evaluator found for condition type: " + conditionEvaluatorKey);
+                }
                 return false;
             }
         } catch (Exception e) {
-            LOGGER.error("Infrastructure error during condition evaluation for typeID={} on item={}",
-                condition.getConditionTypeId(), item != null ? item.getItemId() : "null", e);
+            LOGGER.error("Error during condition evaluation", e);
+            if (tracer != null) {
+                tracer.endOperation(false, "Error during condition evaluation: " + e.getMessage());
+            }
             return false;
         }
     }
diff --git a/plugins/baseplugin/pom.xml b/plugins/baseplugin/pom.xml
index 68e36ca86..b31483a3f 100644
--- a/plugins/baseplugin/pom.xml
+++ b/plugins/baseplugin/pom.xml
@@ -61,6 +61,12 @@
             <artifactId>unomi-scripting</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
 
         <dependency>
             <groupId>javax.servlet</groupId>
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/AllEventToProfilePropertiesAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/AllEventToProfilePropertiesAction.java
index b24bb9674..7289f0398 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/AllEventToProfilePropertiesAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/AllEventToProfilePropertiesAction.java
@@ -23,6 +23,8 @@ import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.EventService;
 import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -34,36 +36,76 @@ import java.util.Map;
 public class AllEventToProfilePropertiesAction implements ActionExecutor {
 
     private ProfileService profileService;
+    private TracerService tracerService;
 
     public void setProfileService(ProfileService profileService) {
         this.profileService = profileService;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     @SuppressWarnings({"unchecked", "rawtypes"})
     public int execute(Action action, Event event) {
-        boolean changed = false;
-        Map<String, Object> properties = new HashMap<String, Object>();
-        if (event.getProperties() != null) {
-            properties.putAll(event.getProperties());
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("sync-event-properties", 
+                "Synchronizing event properties to profile", action);
         }
 
-
         try {
-            Object targetProperties = BeanUtilsBean.getInstance().getPropertyUtils().getProperty(event.getTarget(), "properties");
-            if (targetProperties instanceof Map) {
-                properties.putAll((Map) targetProperties);
+            boolean changed = false;
+            Map<String, Object> properties = new HashMap<String, Object>();
+            if (event.getProperties() != null) {
+                properties.putAll(event.getProperties());
+            }
+
+            if (tracer != null) {
+                tracer.trace("Processing properties", Map.of(
+                    "propertiesCount", properties.size(),
+                    "hasTarget", event.getTarget() != null
+                ));
+            }
+
+            try {
+                Object targetProperties = BeanUtilsBean.getInstance().getPropertyUtils().getProperty(event.getTarget(), "properties");
+                if (targetProperties instanceof Map) {
+                    properties.putAll((Map) targetProperties);
+                }
+            } catch (Exception e) {
+                if (tracer != null) {
+                    tracer.trace("Target properties not available", Map.of());
+                }
+                // Ignore
+            }
+
+            int updatedCount = 0;
+            for (Map.Entry<String, Object> entry : properties.entrySet()) {
+                if (event.getProfile().getProperty(entry.getKey()) == null || !event.getProfile().getProperty(entry.getKey()).equals(event.getProperty(entry.getKey()))) {
+                    String propertyMapping = profileService.getPropertyTypeMapping(entry.getKey());
+                    String propertyName = (propertyMapping != null) ? propertyMapping : entry.getKey();
+                    event.getProfile().setProperty(propertyName, entry.getValue());
+                    changed = true;
+                    updatedCount++;
+                }
             }
+
+            if (tracer != null) {
+                tracer.trace("Properties synchronized", Map.of(
+                    "updatedCount", updatedCount,
+                    "isChanged", changed
+                ));
+                tracer.endOperation(changed, 
+                    changed ? "Properties synchronized successfully" : "No changes needed");
+            }
+            return changed ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
         } catch (Exception e) {
-            // Ignore
-        }
-        for (Map.Entry<String, Object> entry : properties.entrySet()) {
-            if (event.getProfile().getProperty(entry.getKey()) == null || !event.getProfile().getProperty(entry.getKey()).equals(event.getProperty(entry.getKey()))) {
-                String propertyMapping = profileService.getPropertyTypeMapping(entry.getKey());
-                String propertyName = (propertyMapping != null) ? propertyMapping : entry.getKey();
-                event.getProfile().setProperty(propertyName, entry.getValue());
-                changed = true;
+            if (tracer != null) {
+                tracer.endOperation(false, "Error synchronizing properties: " + e.getMessage());
             }
+            throw e;
         }
-        return changed ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
     }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/CopyPropertiesAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/CopyPropertiesAction.java
index 343d8997c..2b7225d7b 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/CopyPropertiesAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/CopyPropertiesAction.java
@@ -26,6 +26,8 @@ import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.EventService;
 import org.apache.unomi.api.services.ProfileService;
 import org.apache.unomi.persistence.spi.PropertyHelper;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -37,49 +39,98 @@ public class CopyPropertiesAction implements ActionExecutor {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(CopyPropertiesAction.class);
     private ProfileService profileService;
+    private TracerService tracerService;
 
     public void setProfileService(ProfileService profileService) {
         this.profileService = profileService;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     @SuppressWarnings({ "unchecked", "rawtypes" })
     public int execute(Action action, Event event) {
-        boolean atLeastOnechanged = false;
-        List<String> mandatoryPropTypeSystemTags = (List<String>) action.getParameterValues().get("mandatoryPropTypeSystemTag");
-        String singleValueStrategy = (String) action.getParameterValues().get("singleValueStrategy");
-        for (Map.Entry<String, Object> entry : getEventPropsToCopy(action, event).entrySet()) {
-            String mappedProperty = resolvePropertyName(entry.getKey());
-
-            // propType Check
-            PropertyType propertyType = profileService.getPropertyType(mappedProperty);
-            Object previousValue = event.getProfile().getProperty(mappedProperty);
-            if (mandatoryPropTypeSystemTags != null && mandatoryPropTypeSystemTags.size() > 0) {
-                if (propertyType == null || propertyType.getMetadata() == null || propertyType.getMetadata().getSystemTags() == null
-                        || !propertyType.getMetadata().getSystemTags().containsAll(mandatoryPropTypeSystemTags)) {
-                    continue;
-                }
-            }
-            String propertyName = "properties." + mappedProperty;
-            boolean changed = false;
-            if (previousValue == null && propertyType == null) {
-                changed = PropertyHelper.setProperty(event.getProfile(), propertyName, entry.getValue(), "alwaysSet");
-            } else {
-                boolean propertyTypeIsMultiValued =
-                        propertyType != null && propertyType.isMultivalued() != null && propertyType.isMultivalued();
-                boolean multipleIsExpected = previousValue != null ? previousValue instanceof List : propertyTypeIsMultiValued;
-
-                if (multipleIsExpected) {
-                    changed = PropertyHelper.setProperty(event.getProfile(), propertyName, entry.getValue(), "addValues");
-                } else if (entry.getValue() instanceof List) {
-                    LOGGER.error("Impossible to copy the property of type List to the profile, either a single value already exist on the profile or the property type is declared as a single value property. Enable debug log level for more information");
-                    LOGGER.debug("cannot copy property {}, because it's a List", mappedProperty);
-                } else {
-                    changed = PropertyHelper.setProperty(event.getProfile(), propertyName, entry.getValue(), singleValueStrategy);
+        RequestTracer tracer = tracerService.getCurrentTracer();
+        if (!tracer.isEnabled()) {
+            tracer.setEnabled(true);
+        }
+
+        tracer.startOperation("copy-properties", "Copying properties from event to profile", new HashMap<String, Object>() {{
+            put("action.id", action.getActionTypeId());
+            put("event.type", event.getEventType());
+        }});
+
+        try {
+            boolean atLeastOnechanged = false;
+            List<String> mandatoryPropTypeSystemTags = (List<String>) action.getParameterValues().get("mandatoryPropTypeSystemTag");
+            String singleValueStrategy = (String) action.getParameterValues().get("singleValueStrategy");
+
+            Map<String, Object> propsToCopy = getEventPropsToCopy(action, event);
+            tracer.trace("Found properties to copy", new HashMap<String, Object>() {{
+                put("properties.count", propsToCopy.size());
+            }});
+
+            for (Map.Entry<String, Object> entry : propsToCopy.entrySet()) {
+                String mappedProperty = resolvePropertyName(entry.getKey());
+                tracer.startOperation("copy-property", "Copying single property", new HashMap<String, Object>() {{
+                    put("property.name", mappedProperty);
+                }});
+
+                try {
+                    // propType Check
+                    PropertyType propertyType = profileService.getPropertyType(mappedProperty);
+                    Object previousValue = event.getProfile().getProperty(mappedProperty);
+                    
+                    if (mandatoryPropTypeSystemTags != null && mandatoryPropTypeSystemTags.size() > 0) {
+                        if (propertyType == null || propertyType.getMetadata() == null || propertyType.getMetadata().getSystemTags() == null
+                                || !propertyType.getMetadata().getSystemTags().containsAll(mandatoryPropTypeSystemTags)) {
+                            tracer.trace("Skipping property due to missing required system tags", null);
+                            continue;
+                        }
+                    }
+
+                    String propertyName = "properties." + mappedProperty;
+                    final boolean changed;
+                    if (previousValue == null && propertyType == null) {
+                        changed = PropertyHelper.setProperty(event.getProfile(), propertyName, entry.getValue(), "alwaysSet");
+                    } else {
+                        boolean propertyTypeIsMultiValued =
+                                propertyType != null && propertyType.isMultivalued() != null && propertyType.isMultivalued();
+                        boolean multipleIsExpected = previousValue != null ? previousValue instanceof List : propertyTypeIsMultiValued;
+
+                        if (multipleIsExpected) {
+                            changed = PropertyHelper.setProperty(event.getProfile(), propertyName, entry.getValue(), "addValues");
+                        } else if (entry.getValue() instanceof List) {
+                            LOGGER.error("Impossible to copy the property of type List to the profile, either a single value already exist on the profile or the property type is declared as a single value property. Enable debug log level for more information");
+                            LOGGER.debug("cannot copy property {}, because it's a List", mappedProperty);
+                            tracer.trace("Error: Cannot copy List to single value property", null);
+                            changed = false;
+                        } else {
+                            changed = PropertyHelper.setProperty(event.getProfile(), propertyName, entry.getValue(), singleValueStrategy);
+                        }
+                    }
+                    
+                    tracer.trace("Property copy result", new HashMap<String, Object>() {{
+                        put("changed", changed);
+                    }});
+                    atLeastOnechanged = atLeastOnechanged || changed;
+                } finally {
+                    tracer.endOperation(null, "Completed property copy operation");
                 }
             }
-            atLeastOnechanged = atLeastOnechanged || changed;
+
+            final boolean finalAtLeastOneChanged = atLeastOnechanged;
+            tracer.trace("Overall copy operation result", new HashMap<String, Object>() {{
+                put("profile_updated", finalAtLeastOneChanged);
+            }});
+            return finalAtLeastOneChanged ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
+        } catch (Exception e) {
+            tracer.trace("Error during property copy operation", e);
+            throw e;
+        } finally {
+            tracer.endOperation(null, "Completed all property copy operations");
         }
-        return atLeastOnechanged ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
     }
 
     private Map<String, Object> getEventPropsToCopy(Action action, Event event) {
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateProfileAgeAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateProfileAgeAction.java
index b8f60df13..34ab5c552 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateProfileAgeAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateProfileAgeAction.java
@@ -21,24 +21,90 @@ import org.apache.unomi.api.Event;
 import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.joda.time.DateTime;
 import org.joda.time.Years;
 
+import java.util.Map;
+
 /**
  * An action that sets the age of a profile based on his birth date
  */
 public class EvaluateProfileAgeAction implements ActionExecutor {
 
+    private ProfileService profileService;
+    private TracerService tracerService;
+
+    public void setProfileService(ProfileService profileService) {
+        this.profileService = profileService;
+    }
+
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     @Override
     public int execute(Action action, Event event) {
-        boolean updated = false;
-        if (event.getProfile().getProperty("birthDate") != null) {
-            Integer y = Years.yearsBetween(new DateTime(event.getProfile().getProperty("birthDate")), new DateTime()).getYears();
-            if (event.getProfile().getProperty("age") == null || event.getProfile().getProperty("age") != y) {
-                updated = true;
-                event.getProfile().setProperty("age", y);
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("evaluate-age",
+                "Evaluating profile age", action);
+        }
+
+        try {
+            if (event.getProfile() == null) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "No profile in event");
+                }
+                return EventService.NO_CHANGE;
+            }
+
+            boolean updated = false;
+            if (event.getProfile().getProperty("birthDate") != null) {
+                Integer y = Years.yearsBetween(new DateTime(event.getProfile().getProperty("birthDate")), new DateTime()).getYears();
+                Integer currentAge = (Integer) event.getProfile().getProperty("age");
+                if (currentAge == null || !currentAge.equals(y)) {
+                    updated = true;
+                    event.getProfile().setProperty("age", y);
+                    if (tracer != null) {
+                        tracer.trace("Age updated", Map.of(
+                            "birthDate", event.getProfile().getProperty("birthDate"),
+                            "newAge", y
+                        ));
+                    }
+                }
+            } else {
+                if (tracer != null) {
+                    tracer.trace("No birth date found", Map.of());
+                }
+            }
+
+            // If this action was triggered by a profileUpdated event, save the profile
+            // but don't return PROFILE_UPDATED to prevent loops
+            if (updated && "profileUpdated".equals(event.getEventType()) && profileService != null) {
+                profileService.save(event.getProfile());
+                if (tracer != null) {
+                    tracer.trace("Profile saved after age evaluation (preventing loop)", Map.of(
+                        "newAge", event.getProfile().getProperty("age")
+                    ));
+                    tracer.endOperation(true, "Profile age updated and saved (loop prevented)");
+                }
+                return EventService.NO_CHANGE;
+            }
+
+            if (tracer != null) {
+                tracer.endOperation(updated,
+                    updated ? "Profile age updated" : "No changes needed");
+            }
+            return updated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error evaluating age: " + e.getMessage());
             }
+            throw e;
         }
-        return updated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
     }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateProfileSegmentsAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateProfileSegmentsAction.java
index 2002eff51..b4f66ab19 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateProfileSegmentsAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateProfileSegmentsAction.java
@@ -22,7 +22,10 @@ import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.segments.SegmentsAndScores;
 import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.ProfileService;
 import org.apache.unomi.api.services.SegmentService;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 
 import java.util.Map;
 import java.util.Set;
@@ -30,6 +33,8 @@ import java.util.Set;
 public class EvaluateProfileSegmentsAction implements ActionExecutor {
 
     private SegmentService segmentService;
+    private ProfileService profileService;
+    private TracerService tracerService;
 
     public SegmentService getSegmentService() {
         return segmentService;
@@ -39,23 +44,80 @@ public class EvaluateProfileSegmentsAction implements ActionExecutor {
         this.segmentService = segmentService;
     }
 
+    public void setProfileService(ProfileService profileService) {
+        this.profileService = profileService;
+    }
+
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     @Override
     public int execute(Action action, Event event) {
-        if (event.getProfile().isAnonymousProfile()) {
-            return EventService.NO_CHANGE;
-        }
-        boolean updated = false;
-        SegmentsAndScores segmentsAndScoringForProfile = segmentService.getSegmentsAndScoresForProfile(event.getProfile());
-        Set<String> segments = segmentsAndScoringForProfile.getSegments();
-        if (!segments.equals(event.getProfile().getSegments())) {
-            event.getProfile().setSegments(segments);
-            updated = true;
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("evaluate-segments",
+                "Evaluating profile segments", action);
         }
-        Map<String, Integer> scores = segmentsAndScoringForProfile.getScores();
-        if (!scores.equals(event.getProfile().getScores())) {
-            event.getProfile().setScores(scores);
-            updated = true;
+
+        try {
+            if (event.getProfile() == null) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "No profile in event");
+                }
+                return EventService.NO_CHANGE;
+            }
+
+            if (event.getProfile().isAnonymousProfile()) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "Skipping anonymous profile");
+                }
+                return EventService.NO_CHANGE;
+            }
+
+            boolean updated = false;
+            SegmentsAndScores segmentsAndScoringForProfile = segmentService.getSegmentsAndScoresForProfile(event.getProfile());
+            Set<String> segments = segmentsAndScoringForProfile.getSegments();
+            if (!segments.equals(event.getProfile().getSegments())) {
+                event.getProfile().setSegments(segments);
+                updated = true;
+            }
+            Map<String, Integer> scores = segmentsAndScoringForProfile.getScores();
+            if (!scores.equals(event.getProfile().getScores())) {
+                event.getProfile().setScores(scores);
+                updated = true;
+            }
+
+            // If this action was triggered by a profileUpdated event, save the profile
+            // but don't return PROFILE_UPDATED to prevent loops
+            if (updated && "profileUpdated".equals(event.getEventType()) && profileService != null) {
+                profileService.save(event.getProfile());
+                if (tracer != null) {
+                    tracer.trace("Profile saved after segment evaluation (preventing loop)", Map.of(
+                        "segmentsCount", segments.size(),
+                        "scoresCount", scores.size()
+                    ));
+                    tracer.endOperation(true, "Profile segments updated and saved (loop prevented)");
+                }
+                return EventService.NO_CHANGE;
+            }
+
+            if (tracer != null) {
+                tracer.trace("Segments evaluated", Map.of(
+                    "segmentsCount", segments.size(),
+                    "scoresCount", scores.size(),
+                    "isUpdated", updated
+                ));
+                tracer.endOperation(updated,
+                    updated ? "Profile segments updated" : "No changes needed");
+            }
+            return updated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error evaluating segments: " + e.getMessage());
+            }
+            throw e;
         }
-        return updated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
     }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateVisitPropertiesAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateVisitPropertiesAction.java
index 43f81cd69..697142a5a 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateVisitPropertiesAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EvaluateVisitPropertiesAction.java
@@ -23,6 +23,8 @@ import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.EventService;
 import org.apache.unomi.persistence.spi.PropertyHelper;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -31,6 +33,7 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.TimeZone;
+import java.util.HashMap;
 
 /**
  * This action is used to calculate the firstVisit, lastVisit and previousVisit date properties on the profile
@@ -38,40 +41,89 @@ import java.util.TimeZone;
  */
 public class EvaluateVisitPropertiesAction implements ActionExecutor {
     private static final Logger LOGGER = LoggerFactory.getLogger(EvaluateVisitPropertiesAction.class.getName());
+    private TracerService tracerService;
+
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
 
     public int execute(Action action, Event event) {
-        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
-        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+        RequestTracer tracer = tracerService.getCurrentTracer();
+        if (!tracer.isEnabled()) {
+            tracer.setEnabled(true);
+        }
 
-        Date currentEventTimeStamp = event.getTimeStamp();
-        Date currentProfileFirstVisit = extractDateFromProperty(event.getProfile(), "firstVisit", dateFormat);
-        Date currentProfilePreviousVisit = extractDateFromProperty(event.getProfile(), "previousVisit", dateFormat);
-        Date currentProfileLastVisit = extractDateFromProperty(event.getProfile(), "lastVisit", dateFormat);
+        tracer.startOperation("evaluate-visit-properties", "Evaluating visit properties", new HashMap<String, Object>() {{
+            put("action.type", action.getActionTypeId());
+            put("event.type", event.getEventType());
+            put("event.timestamp", event.getTimeStamp());
+        }});
 
-        int result = EventService.NO_CHANGE;
+        try {
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+            dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
 
-        if (currentProfileFirstVisit == null || currentProfileFirstVisit.after(currentEventTimeStamp)) {
-            // event < firstVisit < previousVisit < lastVisit. we need to update firstVisit
-            result = PropertyHelper.setProperty(event.getProfile(), "properties.firstVisit", dateFormat.format(currentEventTimeStamp), "alwaysSet") ?
-                    EventService.PROFILE_UPDATED : result;
-        }
+            Date currentEventTimeStamp = event.getTimeStamp();
+            Date currentProfileFirstVisit = extractDateFromProperty(event.getProfile(), "firstVisit", dateFormat);
+            Date currentProfilePreviousVisit = extractDateFromProperty(event.getProfile(), "previousVisit", dateFormat);
+            Date currentProfileLastVisit = extractDateFromProperty(event.getProfile(), "lastVisit", dateFormat);
+
+            tracer.trace("Current visit properties", new HashMap<String, Object>() {{
+                put("first.visit", currentProfileFirstVisit);
+                put("previous.visit", currentProfilePreviousVisit);
+                put("last.visit", currentProfileLastVisit);
+            }});
 
-        if (currentProfileLastVisit == null || currentProfileLastVisit.before(currentEventTimeStamp)) {
-            // firstVisit < previousVisit < lastVisit < event. we need to update lastVisit and previousVisit
-            if (PropertyHelper.setProperty(event.getProfile(), "properties.lastVisit", dateFormat.format(currentEventTimeStamp), "alwaysSet")) {
-                result = EventService.PROFILE_UPDATED;
+            final int[] result = {EventService.NO_CHANGE};
 
-                if (currentProfileLastVisit != null) {
-                    PropertyHelper.setProperty(event.getProfile(), "properties.previousVisit", dateFormat.format(currentProfileLastVisit), "alwaysSet");
+            if (currentProfileFirstVisit == null || currentProfileFirstVisit.after(currentEventTimeStamp)) {
+                // event < firstVisit < previousVisit < lastVisit. we need to update firstVisit
+                boolean updated = PropertyHelper.setProperty(event.getProfile(), "properties.firstVisit", dateFormat.format(currentEventTimeStamp), "alwaysSet");
+                tracer.trace("First visit update", new HashMap<String, Object>() {{
+                    put("updated", updated);
+                    put("new.value", currentEventTimeStamp);
+                }});
+                result[0] = updated ? EventService.PROFILE_UPDATED : result[0];
+            }
+
+            if (currentProfileLastVisit == null || currentProfileLastVisit.before(currentEventTimeStamp)) {
+                // firstVisit < previousVisit < lastVisit < event. we need to update lastVisit and previousVisit
+                boolean updated = PropertyHelper.setProperty(event.getProfile(), "properties.lastVisit", dateFormat.format(currentEventTimeStamp), "alwaysSet");
+                tracer.trace("Last visit update", new HashMap<String, Object>() {{
+                    put("updated", updated);
+                    put("new.value", currentEventTimeStamp);
+                }});
+                if (updated) {
+                    result[0] = EventService.PROFILE_UPDATED;
+
+                    if (currentProfileLastVisit != null) {
+                        boolean prevUpdated = PropertyHelper.setProperty(event.getProfile(), "properties.previousVisit", dateFormat.format(currentProfileLastVisit), "alwaysSet");
+                        tracer.trace("Previous visit update", new HashMap<String, Object>() {{
+                            put("updated", prevUpdated);
+                            put("new.value", currentProfileLastVisit);
+                        }});
+                    }
                 }
+            } else if (currentProfilePreviousVisit != null && currentProfilePreviousVisit.before(currentEventTimeStamp)) {
+                // firstVisit < previousVisit < event < lastVisit. we need to update previousVisit
+                boolean updated = PropertyHelper.setProperty(event.getProfile(), "properties.previousVisit", dateFormat.format(currentEventTimeStamp), "alwaysSet");
+                tracer.trace("Previous visit update", new HashMap<String, Object>() {{
+                    put("updated", updated);
+                    put("new.value", currentEventTimeStamp);
+                }});
+                result[0] = updated ? EventService.PROFILE_UPDATED : result[0];
             }
-        } else if (currentProfilePreviousVisit != null && currentProfilePreviousVisit.before(currentEventTimeStamp)) {
-            // firstVisit < previousVisit < event < lastVisit. we need to update previousVisit
-            result = PropertyHelper.setProperty(event.getProfile(), "properties.previousVisit", dateFormat.format(currentEventTimeStamp), "alwaysSet") ?
-                    EventService.PROFILE_UPDATED : result;
-        }
 
-        return result;
+            tracer.trace("Operation result", new HashMap<String, Object>() {{
+                put("profile.updated", result[0] == EventService.PROFILE_UPDATED);
+            }});
+            return result[0];
+        } catch (Exception e) {
+            tracer.trace("Error during visit properties evaluation", e);
+            throw e;
+        } finally {
+            tracer.endOperation(null, "Completed visit properties evaluation");
+        }
     }
 
     private Date extractDateFromProperty(Profile profile, String propertyName, DateFormat dateFormat) {
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EventToProfilePropertyAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EventToProfilePropertyAction.java
index 32f8a9b04..2920e7ece 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EventToProfilePropertyAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/EventToProfilePropertyAction.java
@@ -21,20 +21,63 @@ import org.apache.unomi.api.Event;
 import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
 
 /**
  * A action to copy an event property to a profile property
  */
 public class EventToProfilePropertyAction implements ActionExecutor {
+    private static final Logger LOGGER = LoggerFactory.getLogger(EventToProfilePropertyAction.class);
+    private TracerService tracerService;
+
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
 
     public int execute(Action action, Event event) {
+        RequestTracer tracer = tracerService.getCurrentTracer();
+        if (!tracer.isEnabled()) {
+            tracer.setEnabled(true);
+        }
+
         String eventPropertyName = (String) action.getParameterValues().get("eventPropertyName");
         String profilePropertyName = (String) action.getParameterValues().get("profilePropertyName");
 
-        if (event.getProfile().getProperty(profilePropertyName) == null || !event.getProfile().getProperty(profilePropertyName).equals(event.getProperty(eventPropertyName))) {
-            event.getProfile().setProperty(profilePropertyName, event.getProperty(eventPropertyName));
-            return EventService.PROFILE_UPDATED;
+        tracer.startOperation("event-to-profile-property", "Copying event property to profile property", new HashMap<String, Object>() {{
+            put("action.type", action.getActionTypeId());
+            put("event.type", event.getEventType());
+            put("event.property", eventPropertyName);
+            put("profile.property", profilePropertyName);
+        }});
+
+        try {
+            Object currentProfileValue = event.getProfile().getProperty(profilePropertyName);
+            Object eventValue = event.getProperty(eventPropertyName);
+            boolean needsUpdate = currentProfileValue == null || !currentProfileValue.equals(eventValue);
+
+            tracer.trace("Property values", new HashMap<String, Object>() {{
+                put("current.profile.value", currentProfileValue);
+                put("event.value", eventValue);
+                put("needs.update", needsUpdate);
+            }});
+
+            if (needsUpdate) {
+                event.getProfile().setProperty(profilePropertyName, eventValue);
+                tracer.trace("Property updated", null);
+                return EventService.PROFILE_UPDATED;
+            }
+            tracer.trace("No update needed", null);
+            return EventService.NO_CHANGE;
+        } catch (Exception e) {
+            tracer.trace("Error during property copy", e);
+            throw e;
+        } finally {
+            tracer.endOperation(null, "Completed event to profile property copy");
         }
-        return EventService.NO_CHANGE;
     }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementPropertyAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementPropertyAction.java
index 275d36c28..273f3efdb 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementPropertyAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementPropertyAction.java
@@ -25,6 +25,8 @@ import org.apache.unomi.api.services.EventService;
 import org.apache.unomi.persistence.spi.PropertyHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
@@ -32,33 +34,77 @@ import java.util.Map;
 
 public class IncrementPropertyAction implements ActionExecutor {
     private static final Logger LOGGER = LoggerFactory.getLogger(IncrementPropertyAction.class.getName());
+    private TracerService tracerService;
+
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
 
     @Override
     public int execute(final Action action, final Event event) {
-        boolean storeInSession = Boolean.TRUE.equals(action.getParameterValues().get("storeInSession"));
-        if (storeInSession && event.getSession() == null) {
-            return EventService.NO_CHANGE;
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("increment-property", 
+                "Incrementing property", action);
         }
 
-        String propertyName = (String) action.getParameterValues().get("propertyName");
-        Profile profile = event.getProfile();
-        Session session = event.getSession();
-
         try {
-            Map<String, Object> properties = storeInSession ? session.getProperties() : profile.getProperties();
-            Object propertyValue = getPropertyValue(action, event, propertyName, properties);
-            if (PropertyHelper.setProperty(properties, propertyName, propertyValue, "alwaysSet")) {
-                return storeInSession ? EventService.SESSION_UPDATED : EventService.PROFILE_UPDATED;
+            boolean storeInSession = Boolean.TRUE.equals(action.getParameterValues().get("storeInSession"));
+            if (storeInSession && event.getSession() == null) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "No session available for storing property");
+                }
+                return EventService.NO_CHANGE;
+            }
+
+            String propertyName = (String) action.getParameterValues().get("propertyName");
+            Profile profile = event.getProfile();
+            Session session = event.getSession();
+
+            if (tracer != null) {
+                Map<String, Object> traceData = new HashMap<>();
+                traceData.put("propertyName", propertyName);
+                traceData.put("storeInSession", storeInSession);
+                traceData.put("hasTarget", event.getTarget() != null);
+                tracer.trace("Processing property increment", traceData);
             }
-        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
-            LOGGER.warn("Error resolving nested property of object. See debug log level for more information");
-            LOGGER.debug("Error resolving nested property of item: {}", storeInSession ? session : profile, e);
-        } catch (IllegalStateException ee) {
-            LOGGER.warn("Error increment existing property, because existing property doesn't have expected type. See debug log level for more information");
-            LOGGER.debug("{}", ee.getMessage(), ee);
-        }
 
-        return EventService.NO_CHANGE;
+            try {
+                Map<String, Object> properties = storeInSession ? session.getProperties() : profile.getProperties();
+                Object propertyValue = getPropertyValue(action, event, propertyName, properties);
+                boolean updated = PropertyHelper.setProperty(properties, propertyName, propertyValue, "alwaysSet");
+                
+                if (tracer != null) {
+                    Map<String, Object> traceData = new HashMap<>();
+                    traceData.put("newValue", propertyValue);
+                    traceData.put("isUpdated", updated);
+                    tracer.trace("Property increment result", traceData);
+                    tracer.endOperation(updated, 
+                        updated ? "Property incremented successfully" : "No changes needed");
+                }
+                return updated ? (storeInSession ? EventService.SESSION_UPDATED : EventService.PROFILE_UPDATED) : EventService.NO_CHANGE;
+            } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "Error resolving nested property: " + e.getMessage());
+                }
+                LOGGER.warn("Error resolving nested property of object. See debug log level for more information");
+                LOGGER.debug("Error resolving nested property of item: {}", storeInSession ? session : profile, e);
+            } catch (IllegalStateException ee) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "Error incrementing property: " + ee.getMessage());
+                }
+                LOGGER.warn("Error increment existing property, because existing property doesn't have expected type. See debug log level for more information");
+                LOGGER.debug("{}", ee.getMessage(), ee);
+            }
+
+            return EventService.NO_CHANGE;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error in property increment: " + e.getMessage());
+            }
+            throw e;
+        }
     }
 
     private Object getPropertyValue(Action action, Event event, String propertyName, Map<String, Object> properties)
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java
index 0079008fe..83db5fee2 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java
@@ -32,6 +32,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.unomi.api.tasks.ScheduledTask;
 import org.apache.unomi.api.tasks.TaskExecutor;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 import org.apache.unomi.api.services.ExecutionContextManager;
 
 import java.util.*;
@@ -47,12 +49,19 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
     private DefinitionsService definitionsService;
     private PrivacyService privacyService;
     private SchedulerService schedulerService;
+    private TracerService tracerService;
     private ExecutionContextManager executionContextManager;
     private SecurityService securityService;
     // TODO we can remove this limit after dealing with: UNOMI-776 (50 is completely arbitrary and it's used to bypass the auto-scroll done by the persistence Service)
     private int maxProfilesInOneMerge = 50;
 
     public int execute(Action action, Event event) {
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("merge-profiles",
+                "Starting profile merge operation", action);
+        }
 
         try {
             Profile eventProfile = event.getProfile();
@@ -65,14 +74,33 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
 
             if (eventProfile instanceof Persona || eventProfile.isAnonymousProfile() || StringUtils.isEmpty(mergePropName) ||
                     StringUtils.isEmpty(mergePropValue)) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "Invalid profile or missing merge parameters");
+                }
                 return EventService.NO_CHANGE;
             }
 
             final List<Profile> profilesToBeMerge = getProfilesToBeMerge(mergePropName, mergePropValue);
 
+            if (tracer != null) {
+                tracer.trace("Found profiles to merge", Map.of(
+                    "count", profilesToBeMerge.size(),
+                    "profileIds", profilesToBeMerge.stream().map(Profile::getItemId).collect(Collectors.toList())
+                ));
+            }
+
             // Check if the user switched to another profile
             if (StringUtils.isNotEmpty(currentProfileMergeValue) && !currentProfileMergeValue.equals(mergePropValue)) {
+                if (tracer != null) {
+                    tracer.trace("Profile switch detected", Map.of(
+                        "fromValue", currentProfileMergeValue,
+                        "toValue", mergePropValue
+                    ));
+                }
                 reassignCurrentBrowsingData(event, profilesToBeMerge, forceEventProfileAsMaster, mergePropName, mergePropValue);
+                if (tracer != null) {
+                    tracer.endOperation(true, "Profile switch completed");
+                }
                 return EventService.PROFILE_UPDATED + EventService.SESSION_UPDATED;
             }
 
@@ -85,6 +113,9 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
 
             // If not profiles to merge we are done here.
             if (profilesToBeMerge.isEmpty()) {
+                if (tracer != null) {
+                    tracer.endOperation(profileUpdated, profileUpdated ? "Profile updated but no merges needed" : "No changes needed");
+                }
                 return profileUpdated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
             }
 
@@ -97,8 +128,18 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
             final Profile masterProfile = profileService.mergeProfiles(forceEventProfileAsMaster ? eventProfile : profilesToBeMerge.get(0), profilesToBeMerge);
             final String masterProfileId = masterProfile.getItemId();
 
+            if (tracer != null) {
+                tracer.trace("Profile merge completed", Map.of(
+                    "masterProfileId", masterProfileId,
+                    "originalProfileId", eventProfileId
+                ));
+            }
+
             // Profile is still using the same profileId after being merged, no need to rewrite exists data, merge is done
             if (!forceEventProfileAsMaster && masterProfileId.equals(eventProfileId)) {
+                if (tracer != null) {
+                    tracer.endOperation(profileUpdated, "Profile merge completed with same ID");
+                }
                 return profileUpdated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
             }
 
@@ -118,6 +159,7 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
             event.setProfileId(anonymousBrowsing ? null : masterProfileId);
             event.setProfile(masterProfile);
 
+            final RequestTracer finalTracer = tracer;
             event.getActionPostExecutors().add(() -> {
                 try {
                     // This is the list of profile Ids to be updated in browsing data (events/sessions)
@@ -146,15 +188,30 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
                         }
                     }
 
+                    if (finalTracer != null) {
+                        finalTracer.trace("Post-merge cleanup completed", Map.of(
+                            "mergedProfileIds", mergedProfileIds,
+                            "masterProfileId", masterProfileId
+                        ));
+                    }
                 } catch (Exception e) {
                     LOGGER.error("unable to execute callback action, profile and session will not be saved", e);
+                    if (finalTracer != null) {
+                        finalTracer.endOperation(false, "Error during post-execution: " + e.getMessage());
+                    }
                     return false;
                 }
                 return true;
             });
 
+            if (tracer != null) {
+                tracer.endOperation(true, "Profile merge completed successfully");
+            }
             return EventService.PROFILE_UPDATED + EventService.SESSION_UPDATED;
         } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error during profile merge: " + e.getMessage());
+            }
             throw e;
         }
     }
@@ -292,6 +349,10 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
         this.maxProfilesInOneMerge = Integer.parseInt(maxProfilesInOneMerge);
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     public void bindExecutionContextManager(ExecutionContextManager executionContextManager) {
         this.executionContextManager = executionContextManager;
     }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/ModifyConsentAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/ModifyConsentAction.java
index be18678b7..fed427373 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/ModifyConsentAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/ModifyConsentAction.java
@@ -25,6 +25,8 @@ import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.EventService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 
 import java.text.ParseException;
 import java.util.Map;
@@ -36,29 +38,69 @@ public class ModifyConsentAction implements ActionExecutor {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(ModifyConsentAction.class.getName());
 
+    private TracerService tracerService;
+
     public static final String CONSENT_PROPERTY_NAME = "consent";
 
     @Override
     public int execute(Action action, Event event) {
-        Profile profile = event.getProfile();
-        boolean isProfileUpdated = false;
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("modify-consent", 
+                "Modifying consent", action);
+        }
 
-        ISO8601DateFormat dateFormat = new ISO8601DateFormat();
-        Map consentMap = (Map) event.getProperties().get(CONSENT_PROPERTY_NAME);
-        if (consentMap != null) {
-            if (consentMap.containsKey("typeIdentifier") && consentMap.containsKey("status")) {
-                Consent consent = null;
-                try {
-                    consent = new Consent(consentMap, dateFormat);
-                    isProfileUpdated = profile.setConsent(consent);
-                } catch (ParseException e) {
-                    LOGGER.error("Error parsing consent dates (statusDate or revokeDate). See debug log level to have more information");
-                    LOGGER.debug("Error parsing consent dates (statusDate or revokeDate).", e);
+        try {
+            Profile profile = event.getProfile();
+            boolean isProfileUpdated = false;
+
+            ISO8601DateFormat dateFormat = new ISO8601DateFormat();
+            Map consentMap = (Map) event.getProperties().get(CONSENT_PROPERTY_NAME);
+            if (consentMap != null) {
+                if (consentMap.containsKey("typeIdentifier") && consentMap.containsKey("status")) {
+                    Consent consent = null;
+                    try {
+                        consent = new Consent(consentMap, dateFormat);
+                        isProfileUpdated = profile.setConsent(consent);
+                        if (tracer != null) {
+                            tracer.trace("Consent modified", Map.of(
+                                "typeIdentifier", consent.getTypeIdentifier(),
+                                "status", consent.getStatus(),
+                                "isUpdated", isProfileUpdated
+                            ));
+                        }
+                    } catch (ParseException e) {
+                        if (tracer != null) {
+                            tracer.endOperation(false, "Error parsing consent dates: " + e.getMessage());
+                        }
+                        LOGGER.error("Error parsing consent dates (statusDate or revokeDate). See debug log level to have more information");
+                        LOGGER.debug("Error parsing consent dates (statusDate or revokeDate).", e);
+                        return EventService.NO_CHANGE;
+                    }
+                } else {
+                    if (tracer != null) {
+                        tracer.endOperation(false, "Missing required consent properties");
+                    }
+                    LOGGER.warn("Event properties for modifyConsent is missing typeIdentifier and grant properties. We will ignore this event.");
+                    return EventService.NO_CHANGE;
                 }
-            } else {
-                LOGGER.warn("Event properties for modifyConsent is missing typeIdentifier and grant properties. We will ignore this event.");
             }
+
+            if (tracer != null) {
+                tracer.endOperation(isProfileUpdated, 
+                    isProfileUpdated ? "Consent updated successfully" : "No changes needed");
+            }
+            return isProfileUpdated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error modifying consent: " + e.getMessage());
+            }
+            throw e;
         }
-        return isProfileUpdated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
+    }
+
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
     }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SendEventAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SendEventAction.java
index 909fb59a5..94effffe1 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SendEventAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SendEventAction.java
@@ -23,34 +23,72 @@ import org.apache.unomi.api.Item;
 import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 
+import java.util.HashMap;
 import java.util.Map;
 
 public class SendEventAction implements ActionExecutor {
 
     private EventService eventService;
+    private TracerService tracerService;
 
     public void setEventService(EventService eventService) {
         this.eventService = eventService;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     @Override
     public int execute(Action action, Event event) {
-        String eventType = (String) action.getParameterValues().get("eventType");
-        Boolean toBePersisted = (Boolean) action.getParameterValues().get("toBePersisted");
-
-        @SuppressWarnings("unchecked")
-        Map<String, Object> eventProperties = (Map<String, Object>) action.getParameterValues().get("eventProperties");
-        Item target = (Item) action.getParameterValues().get("eventTarget");
-
-        Event subEvent = new Event(eventType, event.getSession(), event.getProfile(), event.getScope(), event, target, event.getTimeStamp());
-        subEvent.setProfileId(event.getProfileId());
-        subEvent.getAttributes().putAll(event.getAttributes());
-        subEvent.getProperties().putAll(eventProperties);
-        if (toBePersisted != null && !toBePersisted) {
-            subEvent.setPersistent(false);
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("send-event", 
+                "Sending event", action);
         }
 
-        return eventService.send(subEvent);
+        try {
+            String eventType = (String) action.getParameterValues().get("eventType");
+            Boolean toBePersisted = (Boolean) action.getParameterValues().get("toBePersisted");
+
+            if (tracer != null) {
+                Map<String, Object> traceData = new HashMap<>();
+                traceData.put("eventType", eventType);
+                traceData.put("toBePersisted", toBePersisted);
+                traceData.put("hasTarget", action.getParameterValues().get("eventTarget") != null);
+                tracer.trace("Preparing event", traceData);
+            }
+
+            @SuppressWarnings("unchecked")
+            Map<String, Object> eventProperties = (Map<String, Object>) action.getParameterValues().get("eventProperties");
+            Item target = (Item) action.getParameterValues().get("eventTarget");
+
+            Event subEvent = new Event(eventType, event.getSession(), event.getProfile(), event.getScope(), event, target, event.getTimeStamp());
+            subEvent.setProfileId(event.getProfileId());
+            subEvent.getAttributes().putAll(event.getAttributes());
+            subEvent.getProperties().putAll(eventProperties);
+            if (toBePersisted != null && !toBePersisted) {
+                subEvent.setPersistent(false);
+            }
+
+            int result = eventService.send(subEvent);
+            if (tracer != null) {
+                Map<String, Object> traceData = new HashMap<>();
+                traceData.put("eventId", subEvent.getItemId());
+                traceData.put("result", result);
+                tracer.trace("Event sent", traceData);
+                tracer.endOperation(true, "Event sent successfully");
+            }
+            return result;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error sending event: " + e.getMessage());
+            }
+            throw e;
+        }
     }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetEventOccurenceCountAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetEventOccurenceCountAction.java
index cb5e78f59..6716ce74d 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetEventOccurenceCountAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetEventOccurenceCountAction.java
@@ -24,20 +24,19 @@ import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.EventService;
 import org.apache.unomi.persistence.spi.PersistenceService;
-import org.apache.unomi.persistence.spi.PropertyHelper;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 
+import javax.xml.bind.DatatypeConverter;
 import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.*;
-import java.util.stream.Collectors;
-
-import javax.xml.bind.DatatypeConverter;
 
 public class SetEventOccurenceCountAction implements ActionExecutor {
     private DefinitionsService definitionsService;
-
     private PersistenceService persistenceService;
+    private TracerService tracerService;
 
     public void setDefinitionsService(DefinitionsService definitionsService) {
         this.definitionsService = definitionsService;
@@ -47,85 +46,118 @@ public class SetEventOccurenceCountAction implements ActionExecutor {
         this.persistenceService = persistenceService;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     @Override
     public int execute(Action action, Event event) {
-        final Condition pastEventCondition = (Condition) action.getParameterValues().get("pastEventCondition");
-
-        Condition andCondition = new Condition(definitionsService.getConditionType("booleanCondition"));
-        andCondition.setParameter("operator", "and");
-        ArrayList<Condition> conditions = new ArrayList<Condition>();
-
-        Condition eventCondition = (Condition) pastEventCondition.getParameter("eventCondition");
-        definitionsService.resolveConditionType(eventCondition);
-        conditions.add(eventCondition);
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("set-event-count",
+                "Setting event occurrence count", action);
+        }
 
-        Condition c = new Condition(definitionsService.getConditionType("eventPropertyCondition"));
-        c.setParameter("propertyName", "profileId");
-        c.setParameter("comparisonOperator", "equals");
-        c.setParameter("propertyValue", event.getProfileId());
-        conditions.add(c);
+        try {
+            final Condition pastEventCondition = (Condition) action.getParameterValues().get("pastEventCondition");
+            String generatedPropertyKey = (String) pastEventCondition.getParameter("generatedPropertyKey");
 
-        // may be current event is already persisted and indexed, in that case we filter it from the count to increment it manually at the end
-        Condition eventIdFilter = new Condition(definitionsService.getConditionType("eventPropertyCondition"));
-        eventIdFilter.setParameter("propertyName", "itemId");
-        eventIdFilter.setParameter("comparisonOperator", "notEquals");
-        eventIdFilter.setParameter("propertyValue", event.getItemId());
-        conditions.add(eventIdFilter);
+            if (tracer != null) {
+                tracer.trace("Processing event count", Map.of(
+                    "propertyKey", generatedPropertyKey,
+                    "eventId", event.getItemId()
+                ));
+            }
 
-        Integer numberOfDays = (Integer) pastEventCondition.getParameter("numberOfDays");
-        String fromDate = (String) pastEventCondition.getParameter("fromDate");
-        String toDate = (String) pastEventCondition.getParameter("toDate");
+            Condition andCondition = new Condition(definitionsService.getConditionType("booleanCondition"));
+            andCondition.setParameter("operator", "and");
+            ArrayList<Condition> conditions = new ArrayList<Condition>();
 
-        if (numberOfDays != null) {
-            Condition numberOfDaysCondition = new Condition(definitionsService.getConditionType("eventPropertyCondition"));
-            numberOfDaysCondition.setParameter("propertyName", "timeStamp");
-            numberOfDaysCondition.setParameter("comparisonOperator", "greaterThan");
-            numberOfDaysCondition.setParameter("propertyValueDateExpr", "now-" + numberOfDays + "d");
-            conditions.add(numberOfDaysCondition);
-        }
-        if (fromDate != null)  {
-            Condition startDateCondition = new Condition();
-            startDateCondition.setConditionType(definitionsService.getConditionType("eventPropertyCondition"));
-            startDateCondition.setParameter("propertyName", "timeStamp");
-            startDateCondition.setParameter("comparisonOperator", "greaterThanOrEqualTo");
-            startDateCondition.setParameter("propertyValueDate", fromDate);
-            conditions.add(startDateCondition);
-        }
-        if (toDate != null)  {
-            Condition endDateCondition = new Condition();
-            endDateCondition.setConditionType(definitionsService.getConditionType("eventPropertyCondition"));
-            endDateCondition.setParameter("propertyName", "timeStamp");
-            endDateCondition.setParameter("comparisonOperator", "lessThanOrEqualTo");
-            endDateCondition.setParameter("propertyValueDate", toDate);
-            conditions.add(endDateCondition);
-        }
+            Condition eventCondition = (Condition) pastEventCondition.getParameter("eventCondition");
+            if (eventCondition != null) {
+                definitionsService.getConditionValidationService().validate(eventCondition);
+            }
+            conditions.add(eventCondition);
+
+            Condition c = new Condition(definitionsService.getConditionType("eventPropertyCondition"));
+            c.setParameter("propertyName", "profileId");
+            c.setParameter("comparisonOperator", "equals");
+            c.setParameter("propertyValue", event.getProfileId());
+            conditions.add(c);
+
+            // may be current event is already persisted and indexed, in that case we filter it from the count to increment it manually at the end
+            Condition eventIdFilter = new Condition(definitionsService.getConditionType("eventPropertyCondition"));
+            eventIdFilter.setParameter("propertyName", "itemId");
+            eventIdFilter.setParameter("comparisonOperator", "notEquals");
+            eventIdFilter.setParameter("propertyValue", event.getItemId());
+            conditions.add(eventIdFilter);
+
+            Integer numberOfDays = (Integer) pastEventCondition.getParameter("numberOfDays");
+            String fromDate = (String) pastEventCondition.getParameter("fromDate");
+            String toDate = (String) pastEventCondition.getParameter("toDate");
+
+            if (numberOfDays != null) {
+                Condition numberOfDaysCondition = new Condition(definitionsService.getConditionType("eventPropertyCondition"));
+                numberOfDaysCondition.setParameter("propertyName", "timeStamp");
+                numberOfDaysCondition.setParameter("comparisonOperator", "greaterThan");
+                numberOfDaysCondition.setParameter("propertyValueDateExpr", "now-" + numberOfDays + "d");
+                conditions.add(numberOfDaysCondition);
+            }
+            if (fromDate != null)  {
+                Condition startDateCondition = new Condition();
+                startDateCondition.setConditionType(definitionsService.getConditionType("eventPropertyCondition"));
+                startDateCondition.setParameter("propertyName", "timeStamp");
+                startDateCondition.setParameter("comparisonOperator", "greaterThanOrEqualTo");
+                startDateCondition.setParameter("propertyValueDate", fromDate);
+                conditions.add(startDateCondition);
+            }
+            if (toDate != null)  {
+                Condition endDateCondition = new Condition();
+                endDateCondition.setConditionType(definitionsService.getConditionType("eventPropertyCondition"));
+                endDateCondition.setParameter("propertyName", "timeStamp");
+                endDateCondition.setParameter("comparisonOperator", "lessThanOrEqualTo");
+                endDateCondition.setParameter("propertyValueDate", toDate);
+                conditions.add(endDateCondition);
+            }
 
-        andCondition.setParameter("subConditions", conditions);
+            andCondition.setParameter("subConditions", conditions);
 
-        long count = persistenceService.queryCount(andCondition, Event.ITEM_TYPE);
+            long count = persistenceService.queryCount(andCondition, Event.ITEM_TYPE);
 
-        LocalDateTime fromDateTime = null;
-        if (fromDate != null) {
-            Calendar fromDateCalendar = DatatypeConverter.parseDateTime(fromDate);
-            fromDateTime = LocalDateTime.ofInstant(fromDateCalendar.toInstant(), ZoneId.of("UTC"));
-        }
-        LocalDateTime toDateTime = null;
-        if (toDate != null) {
-            Calendar toDateCalendar = DatatypeConverter.parseDateTime(toDate);
-            toDateTime = LocalDateTime.ofInstant(toDateCalendar.toInstant(), ZoneId.of("UTC"));
-        }
+            LocalDateTime fromDateTime = null;
+            if (fromDate != null) {
+                Calendar fromDateCalendar = DatatypeConverter.parseDateTime(fromDate);
+                fromDateTime = LocalDateTime.ofInstant(fromDateCalendar.toInstant(), ZoneId.of("UTC"));
+            }
+            LocalDateTime toDateTime = null;
+            if (toDate != null) {
+                Calendar toDateCalendar = DatatypeConverter.parseDateTime(toDate);
+                toDateTime = LocalDateTime.ofInstant(toDateCalendar.toInstant(), ZoneId.of("UTC"));
+            }
 
-        LocalDateTime eventTime = LocalDateTime.ofInstant(event.getTimeStamp().toInstant(),ZoneId.of("UTC"));
+            LocalDateTime eventTime = LocalDateTime.ofInstant(event.getTimeStamp().toInstant(),ZoneId.of("UTC"));
 
-        if (inTimeRange(eventTime, numberOfDays, fromDateTime, toDateTime)) {
-            count++;
-        }
+            if (inTimeRange(eventTime, numberOfDays, fromDateTime, toDateTime)) {
+                count++;
+            }
 
-        if (updatePastEvents(event, (String) pastEventCondition.getParameter("generatedPropertyKey"), count)) {
-            return EventService.PROFILE_UPDATED;
+            boolean updated = updatePastEvents(event, generatedPropertyKey, count);
+            if (tracer != null) {
+                tracer.trace("Event count updated", Map.of(
+                    "count", count,
+                    "isUpdated", updated
+                ));
+                tracer.endOperation(updated,
+                    updated ? "Event count updated successfully" : "No changes needed");
+            }
+            return updated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error setting event count: " + e.getMessage());
+            }
+            throw e;
         }
-
-        return EventService.NO_CHANGE;
     }
 
     private boolean updatePastEvents(Event event, String generatedPropertyKey, long count) {
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetPropertyAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetPropertyAction.java
index f67eb158b..df3749bad 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetPropertyAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetPropertyAction.java
@@ -24,6 +24,8 @@ import org.apache.unomi.api.services.EventService;
 import org.apache.unomi.persistence.spi.PropertyHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -37,6 +39,7 @@ public class SetPropertyAction implements ActionExecutor {
     private static final Logger LOGGER = LoggerFactory.getLogger(SetPropertyAction.class.getName());
 
     private EventService eventService;
+    private TracerService tracerService;
     // TODO Temporary solution that should be handle by: https://issues.apache.org/jira/browse/UNOMI-630 (Implement a global solution to avoid multiple same log pollution.)
     private static final AtomicLong nowDeprecatedLogTimestamp = new AtomicLong();
 
@@ -47,48 +50,81 @@ public class SetPropertyAction implements ActionExecutor {
     }
 
     public int execute(Action action, Event event) {
-        boolean storeInSession = Boolean.TRUE.equals(action.getParameterValues().get("storeInSession"));
-        if (storeInSession && event.getSession() == null) {
-            return EventService.NO_CHANGE;
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("set-property", 
+                "Setting property value", action);
         }
 
-        String propertyName = (String) action.getParameterValues().get("setPropertyName");
-        Object propertyValue = getPropertyValue(action, event);
-
-        if (storeInSession) {
-            // in the case of session storage we directly update the session
-            if (PropertyHelper.setProperty(event.getSession(), propertyName, propertyValue, (String) action.getParameterValues().get("setPropertyStrategy"))) {
-                return EventService.SESSION_UPDATED;
+        try {
+            boolean storeInSession = Boolean.TRUE.equals(action.getParameterValues().get("storeInSession"));
+            if (storeInSession && event.getSession() == null) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "No session available for session storage");
+                }
+                return EventService.NO_CHANGE;
             }
-        } else {
-            if (useEventToUpdateProfile) {
-                // in the case of profile storage we use the update profile properties event instead.
-                Map<String, Object> propertyToUpdate = new HashMap<>();
-                propertyToUpdate.put(propertyName, propertyValue);
 
-                Event updateProperties = new Event("updateProperties", event.getSession(), event.getProfile(), event.getScope(), null, null, new Date());
-                updateProperties.setPersistent(false);
+            String propertyName = (String) action.getParameterValues().get("setPropertyName");
+            Object propertyValue = getPropertyValue(action, event);
 
-                updateProperties.setProperty(UpdatePropertiesAction.PROPS_TO_UPDATE, propertyToUpdate);
-                int changes = eventService.send(updateProperties);
+            if (tracer != null) {
+                Map<String, Object> traceData = new HashMap<>();
+                traceData.put("propertyName", propertyName);
+                traceData.put("propertyValue", propertyValue);
+                traceData.put("storeInSession", storeInSession);
+                tracer.trace("Setting property", traceData);
+            }
 
-                if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
-                    return EventService.PROFILE_UPDATED;
+            int result = EventService.NO_CHANGE;
+            if (storeInSession) {
+                // in the case of session storage we directly update the session
+                if (PropertyHelper.setProperty(event.getSession(), propertyName, propertyValue, (String) action.getParameterValues().get("setPropertyStrategy"))) {
+                    result = EventService.SESSION_UPDATED;
                 }
             } else {
-                if (PropertyHelper.setProperty(event.getProfile(), propertyName, propertyValue, (String) action.getParameterValues().get("setPropertyStrategy"))) {
-                    return EventService.PROFILE_UPDATED;
+                if (useEventToUpdateProfile) {
+                    // in the case of profile storage we use the update profile properties event instead.
+                    Map<String, Object> propertyToUpdate = new HashMap<>();
+                    propertyToUpdate.put(propertyName, propertyValue);
+
+                    Event updateProperties = new Event("updateProperties", event.getSession(), event.getProfile(), event.getScope(), null, null, new Date());
+                    updateProperties.setPersistent(false);
+
+                    updateProperties.setProperty(UpdatePropertiesAction.PROPS_TO_UPDATE, propertyToUpdate);
+                    result = eventService.send(updateProperties);
+                    if ((result & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
+                        result = EventService.PROFILE_UPDATED;
+                    }
+                    } else {
+                    if (PropertyHelper.setProperty(event.getProfile(), propertyName, propertyValue, (String) action.getParameterValues().get("setPropertyStrategy"))) {
+                        result = EventService.PROFILE_UPDATED;
+                    }
                 }
             }
-        }
 
-        return EventService.NO_CHANGE;
+            if (tracer != null) {
+                tracer.endOperation(result != EventService.NO_CHANGE, 
+                    result != EventService.NO_CHANGE ? "Property set successfully" : "No changes needed");
+            }
+            return result;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error setting property: " + e.getMessage());
+            }
+            throw e;
+        }
     }
 
     public void setEventService(EventService eventService) {
         this.eventService = eventService;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     private Object getPropertyValue(Action action, Event event) {
         Object propertyValue = action.getParameterValues().get("setPropertyValue");
         if (propertyValue == null) {
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/UpdatePropertiesAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/UpdatePropertiesAction.java
index eb890ad15..af9a2a091 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/UpdatePropertiesAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/UpdatePropertiesAction.java
@@ -29,6 +29,8 @@ import org.apache.unomi.api.services.ProfileService;
 import org.apache.unomi.persistence.spi.PropertyHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 
 import java.util.*;
 
@@ -48,67 +50,94 @@ public class UpdatePropertiesAction implements ActionExecutor {
 
     private ProfileService profileService;
     private EventService eventService;
+    private TracerService tracerService;
 
     public int execute(Action action, Event event) {
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("update-properties", 
+                "Updating properties", action);
+        }
 
-        Profile target = event.getProfile();
-
-        String targetId = (String) event.getProperty(TARGET_ID_KEY);
-        String targetType = (String) event.getProperty(TARGET_TYPE_KEY);
-
-        if (StringUtils.isNotBlank(targetId) && event.getProfile() != null && !targetId.equals(event.getProfile().getItemId())) {
-            target = TARGET_TYPE_PROFILE.equals(targetType) ? profileService.load(targetId) : profileService.loadPersona(targetId);
-            if (target == null) {
-                LOGGER.warn("No profile found with Id : {}. Update skipped.", targetId);
-                return EventService.NO_CHANGE;
+        try {
+            Profile target = event.getProfile();
+            String targetId = (String) event.getProperty(TARGET_ID_KEY);
+            String targetType = (String) event.getProperty(TARGET_TYPE_KEY);
+
+            if (tracer != null) {
+                Map<String, Object> traceData = new HashMap<>();
+                traceData.put("targetId", targetId);
+                traceData.put("targetType", targetType);
+                traceData.put("hasTarget", target != null);
+                tracer.trace("Processing properties update", traceData);
             }
-        }
 
-        boolean isProfileOrPersonaUpdated = false;
+            if (StringUtils.isNotBlank(targetId) && event.getProfile() != null && !targetId.equals(event.getProfile().getItemId())) {
+                target = TARGET_TYPE_PROFILE.equals(targetType) ? profileService.load(targetId) : profileService.loadPersona(targetId);
+                if (target == null) {
+                    if (tracer != null) {
+                        tracer.endOperation(false, "No profile found with Id: " + targetId);
+                    }
+                    LOGGER.warn("No profile found with Id : {}. Update skipped.", targetId);
+                    return EventService.NO_CHANGE;
+                }
+            }
 
-        Map<String, Object> propsToAdd = (HashMap<String, Object>) event.getProperties().get(PROPS_TO_ADD);
+            boolean isProfileOrPersonaUpdated = false;
 
-        if (propsToAdd != null) {
-            isProfileOrPersonaUpdated |= processProperties(target, propsToAdd, "setIfMissing");
-        }
+            Map<String, Object> propsToAdd = (HashMap<String, Object>) event.getProperties().get(PROPS_TO_ADD);
 
-        Map<String, Object> propsToUpdate = (HashMap<String, Object>) event.getProperties().get(PROPS_TO_UPDATE);
-        if (propsToUpdate != null) {
-            isProfileOrPersonaUpdated |= processProperties(target, propsToUpdate, "alwaysSet");
-        }
+            if (propsToAdd != null) {
+                isProfileOrPersonaUpdated |= processProperties(target, propsToAdd, "setIfMissing");
+            }
 
-        Map<String, Object> propsToAddToSet = (HashMap<String, Object>) event.getProperties().get(PROPS_TO_ADD_TO_SET);
-        if (propsToAddToSet != null) {
-            isProfileOrPersonaUpdated |= processProperties(target, propsToAddToSet, "addValues");
-        }
+            Map<String, Object> propsToUpdate = (HashMap<String, Object>) event.getProperties().get(PROPS_TO_UPDATE);
+            if (propsToUpdate != null) {
+                isProfileOrPersonaUpdated |= processProperties(target, propsToUpdate, "alwaysSet");
+            }
 
-        List<String> propsToDelete = (List<String>) event.getProperties().get(PROPS_TO_DELETE);
-        if (propsToDelete != null) {
-            for (String prop : propsToDelete) {
-                isProfileOrPersonaUpdated |= PropertyHelper.setProperty(target, prop, null, "remove");
+            Map<String, Object> propsToAddToSet = (HashMap<String, Object>) event.getProperties().get(PROPS_TO_ADD_TO_SET);
+            if (propsToAddToSet != null) {
+                isProfileOrPersonaUpdated |= processProperties(target, propsToAddToSet, "addValues");
             }
-        }
 
-        if (StringUtils.isNotBlank(targetId) && isProfileOrPersonaUpdated &&
-                event.getProfile() != null && !targetId.equals(event.getProfile().getItemId())) {
-            if (TARGET_TYPE_PROFILE.equals(targetType)) {
-                profileService.save(target);
-                Event profileUpdated = new Event("profileUpdated", null, target, null, null, target, new Date());
-                profileUpdated.setPersistent(false);
-                int changes = eventService.send(profileUpdated);
-                if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
-                    profileService.save(target);
+            List<String> propsToDelete = (List<String>) event.getProperties().get(PROPS_TO_DELETE);
+            if (propsToDelete != null) {
+                for (String prop : propsToDelete) {
+                    isProfileOrPersonaUpdated |= PropertyHelper.setProperty(target, prop, null, "remove");
                 }
-            } else {
-                profileService.savePersona((Persona) target);
             }
 
-            return EventService.NO_CHANGE;
+            if (StringUtils.isNotBlank(targetId) && isProfileOrPersonaUpdated &&
+                    event.getProfile() != null && !targetId.equals(event.getProfile().getItemId())) {
+                if (TARGET_TYPE_PROFILE.equals(targetType)) {
+                    profileService.save(target);
+                    Event profileUpdated = new Event("profileUpdated", null, target, null, null, target, new Date());
+                    profileUpdated.setPersistent(false);
+                    int changes = eventService.send(profileUpdated);
+                    if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
+                        profileService.save(target);
+                    }
+                } else {
+                    profileService.savePersona((Persona) target);
+                }
 
-        }
+                return EventService.NO_CHANGE;
 
-        return isProfileOrPersonaUpdated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
+            }
 
+            if (tracer != null) {
+                tracer.endOperation(isProfileOrPersonaUpdated, 
+                    isProfileOrPersonaUpdated ? "Properties updated successfully" : "No changes needed");
+            }
+            return isProfileOrPersonaUpdated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error updating properties: " + e.getMessage());
+            }
+            throw e;
+        }
     }
 
     private boolean processProperties(Profile target, Map<String, Object> propsMap, String strategy) {
@@ -141,4 +170,8 @@ public class UpdatePropertiesAction implements ActionExecutor {
         this.eventService = eventService;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/BooleanConditionEvaluator.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/BooleanConditionEvaluator.java
index 8eb57b1ea..df9fb677e 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/BooleanConditionEvaluator.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/BooleanConditionEvaluator.java
@@ -21,39 +21,75 @@ import org.apache.unomi.api.Item;
 import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluator;
 import org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluatorDispatcher;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 
 import java.util.List;
 import java.util.Map;
 
-/**
- * Evaluator for AND and OR conditions.
- */
+/** Evaluator for AND and OR conditions. */
 public class BooleanConditionEvaluator implements ConditionEvaluator {
+    private TracerService tracerService;
+
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
 
     @Override
     public boolean eval(Condition condition, Item item, Map<String, Object> context,
             ConditionEvaluatorDispatcher dispatcher) {
-        boolean isAnd = "and".equalsIgnoreCase((String) condition.getParameter("operator"));
-        Object subConditionsParam = condition.getParameter("subConditions");
-        if (subConditionsParam != null && !(subConditionsParam instanceof List)) {
-            throw new IllegalArgumentException("Parameter 'subConditions' of condition type '"
-                + condition.getConditionTypeId() + "' must be a List, got: " + subConditionsParam.getClass().getName());
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("boolean",
+                "Evaluating boolean condition with operator: " + condition.getParameter("operator"), condition);
         }
-        @SuppressWarnings("unchecked")
-        List<Condition> conditions = (List<Condition>) subConditionsParam;
 
-        if (conditions == null || conditions.isEmpty()) {
-            return isAnd;
-        }
+        try {
+            boolean isAnd = "and".equalsIgnoreCase((String) condition.getParameter("operator"));
+            Object subConditionsParam = condition.getParameter("subConditions");
+            if (subConditionsParam != null && !(subConditionsParam instanceof List)) {
+                throw new IllegalArgumentException("Parameter 'subConditions' of condition type '"
+                    + condition.getConditionTypeId() + "' must be a List, got: " + subConditionsParam.getClass().getName());
+            }
+            @SuppressWarnings("unchecked")
+            List<Condition> conditions = (List<Condition>) subConditionsParam;
+
+            if (conditions == null || conditions.isEmpty()) {
+                if (tracer != null) {
+                    tracer.endOperation(isAnd, "No subconditions found, returning " + isAnd);
+                }
+                return isAnd;
+            }
+
+            if (tracer != null) {
+                tracer.trace("Using " + (isAnd ? "AND" : "OR") + " operator for " + conditions.size() + " subconditions", condition);
+            }
 
-        for (Condition sub : conditions) {
-            boolean eval = dispatcher.eval(sub, item, context);
-            if (!eval && isAnd) {
-                return false;
-            } else if (eval && !isAnd) {
-                return true;
+            for (Condition sub : conditions) {
+                boolean eval = dispatcher.eval(sub, item, context);
+                if (!eval && isAnd) {
+                    if (tracer != null) {
+                        tracer.endOperation(false, "AND condition failed on subcondition");
+                    }
+                    return false;
+                } else if (eval && !isAnd) {
+                    if (tracer != null) {
+                        tracer.endOperation(true, "OR condition succeeded on subcondition");
+                    }
+                    return true;
+                }
+            }
+
+            if (tracer != null) {
+                tracer.endOperation(isAnd, "All subconditions processed, returning " + isAnd);
+            }
+            return isAnd;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error during boolean condition evaluation: " + e.getMessage());
             }
+            throw e;
         }
-        return isAnd;
     }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/IdsConditionEvaluator.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/IdsConditionEvaluator.java
index 68d008bf4..37d82bb58 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/IdsConditionEvaluator.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/IdsConditionEvaluator.java
@@ -20,22 +20,51 @@ import org.apache.unomi.api.Item;
 import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluator;
 import org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluatorDispatcher;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 
 import java.util.Collection;
 import java.util.Map;
 
 public class IdsConditionEvaluator implements ConditionEvaluator {
+    private TracerService tracerService;
+
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     @Override
     public boolean eval(Condition condition, Item item, Map<String, Object> context, ConditionEvaluatorDispatcher dispatcher) {
-        Collection<String> ids = (Collection<String>) condition.getParameter("ids");
-        Boolean match = (Boolean) condition.getParameter("match");
-
-        if (ids == null || ids.isEmpty()) {
-            return false;
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("ids", "Evaluating IDs condition", condition);
         }
 
-        boolean contained = ids.contains(item.getItemId());
-        boolean matchValue = match == null || match;
-        return matchValue == contained;
+        try {
+            Collection<String> ids = (Collection<String>) condition.getParameter("ids");
+            Boolean match = (Boolean) condition.getParameter("match");
+
+            if (ids == null || ids.isEmpty()) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "No IDs found");
+                }
+                return false;
+            }
+
+            boolean contained = ids.contains(item.getItemId());
+            boolean matchValue = match == null || match;
+            boolean result = matchValue == contained;
+
+            if (tracer != null) {
+                tracer.endOperation(result, "IDs condition evaluation completed");
+            }
+            return result;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error during IDs condition evaluation: " + e.getMessage());
+            }
+            throw e;
+        }
     }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/NotConditionEvaluator.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/NotConditionEvaluator.java
index fff7dc799..a9494252b 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/NotConditionEvaluator.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/NotConditionEvaluator.java
@@ -21,6 +21,8 @@ import org.apache.unomi.api.Item;
 import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluator;
 import org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluatorDispatcher;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.RequestTracer;
 
 import java.util.Map;
 
@@ -28,13 +30,41 @@ import java.util.Map;
  * Evaluator for NOT condition.
  */
 public class NotConditionEvaluator implements ConditionEvaluator {
+    private TracerService tracerService;
+
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
 
     @Override
     public boolean eval(Condition condition, Item item, Map<String, Object> context, ConditionEvaluatorDispatcher dispatcher) {
-        Condition subCondition = (Condition) condition.getParameter("subCondition");
-        if (subCondition == null) {
-            return false;
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("not",
+                "Evaluating NOT condition", condition);
+        }
+
+        try {
+            Condition subCondition = (Condition) condition.getParameter("subCondition");
+            if (subCondition == null) {
+                if (tracer != null) {
+                    tracer.endOperation(false, "No subcondition found");
+                }
+                return false;
+            }
+
+            boolean result = !dispatcher.eval(subCondition, item, context);
+
+            if (tracer != null) {
+                tracer.endOperation(result, "NOT condition evaluation completed");
+            }
+            return result;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error during NOT condition evaluation: " + e.getMessage());
+            }
+            throw e;
         }
-        return !dispatcher.eval(subCondition, item, context);
     }
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java
index 18d8225a3..8229fe6c1 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java
@@ -32,6 +32,8 @@ import org.apache.unomi.persistence.spi.conditions.geo.DistanceUnit;
 import org.apache.unomi.plugins.baseplugin.conditions.accessors.HardcodedPropertyAccessor;
 import org.apache.unomi.scripting.ExpressionFilterFactory;
 import org.apache.unomi.scripting.SecureFilteringClassLoader;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -56,6 +58,8 @@ public class PropertyConditionEvaluator implements ConditionEvaluator {
     private static final HardcodedPropertyAccessorRegistry hardcodedPropertyAccessorRegistry = new HardcodedPropertyAccessorRegistry();
     private ExpressionFilterFactory expressionFilterFactory;
 
+    private TracerService tracerService;
+
     public void setUsePropertyConditionOptimizations(boolean usePropertyConditionOptimizations) {
         this.usePropertyConditionOptimizations = usePropertyConditionOptimizations;
     }
@@ -64,6 +68,12 @@ public class PropertyConditionEvaluator implements ConditionEvaluator {
         this.expressionFilterFactory = expressionFilterFactory;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
+    public void init() {
+    }
 
     private int compare(Object actualValue, String expectedValue, Object expectedValueDate, Object expectedValueInteger, Object expectedValueDateExpr, Object expectedValueDouble) {
         if (expectedValue == null && expectedValueDate == null && expectedValueInteger == null && getDate(expectedValueDateExpr) == null && expectedValueDouble == null) {
@@ -204,6 +214,14 @@ public class PropertyConditionEvaluator implements ConditionEvaluator {
 
     @Override
     public boolean eval(Condition condition, Item item, Map<String, Object> context, ConditionEvaluatorDispatcher dispatcher) {
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("property",
+                "Evaluating property condition", condition);
+        }
+
+        try {
             String op = (String) condition.getParameter("comparisonOperator");
             String name = (String) condition.getParameter("propertyName");
 
@@ -239,9 +257,32 @@ public class PropertyConditionEvaluator implements ConditionEvaluator {
                 actualValue = ConditionContextHelper.foldToASCII((String) actualValue);
             }
 
+            final Object finalActualValue = actualValue;
+            if (tracer != null) {
+                tracer.trace("Property value comparison: " + name + " " + op + " " + expectedValue,
+                    new HashMap<String, Object>() {{
+                        put("actualValue", finalActualValue);
+                        put("expectedValue", expectedValue);
+                        put("expectedValueInteger", expectedValueInteger);
+                        put("expectedValueDouble", expectedValueDouble);
+                        put("expectedValueDate", expectedValueDate);
+                        put("expectedValueDateExpr", expectedValueDateExpr);
+                    }});
+            }
+
             boolean result = isMatch(op, actualValue, expectedValue, expectedValueInteger, expectedValueDouble, expectedValueDate,
                     expectedValueDateExpr, condition);
+
+            if (tracer != null) {
+                tracer.endOperation(result, "Property condition evaluation completed");
+            }
             return result;
+        } catch (Exception e) {
+            if (tracer != null) {
+                tracer.endOperation(false, "Error during property condition evaluation: " + e.getMessage());
+            }
+            throw e;
+        }
     }
 
     protected boolean isMatch(String op, Object actualValue, String expectedValue, Object expectedValueInteger, Object expectedValueDouble,
diff --git a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 189131ff2..2b10af39a 100644
--- a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -37,6 +37,7 @@
             <cm:property name="maximumIdsQueryCount" value="5000"/>
             <cm:property name="aggregateQueryBucketSize" value="5000"/>
             <cm:property name="pastEventsDisablePartitions" value="false"/>
+            <cm:property name="conditionEvaluationTracing" value="false"/>
         </cm:default-properties>
     </cm:property-placeholder>
 
@@ -44,12 +45,13 @@
     <reference id="persistenceService" interface="org.apache.unomi.persistence.spi.PersistenceService"/>
     <reference id="profileService" interface="org.apache.unomi.api.services.ProfileService"/>
     <reference id="privacyService" interface="org.apache.unomi.api.services.PrivacyService"/>
-    <reference id="schedulerService" interface="org.apache.unomi.api.services.SchedulerService"/>
+    <reference id="schedulerService" interface="org.apache.unomi.api.services.SchedulerService" timeout="2000"/>
     <reference id="segmentService" interface="org.apache.unomi.api.services.SegmentService"/>
     <reference id="eventService" interface="org.apache.unomi.api.services.EventService"/>
     <reference id="configSharingService" interface="org.apache.unomi.api.services.ConfigSharingService"/>
     <reference id="scriptExecutor" interface="org.apache.unomi.scripting.ScriptExecutor"/>
     <reference id="topicService" interface="org.apache.unomi.api.services.TopicService"/>
+    <reference id="tracerService" interface="org.apache.unomi.tracing.api.TracerService"/>
     <reference id="executionContextManager" interface="org.apache.unomi.api.services.ExecutionContextManager">
         <reference-listener ref="mergeProfilesOnPropertyAction" bind-method="bindExecutionContextManager" unbind-method="unbindExecutionContextManager" />
     </reference>
@@ -62,25 +64,32 @@
         <service-properties>
             <entry key="conditionEvaluatorId" value="booleanConditionEvaluator"/>
         </service-properties>
-        <bean class="org.apache.unomi.plugins.baseplugin.conditions.BooleanConditionEvaluator"/>
+        <bean class="org.apache.unomi.plugins.baseplugin.conditions.BooleanConditionEvaluator">
+            <property name="tracerService" ref="tracerService" />
+        </bean>
     </service>
 
     <service interface="org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluator">
         <service-properties>
             <entry key="conditionEvaluatorId" value="idsConditionEvaluator"/>
         </service-properties>
-        <bean class="org.apache.unomi.plugins.baseplugin.conditions.IdsConditionEvaluator"/>
+        <bean class="org.apache.unomi.plugins.baseplugin.conditions.IdsConditionEvaluator">
+            <property name="tracerService" ref="tracerService" />
+        </bean>
     </service>
 
     <service interface="org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluator">
         <service-properties>
             <entry key="conditionEvaluatorId" value="notConditionEvaluator"/>
         </service-properties>
-        <bean class="org.apache.unomi.plugins.baseplugin.conditions.NotConditionEvaluator"/>
+        <bean class="org.apache.unomi.plugins.baseplugin.conditions.NotConditionEvaluator">
+            <property name="tracerService" ref="tracerService" />
+        </bean>
     </service>
 
-    <bean id="propertyConditionEvaluator" class="org.apache.unomi.plugins.baseplugin.conditions.PropertyConditionEvaluator">
+    <bean id="propertyConditionEvaluator" class="org.apache.unomi.plugins.baseplugin.conditions.PropertyConditionEvaluator" init-method="init">
         <property name="usePropertyConditionOptimizations" value="${base.usePropertyConditionOptimizations}"/>
+        <property name="tracerService" ref="tracerService" />
     </bean>
     <service interface="org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluator" ref="propertyConditionEvaluator">
         <service-properties>
@@ -102,6 +111,7 @@
         <bean class="org.apache.unomi.plugins.baseplugin.conditions.GeoLocationByPointSessionConditionEvaluator"/>
     </service>
 
+
     <service interface="org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluator">
         <service-properties>
             <entry key="conditionEvaluatorId" value="nestedConditionEvaluator"/>
@@ -120,6 +130,7 @@
         </service-properties>
         <bean class="org.apache.unomi.plugins.baseplugin.actions.AllEventToProfilePropertiesAction">
             <property name="profileService" ref="profileService"/>
+            <property name="tracerService" ref="tracerService"/>
         </bean>
     </service>
 
@@ -129,6 +140,7 @@
         </service-properties>
         <bean class="org.apache.unomi.plugins.baseplugin.actions.CopyPropertiesAction">
             <property name="profileService" ref="profileService"/>
+            <property name="tracerService" ref="tracerService"/>
         </bean>
     </service>
 
@@ -136,7 +148,9 @@
         <service-properties>
             <entry key="actionExecutorId" value="eventToProfileProperty"/>
         </service-properties>
-        <bean class="org.apache.unomi.plugins.baseplugin.actions.EventToProfilePropertyAction"/>
+        <bean class="org.apache.unomi.plugins.baseplugin.actions.EventToProfilePropertyAction">
+            <property name="tracerService" ref="tracerService"/>
+        </bean>
     </service>
 
     <service interface="org.apache.unomi.api.actions.ActionExecutor">
@@ -145,6 +159,7 @@
         </service-properties>
         <bean class="org.apache.unomi.plugins.baseplugin.actions.SetPropertyAction">
             <property name="eventService" ref="eventService"/>
+            <property name="tracerService" ref="tracerService"/>
             <property name="useEventToUpdateProfile" value="${base.useEventToUpdateProfile}"/>
         </bean>
     </service>
@@ -153,7 +168,9 @@
         <service-properties>
             <entry key="actionExecutorId" value="evaluateVisitProperties"/>
         </service-properties>
-        <bean class="org.apache.unomi.plugins.baseplugin.actions.EvaluateVisitPropertiesAction"/>
+        <bean class="org.apache.unomi.plugins.baseplugin.actions.EvaluateVisitPropertiesAction">
+            <property name="tracerService" ref="tracerService"/>
+        </bean>
     </service>
 
     <service interface="org.apache.unomi.api.actions.ActionExecutor">
@@ -163,6 +180,7 @@
         <bean class="org.apache.unomi.plugins.baseplugin.actions.UpdatePropertiesAction">
             <property name="profileService" ref="profileService"/>
             <property name="eventService" ref="eventService"/>
+            <property name="tracerService" ref="tracerService"/>
         </bean>
     </service>
 
@@ -171,6 +189,7 @@
             <entry key="actionExecutorId" value="modifyConsent"/>
         </service-properties>
         <bean class="org.apache.unomi.plugins.baseplugin.actions.ModifyConsentAction">
+            <property name="tracerService" ref="tracerService"/>
         </bean>
     </service>
 
@@ -180,6 +199,8 @@
         </service-properties>
         <bean class="org.apache.unomi.plugins.baseplugin.actions.EvaluateProfileSegmentsAction">
             <property name="segmentService" ref="segmentService"/>
+            <property name="profileService" ref="profileService"/>
+            <property name="tracerService" ref="tracerService"/>
         </bean>
     </service>
 
@@ -187,7 +208,10 @@
         <service-properties>
             <entry key="actionExecutorId" value="evaluateProfileAge"/>
         </service-properties>
-        <bean class="org.apache.unomi.plugins.baseplugin.actions.EvaluateProfileAgeAction"/>
+        <bean class="org.apache.unomi.plugins.baseplugin.actions.EvaluateProfileAgeAction">
+            <property name="profileService" ref="profileService"/>
+            <property name="tracerService" ref="tracerService"/>
+        </bean>
     </service>
 
     <service interface="org.apache.unomi.api.actions.ActionExecutor">
@@ -197,6 +221,7 @@
         <bean class="org.apache.unomi.plugins.baseplugin.actions.SetEventOccurenceCountAction">
             <property name="definitionsService" ref="definitionsService"/>
             <property name="persistenceService" ref="persistenceService"/>
+            <property name="tracerService" ref="tracerService"/>
         </bean>
     </service>
 
@@ -206,6 +231,7 @@
         </service-properties>
         <bean class="org.apache.unomi.plugins.baseplugin.actions.SendEventAction">
             <property name="eventService" ref="eventService"/>
+            <property name="tracerService" ref="tracerService"/>
         </bean>
     </service>
 
@@ -216,6 +242,7 @@
         <property name="definitionsService" ref="definitionsService"/>
         <property name="privacyService" ref="privacyService"/>
         <property name="schedulerService" ref="schedulerService"/>
+        <property name="tracerService" ref="tracerService"/>
         <property name="maxProfilesInOneMerge" value="${base.maxProfilesInOneMerge}"/>
     </bean>
 
@@ -229,6 +256,8 @@
         <service-properties>
             <entry key="actionExecutorId" value="incrementProperty"/>
         </service-properties>
-        <bean class="org.apache.unomi.plugins.baseplugin.actions.IncrementPropertyAction"/>
+        <bean class="org.apache.unomi.plugins.baseplugin.actions.IncrementPropertyAction">
+            <property name="tracerService" ref="tracerService"/>
+        </bean>
     </service>
 </blueprint>
diff --git a/pom.xml b/pom.xml
index d8261f819..0f98cbbfd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -410,6 +410,7 @@
         <module>persistence-opensearch</module>
         <module>services-common</module>
         <module>services</module>
+        <module>tracing</module>
         <!-- plugins -->
         <module>plugins</module>
         <module>plugins/baseplugin</module>
@@ -430,6 +431,7 @@
         <module>extensions/privacy-extension</module>
         <module>extensions/web-tracker</module>
         <module>tools</module>
+        <module>web-servlets</module>
         <module>wab</module>
         <module>rest</module>
         <module>manual</module>
diff --git a/rest/pom.xml b/rest/pom.xml
index 9e67c1f29..5380ef5b0 100644
--- a/rest/pom.xml
+++ b/rest/pom.xml
@@ -47,6 +47,13 @@
             <artifactId>unomi-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.unomi</groupId>
             <artifactId>unomi-json-schema-services</artifactId>
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
index 0d604271c..f5b70de9c 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
@@ -29,9 +29,11 @@ import org.apache.unomi.api.services.PrivacyService;
 import org.apache.unomi.api.services.ProfileService;
 import org.apache.unomi.api.services.RulesService;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.apache.unomi.persistence.spi.conditions.ConditionContextHelper;
 import org.apache.unomi.rest.exception.InvalidRequestException;
 import org.apache.unomi.rest.service.RestServiceUtils;
 import org.apache.unomi.schema.api.SchemaService;
+import org.apache.unomi.tracing.api.TracerService;
 import org.apache.unomi.utils.EventsRequestContext;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
@@ -78,6 +80,8 @@ public class ContextJsonEndpoint {
     private SchemaService schemaService;
     @Reference
     private ProfileService profileService;
+    @Reference
+    private TracerService tracerService;
 
     @OPTIONS
     @Path("/context.js")
@@ -100,8 +104,9 @@ public class ContextJsonEndpoint {
             @QueryParam("timestamp") Long timestampAsLong,
             @QueryParam("invalidateProfile") boolean invalidateProfile,
             @QueryParam("invalidateSession") boolean invalidateSession,
+            @QueryParam("explain") boolean explain,
             @Context SecurityContext securityContext) throws JsonProcessingException {
-        return contextJSAsGet(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile, invalidateSession, securityContext);
+        return contextJSAsGet(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile, invalidateSession, explain, securityContext);
     }
 
     @GET
@@ -113,9 +118,10 @@ public class ContextJsonEndpoint {
             @QueryParam("timestamp") Long timestampAsLong,
             @QueryParam("invalidateProfile") boolean invalidateProfile,
             @QueryParam("invalidateSession") boolean invalidateSession,
+            @QueryParam("explain") boolean explain,
             @Context SecurityContext securityContext) throws JsonProcessingException {
         ContextResponse contextResponse = contextJSONAsPost(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile,
-                invalidateSession, securityContext);
+                invalidateSession, explain, securityContext);
         String contextAsJSONString = CustomObjectMapper.getObjectMapper().writeValueAsString(contextResponse);
         StringBuilder responseAsString = new StringBuilder();
         responseAsString.append("window.digitalData = window.digitalData || {};\n").append("var cxs = ").append(contextAsJSONString)
@@ -132,8 +138,9 @@ public class ContextJsonEndpoint {
             @QueryParam("timestamp") Long timestampAsLong,
             @QueryParam("invalidateProfile") boolean invalidateProfile,
             @QueryParam("invalidateSession") boolean invalidateSession,
+            @QueryParam("explain") boolean explain,
             @Context SecurityContext securityContext) {
-        return contextJSONAsPost(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile, invalidateSession, securityContext);
+        return contextJSONAsPost(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile, invalidateSession, explain, securityContext);
     }
 
     @POST
@@ -145,14 +152,29 @@ public class ContextJsonEndpoint {
                                              @QueryParam("timestamp") Long timestampAsLong,
                                              @QueryParam("invalidateProfile") boolean invalidateProfile,
                                              @QueryParam("invalidateSession") boolean invalidateSession,
+                                             @QueryParam("explain") boolean explain,
                                              @Context SecurityContext securityContext) {
 
+        // Check if tracing is requested and user has required role
+        if (explain && !(securityContext.isUserInRole(UnomiRoles.ADMINISTRATOR) ||
+                        securityContext.isUserInRole(UnomiRoles.TENANT_ADMINISTRATOR))) {
+            throw new ForbiddenException("Insufficient privileges to access tracing information");
+        }
+
+        if (explain) {
+            tracerService.enableTracing();
+            tracerService.getCurrentTracer().startOperation("context-request", "Processing context request", contextRequest);
+        }
+
         try {
             // Schema validation
             ObjectNode paramsAsJson = JsonNodeFactory.instance.objectNode();
             paramsAsJson.put("personaId", personaId);
             paramsAsJson.put("sessionId", sessionId);
             if (!schemaService.isValid(paramsAsJson.toString(), "https://unomi.apache.org/schemas/json/rest/requestIds/1-0-0")) {
+                if (explain) {
+                    tracerService.getCurrentTracer().endOperation(false, "Schema validation failed");
+                }
                 throw new InvalidRequestException("Invalid parameter", "Invalid received data");
             }
 
@@ -191,9 +213,17 @@ public class ContextJsonEndpoint {
                 contextResponse.setSessionId(sessionId);
             }
 
+            // Add tracing information if requested
+            if (explain) {
+                contextResponse.setRequestTracing(tracerService.getTraceNode());
+                tracerService.getCurrentTracer().endOperation(null, "Context request processed successfully");
+            }
+
             return contextResponse;
         } finally {
-            // @todo placeholder for tracing integration
+            if (explain) {
+                tracerService.disableTracing();
+            }
         }
     }
 
@@ -356,7 +386,7 @@ public class ContextJsonEndpoint {
     private Object sanitizeValue(Object value) {
         if (value instanceof String) {
             String stringValue = (String) value;
-            if (stringValue.startsWith("script::") || stringValue.startsWith("parameter::")) {
+            if (ConditionContextHelper.isParameterReference(value)) {
                 LOGGER.warn("Scripting detected in context request, filtering out. See debug level for more information");
                 LOGGER.debug("Scripting detected in context request with value {}, filtering out...", value);
                 return null;
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
index bd28f4e2b..a5954ba0b 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
@@ -25,6 +25,7 @@ import org.apache.unomi.api.security.UnomiRoles;
 import org.apache.unomi.rest.exception.InvalidRequestException;
 import org.apache.unomi.rest.models.EventCollectorResponse;
 import org.apache.unomi.rest.service.RestServiceUtils;
+import org.apache.unomi.tracing.api.TracerService;
 import org.apache.unomi.utils.EventsRequestContext;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
@@ -50,6 +51,9 @@ public class EventsCollectorEndpoint {
     @Reference
     private RestServiceUtils restServiceUtils;
 
+    @Reference
+    private TracerService tracerService;
+
     @Context
     HttpServletRequest request;
     @Context
@@ -65,23 +69,36 @@ public class EventsCollectorEndpoint {
     @Path("/eventcollector")
     public EventCollectorResponse collectAsGet(@QueryParam("payload") EventsCollectorRequest eventsCollectorRequest,
             @QueryParam("timestamp") Long timestampAsString,
+            @QueryParam("explain") boolean explain,
             @Context SecurityContext securityContext) {
-        return doEvent(eventsCollectorRequest, timestampAsString, securityContext);
+        return doEvent(eventsCollectorRequest, timestampAsString, explain, securityContext);
     }
 
     @POST
     @Path("/eventcollector")
     public EventCollectorResponse collectAsPost(EventsCollectorRequest eventsCollectorRequest,
             @QueryParam("timestamp") Long timestampAsLong,
+            @QueryParam("explain") boolean explain,
             @Context SecurityContext securityContext) {
-        return doEvent(eventsCollectorRequest, timestampAsLong, securityContext);
+        return doEvent(eventsCollectorRequest, timestampAsLong, explain, securityContext);
     }
 
-    private EventCollectorResponse doEvent(EventsCollectorRequest eventsCollectorRequest, Long timestampAsLong, SecurityContext securityContext) {
+    private EventCollectorResponse doEvent(EventsCollectorRequest eventsCollectorRequest, Long timestampAsLong, boolean explain, SecurityContext securityContext) {
         if (eventsCollectorRequest == null) {
             throw new InvalidRequestException("events collector cannot be empty", "Invalid received data");
         }
 
+        // Check if tracing is requested and user has required role
+        if (explain && !(securityContext.isUserInRole(UnomiRoles.ADMINISTRATOR) ||
+                        securityContext.isUserInRole(UnomiRoles.TENANT_ADMINISTRATOR))) {
+            throw new ForbiddenException("Insufficient privileges to access tracing information");
+        }
+
+        if (explain) {
+            tracerService.enableTracing();
+            tracerService.getCurrentTracer().startOperation("event-collection", "Processing event collection request", eventsCollectorRequest);
+        }
+
         try {
             Date timestamp = new Date();
             if (timestampAsLong != null) {
@@ -122,9 +139,17 @@ public class EventsCollectorEndpoint {
 
             EventCollectorResponse response = new EventCollectorResponse(eventsRequestContext.getChanges());
 
+            // Add tracing information if requested
+            if (explain) {
+                response.setRequestTracing(tracerService.getTraceNode());
+                tracerService.getCurrentTracer().endOperation(null, "Event collection request processed successfully");
+            }
+
             return response;
         } finally {
-            // @todo placeholder for tracing integration
+            if (explain) {
+                tracerService.disableTracing();
+            }
         }
     }
 }
diff --git a/rest/src/main/java/org/apache/unomi/rest/models/EventCollectorResponse.java b/rest/src/main/java/org/apache/unomi/rest/models/EventCollectorResponse.java
index bca06297b..7b8ba7119 100644
--- a/rest/src/main/java/org/apache/unomi/rest/models/EventCollectorResponse.java
+++ b/rest/src/main/java/org/apache/unomi/rest/models/EventCollectorResponse.java
@@ -17,12 +17,13 @@
 
 package org.apache.unomi.rest.models;
 
+import org.apache.unomi.tracing.api.TraceNode;
 import java.io.Serializable;
 
 /**
  * Response model for the event collector endpoint.
  * This class provides information about the result of event processing, including
- * which entities were updated.
+ * which entities were updated and tracing information.
  */
 public class EventCollectorResponse implements Serializable {
     /**
@@ -41,9 +42,20 @@ public class EventCollectorResponse implements Serializable {
      */
     private int updated;
 
+    /**
+     * Contains tracing information about the request processing.
+     * This can be used for debugging and monitoring purposes.
+     */
+    private TraceNode requestTracing;
+
     public EventCollectorResponse() {
     }
 
+    /**
+     * Creates a new EventCollectorResponse with the specified update flags.
+     *
+     * @param updated The bitwise combination of EventService flags indicating what was updated
+     */
     public EventCollectorResponse(int updated) {
         this.updated = updated;
     }
@@ -65,4 +77,22 @@ public class EventCollectorResponse implements Serializable {
     public int getUpdated() {
         return updated;
     }
+
+    /**
+     * Gets the tracing information for the request.
+     *
+     * @return The TraceNode containing request tracing information
+     */
+    public TraceNode getRequestTracing() {
+        return requestTracing;
+    }
+
+    /**
+     * Sets the tracing information for the request.
+     *
+     * @param requestTracing The TraceNode containing request tracing information
+     */
+    public void setRequestTracing(TraceNode requestTracing) {
+        this.requestTracing = requestTracing;
+    }
 }
diff --git a/services/pom.xml b/services/pom.xml
index 280d8df57..eb800cbda 100644
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -46,6 +46,19 @@
             <artifactId>unomi-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-tracing-impl</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.unomi</groupId>
             <artifactId>unomi-metrics</artifactId>
diff --git a/services/src/main/java/org/apache/unomi/services/actions/impl/ActionExecutorDispatcherImpl.java b/services/src/main/java/org/apache/unomi/services/actions/impl/ActionExecutorDispatcherImpl.java
index ec4fa5535..91683c61d 100644
--- a/services/src/main/java/org/apache/unomi/services/actions/impl/ActionExecutorDispatcherImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/actions/impl/ActionExecutorDispatcherImpl.java
@@ -23,11 +23,14 @@ import org.apache.unomi.api.actions.ActionDispatcher;
 import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.TypeResolutionService;
 import org.apache.unomi.api.utils.ParserHelper;
 import org.apache.unomi.metrics.MetricAdapter;
 import org.apache.unomi.metrics.MetricsService;
 import org.apache.unomi.scripting.ScriptExecutor;
 import org.apache.unomi.services.actions.ActionExecutorDispatcher;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.slf4j.Logger;
@@ -46,6 +49,7 @@ public class ActionExecutorDispatcherImpl implements ActionExecutorDispatcher {
     private final Map<String, ActionDispatcher> actionDispatchers = new ConcurrentHashMap<>();
     private BundleContext bundleContext;
     private ScriptExecutor scriptExecutor;
+    private TracerService tracerService;
     private DefinitionsService definitionsService;
 
     public void setMetricsService(MetricsService metricsService) {
@@ -60,10 +64,22 @@ public class ActionExecutorDispatcherImpl implements ActionExecutorDispatcher {
         this.scriptExecutor = scriptExecutor;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     public void setDefinitionsService(DefinitionsService definitionsService) {
         this.definitionsService = definitionsService;
     }
 
+    /**
+     * Helper method to get TypeResolutionService from DefinitionsService.
+     * Returns null if DefinitionsService is not available or doesn't have TypeResolutionService.
+     */
+    private TypeResolutionService getTypeResolutionService() {
+        return definitionsService != null ? definitionsService.getTypeResolutionService() : null;
+    }
+
     public ActionExecutorDispatcherImpl() {
         valueExtractors.putAll(ParserHelper.DEFAULT_VALUE_EXTRACTORS);
         valueExtractors.put("script", new ParserHelper.ValueExtractor() {
@@ -91,43 +107,98 @@ public class ActionExecutorDispatcherImpl implements ActionExecutorDispatcher {
         if (action == null) {
             throw new UnsupportedOperationException("Null action passed for event : " + event);
         }
-        // Defensively resolve the action type if missing (e.g. deserialized actions only have actionTypeId).
-        // This matches the behaviour from unomi-3-dev.
-        if (action.getActionType() == null && definitionsService != null) {
-            ParserHelper.resolveActionType(definitionsService, action);
-        }
+
         String actionKey = null;
-        if (action.getActionType() != null) {
-            actionKey = action.getActionType().getActionExecutor();
-        }
-        if (actionKey == null) {
-            LOGGER.warn("Action type or executor is null for actionTypeId={}, action won't execute", action.getActionTypeId());
-            return EventService.NO_CHANGE;
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.startOperation("action-execution",
+                "Executing action: " + action.getActionTypeId(), action);
         }
 
-        int colonPos = actionKey.indexOf(":");
-        if (colonPos > 0) {
-            String actionPrefix = actionKey.substring(0, colonPos);
-            String actionName = actionKey.substring(colonPos + 1);
-            ActionDispatcher actionDispatcher = actionDispatchers.get(actionPrefix);
-            if (actionDispatcher == null) {
-                LOGGER.warn("Couldn't find any action dispatcher for prefix '{}', action {} won't execute !", actionPrefix, actionKey);
+        try {
+            // Resolve action type if needed (actions deserialized from JSON may only have actionTypeId)
+            if (action.getActionType() == null) {
+                TypeResolutionService typeResolutionService = getTypeResolutionService();
+                if (typeResolutionService != null) {
+                    typeResolutionService.resolveActionType(action);
+                } else {
+                    LOGGER.debug("TypeResolutionService not available, cannot resolve action type for actionTypeId={}", action.getActionTypeId());
+                }
+                if (action.getActionType() == null) {
+                    LOGGER.warn("Action type is null for actionTypeId={}, action won't execute", action.getActionTypeId());
+                    if (tracer != null) {
+                        tracer.endOperation(EventService.NO_CHANGE, "Action type not resolved");
+                    }
+                    return EventService.NO_CHANGE;
+                }
             }
-            return actionDispatcher.execute(action, event, actionName);
-        } else if (executors.containsKey(actionKey)) {
-            ActionExecutor actionExecutor = executors.get(actionKey);
-            try {
-                return new MetricAdapter<Integer>(metricsService, this.getClass().getName() + ".action." + actionKey) {
-                    @Override
-                    public Integer execute(Object... args) throws Exception {
-                        return actionExecutor.execute(getContextualAction(action, event), event);
+
+            actionKey = action.getActionType().getActionExecutor();
+            if (actionKey == null) {
+                LOGGER.warn("No action executor configured for actionTypeId={}, action won't execute", action.getActionTypeId());
+                if (tracer != null) {
+                    tracer.endOperation(EventService.NO_CHANGE, "No action executor configured for action type");
+                }
+                return EventService.NO_CHANGE;
+            }
+
+            int colonPos = actionKey.indexOf(":");
+            if (colonPos > 0) {
+                String actionPrefix = actionKey.substring(0, colonPos);
+                String actionName = actionKey.substring(colonPos + 1);
+                ActionDispatcher actionDispatcher = actionDispatchers.get(actionPrefix);
+                if (actionDispatcher == null) {
+                    LOGGER.warn("Couldn't find any action dispatcher for prefix '{}', action {} won't execute !", actionPrefix, actionKey);
+                    if (tracer != null) {
+                        tracer.endOperation(EventService.NO_CHANGE,
+                            "No action dispatcher found for prefix: " + actionPrefix);
                     }
-                }.runWithTimer();
-            } catch (Exception e) {
-                LOGGER.error("Error executing action with key={}", actionKey, e);
+                    return EventService.NO_CHANGE;
+                }
+                int result = actionDispatcher.execute(action, event, actionName);
+                if (tracer != null) {
+                    tracer.endOperation(result, "Action execution completed");
+                }
+                return result;
+            } else if (executors.containsKey(actionKey)) {
+                ActionExecutor actionExecutor = executors.get(actionKey);
+                try {
+                    int result = new MetricAdapter<Integer>(metricsService, this.getClass().getName() + ".action." + actionKey) {
+                        @Override
+                        public Integer execute(Object... args) throws Exception {
+                            return actionExecutor.execute(getContextualAction(action, event), event);
+                        }
+                    }.runWithTimer();
+
+                    if (tracer != null) {
+                        tracer.endOperation(result, "Action execution completed");
+                    }
+                    return result;
+                } catch (Exception e) {
+                    LOGGER.error("Error executing action with key={}", actionKey, e);
+                    if (tracer != null) {
+                        tracer.endOperation(EventService.NO_CHANGE,
+                            "Error during action execution: " + e.getMessage());
+                    }
+                    return EventService.NO_CHANGE;
+                }
+            } else {
+                LOGGER.error("Couldn't find executor with key={}", actionKey);
+                if (tracer != null) {
+                    tracer.endOperation(EventService.NO_CHANGE,
+                        "No executor found for action type: " + actionKey);
+                }
+                return EventService.NO_CHANGE;
             }
+        } catch (Exception e) {
+            LOGGER.error("Error during action execution", e);
+            if (tracer != null) {
+                tracer.endOperation(EventService.NO_CHANGE,
+                    "Error during action execution: " + e.getMessage());
+            }
+            return EventService.NO_CHANGE;
         }
-        return EventService.NO_CHANGE;
     }
 
     public void bindExecutor(ServiceReference<ActionExecutor> actionExecutorServiceReference) {
diff --git a/services/src/main/java/org/apache/unomi/services/impl/definitions/DefinitionsServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/definitions/DefinitionsServiceImpl.java
index c8584ef27..db2cbd393 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/definitions/DefinitionsServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/definitions/DefinitionsServiceImpl.java
@@ -33,6 +33,8 @@ import org.apache.unomi.api.utils.ConditionBuilder;
 import org.apache.unomi.services.common.cache.AbstractMultiTypeCachingService;
 import org.apache.unomi.services.impl.TypeResolutionServiceImpl;
 import org.apache.unomi.services.impl.validation.ConditionValidationServiceImpl;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.SynchronousBundleListener;
@@ -68,6 +70,7 @@ public class DefinitionsServiceImpl extends AbstractMultiTypeCachingService impl
     private static final long TASK_TIMEOUT_MS = 60000; // 1 minute timeout for tasks
 
     private ConditionValidationServiceImpl conditionValidationService;
+    private TracerService tracerService;
     private EventAdmin eventAdmin;
     private TypeResolutionServiceImpl typeResolutionService;
 
@@ -87,6 +90,10 @@ public class DefinitionsServiceImpl extends AbstractMultiTypeCachingService impl
         super.setCacheService(cacheService);
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     public void setEventAdmin(EventAdmin eventAdmin) {
         this.eventAdmin = eventAdmin;
     }
@@ -573,9 +580,25 @@ public class DefinitionsServiceImpl extends AbstractMultiTypeCachingService impl
         // Delegate to TypeResolutionService for resolution
         boolean resolved = typeResolutionService.resolveConditionType(rootCondition, "condition type " + rootCondition.getConditionTypeId());
         if (resolved) {
+            // Start validation operation in tracer
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.startOperation("condition-validation", "Validating condition: " + rootCondition.getConditionTypeId(), rootCondition);
+                }
+            }
+
             // Validate the condition after resolving its type (validation service will auto-resolve if needed)
             List<ValidationError> validationErrors = conditionValidationService.validate(rootCondition);
 
+            // Add validation info to tracer
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.addValidationInfo(validationErrors, "condition-validation");
+                    tracer.endOperation(!validationErrors.isEmpty(), String.format("Validation completed with %d errors", validationErrors.size()));
+                }
+            }
 
             // Separate errors and warnings
             List<ValidationError> errors = validationErrors.stream()
diff --git a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
index 94da42cda..3442a59ac 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
@@ -25,11 +25,14 @@ import org.apache.unomi.api.query.Query;
 import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.EventListenerService;
 import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.TypeResolutionService;
 import org.apache.unomi.api.tenants.Tenant;
 import org.apache.unomi.api.tenants.TenantService;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
 import org.apache.unomi.services.common.security.IPValidationUtils;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.slf4j.Logger;
@@ -106,6 +109,8 @@ public class EventServiceImpl implements EventService {
 
     private Set<String> restrictedEventTypeIds = new LinkedHashSet<String>();
 
+    private TracerService tracerService;
+
     public void setPredefinedEventTypeIds(Set<String> predefinedEventTypeIds) {
         this.predefinedEventTypeIds = predefinedEventTypeIds;
     }
@@ -122,6 +127,14 @@ public class EventServiceImpl implements EventService {
         this.definitionsService = definitionsService;
     }
 
+    /**
+     * Helper method to get TypeResolutionService from DefinitionsService.
+     * Returns null if DefinitionsService is not available or doesn't have TypeResolutionService.
+     */
+    private TypeResolutionService getTypeResolutionService() {
+        return definitionsService != null ? definitionsService.getTypeResolutionService() : null;
+    }
+
     public void setTenantService(TenantService tenantService) {
         this.tenantService = tenantService;
     }
@@ -130,6 +143,10 @@ public class EventServiceImpl implements EventService {
         this.bundleContext = bundleContext;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     @Override
     public boolean isEventAllowedForTenant(Event event, String tenantId, String sourceIP) {
         if (event == null || tenantId == null) {
@@ -191,6 +208,12 @@ public class EventServiceImpl implements EventService {
     }
 
     public int send(Event event) {
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+            tracer.trace("Sending event: " + event.getEventType(), event.getItemId());
+        }
+
         // Get current event stack from ThreadLocal
         List<EventInfo> eventStack = EVENT_STACK.get();
 
@@ -200,6 +223,9 @@ public class EventServiceImpl implements EventService {
             String chainKey = recursionChainKey(eventStack);
             if (LOGGED_RECURSION_CHAINS.add(chainKey)) {
                 EventInfo currentEventInfo = new EventInfo(event);
+                if (tracer != null) {
+                    tracer.trace("Max recursion depth reached for event: " + event.getEventType(), event.getItemId());
+                }
                 StringBuilder errorMsg = new StringBuilder("Max recursion depth reached (depth: ").append(eventStack.size() + 1)
                         .append(", max: ").append(MAX_RECURSION_DEPTH + 1)
                         .append("). Current event: ").append(currentEventInfo);
@@ -231,11 +257,22 @@ public class EventServiceImpl implements EventService {
     }
 
     private int sendInternal(Event event) {
+        RequestTracer tracer = null;
+        if (tracerService != null && tracerService.isTracingEnabled()) {
+            tracer = tracerService.getCurrentTracer();
+        }
+
         boolean saveSucceeded = true;
         if (event.isPersistent()) {
             try {
+                if (tracer != null) {
+                    tracer.trace("Saving persistent event: " + event.getEventType(), event.getItemId());
+                }
                 saveSucceeded = persistenceService.save(event, null, true);
             } catch (Throwable t) {
+                if (tracer != null) {
+                    tracer.trace("Failed to save event: " + event.getEventType() + ", error: " + t.getMessage(), event.getItemId());
+                }
                 LOGGER.error("Failed to save event: ", t);
                 return NO_CHANGE;
             }
@@ -248,20 +285,35 @@ public class EventServiceImpl implements EventService {
             final Session session = event.getSession();
             if (event.isPersistent() && session != null) {
                 session.setLastEventDate(event.getTimeStamp());
+                if (tracer != null) {
+                    tracer.trace("Updated session last event date", session.getItemId());
+                }
             }
 
             if (event.getProfile() != null) {
+                if (tracer != null) {
+                    tracer.trace("Processing event for profile: " + event.getProfile().getItemId(), event.getItemId());
+                }
                 for (EventListenerService eventListenerService : eventListeners) {
                     if (eventListenerService.canHandle(event)) {
+                        if (tracer != null) {
+                            tracer.trace("Event listener service handling event: " + eventListenerService.getClass().getSimpleName(), event.getItemId());
+                        }
                         changes |= eventListenerService.onEvent(event);
                     }
                 }
                 // At the end of the processing event execute the post executor actions
                 for (ActionPostExecutor actionPostExecutor : event.getActionPostExecutors()) {
+                    if (tracer != null) {
+                        tracer.trace("Executing post action executor", event.getItemId());
+                    }
                     changes |= actionPostExecutor.execute() ? changes : NO_CHANGE;
                 }
 
                 if ((changes & PROFILE_UPDATED) == PROFILE_UPDATED) {
+                    if (tracer != null) {
+                        tracer.trace("Profile updated, sending profileUpdated event", event.getItemId());
+                    }
                     Event profileUpdated = new Event("profileUpdated", session, event.getProfile(), event.getScope(), event.getSource(), event.getProfile(), event.getTimeStamp());
                     profileUpdated.setPersistent(false);
                     profileUpdated.getAttributes().putAll(event.getAttributes());
@@ -358,6 +410,10 @@ public class EventServiceImpl implements EventService {
 
     @Override
     public PartialList<Event> searchEvents(Condition condition, int offset, int size) {
+        TypeResolutionService typeResolutionService = getTypeResolutionService();
+        if (typeResolutionService != null) {
+            typeResolutionService.resolveConditionType(condition, "event search");
+        }
         // Note: Effective condition resolution happens in the query builder dispatcher or condition evaluator dispatcher
         // For in-memory persistence, the condition evaluator dispatcher will resolve the effective condition
         return persistenceService.query(condition, "timeStamp", Event.class, offset, size);
@@ -403,6 +459,7 @@ public class EventServiceImpl implements EventService {
             return persistenceService.continueScrollQuery(Event.class, query.getScrollIdentifier(), query.getScrollTimeValidity());
         }
         if (query.getCondition() != null) {
+            definitionsService.getConditionValidationService().validate(query.getCondition());
             if (StringUtils.isNotBlank(query.getText())) {
                 return persistenceService.queryFullText(query.getText(), query.getCondition(), query.getSortby(), Event.class, query.getOffset(), query.getLimit());
             } else {
diff --git a/services/src/main/java/org/apache/unomi/services/impl/goals/GoalsServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/goals/GoalsServiceImpl.java
index 7711fb6b2..6260aee0b 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/goals/GoalsServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/goals/GoalsServiceImpl.java
@@ -31,18 +31,20 @@ import org.apache.unomi.api.goals.GoalReport;
 import org.apache.unomi.api.query.AggregateQuery;
 import org.apache.unomi.api.query.Query;
 import org.apache.unomi.api.rules.Rule;
+import org.apache.unomi.api.services.ConditionValidationService.ValidationError;
+import org.apache.unomi.api.services.ConditionValidationService.ValidationErrorType;
 import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.GoalsService;
 import org.apache.unomi.api.services.RulesService;
+import org.apache.unomi.api.services.TypeResolutionService;
 import org.apache.unomi.api.services.cache.CacheableTypeConfig;
-import org.apache.unomi.api.utils.ParserHelper;
 import org.apache.unomi.persistence.spi.aggregate.*;
 import org.apache.unomi.services.common.cache.AbstractMultiTypeCachingService;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
-import java.net.URL;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -54,6 +56,8 @@ public class GoalsServiceImpl extends AbstractMultiTypeCachingService implements
 
     private RulesService rulesService;
 
+    private TracerService tracerService;
+
     private long goalRefreshInterval = 5000; // 5 seconds
     private long campaignRefreshInterval = 5000; // 5 seconds
 
@@ -65,6 +69,18 @@ public class GoalsServiceImpl extends AbstractMultiTypeCachingService implements
         this.rulesService = rulesService;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
+    /**
+     * Helper method to get TypeResolutionService from DefinitionsService.
+     * Returns null if DefinitionsService is not available or doesn't have TypeResolutionService.
+     */
+    private TypeResolutionService getTypeResolutionService() {
+        return definitionsService != null ? definitionsService.getTypeResolutionService() : null;
+    }
+
     public void setGoalRefreshInterval(long goalRefreshInterval) {
         this.goalRefreshInterval = goalRefreshInterval;
     }
@@ -152,13 +168,7 @@ public class GoalsServiceImpl extends AbstractMultiTypeCachingService implements
 
     public Set<Metadata> getGoalMetadatas(Query query) {
         if (query.getCondition() != null) {
-            ParserHelper.resolveConditionType(definitionsService, query.getCondition(), "goal metadata query");
-            if (definitionsService.getConditionValidationService() != null) {
-                List<?> validationErrors = definitionsService.getConditionValidationService().validate(query.getCondition());
-                if (!validationErrors.isEmpty()) {
-                    LOGGER.warn("Goal query condition has {} validation issue(s)", validationErrors.size());
-                }
-            }
+            definitionsService.getConditionValidationService().validate(query.getCondition());
         }
         Set<Metadata> descriptions = new LinkedHashSet<>();
 
@@ -188,8 +198,119 @@ public class GoalsServiceImpl extends AbstractMultiTypeCachingService implements
             LOGGER.warn("Trying to save null goal, aborting...");
             return;
         }
-        ParserHelper.resolveConditionType(definitionsService, goal.getStartEvent(), "goal "+goal.getItemId()+" start event");
-        ParserHelper.resolveConditionType(definitionsService, goal.getTargetEvent(), "goal "+goal.getItemId()+" start event");
+        boolean isValid = true;
+        if (goal.getStartEvent() != null) {
+            // Resolve condition type (skips parameter resolution - happens on-demand in query builders/evaluators)
+            TypeResolutionService typeResolutionService = getTypeResolutionService();
+            boolean startValid = typeResolutionService != null && typeResolutionService.resolveCondition("goals", goal, goal.getStartEvent(), "goal "+goal.getItemId()+" start event");
+            isValid = isValid && startValid;
+            // Start validation operation in tracer for start event
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.startOperation("goal-start-event-validation", "Validating goal start event: " + goal.getItemId(), goal.getStartEvent());
+                }
+            }
+
+            // Validate condition (skips parameters with references/scripts)
+            // Validation service will auto-resolve types if needed
+            List<ValidationError> validationErrors = definitionsService.getConditionValidationService().validate(goal.getStartEvent());
+
+            // Add validation info to tracer
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.addValidationInfo(validationErrors, "goal-start-event-validation");
+                    tracer.endOperation(!validationErrors.isEmpty(), String.format("Goal start event validation completed with %d errors", validationErrors.size()));
+                }
+            }
+
+            // Separate errors and warnings
+            List<ValidationError> errors = validationErrors.stream()
+                .filter(error -> error.getType() != ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
+                .collect(Collectors.toList());
+
+            List<ValidationError> warnings = validationErrors.stream()
+                .filter(error -> error.getType() == ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
+                .collect(Collectors.toList());
+
+            // Log warnings but don't block the operation
+            if (!warnings.isEmpty()) {
+                StringBuilder warningMessage = new StringBuilder("Goal start event has warnings:");
+                for (ValidationError warning : warnings) {
+                    warningMessage.append("\n- ").append(warning.getMessage());
+                }
+                LOGGER.warn(warningMessage.toString());
+            }
+
+            // Only throw exception for actual errors
+            if (!errors.isEmpty()) {
+                StringBuilder errorMessage = new StringBuilder("Invalid goal start event:");
+                for (ValidationError error : errors) {
+                    errorMessage.append("\n- ").append(error.getMessage());
+                }
+                if (typeResolutionService != null) {
+                    typeResolutionService.markInvalid("goals", goal.getItemId(), "Start event validation errors: " + errorMessage.toString());
+                }
+                throw new IllegalArgumentException(errorMessage.toString());
+            }
+        }
+        if (goal.getTargetEvent() != null) {
+            // Resolve condition type (skips parameter resolution - happens on-demand in query builders/evaluators)
+            TypeResolutionService typeResolutionService = getTypeResolutionService();
+            boolean targetValid = typeResolutionService != null && typeResolutionService.resolveCondition("goals", goal, goal.getTargetEvent(), "goal "+goal.getItemId()+" target event");
+            isValid = isValid && targetValid;
+            // Start validation operation in tracer for target event
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.startOperation("goal-target-event-validation", "Validating goal target event: " + goal.getItemId(), goal.getTargetEvent());
+                }
+            }
+
+            // Validate condition (skips parameters with references/scripts)
+            // Validation service will auto-resolve types if needed
+            List<ValidationError> targetValidationErrors = definitionsService.getConditionValidationService().validate(goal.getTargetEvent());
+
+            // Add validation info to tracer
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.addValidationInfo(targetValidationErrors, "goal-target-event-validation");
+                    tracer.endOperation(!targetValidationErrors.isEmpty(), String.format("Goal target event validation completed with %d errors", targetValidationErrors.size()));
+                }
+            }
+
+            // Separate errors and warnings
+            List<ValidationError> targetErrors = targetValidationErrors.stream()
+                .filter(error -> error.getType() != ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
+                .collect(Collectors.toList());
+
+            List<ValidationError> targetWarnings = targetValidationErrors.stream()
+                .filter(error -> error.getType() == ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
+                .collect(Collectors.toList());
+
+            // Log warnings but don't block the operation
+            if (!targetWarnings.isEmpty()) {
+                StringBuilder warningMessage = new StringBuilder("Goal target event has warnings:");
+                for (ValidationError warning : targetWarnings) {
+                    warningMessage.append("\n- ").append(warning.getMessage());
+                }
+                LOGGER.warn(warningMessage.toString());
+            }
+
+            // Only throw exception for actual errors
+            if (!targetErrors.isEmpty()) {
+                StringBuilder errorMessage = new StringBuilder("Invalid goal target event:");
+                for (ValidationError error : targetErrors) {
+                    errorMessage.append("\n- ").append(error.getMessage());
+                }
+                if (typeResolutionService != null) {
+                    typeResolutionService.markInvalid("goals", goal.getItemId(), "Target event validation errors: " + errorMessage.toString());
+                }
+                throw new IllegalArgumentException(errorMessage.toString());
+            }
+        }
 
         if (goal.getMetadata().isEnabled()) {
             if (goal.getStartEvent() != null) {
@@ -271,13 +392,7 @@ public class GoalsServiceImpl extends AbstractMultiTypeCachingService implements
 
     public Set<Metadata> getCampaignMetadatas(Query query) {
         if (query.getCondition() != null) {
-            ParserHelper.resolveConditionType(definitionsService, query.getCondition(), "campaign metadata query");
-            if (definitionsService.getConditionValidationService() != null) {
-                List<?> validationErrors = definitionsService.getConditionValidationService().validate(query.getCondition());
-                if (!validationErrors.isEmpty()) {
-                    LOGGER.warn("Campaign query condition has {} validation issue(s)", validationErrors.size());
-                }
-            }
+            definitionsService.getConditionValidationService().validate(query.getCondition());
         }
         Set<Metadata> descriptions = new HashSet<Metadata>();
         for (Campaign definition : persistenceService.query(query.getCondition(), query.getSortby(), Campaign.class, query.getOffset(), query.getLimit()).getList()) {
@@ -288,13 +403,7 @@ public class GoalsServiceImpl extends AbstractMultiTypeCachingService implements
 
     public PartialList<CampaignDetail> getCampaignDetails(Query query) {
         if (query.getCondition() != null) {
-            ParserHelper.resolveConditionType(definitionsService, query.getCondition(), "campaign details query");
-            if (definitionsService.getConditionValidationService() != null) {
-                List<?> validationErrors = definitionsService.getConditionValidationService().validate(query.getCondition());
-                if (!validationErrors.isEmpty()) {
-                    LOGGER.warn("Campaign details query condition has {} validation issue(s)", validationErrors.size());
-                }
-            }
+            definitionsService.getConditionValidationService().validate(query.getCondition());
         }
         PartialList<Campaign> campaigns = persistenceService.query(query.getCondition(), query.getSortby(), Campaign.class, query.getOffset(), query.getLimit());
         List<CampaignDetail> details = new LinkedList<>();
@@ -406,12 +515,9 @@ public class GoalsServiceImpl extends AbstractMultiTypeCachingService implements
         }
 
         if (query != null && query.getCondition() != null) {
-            ParserHelper.resolveConditionType(definitionsService, query.getCondition(), "goal report query");
-            if (definitionsService.getConditionValidationService() != null) {
-                List<?> validationErrors = definitionsService.getConditionValidationService().validate(query.getCondition());
-                if (!validationErrors.isEmpty()) {
-                    LOGGER.warn("Events query condition has {} validation issue(s)", validationErrors.size());
-                }
+            TypeResolutionService typeResolutionService = getTypeResolutionService();
+            if (typeResolutionService != null) {
+                typeResolutionService.resolveConditionType(query.getCondition(), "goal " + goalId + " report");
             }
             list.add(query.getCondition());
         }
@@ -495,13 +601,7 @@ public class GoalsServiceImpl extends AbstractMultiTypeCachingService implements
             persistenceService.refreshIndex(CampaignEvent.class);
         }
         if (query.getCondition() != null) {
-            ParserHelper.resolveConditionType(definitionsService, query.getCondition(), "campaign events query");
-            if (definitionsService.getConditionValidationService() != null) {
-                List<?> validationErrors = definitionsService.getConditionValidationService().validate(query.getCondition());
-                if (!validationErrors.isEmpty()) {
-                    LOGGER.warn("Campaign events query condition has {} validation issue(s)", validationErrors.size());
-                }
-            }
+            definitionsService.getConditionValidationService().validate(query.getCondition());
         }
         return persistenceService.query(query.getCondition(), query.getSortby(), CampaignEvent.class, query.getOffset(), query.getLimit());
     }
@@ -545,14 +645,20 @@ public class GoalsServiceImpl extends AbstractMultiTypeCachingService implements
 
 
     /**
-     * Hook for campaign type resolution (validation stack not backported on this branch).
+     * Resolve a campaign's types and track its validity status using the TypeResolutionService.
      *
-     * @param campaign the campaign being saved
+     * @param campaign the campaign to resolve
      */
     private void resolveCampaign(Campaign campaign) {
         if (campaign == null) {
             return;
         }
-    }
+        TypeResolutionService typeResolutionService = getTypeResolutionService();
+        if (typeResolutionService == null) {
+            return;
+        }
 
+        typeResolutionService.resolveCondition("campaigns", campaign, campaign.getEntryCondition(), "campaign " + campaign.getItemId());
+    }
 }
+
diff --git a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
index bfe2ad07c..b321d8248 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
@@ -27,17 +27,18 @@ import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.query.Query;
 import org.apache.unomi.api.rules.Rule;
 import org.apache.unomi.api.rules.RuleStatistics;
+import org.apache.unomi.api.services.ConditionValidationService.ValidationError;
+import org.apache.unomi.api.services.ConditionValidationService.ValidationErrorType;
 import org.apache.unomi.api.services.*;
 import org.apache.unomi.api.services.cache.CacheableTypeConfig;
 import org.apache.unomi.api.tasks.ScheduledTask;
 import org.apache.unomi.api.tenants.Tenant;
-import org.apache.unomi.api.services.ConditionValidationService.ValidationError;
-import org.apache.unomi.api.services.ConditionValidationService.ValidationErrorType;
-import org.apache.unomi.api.services.TypeResolutionService;
 import org.apache.unomi.api.utils.ParserHelper;
 import org.apache.unomi.persistence.spi.config.ConfigurationUpdateHelper;
 import org.apache.unomi.services.actions.ActionExecutorDispatcher;
 import org.apache.unomi.services.common.cache.AbstractMultiTypeCachingService;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.osgi.framework.*;
 import org.osgi.service.cm.ManagedService;
 import org.osgi.service.event.EventHandler;
@@ -61,6 +62,7 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
     private DefinitionsService definitionsService;
     private EventService eventService;
     private ActionExecutorDispatcher actionExecutorDispatcher;
+    private TracerService tracerService;
 
     private Integer rulesRefreshInterval = 1000;
     private Integer rulesStatisticsRefreshInterval = 10000;
@@ -113,9 +115,13 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
         this.optimizedRulesActivated = optimizedRulesActivated;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
     /**
      * Helper method to get TypeResolutionService from DefinitionsService.
-     * Returns null if DefinitionsService is not available or does not expose TypeResolutionService.
+     * Returns null if DefinitionsService is not available or doesn't have TypeResolutionService.
      */
     private TypeResolutionService getTypeResolutionService() {
         return definitionsService != null ? definitionsService.getTypeResolutionService() : null;
@@ -677,10 +683,29 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
             }
         }
 
-
         if (rule.getCondition() != null) {
+            // Start validation operation in tracer
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.startOperation("rule-condition-validation", "Validating rule condition: " + rule.getItemId(), rule.getCondition());
+                }
+            }
+
+            // Validate condition (skips parameters with references/scripts)
+            // Validation service will auto-resolve types if needed
             List<ValidationError> validationErrors = definitionsService.getConditionValidationService().validate(rule.getCondition());
 
+            // Add validation info to tracer
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.addValidationInfo(validationErrors, "rule-condition-validation");
+                    tracer.endOperation(!validationErrors.isEmpty(), String.format("Rule validation completed with %d errors", validationErrors.size()));
+                }
+            }
+
+            // Separate errors and warnings
             List<ValidationError> errors = validationErrors.stream()
                 .filter(error -> error.getType() != ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
                 .collect(Collectors.toList());
@@ -689,6 +714,7 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
                 .filter(error -> error.getType() == ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
                 .collect(Collectors.toList());
 
+            // Log warnings but don't block the operation
             if (!warnings.isEmpty()) {
                 StringBuilder warningMessage = new StringBuilder("Rule condition has warnings:");
                 for (ValidationError warning : warnings) {
@@ -697,6 +723,7 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
                 LOGGER.warn(warningMessage.toString());
             }
 
+            // Only throw exception for actual errors
             if (!errors.isEmpty()) {
                 StringBuilder errorMessage = new StringBuilder("Invalid rule condition:");
                 for (ValidationError error : errors) {
@@ -846,6 +873,7 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
             return true;
         }
 
+        // Check if rule has missing plugins or is invalid (set by resolveRule)
         boolean hasMissingPlugins = rule.getMetadata().isMissingPlugins();
         TypeResolutionService typeResolutionService = getTypeResolutionService();
         boolean isInvalid = typeResolutionService != null && typeResolutionService.isInvalid("rules", rule.getItemId());
@@ -913,7 +941,7 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
         // Before defaulting to wildcard when eventTypeIds is empty, check if rule has unresolved types
         // This relies on ensureRuleResolvedForIndexing() having been called, which marks the rule appropriately
         // We check for unresolved types by looking at the rule's resolution status (missingPlugins or invalid)
-        // This avoids duplicating the resolution logic - we rely on ensureRuleResolvedForIndexing / ParserHelper
+        // This avoids duplicating the resolution logic - we rely on TypeResolutionService infrastructure
         if (eventTypeIds.isEmpty()) {
             // Check if rule has unresolved types by checking resolution status
             // Note: shouldExcludeRuleFromEventTypeIndex() also checks disabled, so we need to check specifically
@@ -999,13 +1027,17 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
             return false;
         }
 
+        // Check if rule needs resolution (invalid or missing plugins)
         boolean wasInvalid = typeResolutionService.isInvalid("rules", rule.getItemId());
         boolean hadMissingPlugins = rule.getMetadata() != null && rule.getMetadata().isMissingPlugins();
 
+        // If rule is already valid, no need to resolve
         if (!wasInvalid && !hadMissingPlugins) {
             return true;
         }
 
+        // Attempt to resolve the rule (conditions + actions)
+        // This will update missingPlugins flag and invalid status if resolution succeeds
         return typeResolutionService.resolveRule("rules", rule);
     }
 
@@ -1027,6 +1059,10 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
             return false;
         }
 
+        // Always attempt to resolve the rule to ensure resolution status is up-to-date
+        // This is idempotent - resolveRule() efficiently checks if types are already resolved
+        // and only performs actual resolution if needed. This ensures newly loaded rules
+        // are properly checked for unresolved types before indexing.
         return typeResolutionService.resolveRule("rules", rule);
     }
 
@@ -1043,6 +1079,7 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
             return false;
         }
 
+        // Check if rule was previously invalid or had missing plugins
         TypeResolutionService typeResolutionService = getTypeResolutionService();
         if (typeResolutionService == null) {
             return false;
@@ -1144,6 +1181,7 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
                         trackedConditions.add(trackedCondition);
                     }
                 } else {
+                    // Resolve condition type if needed before accessing it
                     if (trackedCondition.getConditionType() == null) {
                         TypeResolutionService typeResolutionService = getTypeResolutionService();
                         if (typeResolutionService != null) {
@@ -1230,9 +1268,15 @@ public class RulesServiceImpl extends AbstractMultiTypeCachingService implements
                         List<Rule> rules = persistenceService.query("tenantId", tId, "priority", Rule.class);
 
                         for (Rule rule : rules) {
+                            // Only re-evaluate rules that were previously invalid or had missing plugins
+                            TypeResolutionService typeResolutionService = getTypeResolutionService();
+                            if (typeResolutionService == null) {
+                                continue;
+                            }
+                            boolean wasInvalid = typeResolutionService.isInvalid("rules", rule.getItemId());
                             boolean hadMissingPlugins = rule.getMetadata() != null && rule.getMetadata().isMissingPlugins();
 
-                            if (hadMissingPlugins) {
+                            if (wasInvalid || hadMissingPlugins) {
                                 // Re-evaluate this rule
                                 boolean resolved = reEvaluateRuleResolution(rule);
 
diff --git a/services/src/main/java/org/apache/unomi/services/impl/segments/SegmentServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/segments/SegmentServiceImpl.java
index fd8f40002..be26b3434 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/segments/SegmentServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/segments/SegmentServiceImpl.java
@@ -28,6 +28,8 @@ import org.apache.unomi.api.exceptions.BadSegmentConditionException;
 import org.apache.unomi.api.query.Query;
 import org.apache.unomi.api.rules.Rule;
 import org.apache.unomi.api.segments.*;
+import org.apache.unomi.api.services.ConditionValidationService.ValidationError;
+import org.apache.unomi.api.services.ConditionValidationService.ValidationErrorType;
 import org.apache.unomi.api.services.*;
 import org.apache.unomi.api.services.cache.CacheableTypeConfig;
 import org.apache.unomi.api.tasks.ScheduledTask;
@@ -35,18 +37,13 @@ import org.apache.unomi.api.tasks.TaskExecutor;
 import org.apache.unomi.api.tenants.TenantService;
 import org.apache.unomi.api.utils.ConditionBuilder;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
-import org.apache.unomi.persistence.spi.PropertyHelper;
 import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
 import org.apache.unomi.services.common.cache.AbstractMultiTypeCachingService;
 import org.apache.unomi.services.impl.scheduler.SchedulerServiceImpl;
-import org.apache.unomi.api.services.ConditionValidationService.ValidationError;
-import org.apache.unomi.api.services.ConditionValidationService.ValidationErrorType;
-import org.apache.unomi.api.utils.ParserHelper;
-import org.apache.unomi.api.exceptions.BadSegmentConditionException;
-import org.osgi.framework.Bundle;
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
-import org.osgi.framework.SynchronousBundleListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -73,6 +70,7 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
     private EventService eventService;
     private RulesService rulesService;
     private DefinitionsService definitionsService;
+    private TracerService tracerService;
 
     private long taskExecutionPeriod = 1;
     private int segmentUpdateBatchSize = 1000;
@@ -102,6 +100,14 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
         this.definitionsService = definitionsService;
     }
 
+    public void setTracerService(TracerService tracerService) {
+        this.tracerService = tracerService;
+    }
+
+    /**
+     * Helper method to get TypeResolutionService from DefinitionsService.
+     * Returns null if DefinitionsService is not available or doesn't have TypeResolutionService.
+     */
     private TypeResolutionService getTypeResolutionService() {
         return definitionsService != null ? definitionsService.getTypeResolutionService() : null;
     }
@@ -368,9 +374,6 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
     public Segment getSegmentDefinition(String segmentId) {
         String currentTenant = contextManager.getCurrentContext().getTenantId();
         Segment segment = cacheService.getWithInheritance(segmentId, currentTenant, Segment.class);
-        if (segment != null && segment.getMetadata().isEnabled()) {
-            ParserHelper.resolveConditionType(definitionsService, segment.getCondition(), "segment " + segmentId);
-        }
         return segment;
     }
 
@@ -388,52 +391,71 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
                 throw new BadSegmentConditionException();
             }
         } else {
+            // Start validation operation in tracer
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.startOperation("segment-condition-validation", "Validating segment condition: " + segment.getItemId(), segment.getCondition());
+                }
+            }
+
             if (segment.getMetadata().isEnabled()) {
                 TypeResolutionService typeResolutionService = getTypeResolutionService();
                 if (typeResolutionService != null) {
                     typeResolutionService.resolveCondition("segments", segment, segment.getCondition(), "segment " + segment.getItemId());
                 }
+            }
 
-                if (!persistenceService.isValidCondition(segment.getCondition(), new Profile(VALIDATION_PROFILE_ID))) {
-                    throw new BadSegmentConditionException();
-                }
+            // Validate condition (skips parameters with references/scripts)
+            // Validation service will auto-resolve types if needed
+            List<ValidationError> validationErrors = definitionsService.getConditionValidationService().validate(segment.getCondition());
 
-                if (definitionsService.getConditionValidationService() != null) {
-                    List<ValidationError> validationErrors = definitionsService.getConditionValidationService().validate(segment.getCondition());
+            // Add validation info to tracer
+            if (tracerService != null) {
+                RequestTracer tracer = tracerService.getCurrentTracer();
+                if (tracer != null && tracer.isEnabled()) {
+                    tracer.addValidationInfo(validationErrors, "segment-condition-validation");
+                    tracer.endOperation(!validationErrors.isEmpty(), String.format("Segment validation completed with %d errors", validationErrors.size()));
+                }
+            }
 
-                    List<ValidationError> errors = validationErrors.stream()
-                        .filter(error -> error.getType() != ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
-                        .collect(Collectors.toList());
+            // Separate errors and warnings
+            List<ValidationError> errors = validationErrors.stream()
+                .filter(error -> error.getType() != ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
+                .collect(Collectors.toList());
 
-                    List<ValidationError> warnings = validationErrors.stream()
-                        .filter(error -> error.getType() == ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
-                        .collect(Collectors.toList());
+            List<ValidationError> warnings = validationErrors.stream()
+                .filter(error -> error.getType() == ValidationErrorType.MISSING_RECOMMENDED_PARAMETER)
+                .collect(Collectors.toList());
 
-                    if (!warnings.isEmpty()) {
-                        StringBuilder warningMessage = new StringBuilder("Segment condition has warnings:");
-                        for (ValidationError warning : warnings) {
-                            warningMessage.append("\n- ").append(warning.getDetailedMessage());
-                        }
-                        LOGGER.warn(warningMessage.toString());
-                    }
+            // Log warnings but don't block the operation
+            if (!warnings.isEmpty()) {
+                StringBuilder warningMessage = new StringBuilder("Segment condition has warnings:");
+                for (ValidationError warning : warnings) {
+                    warningMessage.append("\n- ").append(warning.getDetailedMessage());
+                }
+                LOGGER.warn(warningMessage.toString());
+            }
 
-                    if (!errors.isEmpty()) {
-                        StringBuilder errorMessage = new StringBuilder("Invalid segment condition:");
-                        for (ValidationError error : errors) {
-                            errorMessage.append("\n- ").append(error.getDetailedMessage());
-                        }
-                        throw new BadSegmentConditionException(errorMessage.toString());
-                    }
+            // Only throw exception for actual errors
+            if (!errors.isEmpty()) {
+                StringBuilder errorMessage = new StringBuilder("Invalid segment condition:");
+                for (ValidationError error : errors) {
+                    errorMessage.append("\n- ").append(error.getDetailedMessage());
                 }
+                throw new BadSegmentConditionException(errorMessage.toString());
             }
+
         }
 
+        // Update auto-generated rules if metadata is enabled and no missing plugins
         if (segment.getMetadata().isEnabled() && !segment.getMetadata().isMissingPlugins()) {
             updateAutoGeneratedRules(segment.getMetadata(), segment.getCondition());
         }
 
         segment.setTenantId(contextManager.getCurrentContext().getTenantId());
 
+        // Save segment and update cache
         persistenceService.save(segment, null, true);
         cacheService.put(Segment.ITEM_TYPE, segment.getItemId(), segment.getTenantId(), segment);
         updateExistingProfilesForSegment(segment);
@@ -639,16 +661,22 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
     }
 
     public Boolean isProfileInSegment(Profile profile, String segmentId) {
+        RequestTracer tracer = tracerService.getCurrentTracer();
+        tracer.trace("Checking if profile is in segment: " + segmentId, profile.getItemId());
         Set<String> matchingSegments = getSegmentsAndScoresForProfile(profile).getSegments();
         boolean isInSegment = matchingSegments.contains(segmentId);
+        tracer.trace("Profile " + profile.getItemId() + " is " + (isInSegment ? "in" : "not in") + " segment: " + segmentId, profile.getItemId());
         return isInSegment;
     }
 
     public SegmentsAndScores getSegmentsAndScoresForProfile(Profile profile) {
+        RequestTracer tracer = tracerService.getCurrentTracer();
+        tracer.trace("Getting segments and scores for profile: " + profile.getItemId(), profile.getItemId());
         Set<String> segments = new HashSet<String>();
         Map<String, Integer> scores = new HashMap<String, Integer>();
 
         String currentTenant = contextManager.getCurrentContext().getTenantId();
+        tracer.trace("Current tenant: " + currentTenant, profile.getItemId());
 
         // Get system tenant segments and scoring first
         Map<String, Segment> systemSegments = cacheService.getTenantCache("system", Segment.class);
@@ -660,11 +688,9 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
                     LOGGER.warn("Found empty condition for segment {}, will skip", segment);
                     continue;
                 }
-                if (segment.getMetadata().isEnabled()) {
-                    ParserHelper.resolveConditionType(definitionsService, segment.getCondition(), "segment " + segment.getItemId());
-                    if (persistenceService.testMatch(segment.getCondition(), profile)) {
-                        segments.add(segment.getMetadata().getId());
-                    }
+                if (segment.getMetadata().isEnabled() && persistenceService.testMatch(segment.getCondition(), profile)) {
+                    segments.add(segment.getMetadata().getId());
+                    tracer.trace("Profile matches system segment: " + segment.getMetadata().getId(), profile.getItemId());
                 }
             }
         }
@@ -679,11 +705,9 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
                     LOGGER.warn("Found empty condition for segment {}, will skip", segment);
                     continue;
                 }
-                if (segment.getMetadata().isEnabled()) {
-                    ParserHelper.resolveConditionType(definitionsService, segment.getCondition(), "segment " + segment.getItemId());
-                    if (persistenceService.testMatch(segment.getCondition(), profile)) {
-                        segments.add(segment.getMetadata().getId());
-                    }
+                if (segment.getMetadata().isEnabled() && persistenceService.testMatch(segment.getCondition(), profile)) {
+                    segments.add(segment.getMetadata().getId());
+                    tracer.trace("Profile matches tenant segment: " + segment.getMetadata().getId(), profile.getItemId());
                 }
             }
         }
@@ -700,12 +724,12 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
     }
 
     private void processScoring(Map<String, Scoring> scoringMap, Profile profile, Map<String, Integer> scores) {
+        RequestTracer tracer = tracerService.getCurrentTracer();
         Map<String, Integer> scoreModifiers = (Map<String, Integer>) profile.getSystemProperties().get("scoreModifiers");
         for (Scoring scoring : scoringMap.values()) {
             if (scoring.getMetadata().isEnabled()) {
                 int score = 0;
                 for (ScoringElement scoringElement : scoring.getElements()) {
-                    ParserHelper.resolveConditionType(definitionsService, scoringElement.getCondition(), "scoring " + scoring.getItemId());
                     if (persistenceService.testMatch(scoringElement.getCondition(), profile)) {
                         score += scoringElement.getValue();
                     }
@@ -770,20 +794,15 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
     @Override
     public Scoring getScoringDefinition(String scoringId) {
         String currentTenant = contextManager.getCurrentContext().getTenantId();
-        Scoring definition = cacheService.getWithInheritance(scoringId, currentTenant, Scoring.class);
-        if (definition != null && definition.getMetadata().isEnabled() && definition.getElements() != null) {
-            for (ScoringElement element : definition.getElements()) {
-                ParserHelper.resolveConditionType(definitionsService, element.getCondition(), "scoring " + scoringId);
-            }
-        }
-        return definition;
+        return cacheService.getWithInheritance(scoringId, currentTenant, Scoring.class);
     }
 
     @Override
     public void setScoringDefinition(Scoring scoring) {
+        resolveScoring(scoring);
+
         if (scoring.getMetadata().isEnabled()) {
             for (ScoringElement element : scoring.getElements()) {
-                ParserHelper.resolveConditionType(definitionsService, element.getCondition(), "scoring " + scoring.getItemId() + " element ");
                 if (!scoring.getMetadata().isMissingPlugins()) {
                     updateAutoGeneratedRules(scoring.getMetadata(), element.getCondition());
                 }
@@ -1009,6 +1028,16 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
     }
 
     private void getAutoGeneratedRules(Metadata metadata, Condition condition, Condition parentCondition, List<Rule> rules) {
+        // Resolve condition type if needed before accessing it
+        if (condition.getConditionType() == null) {
+            TypeResolutionService typeResolutionService = getTypeResolutionService();
+            if (typeResolutionService != null) {
+                typeResolutionService.resolveConditionType(condition, "auto-generated rules");
+            }
+        }
+        if (condition.getConditionType() == null) {
+            return; // Cannot proceed without condition type
+        }
         Set<String> tags = condition.getConditionType().getMetadata().getSystemTags();
         if (tags.contains("eventCondition") && !tags.contains("profileCondition")) {
             String key = getGeneratedPropertyKey(condition, parentCondition);
@@ -1072,7 +1101,7 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
 
         l.add(eventCondition);
 
-        Integer numberOfDays = PropertyHelper.getInteger(parentCondition.getParameter("numberOfDays"));
+        Integer numberOfDays = (Integer) parentCondition.getParameter("numberOfDays");
         String fromDate = (String) parentCondition.getParameter("fromDate");
         String toDate = (String) parentCondition.getParameter("toDate");
 
@@ -1598,7 +1627,7 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
 
     protected <T extends MetadataItem> PartialList<Metadata> getMetadatas(Query query, Class<T> clazz) {
         if (query.getCondition() != null) {
-            definitionsService.resolveConditionType(query.getCondition());
+            definitionsService.getConditionValidationService().validate(query.getCondition());
         }
         String currentTenantId = contextManager.getCurrentContext().getTenantId();
         if (currentTenantId == null) {
@@ -1680,4 +1709,52 @@ public class SegmentServiceImpl extends AbstractMultiTypeCachingService implemen
     }
 
 
+    /**
+     * Resolve a scoring's types and track its validity status using the TypeResolutionService.
+     * Automatically sets/clears the missingPlugins flag based on resolution success.
+     *
+     * @param scoring the scoring to resolve
+     */
+    private void resolveScoring(Scoring scoring) {
+        if (scoring == null) {
+            return;
+        }
+        TypeResolutionService typeResolutionService = getTypeResolutionService();
+        if (typeResolutionService == null) {
+            return;
+        }
+
+        String scoringId = scoring.getMetadata().getId();
+        boolean allResolved = true;
+        List<String> reasons = new ArrayList<>();
+
+        if (scoring.getElements() != null) {
+            for (ScoringElement element : scoring.getElements()) {
+                if (element.getCondition() != null) {
+                    // Use partial resolution - resolves types but skips parameter resolution for references/scripts
+                    boolean elementValid = typeResolutionService != null && typeResolutionService.resolveConditionType(element.getCondition(),
+                        "scoring element for scoring " + scoringId);
+                    allResolved = allResolved && elementValid;
+                    if (!elementValid) {
+                        String unresolvedTypeId = element.getCondition().getConditionTypeId();
+                        reasons.add("Unresolved condition type" + (unresolvedTypeId != null ? ": " + unresolvedTypeId : "") + " in scoring element");
+                    }
+                }
+            }
+        }
+
+        // Set/clear missingPlugins based on resolution success
+        if (scoring.getMetadata() != null) {
+            scoring.getMetadata().setMissingPlugins(!allResolved);
+        }
+
+        // Track invalid objects
+        if (typeResolutionService != null) {
+            if (!allResolved) {
+                typeResolutionService.markInvalid("scoring", scoringId, String.join("; ", reasons));
+            } else {
+                typeResolutionService.markValid("scoring", scoringId);
+            }
+        }
+    }
 }
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 5a2881f98..1bba07ee3 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -30,21 +30,12 @@
     <reference id="metricsService" interface="org.apache.unomi.metrics.MetricsService"/>
     <reference id="scriptExecutor" interface="org.apache.unomi.scripting.ScriptExecutor"/>
     <reference id="configAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
+    <reference id="tracerService" interface="org.apache.unomi.tracing.api.TracerService"/>
     <reference id="bundleWatcher" interface="org.apache.unomi.lifecycle.BundleWatcher"/>
     <reference id="executionContextManager" interface="org.apache.unomi.api.services.ExecutionContextManager" />
     <reference id="auditService" interface="org.apache.unomi.api.tenants.AuditService" availability="optional"/>
     <reference id="eventAdmin" interface="org.osgi.service.event.EventAdmin"/>
 
-    <!-- Reference list for custom validators from plugins -->
-    <reference-list id="customValidators"
-                   interface="org.apache.unomi.api.services.ValueTypeValidator"
-                   availability="optional">
-        <reference-listener bind-method="bindValidator"
-                          unbind-method="unbindValidator"
-                          ref="definitionsServiceImpl"/>
-    </reference-list>
-
-
     <!-- Reference Lists -->
     <reference-list id="eventListenerServices"
                     interface="org.apache.unomi.api.services.EventListenerService"
@@ -90,6 +81,15 @@
                            unbind-method="unbindListener"/>
     </reference-list>
 
+    <!-- Reference list for custom validators from plugins -->
+    <reference-list id="customValidators"
+                   interface="org.apache.unomi.api.services.ValueTypeValidator"
+                   availability="optional">
+        <reference-listener bind-method="bindValidator"
+                          unbind-method="unbindValidator"
+                          ref="definitionsServiceImpl"/>
+    </reference-list>
+
     <!-- Configuration -->
     <cm:property-placeholder persistent-id="org.apache.unomi.services"
                              update-strategy="none" placeholder-prefix="${services.">
@@ -246,6 +246,7 @@
         <property name="tenantService" ref="tenantServiceImpl"/>
         <property name="cacheService" ref="multiTypeCacheServiceImpl"/>
         <property name="contextManager" ref="executionContextManager"/>
+        <property name="tracerService" ref="tracerService"/>
         <property name="eventAdmin" ref="eventAdmin"/>
         <property name="auditService" ref="auditService"/>
         <property name="conditionValidationServiceBuiltInValidators">
@@ -267,6 +268,7 @@
         <property name="persistenceService" ref="persistenceService"/>
         <property name="definitionsService" ref="definitionsServiceImpl"/>
         <property name="bundleContext" ref="blueprintBundleContext"/>
+        <property name="tracerService" ref="tracerService" />
         <property name="predefinedEventTypeIds">
             <set>
                 <value>view</value>
@@ -296,6 +298,7 @@
         <property name="bundleContext" ref="blueprintBundleContext"/>
         <property name="tenantService" ref="tenantServiceImpl"/>
         <property name="contextManager" ref="executionContextManager"/>
+        <property name="tracerService" ref="tracerService"/>
         <property name="contextManager" ref="executionContextManager"/>
         <property name="cacheService" ref="multiTypeCacheServiceImpl"/>
         <property name="tenantService" ref="tenantServiceImpl"/>
@@ -322,6 +325,7 @@
         <property name="metricsService" ref="metricsService"/>
         <property name="scriptExecutor" ref="scriptExecutor"/>
         <property name="bundleContext" ref="blueprintBundleContext"/>
+        <property name="tracerService" ref="tracerService" />
         <property name="definitionsService" ref="definitionsServiceImpl"/>
     </bean>
 
@@ -338,6 +342,7 @@
         <property name="optimizedRulesActivated" value="${services.rules.optimizationActivated}"/>
         <property name="tenantService" ref="tenantServiceImpl"/>
         <property name="contextManager" ref="executionContextManager"/>
+        <property name="tracerService" ref="tracerService"/>
         <property name="cacheService" ref="multiTypeCacheServiceImpl"/>
         <property name="auditService" ref="auditService"/>
     </bean>
@@ -364,6 +369,7 @@
         <property name="tenantService" ref="tenantServiceImpl"/>
         <property name="contextManager" ref="executionContextManager"/>
         <property name="cacheService" ref="multiTypeCacheServiceImpl"/>
+        <property name="tracerService" ref="tracerService"/>
         <property name="auditService" ref="auditService"/>
     </bean>
 
@@ -677,7 +683,6 @@
 
     <service id="clusterService" ref="clusterServiceImpl" interface="org.apache.unomi.api.services.ClusterService"/>
 
-
     <!-- Value Type Validator Services -->
     <service ref="stringValueTypeValidator" interface="org.apache.unomi.api.services.ValueTypeValidator"/>
     <service ref="integerValueTypeValidator" interface="org.apache.unomi.api.services.ValueTypeValidator"/>
diff --git a/tracing/pom.xml b/tracing/pom.xml
new file mode 100644
index 000000000..4e3424860
--- /dev/null
+++ b/tracing/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.unomi</groupId>
+        <artifactId>unomi-root</artifactId>
+        <version>3.1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>unomi-tracing</artifactId>
+    <name>Apache Unomi :: Tracing</name>
+    <description>Apache Unomi Tracing module</description>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>tracing-api</module>
+        <module>tracing-impl</module>
+    </modules>
+</project> 
\ No newline at end of file
diff --git a/tracing/tracing-api/pom.xml b/tracing/tracing-api/pom.xml
new file mode 100644
index 000000000..379a3b6c7
--- /dev/null
+++ b/tracing/tracing-api/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.unomi</groupId>
+        <artifactId>unomi-tracing</artifactId>
+        <version>3.1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>unomi-tracing-api</artifactId>
+    <name>Apache Unomi :: Tracing :: API</name>
+    <description>Apache Unomi Tracing API module</description>
+    <packaging>bundle</packaging>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.unomi</groupId>
+                <artifactId>unomi-bom</artifactId>
+                <version>${project.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.apache.unomi.tracing.api
+                        </Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/RequestTracer.java b/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/RequestTracer.java
new file mode 100644
index 000000000..4ec886077
--- /dev/null
+++ b/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/RequestTracer.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.tracing.api;
+
+import java.util.Collection;
+
+/**
+ * Interface for tracing request processing and internal operations.
+ * This provides a generic tracing mechanism that can be used to track various operations
+ * including but not limited to:
+ * - Condition evaluations
+ * - Authentication
+ * - Event validation
+ * - Event processing
+ * - Rules evaluation
+ * - Action execution
+ */
+public interface RequestTracer {
+    /**
+     * Start tracing a new operation
+     * @param operationType the type of operation being traced (e.g. "condition-evaluation", "authentication", "event-validation")
+     * @param description description of what is being traced
+     * @param context additional context information for the operation
+     */
+    void startOperation(String operationType, String description, Object context);
+
+    /**
+     * End the current operation with a result
+     * @param result the result of the operation
+     * @param description explanation of the result. The implementation should include timing information.
+     */
+    void endOperation(Object result, String description);
+
+    /**
+     * Add a trace message to the current operation
+     * @param message the trace message
+     * @param context additional context information for the trace
+     */
+    void trace(String message, Object context);
+
+    /**
+     * Add validation information to the current operation
+     * @param validationMessages collection of validation messages/errors
+     * @param schemaId the ID of the schema that was used for validation
+     */
+    void addValidationInfo(Collection<?> validationMessages, String schemaId);
+
+    /**
+     * Get the root trace node
+     * @return the root trace node or null if tracing is not enabled
+     */
+    TraceNode getTraceNode();
+
+    /**
+     * Check if tracing is enabled
+     * @return true if tracing is enabled
+     */
+    boolean isEnabled();
+
+    /**
+     * Enable or disable tracing
+     * @param enabled true to enable tracing, false to disable
+     */
+    void setEnabled(boolean enabled);
+
+    /**
+     * Reset the tracer, clearing all stored traces
+     */
+    void reset();
+} 
\ No newline at end of file
diff --git a/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TraceNode.java b/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TraceNode.java
new file mode 100644
index 000000000..be75ec09b
--- /dev/null
+++ b/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TraceNode.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.tracing.api;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a node in the request tracing tree structure.
+ * Each node contains information about an operation, its timing, and any child operations.
+ */
+public class TraceNode implements Serializable {
+    private String operationType;
+    private String description;
+    private Object context;
+    private Object result;
+    private long startTime;
+    private long endTime;
+    private List<String> traces;
+    private List<TraceNode> children;
+
+    public TraceNode() {
+        this.traces = new ArrayList<>();
+        this.children = new ArrayList<>();
+    }
+
+    public String getOperationType() {
+        return operationType;
+    }
+
+    public void setOperationType(String operationType) {
+        this.operationType = operationType;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Object getContext() {
+        return context;
+    }
+
+    public void setContext(Object context) {
+        this.context = context;
+    }
+
+    public Object getResult() {
+        return result;
+    }
+
+    public void setResult(Object result) {
+        this.result = result;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(long startTime) {
+        this.startTime = startTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(long endTime) {
+        this.endTime = endTime;
+    }
+
+    public long getDuration() {
+        return endTime - startTime;
+    }
+
+    public List<String> getTraces() {
+        return traces;
+    }
+
+    public void setTraces(List<String> traces) {
+        this.traces = traces;
+    }
+
+    public List<TraceNode> getChildren() {
+        return children;
+    }
+
+    public void setChildren(List<TraceNode> children) {
+        this.children = children;
+    }
+} 
\ No newline at end of file
diff --git a/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TracerService.java b/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TracerService.java
new file mode 100644
index 000000000..a603be17e
--- /dev/null
+++ b/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TracerService.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.tracing.api;
+
+/**
+ * Service for managing request tracing throughout the application.
+ * This service provides access to tracers that can monitor various operations
+ * including condition evaluations, authentication, event processing, etc.
+ */
+public interface TracerService {
+    /**
+     * Get the current request tracer
+     * @return the current request tracer
+     */
+    RequestTracer getCurrentTracer();
+
+    /**
+     * Enable tracing for the current request
+     */
+    void enableTracing();
+
+    /**
+     * Disable tracing for the current request
+     */
+    void disableTracing();
+
+    /**
+     * Check if tracing is enabled for the current request
+     * @return true if tracing is enabled
+     */
+    boolean isTracingEnabled();
+
+    /**
+     * Get the root trace node for the current request
+     * @return the root trace node or null if tracing is not enabled
+     */
+    TraceNode getTraceNode();
+} 
\ No newline at end of file
diff --git a/extensions/geonames/services/pom.xml b/tracing/tracing-impl/pom.xml
similarity index 55%
copy from extensions/geonames/services/pom.xml
copy to tracing/tracing-impl/pom.xml
index c83b1299a..27a0f2028 100644
--- a/extensions/geonames/services/pom.xml
+++ b/tracing/tracing-impl/pom.xml
@@ -15,17 +15,18 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
+
     <parent>
         <groupId>org.apache.unomi</groupId>
-        <artifactId>cxs-geonames</artifactId>
+        <artifactId>unomi-tracing</artifactId>
         <version>3.1.0-SNAPSHOT</version>
     </parent>
-    <artifactId>cxs-geonames-services</artifactId>
-    <name>Apache Unomi :: Extensions :: Geonames Database :: Service</name>
-    <description>Service implementation for the Apache Unomi Context Server extension that integrates with the Geonames database</description>
+
+    <artifactId>unomi-tracing-impl</artifactId>
+    <name>Apache Unomi :: Tracing :: Implementation</name>
+    <description>Apache Unomi Tracing Implementation module</description>
     <packaging>bundle</packaging>
 
     <dependencyManagement>
@@ -43,38 +44,34 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.unomi</groupId>
-            <artifactId>unomi-api</artifactId>
+            <artifactId>unomi-tracing-api</artifactId>
+            <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.unomi</groupId>
-            <artifactId>unomi-persistence-spi</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.unomi</groupId>
-            <artifactId>unomi-services</artifactId>
+            <artifactId>unomi-api</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.apache.cxf</groupId>
-            <artifactId>cxf-rt-rs-security-cors</artifactId>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>commons-beanutils</groupId>
-            <artifactId>commons-beanutils</artifactId>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-            <scope>provided</scope>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
         </dependency>
     </dependencies>
 
@@ -86,39 +83,12 @@
                 <extensions>true</extensions>
                 <configuration>
                     <instructions>
-                        <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
-                        <Import-Package>
-                            sun.misc;resolution:=optional,
-                            *
-                        </Import-Package>
+                        <Export-Package>
+                            org.apache.unomi.tracing.impl
+                        </Export-Package>
                     </instructions>
                 </configuration>
             </plugin>
-            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>build-helper-maven-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>attach-artifacts</id>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>attach-artifact</goal>
-                        </goals>
-                        <configuration>
-                            <artifacts>
-                                <artifact>
-                                    <file>
-                                        src/main/resources/org.apache.unomi.geonames.cfg
-                                    </file>
-                                    <type>cfg</type>
-                                    <classifier>geonamescfg</classifier>
-                                </artifact>
-                            </artifacts>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-
         </plugins>
     </build>
 </project>
diff --git a/tracing/tracing-impl/src/main/java/org/apache/unomi/tracing/impl/DefaultRequestTracer.java b/tracing/tracing-impl/src/main/java/org/apache/unomi/tracing/impl/DefaultRequestTracer.java
new file mode 100644
index 000000000..d15d87b06
--- /dev/null
+++ b/tracing/tracing-impl/src/main/java/org/apache/unomi/tracing/impl/DefaultRequestTracer.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.tracing.impl;
+
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TraceNode;
+
+import java.util.Collection;
+import java.util.Stack;
+
+/**
+ * Default implementation of the RequestTracer interface that stores trace information in a tree structure
+ */
+public class DefaultRequestTracer implements RequestTracer {
+
+    private final ThreadLocal<Boolean> enabled = ThreadLocal.withInitial(() -> false);
+    private final ThreadLocal<TraceNode> currentNode = new ThreadLocal<>();
+    private final ThreadLocal<TraceNode> rootNode = new ThreadLocal<>();
+    private final ThreadLocal<Stack<TraceNode>> nodeStack = ThreadLocal.withInitial(Stack::new);
+    private static final int MAX_CONTEXT_STRING_LENGTH = 4096;
+
+    private static String safeContextToString(Object context) {
+        if (context == null) {
+            return "null";
+        }
+        try {
+            String rendered = String.valueOf(context);
+            if (rendered != null && rendered.length() > MAX_CONTEXT_STRING_LENGTH) {
+                return rendered.substring(0, MAX_CONTEXT_STRING_LENGTH) + "...(truncated)";
+            }
+            return rendered;
+        } catch (StackOverflowError e) {
+            return "<context-toString StackOverflowError class=" + context.getClass().getName() + ">";
+        } catch (Throwable t) {
+            return "<context-toString failed class=" + context.getClass().getName() + " error=" + t.getClass().getName() + ">";
+        }
+    }
+
+    @Override
+    public void startOperation(String operationType, String description, Object context) {
+        if (!isEnabled()) {
+            return;
+        }
+
+        TraceNode node = new TraceNode();
+        node.setOperationType(operationType);
+        node.setDescription(description);
+        node.setContext(context);
+        node.setStartTime(System.currentTimeMillis());
+
+        if (rootNode.get() == null) {
+            rootNode.set(node);
+            currentNode.set(node);
+        } else {
+            TraceNode parent = currentNode.get();
+            parent.getChildren().add(node);
+            nodeStack.get().push(currentNode.get());
+            currentNode.set(node);
+        }
+    }
+
+    @Override
+    public void endOperation(Object result, String description) {
+        if (!isEnabled()) {
+            return;
+        }
+
+        TraceNode node = currentNode.get();
+        if (node != null) {
+            node.setResult(result);
+            node.setDescription(description);
+            node.setEndTime(System.currentTimeMillis());
+
+            if (!nodeStack.get().isEmpty()) {
+                currentNode.set(nodeStack.get().pop());
+            }
+        }
+    }
+
+    @Override
+    public void trace(String message, Object context) {
+        if (!isEnabled()) {
+            return;
+        }
+
+        TraceNode node = currentNode.get();
+        if (node != null) {
+            if (context != null) {
+                node.getTraces().add(message + " - Context: " + safeContextToString(context));
+            } else {
+                node.getTraces().add(message);
+            }
+        }
+    }
+
+    @Override
+    public void addValidationInfo(Collection<?> validationMessages, String schemaId) {
+        if (!isEnabled()) {
+            return;
+        }
+
+        TraceNode node = currentNode.get();
+        if (node != null) {
+            node.getTraces().add("Validation against schema " + schemaId + ": " + safeContextToString(validationMessages));
+        }
+    }
+
+    @Override
+    public TraceNode getTraceNode() {
+        if (!isEnabled() || rootNode.get() == null) {
+            return null;
+        }
+        return rootNode.get();
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled.get();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        this.enabled.set(enabled);
+    }
+
+    @Override
+    public void reset() {
+        rootNode.remove();
+        currentNode.remove();
+        nodeStack.get().clear();
+    }
+}
diff --git a/tracing/tracing-impl/src/main/java/org/apache/unomi/tracing/impl/DefaultTracerService.java b/tracing/tracing-impl/src/main/java/org/apache/unomi/tracing/impl/DefaultTracerService.java
new file mode 100644
index 000000000..43e24aa45
--- /dev/null
+++ b/tracing/tracing-impl/src/main/java/org/apache/unomi/tracing/impl/DefaultTracerService.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.tracing.impl;
+
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TracerService;
+import org.apache.unomi.tracing.api.TraceNode;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Default implementation of the TracerService
+ */
+@Component(service = TracerService.class, immediate = true)
+public class DefaultTracerService implements TracerService {
+
+    private final ThreadLocal<RequestTracer> currentTracer = new ThreadLocal<RequestTracer>() {
+        @Override
+        protected RequestTracer initialValue() {
+            return new DefaultRequestTracer();
+        }
+    };
+
+    @Override
+    public RequestTracer getCurrentTracer() {
+        return currentTracer.get();
+    }
+
+    @Override
+    public void enableTracing() {
+        RequestTracer tracer = getCurrentTracer();
+        tracer.setEnabled(true);
+        tracer.reset();
+    }
+
+    @Override
+    public void disableTracing() {
+        RequestTracer tracer = getCurrentTracer();
+        tracer.setEnabled(false);
+        tracer.reset();
+    }
+
+    @Override
+    public boolean isTracingEnabled() {
+        return getCurrentTracer().isEnabled();
+    }
+
+    @Override
+    public TraceNode getTraceNode() {
+        return getCurrentTracer().getTraceNode();
+    }
+
+    public void cleanup() {
+        currentTracer.remove();
+    }
+} 
\ No newline at end of file
diff --git a/tracing/tracing-impl/src/main/java/org/apache/unomi/tracing/impl/LoggingRequestTracer.java b/tracing/tracing-impl/src/main/java/org/apache/unomi/tracing/impl/LoggingRequestTracer.java
new file mode 100644
index 000000000..34b7ec0e0
--- /dev/null
+++ b/tracing/tracing-impl/src/main/java/org/apache/unomi/tracing/impl/LoggingRequestTracer.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.tracing.impl;
+
+import org.apache.unomi.api.conditions.Condition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+
+/**
+ * Implementation of RequestTracer that extends DefaultRequestTracer to leverage its tree structure
+ * while also logging operations to SLF4J with proper indentation.
+ */
+public class LoggingRequestTracer extends DefaultRequestTracer {
+    private static final Logger logger = LoggerFactory.getLogger(LoggingRequestTracer.class);
+    protected static final String INDENT_STRING = "  "; // Two spaces per level
+    
+    protected final ThreadLocal<Integer> indentLevel;
+
+    public LoggingRequestTracer(boolean enabled) {
+        setEnabled(enabled);
+        this.indentLevel = ThreadLocal.withInitial(() -> 0);
+    }
+
+    @Override
+    public void startOperation(String operationType, String description, Object context) {
+        super.startOperation(operationType, description, context);
+        if (!isEnabled()) {
+            return;
+        }
+        
+        logMessage(formatStartOperation(operationType, description), context);
+        indentLevel.set(indentLevel.get() + 1);
+    }
+
+    @Override
+    public void endOperation(Object result, String description) {
+        if (isEnabled()) {
+            indentLevel.set(Math.max(0, indentLevel.get() - 1));
+            logMessage(formatEndOperation(description, result), result);
+        }
+        super.endOperation(result, description);
+    }
+
+    @Override
+    public void trace(String message, Object context) {
+        super.trace(message, context);
+        if (!isEnabled()) {
+            return;
+        }
+        
+        logMessage(message, context);
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        indentLevel.set(0);
+    }
+
+    /**
+     * Format a message for operation start.
+     *
+     * @param operationType the type of operation
+     * @param description the operation description
+     * @return formatted message
+     */
+    protected String formatStartOperation(String operationType, String description) {
+        return String.format("Started operation: %s - %s",
+            Objects.toString(operationType, "null"),
+            Objects.toString(description, "null"));
+    }
+
+    /**
+     * Format a message for operation end.
+     *
+     * @param description the operation description
+     * @param result the operation result
+     * @return formatted message
+     */
+    protected String formatEndOperation(String description, Object result) {
+        return String.format("Ended operation: %s - Result: %s",
+            Objects.toString(description, "null"),
+            Objects.toString(result, "null"));
+    }
+
+    /**
+     * Format a trace message with optional context.
+     *
+     * @param message the base message
+     * @param context optional context object
+     * @return formatted message
+     */
+    public String formatMessage(String message, Object context) {
+        if (context instanceof Condition) {
+            Condition condition = (Condition) context;
+            if (message.startsWith("Starting ")) {
+                return String.format("Starting %s of condition %s - %s", 
+                    message.substring(9), condition.getConditionTypeId(), message);
+            } else {
+                return String.format("Condition %s - %s", condition.getConditionTypeId(), message);
+            }
+        } else if (context != null) {
+            return String.format("%s - Context: %s",
+                Objects.toString(message, "null"),
+                Objects.toString(context, "null"));
+        }
+        return Objects.toString(message, "null");
+    }
+
+    /**
+     * Add indentation to a message based on the current nesting level.
+     *
+     * @param message the message to indent
+     * @return the indented message
+     */
+    public String indent(String message) {
+        if (message == null) {
+            return "";
+        }
+        
+        int level = indentLevel.get();
+        if (level <= 0) {
+            return message;
+        }
+
+        StringBuilder indentation = new StringBuilder(level * INDENT_STRING.length());
+        for (int i = 0; i < level; i++) {
+            indentation.append(INDENT_STRING);
+        }
+        return indentation.append(message).toString();
+    }
+
+    /**
+     * Log a message with proper formatting and indentation.
+     *
+     * @param message the message to log
+     * @param context optional context object
+     */
+    protected void logMessage(String message, Object context) {
+        String formattedMessage = formatMessage(message, context);
+        String indentedMessage = indent(formattedMessage);
+        logger.debug(indentedMessage);
+    }
+} 
\ No newline at end of file
diff --git a/tracing/tracing-impl/src/test/java/org/apache/unomi/tracing/impl/DefaultTracerServiceTest.java b/tracing/tracing-impl/src/test/java/org/apache/unomi/tracing/impl/DefaultTracerServiceTest.java
new file mode 100644
index 000000000..d808fc09d
--- /dev/null
+++ b/tracing/tracing-impl/src/test/java/org/apache/unomi/tracing/impl/DefaultTracerServiceTest.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.tracing.impl;
+
+import org.apache.unomi.tracing.api.RequestTracer;
+import org.apache.unomi.tracing.api.TraceNode;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link DefaultTracerService}
+ */
+public class DefaultTracerServiceTest {
+
+    private DefaultTracerService tracerService;
+    private static final int THREAD_COUNT = 10;
+    private static final int TIMEOUT_SECONDS = 10;
+
+    @BeforeEach
+    public void setUp() {
+        tracerService = new DefaultTracerService();
+    }
+
+    @AfterEach
+    public void tearDown() {
+        tracerService.cleanup();
+    }
+
+    @Test
+    public void testGetCurrentTracer() {
+        RequestTracer tracer = tracerService.getCurrentTracer();
+        assertNotNull(tracer, "Current tracer should not be null");
+        assertTrue(tracer instanceof DefaultRequestTracer, "Current tracer should be an instance of DefaultRequestTracer");
+        
+        // Test that we get the same tracer instance for the same thread
+        RequestTracer secondTracer = tracerService.getCurrentTracer();
+        assertSame(tracer, secondTracer, "Should get same tracer instance within same thread");
+    }
+
+    @Test
+    public void testEnableDisableTracing() {
+        assertFalse(tracerService.isTracingEnabled(), "Tracing should be disabled by default");
+
+        tracerService.enableTracing();
+        assertTrue(tracerService.isTracingEnabled(), "Tracing should be enabled after enableTracing()");
+
+        tracerService.disableTracing();
+        assertFalse(tracerService.isTracingEnabled(), "Tracing should be disabled after disableTracing()");
+        
+        // Verify that enable/disable resets the trace
+        tracerService.enableTracing();
+        RequestTracer tracer = tracerService.getCurrentTracer();
+        tracer.startOperation("test", "description", null);
+        assertNotNull(tracerService.getTraceNode(), "Should have trace node after operation");
+        
+        tracerService.disableTracing();
+        assertNull(tracerService.getTraceNode(), "Trace node should be reset after disable");
+    }
+
+    @Test
+    public void testTracingOperations() {
+        tracerService.enableTracing();
+        RequestTracer tracer = tracerService.getCurrentTracer();
+
+        // Start a root operation
+        tracer.startOperation("test", "Root operation", null);
+        long startTime = System.currentTimeMillis();
+        
+        // Add some traces
+        tracer.trace("Test message", null);
+        tracer.trace("Test with context", "context-data");
+
+        // Start a child operation
+        tracer.startOperation("child", "Child operation", "child-context");
+        tracer.trace("Child trace", null);
+        tracer.endOperation("child-result", "Child completed");
+
+        // End root operation
+        tracer.endOperation("root-result", "Root completed");
+        long endTime = System.currentTimeMillis();
+
+        // Get and verify the trace tree
+        TraceNode rootNode = tracerService.getTraceNode();
+        assertNotNull(rootNode, "Root node should not be null");
+        assertEquals("test", rootNode.getOperationType(), "Root operation type should match");
+        assertEquals("Root completed", rootNode.getDescription(), "Root description should match");
+        assertEquals("root-result", rootNode.getResult(), "Root result should match");
+        assertEquals(2, rootNode.getTraces().size(), "Root should have 2 traces");
+        assertEquals(1, rootNode.getChildren().size(), "Root should have 1 child");
+        assertTrue(rootNode.getStartTime() >= startTime, "Root start time should be valid");
+        assertTrue(rootNode.getEndTime() <= endTime, "Root end time should be valid");
+
+        TraceNode childNode = rootNode.getChildren().get(0);
+        assertEquals("child", childNode.getOperationType(), "Child operation type should match");
+        assertEquals("Child completed", childNode.getDescription(), "Child description should match");
+        assertEquals("child-context", childNode.getContext(), "Child context should match");
+        assertEquals("child-result", childNode.getResult(), "Child result should match");
+        assertEquals(1, childNode.getTraces().size(), "Child should have 1 trace");
+        assertTrue(childNode.getStartTime() >= rootNode.getStartTime(), "Child start time should be after root");
+        assertTrue(childNode.getEndTime() <= rootNode.getEndTime(), "Child end time should be before root end");
+    }
+
+    @Test
+    public void testValidationInfo() {
+        tracerService.enableTracing();
+        RequestTracer tracer = tracerService.getCurrentTracer();
+
+        tracer.startOperation("validation", "Validation test", null);
+        tracer.addValidationInfo(Arrays.asList("error1", "error2"), "test-schema");
+        tracer.endOperation(false, "Validation failed");
+
+        TraceNode node = tracerService.getTraceNode();
+        assertNotNull(node, "Node should not be null");
+        assertEquals(1, node.getTraces().size(), "Should have 1 validation trace");
+        String validationTrace = node.getTraces().get(0);
+        assertTrue(validationTrace.contains("test-schema"), "Validation trace should contain schema id");
+        assertTrue(validationTrace.contains("error1"), "Validation trace should contain first error");
+        assertTrue(validationTrace.contains("error2"), "Validation trace should contain second error");
+    }
+
+    @Test
+    public void testTracingDisabled() {
+        RequestTracer tracer = tracerService.getCurrentTracer();
+
+        // Operations should not create any nodes when tracing is disabled
+        tracer.startOperation("test", "Test operation", null);
+        tracer.trace("Test message", null);
+        tracer.addValidationInfo(Arrays.asList("error"), "schema");
+        tracer.endOperation("result", "Completed");
+
+        assertNull(tracerService.getTraceNode(), "No trace node should be created when tracing is disabled");
+    }
+
+    @Test
+    public void testReset() {
+        tracerService.enableTracing();
+        RequestTracer tracer = tracerService.getCurrentTracer();
+
+        tracer.startOperation("test", "Test operation", null);
+        tracer.trace("Test message", null);
+        assertNotNull(tracerService.getTraceNode(), "Should have trace node before reset");
+
+        tracer.reset();
+        assertNull(tracerService.getTraceNode(), "Should not have trace node after reset");
+        
+        // Verify that tracing is still enabled after reset
+        assertTrue(tracerService.isTracingEnabled(), "Tracing should still be enabled after reset");
+    }
+
+    @Test
+    public void testCleanup() {
+        tracerService.enableTracing();
+        RequestTracer tracer = tracerService.getCurrentTracer();
+
+        tracer.startOperation("test", "Test operation", null);
+        RequestTracer originalTracer = tracerService.getCurrentTracer();
+
+        tracerService.cleanup();
+        RequestTracer newTracer = tracerService.getCurrentTracer();
+
+        assertNotSame(originalTracer, newTracer, "Should get new tracer instance after cleanup");
+        assertFalse(newTracer.isEnabled(), "New tracer should be disabled");
+        assertNull(tracerService.getTraceNode(), "New tracer should have no trace node");
+    }
+
+    @Test
+    public void testThreadSafety() throws InterruptedException {
+        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
+        CountDownLatch startLatch = new CountDownLatch(1);
+        CountDownLatch completionLatch = new CountDownLatch(THREAD_COUNT);
+
+        try {
+            // Create tasks that will all start simultaneously
+            for (int i = 0; i < THREAD_COUNT; i++) {
+                final int threadId = i;
+                executor.submit(() -> {
+                    try {
+                        startLatch.await(); // Wait for all threads to be ready
+                        
+                        RequestTracer tracer = tracerService.getCurrentTracer();
+                        tracer.setEnabled(true);
+                        
+                        // Perform operations specific to this thread
+                        tracer.startOperation("thread-" + threadId, "Thread operation", null);
+                        tracer.trace("Thread message", "Thread-" + threadId);
+                        tracer.endOperation("success", "Thread completed");
+                        
+                        // Verify this thread's trace
+                        TraceNode node = tracer.getTraceNode();
+                        assertNotNull(node, "Thread " + threadId + " should have a trace node");
+                        assertEquals("thread-" + threadId, node.getOperationType(),
+                                "Thread " + threadId + " should have correct operation type");
+                        
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        throw new RuntimeException("Thread interrupted during test", e);
+                    } finally {
+                        completionLatch.countDown();
+                    }
+                });
+            }
+
+            // Start all threads simultaneously
+            startLatch.countDown();
+            
+            // Wait for all threads to complete
+            assertTrue(completionLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS),
+                    "All threads should complete within timeout");
+        } finally {
+            executor.shutdown();
+            if (!executor.awaitTermination(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
+                executor.shutdownNow();
+            }
+        }
+    }
+
+    @Test
+    public void testNullHandling() {
+        tracerService.enableTracing();
+        RequestTracer tracer = tracerService.getCurrentTracer();
+
+        // Test null handling in various operations
+        tracer.startOperation(null, null, null);
+        tracer.trace(null, null);
+        tracer.addValidationInfo(null, null);
+        tracer.endOperation(null, null);
+
+        TraceNode node = tracerService.getTraceNode();
+        assertNotNull(node, "Node should exist even with null values");
+        assertNull(node.getOperationType(), "Operation type should be null");
+        assertNull(node.getDescription(), "Description should be null");
+        assertNull(node.getContext(), "Context should be null");
+        assertNull(node.getResult(), "Result should be null");
+    }
+
+    @Test
+    public void testTraceShouldNotFailWhenContextToStringOverflowsStack() {
+        tracerService.enableTracing();
+        RequestTracer tracer = tracerService.getCurrentTracer();
+
+        tracer.startOperation("test", "Root operation", null);
+        Object badContext = new Object() {
+            @Override
+            public String toString() {
+                return toString();
+            }
+        };
+
+        assertDoesNotThrow(() -> tracer.trace("Test with bad context", badContext),
+                "Tracer.trace should not throw even if context.toString overflows the stack");
+
+        TraceNode rootNode = tracerService.getTraceNode();
+        assertNotNull(rootNode, "Root node should be created");
+        assertEquals(1, rootNode.getTraces().size(), "Trace should be recorded even when context rendering fails");
+        assertTrue(rootNode.getTraces().get(0).contains("StackOverflowError"),
+                "Trace should contain a StackOverflowError marker when context rendering overflows");
+    }
+} 
\ No newline at end of file
diff --git a/wab/pom.xml b/wab/pom.xml
index b391ea26b..2b121a2aa 100644
--- a/wab/pom.xml
+++ b/wab/pom.xml
@@ -46,107 +46,21 @@
             <artifactId>unomi-api</artifactId>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>org.apache.unomi</groupId>
-            <artifactId>unomi-persistence-spi</artifactId>
-            <scope>provided</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.framework</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.util.tracker</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.service.component</artifactId>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.component.annotations</artifactId>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.service.metatype</artifactId>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.metatype.annotations</artifactId>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>javax.servlet-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-databind</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.dataformat</groupId>
-            <artifactId>jackson-dataformat-yaml</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-annotations</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-core</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.ops4j.pax.web</groupId>
-            <artifactId>pax-web-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>commons-io</groupId>
-            <artifactId>commons-io</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>commons-collections</groupId>
-            <artifactId>commons-collections</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.yaml</groupId>
-            <artifactId>snakeyaml</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>commons-beanutils</groupId>
-            <artifactId>commons-beanutils</artifactId>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <scope>provided</scope>
         </dependency>
-
-        <dependency>
-            <groupId>com.opencsv</groupId>
-            <artifactId>opencsv</artifactId>
-        </dependency>
     </dependencies>
 
     <build>
@@ -164,30 +78,6 @@
                     </instructions>
                 </configuration>
             </plugin>
-            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>build-helper-maven-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>attach-artifacts</id>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>attach-artifact</goal>
-                        </goals>
-                        <configuration>
-                            <artifacts>
-                                <artifact>
-                                    <file>
-                                        src/main/resources/org.apache.unomi.web.cfg
-                                    </file>
-                                    <type>cfg</type>
-                                    <classifier>unomicfg</classifier>
-                                </artifact>
-                            </artifacts>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
         </plugins>
     </build>
 
diff --git a/wab/pom.xml b/web-servlets/pom.xml
similarity index 89%
copy from wab/pom.xml
copy to web-servlets/pom.xml
index b391ea26b..d940c6276 100644
--- a/wab/pom.xml
+++ b/web-servlets/pom.xml
@@ -23,9 +23,9 @@
         <artifactId>unomi-root</artifactId>
         <version>3.1.0-SNAPSHOT</version>
     </parent>
-    <artifactId>unomi-wab</artifactId>
-    <name>Apache Unomi :: Core Web Application</name>
-    <description>Apache Unomi Context Server Core Web application</description>
+    <artifactId>unomi-web-servlets</artifactId>
+    <name>Apache Unomi :: Core Web Servlets</name>
+    <description>Apache Unomi Context Server Core Web Servlets</description>
     <packaging>bundle</packaging>
 
     <dependencyManagement>
@@ -41,6 +41,7 @@
     </dependencyManagement>
 
     <dependencies>
+        <!-- Unomi dependencies -->
         <dependency>
             <groupId>org.apache.unomi</groupId>
             <artifactId>unomi-api</artifactId>
@@ -51,7 +52,13 @@
             <artifactId>unomi-persistence-spi</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-rest</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
+        <!-- Standard API Dependencies -->
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.framework</artifactId>
@@ -87,6 +94,8 @@
             <artifactId>javax.servlet-api</artifactId>
             <scope>provided</scope>
         </dependency>
+
+        <!-- Libraries -->
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
@@ -142,7 +151,6 @@
             <artifactId>slf4j-api</artifactId>
             <scope>provided</scope>
         </dependency>
-
         <dependency>
             <groupId>com.opencsv</groupId>
             <artifactId>opencsv</artifactId>
@@ -151,19 +159,6 @@
 
     <build>
         <plugins>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <configuration>
-                    <instructions>
-                        <_wab>src/main/webapp</_wab>
-                        <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
-                        <Embed-Directory>WEB-INF/lib</Embed-Directory>
-                        <Web-ContextPath>/</Web-ContextPath>
-                    </instructions>
-                </configuration>
-            </plugin>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>build-helper-maven-plugin</artifactId>
diff --git a/wab/src/main/java/org/apache/unomi/web/ClientServlet.java b/web-servlets/src/main/java/org/apache/unomi/web/servlets/ClientServlet.java
similarity index 84%
rename from wab/src/main/java/org/apache/unomi/web/ClientServlet.java
rename to web-servlets/src/main/java/org/apache/unomi/web/servlets/ClientServlet.java
index 94850e6ad..e3cb35acb 100644
--- a/wab/src/main/java/org/apache/unomi/web/ClientServlet.java
+++ b/web-servlets/src/main/java/org/apache/unomi/web/servlets/ClientServlet.java
@@ -15,25 +15,33 @@
  * limitations under the License.
  */
 
-package org.apache.unomi.web;
+package org.apache.unomi.web.servlets;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.servlet.Servlet;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import org.osgi.service.component.annotations.Component;
 
 /**
  * @deprecated this servlet is now deprecated, because it have been migrated to REST endpoint.
  * A servlet filter to serve a context-specific Javascript containing the current request context object.
  */
 @Deprecated
-@WebServlet(name = "ClientServlet", urlPatterns = "/client/*")
+@Component(
+        service = Servlet.class,
+        immediate = true,
+        property = {
+                "osgi.http.whiteboard.servlet.name=ClientServlet",
+                "osgi.http.whiteboard.servlet.pattern=/client/*"
+        }
+)
 public class ClientServlet extends HttpServlet {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(ClientServlet.class.getName());
diff --git a/wab/src/main/java/org/apache/unomi/web/ContextServlet.java b/web-servlets/src/main/java/org/apache/unomi/web/servlets/ContextServlet.java
similarity index 82%
rename from wab/src/main/java/org/apache/unomi/web/ContextServlet.java
rename to web-servlets/src/main/java/org/apache/unomi/web/servlets/ContextServlet.java
index 6c04f60b6..a86f5d06c 100644
--- a/wab/src/main/java/org/apache/unomi/web/ContextServlet.java
+++ b/web-servlets/src/main/java/org/apache/unomi/web/servlets/ContextServlet.java
@@ -15,25 +15,34 @@
  * limitations under the License.
  */
 
-package org.apache.unomi.web;
+package org.apache.unomi.web.servlets;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.servlet.Servlet;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import org.osgi.service.component.annotations.Component;
 
 /**
  * @deprecated this servlet is now deprecated, because it have been migrated to REST endpoint.
  * A servlet filter to serve a context-specific Javascript containing the current request context object.
  */
 @Deprecated
-@WebServlet(name = "ContextServlet", urlPatterns = {"/context.json", "/context.js"})
+@Component(
+        service = Servlet.class,
+        immediate = true,
+        property = {
+                "osgi.http.whiteboard.servlet.name=ContextServlet",
+                "osgi.http.whiteboard.servlet.pattern=/context.json",
+                "osgi.http.whiteboard.servlet.pattern=/context.js"
+        }
+)
 public class ContextServlet extends HttpServlet {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(ContextServlet.class.getName());
diff --git a/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java b/web-servlets/src/main/java/org/apache/unomi/web/servlets/EventsCollectorServlet.java
similarity index 83%
rename from wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
rename to web-servlets/src/main/java/org/apache/unomi/web/servlets/EventsCollectorServlet.java
index bb72b2cb2..2a953c57f 100644
--- a/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
+++ b/web-servlets/src/main/java/org/apache/unomi/web/servlets/EventsCollectorServlet.java
@@ -15,24 +15,32 @@
  * limitations under the License.
  */
 
-package org.apache.unomi.web;
+package org.apache.unomi.web.servlets;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.servlet.Servlet;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import org.osgi.service.component.annotations.Component;
 
 /**
  * @deprecated this servlet is now deprecated, because it have been migrated to REST endpoint.
  */
 @Deprecated
-@WebServlet(name = "EventsCollectorServlet", urlPatterns = "/eventcollector")
+@Component(
+        service = Servlet.class,
+        immediate = true,
+        property = {
+                "osgi.http.whiteboard.servlet.name=EventsCollectorServlet",
+                "osgi.http.whiteboard.servlet.pattern=/eventcollector"
+        }
+)
 public class EventsCollectorServlet extends HttpServlet {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(EventsCollectorServlet.class.getName());
diff --git a/wab/src/main/java/org/apache/unomi/web/HttpServletRequestForwardWrapper.java b/web-servlets/src/main/java/org/apache/unomi/web/servlets/HttpServletRequestForwardWrapper.java
similarity index 98%
rename from wab/src/main/java/org/apache/unomi/web/HttpServletRequestForwardWrapper.java
rename to web-servlets/src/main/java/org/apache/unomi/web/servlets/HttpServletRequestForwardWrapper.java
index 21399ddc4..b801754f4 100644
--- a/wab/src/main/java/org/apache/unomi/web/HttpServletRequestForwardWrapper.java
+++ b/web-servlets/src/main/java/org/apache/unomi/web/servlets/HttpServletRequestForwardWrapper.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.web;
+package org.apache.unomi.web.servlets;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/wab/src/main/java/org/apache/unomi/web/HttpUtils.java b/web-servlets/src/main/java/org/apache/unomi/web/servlets/HttpUtils.java
similarity index 98%
rename from wab/src/main/java/org/apache/unomi/web/HttpUtils.java
rename to web-servlets/src/main/java/org/apache/unomi/web/servlets/HttpUtils.java
index 664d105d0..ddc9dff31 100644
--- a/wab/src/main/java/org/apache/unomi/web/HttpUtils.java
+++ b/web-servlets/src/main/java/org/apache/unomi/web/servlets/HttpUtils.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.unomi.web;
+package org.apache.unomi.web.servlets;
 
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
diff --git a/wab/src/main/java/org/apache/unomi/web/WebConfig.java b/web-servlets/src/main/java/org/apache/unomi/web/servlets/WebConfig.java
similarity index 99%
rename from wab/src/main/java/org/apache/unomi/web/WebConfig.java
rename to web-servlets/src/main/java/org/apache/unomi/web/servlets/WebConfig.java
index db45ed8e4..f5d1a145d 100644
--- a/wab/src/main/java/org/apache/unomi/web/WebConfig.java
+++ b/web-servlets/src/main/java/org/apache/unomi/web/servlets/WebConfig.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.web;
+package org.apache.unomi.web.servlets;
 
 import org.apache.unomi.api.services.ConfigSharingService;
 import org.osgi.service.component.annotations.Activate;
diff --git a/wab/src/main/resources/org.apache.unomi.web.cfg b/web-servlets/src/main/resources/org.apache.unomi.web.cfg
similarity index 100%
rename from wab/src/main/resources/org.apache.unomi.web.cfg
rename to web-servlets/src/main/resources/org.apache.unomi.web.cfg