thingsboard-aplcache

Details

diff --git a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
index b79359b..4d1d50e 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
@@ -18,20 +18,64 @@ package org.thingsboard.server.controller;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.thingsboard.server.common.data.audit.AuditLog;
+import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.EntityIdFactory;
 import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
 import org.thingsboard.server.common.data.page.TimePageData;
 import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.exception.ThingsboardException;
 
+import java.util.UUID;
+
 @RestController
 @RequestMapping("/api")
 public class AuditLogController extends BaseController {
 
     @PreAuthorize("hasAuthority('TENANT_ADMIN')")
-    @RequestMapping(value = "/audit/logs/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET)
+    @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"limit"}, method = RequestMethod.GET)
     @ResponseBody
-    public TimePageData<AuditLog> getAuditLogs(
+    public TimePageData<AuditLog> getAuditLogsByCustomerId(
+            @PathVariable("customerId") String strCustomerId,
+            @RequestParam int limit,
+            @RequestParam(required = false) Long startTime,
+            @RequestParam(required = false) Long endTime,
+            @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
+            @RequestParam(required = false) String offset) throws ThingsboardException {
+        try {
+            checkParameter("CustomerId", strCustomerId);
+            TenantId tenantId = getCurrentUser().getTenantId();
+            TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
+            return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/audit/logs/user/{userId}", params = {"limit"}, method = RequestMethod.GET)
+    @ResponseBody
+    public TimePageData<AuditLog> getAuditLogsByUserId(
+            @PathVariable("userId") String strUserId,
+            @RequestParam int limit,
+            @RequestParam(required = false) Long startTime,
+            @RequestParam(required = false) Long endTime,
+            @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
+            @RequestParam(required = false) String offset) throws ThingsboardException {
+        try {
+            checkParameter("UserId", strUserId);
+            TenantId tenantId = getCurrentUser().getTenantId();
+            TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
+            return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET)
+    @ResponseBody
+    public TimePageData<AuditLog> getAuditLogsByEntityId(
             @PathVariable("entityType") String strEntityType,
             @PathVariable("entityId") String strEntityId,
             @RequestParam int limit,
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index 8d08706..0844ce0 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -85,12 +85,16 @@ public class DeviceController extends BaseController {
                             savedDevice.getName(),
                             savedDevice.getType());
 
-            // TODO: refactor to ANNOTATION usage
-            if (device.getId() == null) {
-                logDeviceAction(savedDevice, ActionType.ADDED);
-            } else {
-                logDeviceAction(savedDevice, ActionType.UPDATED);
-            }
+            auditLogService.logEntityAction(
+                    getCurrentUser(),
+                    savedDevice.getId(),
+                    savedDevice.getName(),
+                    savedDevice.getCustomerId(),
+                    device.getId() == null ? ActionType.ADDED : ActionType.UPDATED,
+                    null,
+                    ActionStatus.SUCCESS,
+                    null);
+
 
             return savedDevice;
         } catch (Exception e) {
@@ -107,8 +111,15 @@ public class DeviceController extends BaseController {
             DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
             Device device = checkDeviceId(deviceId);
             deviceService.deleteDevice(deviceId);
-            // TODO: refactor to ANNOTATION usage
-            logDeviceAction(device, ActionType.DELETED);
+            auditLogService.logEntityAction(
+                    getCurrentUser(),
+                    device.getId(),
+                    device.getName(),
+                    device.getCustomerId(),
+                    ActionType.DELETED,
+                    null,
+                    ActionStatus.SUCCESS,
+                    null);
         } catch (Exception e) {
             throw handleException(e);
         }
@@ -189,8 +200,15 @@ public class DeviceController extends BaseController {
             Device device = checkDeviceId(deviceCredentials.getDeviceId());
             DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials));
             actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId());
-            // TODO: refactor to ANNOTATION usage
-            logDeviceAction(device, ActionType.CREDENTIALS_UPDATED);
+            auditLogService.logEntityAction(
+                    getCurrentUser(),
+                    device.getId(),
+                    device.getName(),
+                    device.getCustomerId(),
+                    ActionType.CREDENTIALS_UPDATED,
+                    null,
+                    ActionStatus.SUCCESS,
+                    null);
             return result;
         } catch (Exception e) {
             throw handleException(e);
@@ -321,19 +339,4 @@ public class DeviceController extends BaseController {
             throw handleException(e);
         }
     }
-
-    // TODO: refactor to ANNOTATION usage
-    private void logDeviceAction(Device device, ActionType actionType) throws ThingsboardException {
-        auditLogService.logAction(
-                getCurrentUser().getTenantId(),
-                device.getId(),
-                device.getName(),
-                device.getCustomerId(),
-                getCurrentUser().getId(),
-                getCurrentUser().getName(),
-                actionType,
-                null,
-                ActionStatus.SUCCESS,
-                null);
-    }
 }
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 83630ce..3d8501f 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -244,7 +244,6 @@ spring:
     username: "${SPRING_DATASOURCE_USERNAME:sa}"
     password: "${SPRING_DATASOURCE_PASSWORD:}"
 
-
 # PostgreSQL DAO Configuration
 #spring:
 #  data:
@@ -260,3 +259,8 @@ spring:
 #    url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}"
 #    username: "${SPRING_DATASOURCE_USERNAME:postgres}"
 #    password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
+
+# Audit log parameters
+audit_log:
+  # Enable/disable audit log functionality.
+  enabled: "${AUDIT_LOG_ENABLED:true}"
\ No newline at end of file
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java
index 5e74416..a826ee8 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java
@@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.audit.AuditLog;
 import org.thingsboard.server.common.data.page.TimePageData;
 import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.dao.model.ModelConstants;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -36,6 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 public abstract class BaseAuditLogControllerTest extends AbstractControllerTest {
 
     private Tenant savedTenant;
+    private User tenantAdmin;
 
     @Before
     public void beforeTest() throws Exception {
@@ -46,14 +48,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest 
         savedTenant = doPost("/api/tenant", tenant, Tenant.class);
         Assert.assertNotNull(savedTenant);
 
-        User tenantAdmin = new User();
+        tenantAdmin = new User();
         tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
         tenantAdmin.setTenantId(savedTenant.getId());
         tenantAdmin.setEmail("tenant2@thingsboard.org");
         tenantAdmin.setFirstName("Joe");
         tenantAdmin.setLastName("Downs");
 
-        createUserAndLogin(tenantAdmin, "testPassword1");
+        tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
     }
 
     @After
@@ -65,7 +67,7 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest 
     }
 
     @Test
-    public void testSaveDeviceAuditLogs() throws Exception {
+    public void testAuditLogs() throws Exception {
         for (int i = 0; i < 178; i++) {
             Device device = new Device();
             device.setName("Device" + i);
@@ -87,10 +89,38 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest 
         } while (pageData.hasNext());
 
         Assert.assertEquals(178, loadedAuditLogs.size());
+
+        loadedAuditLogs = new ArrayList<>();
+        pageLink = new TimePageLink(23);
+        do {
+            pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?",
+                    new TypeReference<TimePageData<AuditLog>>() {
+                    }, pageLink);
+            loadedAuditLogs.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Assert.assertEquals(178, loadedAuditLogs.size());
+
+        loadedAuditLogs = new ArrayList<>();
+        pageLink = new TimePageLink(23);
+        do {
+            pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?",
+                    new TypeReference<TimePageData<AuditLog>>() {
+                    }, pageLink);
+            loadedAuditLogs.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Assert.assertEquals(178, loadedAuditLogs.size());
     }
 
     @Test
-    public void testUpdateDeviceAuditLogs() throws Exception {
+    public void testAuditLogs_byTenantIdAndEntityId() throws Exception {
         Device device = new Device();
         device.setName("Device name");
         device.setType("default");
@@ -104,7 +134,7 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest 
         TimePageLink pageLink = new TimePageLink(23);
         TimePageData<AuditLog> pageData;
         do {
-            pageData = doGetTypedWithTimePageLink("/api/audit/logs/DEVICE/" + savedDevice.getId().getId() + "?",
+            pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?",
                     new TypeReference<TimePageData<AuditLog>>() {
                     }, pageLink);
             loadedAuditLogs.addAll(pageData.getData());
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java
index f401ad1..8562b6a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java
@@ -17,7 +17,9 @@ package org.thingsboard.server.dao.audit;
 
 import com.google.common.util.concurrent.ListenableFuture;
 import org.thingsboard.server.common.data.audit.AuditLog;
+import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.UserId;
 import org.thingsboard.server.common.data.page.TimePageLink;
 
 import java.util.List;
@@ -29,9 +31,17 @@ public interface AuditLogDao {
 
     ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog);
 
+    ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog);
+
+    ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog);
+
     ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog);
 
     List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink);
 
+    List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink);
+
+    List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink);
+
     List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink);
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogQueryCursor.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogQueryCursor.java
new file mode 100644
index 0000000..78b043b
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogQueryCursor.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.server.dao.audit;
+
+import lombok.Getter;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.dao.model.nosql.AuditLogEntity;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class AuditLogQueryCursor {
+    @Getter
+    private final UUID tenantId;
+    @Getter
+    private final List<AuditLogEntity> data;
+    @Getter
+    private final TimePageLink pageLink;
+
+    private final List<Long> partitions;
+
+    private int partitionIndex;
+    private int currentLimit;
+
+    public AuditLogQueryCursor(UUID tenantId, TimePageLink pageLink, List<Long> partitions) {
+        this.tenantId = tenantId;
+        this.partitions = partitions;
+        this.partitionIndex = partitions.size() - 1;
+        this.data = new ArrayList<>();
+        this.currentLimit = pageLink.getLimit();
+        this.pageLink = pageLink;
+    }
+
+    public boolean hasNextPartition() {
+        return partitionIndex >= 0;
+    }
+
+    public boolean isFull() {
+        return currentLimit <= 0;
+    }
+
+    public long getNextPartition() {
+        long partition = partitions.get(partitionIndex);
+        partitionIndex--;
+        return partition;
+    }
+
+    public int getCurrentLimit() {
+        return currentLimit;
+    }
+
+    public void addData(List<AuditLogEntity> newData) {
+        currentLimit -= newData.size();
+        data.addAll(newData);
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
index 5593ede..d6e61ab 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.audit;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.server.common.data.User;
 import org.thingsboard.server.common.data.audit.ActionStatus;
 import org.thingsboard.server.common.data.audit.ActionType;
 import org.thingsboard.server.common.data.audit.AuditLog;
@@ -31,20 +32,21 @@ import java.util.List;
 
 public interface AuditLogService {
 
+    TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink);
+
+    TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink);
+
     TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink);
 
     TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink);
 
-    ListenableFuture<List<Void>> logAction(TenantId tenantId,
-                                           EntityId entityId,
-                                           String entityName,
-                                           CustomerId customerId,
-                                           UserId userId,
-                                           String userName,
-                                           ActionType actionType,
-                                           JsonNode actionData,
-                                           ActionStatus actionStatus,
-                                           String actionFailureDetails);
-
+    ListenableFuture<List<Void>> logEntityAction(User user,
+                                                 EntityId entityId,
+                                                 String entityName,
+                                                 CustomerId customerId,
+                                                 ActionType actionType,
+                                                 JsonNode actionData,
+                                                 ActionStatus actionStatus,
+                                                 String actionFailureDetails);
 
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
index 1baf573..1ee26e5 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
@@ -15,20 +15,20 @@
  */
 package org.thingsboard.server.dao.audit;
 
+import com.datastax.driver.core.utils.UUIDs;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.User;
 import org.thingsboard.server.common.data.audit.ActionStatus;
 import org.thingsboard.server.common.data.audit.ActionType;
 import org.thingsboard.server.common.data.audit.AuditLog;
-import org.thingsboard.server.common.data.id.CustomerId;
-import org.thingsboard.server.common.data.id.EntityId;
-import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.common.data.id.*;
 import org.thingsboard.server.common.data.page.TimePageData;
 import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.dao.exception.DataValidationException;
@@ -41,6 +41,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
 
 @Slf4j
 @Service
+@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true")
 public class AuditLogServiceImpl implements AuditLogService {
 
     private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@@ -50,6 +51,24 @@ public class AuditLogServiceImpl implements AuditLogService {
     private AuditLogDao auditLogDao;
 
     @Override
+    public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
+        log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink);
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        validateId(customerId, "Incorrect customerId " + customerId);
+        List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndCustomerId(tenantId.getId(), customerId, pageLink);
+        return new TimePageData<>(auditLogs, pageLink);
+    }
+
+    @Override
+    public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) {
+        log.trace("Executing findAuditLogsByTenantIdAndUserId [{}], [{}], [{}]", tenantId, userId, pageLink);
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        validateId(userId, "Incorrect userId" + userId);
+        List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndUserId(tenantId.getId(), userId, pageLink);
+        return new TimePageData<>(auditLogs, pageLink);
+    }
+
+    @Override
     public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) {
         log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink);
         validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
@@ -66,6 +85,28 @@ public class AuditLogServiceImpl implements AuditLogService {
         return new TimePageData<>(auditLogs, pageLink);
     }
 
+    @Override
+    public ListenableFuture<List<Void>> logEntityAction(User user,
+                                                        EntityId entityId,
+                                                        String entityName,
+                                                        CustomerId customerId,
+                                                        ActionType actionType,
+                                                        JsonNode actionData,
+                                                        ActionStatus actionStatus,
+                                                        String actionFailureDetails) {
+        return logAction(
+                user.getTenantId(),
+                entityId,
+                entityName,
+                customerId,
+                user.getId(),
+                user.getName(),
+                actionType,
+                actionData,
+                actionStatus,
+                actionFailureDetails);
+    }
+
     private AuditLog createAuditLogEntry(TenantId tenantId,
                                          EntityId entityId,
                                          String entityName,
@@ -77,6 +118,7 @@ public class AuditLogServiceImpl implements AuditLogService {
                                          ActionStatus actionStatus,
                                          String actionFailureDetails) {
         AuditLog result = new AuditLog();
+        result.setId(new AuditLogId(UUIDs.timeBased()));
         result.setTenantId(tenantId);
         result.setEntityId(entityId);
         result.setEntityName(entityName);
@@ -90,17 +132,16 @@ public class AuditLogServiceImpl implements AuditLogService {
         return result;
     }
 
-    @Override
-    public ListenableFuture<List<Void>> logAction(TenantId tenantId,
-                                                  EntityId entityId,
-                                                  String entityName,
-                                                  CustomerId customerId,
-                                                  UserId userId,
-                                                  String userName,
-                                                  ActionType actionType,
-                                                  JsonNode actionData,
-                                                  ActionStatus actionStatus,
-                                                  String actionFailureDetails) {
+    private ListenableFuture<List<Void>> logAction(TenantId tenantId,
+                                                   EntityId entityId,
+                                                   String entityName,
+                                                   CustomerId customerId,
+                                                   UserId userId,
+                                                   String userName,
+                                                   ActionType actionType,
+                                                   JsonNode actionData,
+                                                   ActionStatus actionStatus,
+                                                   String actionFailureDetails) {
         AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName,
                 actionType, actionData, actionStatus, actionFailureDetails);
         log.trace("Executing logAction [{}]", auditLogEntry);
@@ -109,6 +150,8 @@ public class AuditLogServiceImpl implements AuditLogService {
         futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry));
         futures.add(auditLogDao.saveByTenantId(auditLogEntry));
         futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry));
+        futures.add(auditLogDao.saveByTenantIdAndCustomerId(auditLogEntry));
+        futures.add(auditLogDao.saveByTenantIdAndUserId(auditLogEntry));
         return Futures.allAsList(futures);
     }
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java
index 2fcb732..8262933 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java
@@ -19,7 +19,8 @@ import com.datastax.driver.core.BoundStatement;
 import com.datastax.driver.core.PreparedStatement;
 import com.datastax.driver.core.ResultSet;
 import com.datastax.driver.core.ResultSetFuture;
-import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
 import com.google.common.base.Function;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -29,8 +30,9 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.core.env.Environment;
 import org.springframework.stereotype.Component;
 import org.thingsboard.server.common.data.audit.AuditLog;
-import org.thingsboard.server.common.data.id.AuditLogId;
+import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.UserId;
 import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.dao.DaoUtil;
 import org.thingsboard.server.dao.model.ModelConstants;
@@ -52,6 +54,7 @@ import java.util.Optional;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
 
 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -82,7 +85,11 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
     private String partitioning;
     private TsPartitionDate tsFormat;
 
-    private PreparedStatement[] saveStmts;
+    private PreparedStatement partitionInsertStmt;
+    private PreparedStatement saveByTenantStmt;
+    private PreparedStatement saveByTenantIdAndUserIdStmt;
+    private PreparedStatement saveByTenantIdAndEntityIdStmt;
+    private PreparedStatement saveByTenantIdAndCustomerIdStmt;
 
     private boolean isInstall() {
         return environment.acceptsProfiles("install");
@@ -123,11 +130,9 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
     public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) {
         log.debug("Save saveByTenantId [{}] ", auditLog);
 
-        AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased());
-
         long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
         BoundStatement stmt = getSaveByTenantStmt().bind();
-        stmt.setUUID(0, auditLogId.getId())
+        stmt = stmt.setUUID(0, auditLog.getId().getId())
                 .setUUID(1, auditLog.getTenantId().getId())
                 .setUUID(2, auditLog.getEntityId().getId())
                 .setString(3, auditLog.getEntityId().getEntityType().name())
@@ -140,18 +145,45 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
     public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) {
         log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog);
 
-        AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased());
-
         BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind();
-        stmt.setUUID(0, auditLogId.getId())
-                .setUUID(1, auditLog.getTenantId().getId())
-                .setUUID(2, auditLog.getEntityId().getId())
-                .setString(3, auditLog.getEntityId().getEntityType().name())
-                .setString(4, auditLog.getActionType().name());
+        stmt = setSaveStmtVariables(stmt, auditLog);
         return getFuture(executeAsyncWrite(stmt), rs -> null);
     }
 
     @Override
+    public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) {
+        log.debug("Save saveByTenantIdAndCustomerId [{}] ", auditLog);
+
+        BoundStatement stmt = getSaveByTenantIdAndCustomerIdStmt().bind();
+        stmt = setSaveStmtVariables(stmt, auditLog);
+        return getFuture(executeAsyncWrite(stmt), rs -> null);
+    }
+
+    @Override
+    public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) {
+        log.debug("Save saveByTenantIdAndUserId [{}] ", auditLog);
+
+        BoundStatement stmt = getSaveByTenantIdAndUserIdStmt().bind();
+        stmt = setSaveStmtVariables(stmt, auditLog);
+        return getFuture(executeAsyncWrite(stmt), rs -> null);
+    }
+
+    private BoundStatement setSaveStmtVariables(BoundStatement stmt, AuditLog auditLog) {
+        return stmt.setUUID(0, auditLog.getId().getId())
+                .setUUID(1, auditLog.getTenantId().getId())
+                .setUUID(2, auditLog.getCustomerId().getId())
+                .setUUID(3, auditLog.getEntityId().getId())
+                .setString(4, auditLog.getEntityId().getEntityType().name())
+                .setString(5, auditLog.getEntityName())
+                .setUUID(6, auditLog.getUserId().getId())
+                .setString(7, auditLog.getUserName())
+                .setString(8, auditLog.getActionType().name())
+                .setString(9, auditLog.getActionData() != null ? auditLog.getActionData().toString() : null)
+                .setString(10, auditLog.getActionStatus().name())
+                .setString(11, auditLog.getActionFailureDetails());
+    }
+
+    @Override
     public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) {
         log.debug("Save savePartitionsByTenantId [{}] ", auditLog);
 
@@ -163,35 +195,66 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
         return getFuture(executeAsyncWrite(stmt), rs -> null);
     }
 
-    private PreparedStatement getPartitionInsertStmt() {
-        // TODO: ADD CACHE LOGIC
-        return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF +
-                "(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
-                "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +
-                " VALUES(?, ?)");
+    private PreparedStatement getSaveByTenantIdAndEntityIdStmt() {
+        if (saveByTenantIdAndEntityIdStmt == null) {
+            saveByTenantIdAndEntityIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF);
+        }
+        return saveByTenantIdAndEntityIdStmt;
     }
 
-    private PreparedStatement getSaveByTenantIdAndEntityIdStmt() {
-        // TODO: ADD CACHE LOGIC
-        return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF +
-                "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY +
-                "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
-                "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY +
-                "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY +
-                "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY + ")" +
-                " VALUES(?, ?, ?, ?, ?)");
+    private PreparedStatement getSaveByTenantIdAndCustomerIdStmt() {
+        if (saveByTenantIdAndCustomerIdStmt == null) {
+            saveByTenantIdAndCustomerIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_CUSTOMER_ID_CF);
+        }
+        return saveByTenantIdAndCustomerIdStmt;
     }
 
-    private PreparedStatement getSaveByTenantStmt() {
-        // TODO: ADD CACHE LOGIC
-        return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF +
+    private PreparedStatement getSaveByTenantIdAndUserIdStmt() {
+        if (saveByTenantIdAndUserIdStmt == null) {
+            saveByTenantIdAndUserIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_USER_ID_CF);
+        }
+        return saveByTenantIdAndUserIdStmt;
+    }
+
+    private PreparedStatement getSaveByTenantIdAndCFName(String cfName) {
+        return getSession().prepare(INSERT_INTO + cfName +
                 "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY +
                 "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
+                "," + ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY +
                 "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY +
                 "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY +
+                "," + ModelConstants.AUDIT_LOG_ENTITY_NAME_PROPERTY +
+                "," + ModelConstants.AUDIT_LOG_USER_ID_PROPERTY +
+                "," + ModelConstants.AUDIT_LOG_USER_NAME_PROPERTY +
                 "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY +
-                "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +
-                " VALUES(?, ?, ?, ?, ?, ?)");
+                "," + ModelConstants.AUDIT_LOG_ACTION_DATA_PROPERTY +
+                "," + ModelConstants.AUDIT_LOG_ACTION_STATUS_PROPERTY +
+                "," + ModelConstants.AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY + ")" +
+                " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+    }
+
+    private PreparedStatement getPartitionInsertStmt() {
+        if (partitionInsertStmt == null) {
+            partitionInsertStmt = getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF +
+                    "(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
+                    "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +
+                    " VALUES(?, ?)");
+        }
+        return partitionInsertStmt;
+    }
+
+    private PreparedStatement getSaveByTenantStmt() {
+        if (saveByTenantStmt == null) {
+            saveByTenantStmt = getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF +
+                    "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY +
+                    "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
+                    "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY +
+                    "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY +
+                    "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY +
+                    "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +
+                    " VALUES(?, ?, ?, ?, ?, ?)");
+        }
+        return saveByTenantStmt;
     }
 
     private long toPartitionTs(long ts) {
@@ -199,7 +262,6 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
         return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli();
     }
 
-
     @Override
     public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) {
         log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink);
@@ -213,30 +275,75 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
     }
 
     @Override
+    public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) {
+        log.trace("Try to find audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink);
+        List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_CUSTOMER_ID_CF,
+                Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId),
+                        eq(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY, customerId.getId())),
+                pageLink);
+        log.trace("Found audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink);
+        return DaoUtil.convertDataList(entities);
+    }
+
+    @Override
+    public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) {
+        log.trace("Try to find audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink);
+        List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_USER_ID_CF,
+                Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId),
+                        eq(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY, userId.getId())),
+                pageLink);
+        log.trace("Found audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink);
+        return DaoUtil.convertDataList(entities);
+    }
+
+    @Override
     public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) {
         log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink);
 
-        // TODO: ADD AUDIT LOG PARTITION CURSOR LOGIC
-
         long minPartition;
-        long maxPartition;
-
         if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) {
             minPartition = toPartitionTs(pageLink.getStartTime());
         } else {
             minPartition = toPartitionTs(LocalDate.now().minusMonths(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
         }
 
+        long maxPartition;
         if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) {
             maxPartition = toPartitionTs(pageLink.getEndTime());
         } else {
             maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
         }
-        List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF,
-                Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId),
-                        eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, maxPartition)),
-                pageLink);
+
+        List<Long> partitions = fetchPartitions(tenantId, minPartition, maxPartition)
+                .all()
+                .stream()
+                .map(row -> row.getLong(ModelConstants.PARTITION_COLUMN))
+                .collect(Collectors.toList());
+
+        AuditLogQueryCursor cursor = new AuditLogQueryCursor(tenantId, pageLink, partitions);
+        List<AuditLogEntity> entities = fetchSequentiallyWithLimit(cursor);
         log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink);
         return DaoUtil.convertDataList(entities);
     }
+
+    private List<AuditLogEntity> fetchSequentiallyWithLimit(AuditLogQueryCursor cursor) {
+        if (cursor.isFull() || !cursor.hasNextPartition()) {
+            return cursor.getData();
+        } else {
+            cursor.addData(findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF,
+                    Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, cursor.getTenantId()),
+                            eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, cursor.getNextPartition())),
+                    cursor.getPageLink()));
+            return fetchSequentiallyWithLimit(cursor);
+        }
+    }
+
+    private ResultSet fetchPartitions(UUID tenantId, long minPartition, long maxPartition) {
+        Select.Where select = QueryBuilder.select(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY).from(ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF)
+                .where(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId));
+        select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition));
+        select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition));
+        return getSession().execute(select);
+    }
+
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java
new file mode 100644
index 0000000..c610769
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.server.dao.audit;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.audit.ActionStatus;
+import org.thingsboard.server.common.data.audit.ActionType;
+import org.thingsboard.server.common.data.audit.AuditLog;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
+
+import java.util.List;
+
+@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false")
+public class DummyAuditLogServiceImpl implements AuditLogService {
+
+    @Override
+    public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
+        return null;
+    }
+
+    @Override
+    public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) {
+        return null;
+    }
+
+    @Override
+    public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) {
+        return null;
+    }
+
+    @Override
+    public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) {
+        return null;
+    }
+
+    @Override
+    public ListenableFuture<List<Void>> logEntityAction(User user, EntityId entityId, String entityName, CustomerId customerId, ActionType actionType, JsonNode actionData, ActionStatus actionStatus, String actionFailureDetails) {
+        return null;
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
index 58c8d2f..66e03b0 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -147,6 +147,8 @@ public class ModelConstants {
     public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log";
 
     public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id";
+    public static final String AUDIT_LOG_BY_CUSTOMER_ID_CF = "audit_log_by_customer_id";
+    public static final String AUDIT_LOG_BY_USER_ID_CF = "audit_log_by_user_id";
     public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id";
     public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions";
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java
index 7e4fdc1..bac44c5 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java
@@ -41,4 +41,20 @@ public interface AuditLogRepository extends CrudRepository<AuditLogEntity, Strin
                                                    @Param("entityType") EntityType entityType,
                                                    @Param("idOffset") String idOffset,
                                                    Pageable pageable);
+
+    @Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
+            "AND al.customerId = :customerId " +
+            "AND al.id > :idOffset ORDER BY al.id")
+    List<AuditLogEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
+                                                     @Param("customerId") String customerId,
+                                                     @Param("idOffset") String idOffset,
+                                                     Pageable pageable);
+
+    @Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
+            "AND al.userId = :userId " +
+            "AND al.id > :idOffset ORDER BY al.id")
+    List<AuditLogEntity> findByTenantIdAndUserId(@Param("tenantId") String tenantId,
+                                                 @Param("userId") String userId,
+                                                 @Param("idOffset") String idOffset,
+                                                 Pageable pageable);
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java
index fe7edf8..b4abf07 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java
@@ -23,7 +23,9 @@ import org.springframework.data.domain.PageRequest;
 import org.springframework.data.repository.CrudRepository;
 import org.springframework.stereotype.Component;
 import org.thingsboard.server.common.data.audit.AuditLog;
+import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.UserId;
 import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.dao.DaoUtil;
 import org.thingsboard.server.dao.audit.AuditLogDao;
@@ -77,6 +79,16 @@ public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> imp
     }
 
     @Override
+    public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) {
+        return insertService.submit(() -> null);
+    }
+
+    @Override
+    public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) {
+        return insertService.submit(() -> null);
+    }
+
+    @Override
     public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) {
         return insertService.submit(() -> null);
     }
@@ -93,6 +105,26 @@ public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> imp
     }
 
     @Override
+    public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) {
+        return DaoUtil.convertDataList(
+                auditLogRepository.findByTenantIdAndCustomerId(
+                        fromTimeUUID(tenantId),
+                        fromTimeUUID(customerId.getId()),
+                        pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
+                        new PageRequest(0, pageLink.getLimit())));
+    }
+
+    @Override
+    public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) {
+        return DaoUtil.convertDataList(
+                auditLogRepository.findByTenantIdAndUserId(
+                        fromTimeUUID(tenantId),
+                        fromTimeUUID(userId.getId()),
+                        pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
+                        new PageRequest(0, pageLink.getLimit())));
+    }
+
+    @Override
     public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) {
         return DaoUtil.convertDataList(
                 auditLogRepository.findByTenantId(
diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql
index 6dc2bec..bc7884d 100644
--- a/dao/src/main/resources/cassandra/schema.cql
+++ b/dao/src/main/resources/cassandra/schema.cql
@@ -566,6 +566,40 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id (
   PRIMARY KEY ((tenant_id, entity_id, entity_type), id)
 );
 
+CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id (
+  tenant_id timeuuid,
+  id timeuuid,
+  customer_id timeuuid,
+  entity_id timeuuid,
+  entity_type text,
+  entity_name text,
+  user_id timeuuid,
+  user_name text,
+  action_type text,
+  action_data text,
+  action_status text,
+  action_failure_details text,
+  PRIMARY KEY ((tenant_id, customer_id), id)
+);
+
+CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id (
+  tenant_id timeuuid,
+  id timeuuid,
+  customer_id timeuuid,
+  entity_id timeuuid,
+  entity_type text,
+  entity_name text,
+  user_id timeuuid,
+  user_name text,
+  action_type text,
+  action_data text,
+  action_status text,
+  action_failure_details text,
+  PRIMARY KEY ((tenant_id, user_id), id)
+);
+
+
+
 CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id (
   tenant_id timeuuid,
   id timeuuid,
diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties
index d87a181..cbca287 100644
--- a/dao/src/test/resources/application-test.properties
+++ b/dao/src/test/resources/application-test.properties
@@ -7,4 +7,6 @@ zk.enabled=false
 zk.url=localhost:2181
 zk.zk_dir=/thingsboard
 
-updates.enabled=false
\ No newline at end of file
+updates.enabled=false
+
+audit_log.enabled=true
\ No newline at end of file