Skip to main content
Refine’s audit log system automatically tracks all CRUD operations, providing a complete history of data changes for compliance, debugging, and user activity monitoring.

Understanding Audit Log Providers

An audit log provider implements methods to log and query data changes:
import { AuditLogProvider } from "@refinedev/core";

const auditLogProvider: AuditLogProvider = {
  create: (params: {
    resource: string;
    action: string;
    data?: any;
    author?: any;
    meta?: Record<string, any>;
  }) => Promise<void>,
  
  get: (params: {
    resource: string;
    action?: string;
    author?: any;
    meta?: Record<string, any>;
  }) => Promise<any[]>,
  
  update: (params: {
    id: string;
    name: string;
  }) => Promise<any>,
};

Quick Setup

1
Create Audit Log Service
2
First, create a backend service to store audit logs:
3
interface AuditLog {
  id: string;
  resource: string;
  action: string;
  data?: any;
  previousData?: any;
  author?: {
    id: string;
    name: string;
  };
  timestamp: Date;
  meta?: Record<string, any>;
}

const auditLogs: AuditLog[] = [];

export const auditLogService = {
  create: async (log: Omit<AuditLog, "id" | "timestamp">) => {
    const newLog: AuditLog = {
      ...log,
      id: Date.now().toString(),
      timestamp: new Date(),
    };
    auditLogs.push(newLog);
    return newLog;
  },

  get: async (filters: Partial<AuditLog>) => {
    return auditLogs.filter((log) => {
      if (filters.resource && log.resource !== filters.resource) return false;
      if (filters.action && log.action !== filters.action) return false;
      if (filters.author && log.author?.id !== filters.author.id) return false;
      return true;
    });
  },

  getAll: () => auditLogs,
};
4
Create Audit Log Provider
5
import { AuditLogProvider } from "@refinedev/core";

export const auditLogProvider: AuditLogProvider = {
  create: async (params) => {
    const { resource, action, data, author, meta } = params;

    await fetch("http://localhost:3001/audit-logs", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        resource,
        action,
        data,
        author,
        meta,
        timestamp: new Date(),
      }),
    });
  },

  get: async (params) => {
    const { resource, action, author, meta } = params;

    const queryParams = new URLSearchParams();
    if (resource) queryParams.append("resource", resource);
    if (action) queryParams.append("action", action);
    if (author?.id) queryParams.append("authorId", author.id);

    const response = await fetch(
      `http://localhost:3001/audit-logs?${queryParams}`
    );
    
    return response.json();
  },

  update: async (params) => {
    const { id, name } = params;

    await fetch(`http://localhost:3001/audit-logs/${id}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name }),
    });

    return { id, name };
  },
};
6
Configure Refine
7
import { Refine } from "@refinedev/core";
import { auditLogProvider } from "./auditLogProvider";

const App = () => (
  <Refine
    auditLogProvider={auditLogProvider}
    // ... other props
  >
    {/* Your app */}
  </Refine>
);

Automatic Logging

With an audit log provider configured, Refine automatically logs all CRUD operations:

Create Operations

import { useCreate } from "@refinedev/core";

const PostCreate = () => {
  const { mutate } = useCreate();

  const handleSubmit = (values: any) => {
    mutate({
      resource: "posts",
      values: { title: "New Post", content: "..." },
    });
    
    // Automatically logs:
    // {
    //   resource: "posts",
    //   action: "create",
    //   data: { title: "New Post", content: "..." },
    //   author: { id: "1", name: "John Doe" },
    //   timestamp: "2024-01-15T10:30:00Z",
    // }
  };
};

Update Operations

import { useUpdate } from "@refinedev/core";

const PostEdit = () => {
  const { mutate } = useUpdate();

  const handleSubmit = (values: any) => {
    mutate({
      resource: "posts",
      id: "1",
      values: { title: "Updated Title" },
    });
    
    // Logs:
    // {
    //   resource: "posts",
    //   action: "update",
    //   data: { id: "1", title: "Updated Title" },
    //   previousData: { id: "1", title: "Old Title" },
    //   author: { id: "1", name: "John Doe" },
    // }
  };
};

Delete Operations

import { useDelete } from "@refinedev/core";

const PostList = () => {
  const { mutate } = useDelete();

  const handleDelete = (id: string) => {
    mutate({
      resource: "posts",
      id,
    });
    
    // Logs:
    // {
    //   resource: "posts",
    //   action: "delete",
    //   data: { id: "1" },
    //   previousData: { id: "1", title: "...", content: "..." },
    // }
  };
};

Viewing Audit Logs

useLog Hook

Query audit logs in your components:
import { useLog } from "@refinedev/core";

const AuditLogList = () => {
  const { data, isLoading } = useLog({
    resource: "posts",
    action: "create",
    author: { id: "1" },
    meta: {
      startDate: "2024-01-01",
      endDate: "2024-12-31",
    },
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <table>
      <thead>
        <tr>
          <th>Resource</th>
          <th>Action</th>
          <th>Author</th>
          <th>Timestamp</th>
          <th>Details</th>
        </tr>
      </thead>
      <tbody>
        {data?.map((log) => (
          <tr key={log.id}>
            <td>{log.resource}</td>
            <td>{log.action}</td>
            <td>{log.author?.name}</td>
            <td>{new Date(log.timestamp).toLocaleString()}</td>
            <td>
              <button onClick={() => showDetails(log)}>View</button>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
};

Resource-Specific Logs

const PostHistory = ({ postId }: { postId: string }) => {
  const { data: logs } = useLog({
    resource: "posts",
    meta: {
      id: postId,
    },
  });

  return (
    <div>
      <h3>Post History</h3>
      {logs?.map((log) => (
        <div key={log.id}>
          <strong>{log.action}</strong> by {log.author?.name}
          <span>{new Date(log.timestamp).toLocaleDateString()}</span>
          
          {log.action === "update" && (
            <div>
              <h4>Changes:</h4>
              <pre>{JSON.stringify(log.data, null, 2)}</pre>
            </div>
          )}
        </div>
      ))}
    </div>
  );
};

User Activity

const UserActivity = ({ userId }: { userId: string }) => {
  const { data: logs } = useLog({
    author: { id: userId },
    meta: {
      limit: 50,
      orderBy: "timestamp",
      order: "desc",
    },
  });

  const activityByDay = logs?.reduce((acc, log) => {
    const day = new Date(log.timestamp).toLocaleDateString();
    if (!acc[day]) acc[day] = [];
    acc[day].push(log);
    return acc;
  }, {} as Record<string, any[]>);

  return (
    <div>
      <h3>User Activity</h3>
      {Object.entries(activityByDay || {}).map(([day, logs]) => (
        <div key={day}>
          <h4>{day}</h4>
          <ul>
            {logs.map((log) => (
              <li key={log.id}>
                {log.action} on {log.resource}
              </li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
};

Custom Logging

useLogList Hook

Manually log custom events:
import { useLogList } from "@refinedev/core";

const CustomAction = () => {
  const { log } = useLogList();

  const handleExport = async () => {
    // Perform export
    const data = await exportData();

    // Log the action
    log({
      resource: "posts",
      action: "export",
      data: {
        format: "csv",
        count: data.length,
      },
      meta: {
        exportedAt: new Date(),
      },
    });
  };
};

Login/Logout Tracking

import { useLogin, useLogout } from "@refinedev/core";
import { useLogList } from "@refinedev/core";

const AuthProvider = () => {
  const { log } = useLogList();

  return {
    login: async ({ email, password }) => {
      const response = await authService.login(email, password);
      
      if (response.success) {
        // Log successful login
        await log({
          resource: "auth",
          action: "login",
          data: { email },
          meta: {
            ip: await getClientIP(),
            userAgent: navigator.userAgent,
          },
        });
      }
      
      return response;
    },

    logout: async () => {
      // Log logout
      await log({
        resource: "auth",
        action: "logout",
      });
      
      return { success: true };
    },
  };
};

Advanced Features

Diff Tracking

Track what changed in updates:
const auditLogProvider: AuditLogProvider = {
  create: async (params) => {
    const { resource, action, data, previousData } = params;

    let changes = {};
    if (action === "update" && previousData) {
      changes = Object.keys(data).reduce((acc, key) => {
        if (data[key] !== previousData[key]) {
          acc[key] = {
            from: previousData[key],
            to: data[key],
          };
        }
        return acc;
      }, {});
    }

    await fetch("/api/audit-logs", {
      method: "POST",
      body: JSON.stringify({
        ...params,
        changes,
      }),
    });
  },
};

Sensitive Data Masking

const maskSensitiveData = (data: any) => {
  const masked = { ...data };
  const sensitiveFields = ["password", "ssn", "creditCard"];

  sensitiveFields.forEach((field) => {
    if (masked[field]) {
      masked[field] = "***REDACTED***";
    }
  });

  return masked;
};

const auditLogProvider: AuditLogProvider = {
  create: async (params) => {
    const maskedData = maskSensitiveData(params.data);
    
    await fetch("/api/audit-logs", {
      method: "POST",
      body: JSON.stringify({
        ...params,
        data: maskedData,
      }),
    });
  },
};

Batch Operations

const BatchAuditLogger = () => {
  const logs: any[] = [];
  let timeout: NodeJS.Timeout;

  const flush = async () => {
    if (logs.length === 0) return;

    await fetch("/api/audit-logs/batch", {
      method: "POST",
      body: JSON.stringify(logs),
    });

    logs.length = 0;
  };

  return {
    create: async (params: any) => {
      logs.push(params);

      clearTimeout(timeout);
      timeout = setTimeout(flush, 1000);
    },
    flush,
  };
};

Retention Policies

const cleanupOldLogs = async () => {
  const retentionDays = 90;
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - retentionDays);

  await fetch("/api/audit-logs/cleanup", {
    method: "DELETE",
    body: JSON.stringify({ before: cutoffDate }),
  });
};

// Run cleanup periodically
setInterval(cleanupOldLogs, 24 * 60 * 60 * 1000); // Daily

Visualization

Activity Timeline

import { useLog } from "@refinedev/core";
import { Timeline } from "your-ui-library";

const ActivityTimeline = ({ resource, id }: { resource: string; id: string }) => {
  const { data: logs } = useLog({
    resource,
    meta: { id },
  });

  return (
    <Timeline>
      {logs?.map((log) => (
        <Timeline.Item key={log.id}>
          <strong>{log.action}</strong> by {log.author?.name}
          <br />
          <small>{new Date(log.timestamp).toLocaleString()}</small>
          
          {log.changes && (
            <div>
              {Object.entries(log.changes).map(([field, change]) => (
                <div key={field}>
                  <code>{field}</code>:
                  <span>{change.from}</span><span>{change.to}</span>
                </div>
              ))}
            </div>
          )}
        </Timeline.Item>
      ))}
    </Timeline>
  );
};

Activity Chart

import { useLog } from "@refinedev/core";
import { BarChart } from "your-chart-library";

const ActivityChart = () => {
  const { data: logs } = useLog({
    meta: {
      startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
    },
  });

  const activityByDay = logs?.reduce((acc, log) => {
    const day = new Date(log.timestamp).toLocaleDateString();
    acc[day] = (acc[day] || 0) + 1;
    return acc;
  }, {} as Record<string, number>);

  const chartData = Object.entries(activityByDay || {}).map(
    ([day, count]) => ({ day, count })
  );

  return (
    <BarChart
      data={chartData}
      xKey="day"
      yKey="count"
      title="Activity Last 30 Days"
    />
  );
};

Compliance Features

GDPR Data Export

const exportUserData = async (userId: string) => {
  const logs = await auditLogService.get({
    author: { id: userId },
  });

  const userData = {
    auditLogs: logs,
    exportedAt: new Date(),
  };

  // Generate downloadable file
  const blob = new Blob([JSON.stringify(userData, null, 2)], {
    type: "application/json",
  });
  
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = `user-data-${userId}.json`;
  a.click();
};

Data Deletion

const deleteUserLogs = async (userId: string) => {
  // Log the deletion request
  await auditLogService.create({
    resource: "audit-logs",
    action: "delete",
    data: { userId },
    meta: {
      reason: "GDPR data deletion request",
    },
  });

  // Anonymize or delete logs
  await fetch(`/api/audit-logs/user/${userId}`, {
    method: "DELETE",
  });
};

Best Practices

  1. Log meaningful actions - Focus on important operations
  2. Include context - Store relevant metadata
  3. Mask sensitive data - Don’t log passwords or PII
  4. Set retention policies - Comply with regulations
  5. Index properly - Optimize database queries
  6. Monitor storage - Audit logs can grow large
  7. Implement access control - Protect audit log access
  8. Use async logging - Don’t block operations
  9. Test thoroughly - Ensure logs are reliable
  10. Document your schema - Make logs searchable

Security Considerations

Tamper-Proof Logs

import crypto from "crypto";

const createHash = (data: any) => {
  return crypto
    .createHash("sha256")
    .update(JSON.stringify(data))
    .digest("hex");
};

const auditLogProvider: AuditLogProvider = {
  create: async (params) => {
    const hash = createHash(params);
    
    await fetch("/api/audit-logs", {
      method: "POST",
      body: JSON.stringify({
        ...params,
        hash,
      }),
    });
  },
};

Access Control

const AuditLogList = () => {
  const { data: permissions } = usePermissions();
  const { data: logs } = useLog();

  if (!permissions?.includes("audit_logs.view")) {
    return <div>Access denied</div>;
  }

  return <LogTable logs={logs} />;
};

Troubleshooting

Logs Not Being Created

  1. Verify audit log provider is configured
  2. Check network requests for errors
  3. Ensure backend endpoint is working
  4. Check for JavaScript errors

Performance Issues

  1. Implement batching for high-volume operations
  2. Use async logging (don’t await)
  3. Add database indexes
  4. Consider using a dedicated logging service

Storage Growth

  1. Implement retention policies
  2. Archive old logs
  3. Compress stored data
  4. Use time-series databases

Next Steps

Build docs developers (and LLMs) love