Back to Blog
Lesson 13 of the Redis: Redis Essentials & Data Types course
DatabasesJuly 31, 20264 min read

Using Lists for Request Logging in Redis: A Practical Guide

Learn to implement efficient request logging using Redis lists. Discover how to append activity, limit log size, and retrieve recent history for your API.

RedisLoggingListsData StructuresBackend
High-resolution image of stacked and chopped firewood ready for use.

Previously in this course, we covered managing hash operations to store structured user data. In this lesson, we add a logging layer to our ongoing API project, using Redis Lists to track recent user activity.

While hashes are perfect for single-entity state, they fall short when you need to maintain an ordered sequence of events. For tracking "who did what and when," we turn to the Redis List data type.

Why Use Redis Lists for Logging?

A Redis List is a linked list of strings. It excels at ordered data, allowing you to push elements to either the head or the tail with $O(1)$ performance. Unlike a relational database, where an INSERT into a massive logs table might require index updates and disk I/O, appending to a Redis list is an in-memory operation that doesn't slow down as your log grows.

In our API project, we want to keep track of the last 100 requests. If we stored this in a database, we would constantly be cleaning up old rows to prevent the table from ballooning. With Redis, we can handle the append and the cleanup in a single, atomic flow.

The Logging Pattern: LPUSH and LTRIM

Detailed view of cut tree logs stacked in a forest, marked with numbers.

To implement a "recent activity" log, we follow a two-step pattern:

  1. LPUSH: Add the new log entry to the head of the list.
  2. LTRIM: Trim the list to a fixed size, discarding the oldest entries.

Worked Example: Implementing the Logger

Assuming you have already set up your project baseline, let's create a simple function to record an activity log.

JAVASCRIPT
// activityLogger.js
const { createClient } = require(CE9178">'redis');
const client = createClient();
client.connect();

async function logRequest(userId, action) {
  const logKey = CE9178">`logs:user:${userId}`;
  const logEntry = JSON.stringify({
    action,
    timestamp: new Date().toISOString()
  });

  // 1. Push the new log to the left(start) of the list
  await client.lPush(logKey, logEntry);

  // 2. Keep only the most recent 10 logs
  // Index 0 is the newest, 9 is the oldest
  await client.lTrim(logKey, 0, 9);
}

// Example usage
logRequest(CE9178">'user_123', CE9178">'GET /api/products');

In this example, LPUSH places the newest entry at index 0. When we call LTRIM(key, 0, 9), Redis keeps the first 10 elements (indices 0 through 9) and discards everything else. This ensures our memory footprint remains constant regardless of how many requests a user makes.

Retrieving History

Since the list is ordered chronologically (newest first), retrieving the full history is trivial. We use LRANGE to fetch the entire list.

JAVASCRIPT
async function getRecentActivity(userId) {
  const logKey = CE9178">`logs:user:${userId}`;
  // Fetch all logs from index 0 to 9
  return await client.lRange(logKey, 0, 9);
}

Hands-on Exercise

  1. Modify the logRequest function above to accept a custom limit (e.g., maxLogs).
  2. Implement a route in your API that calls logRequest every time a request is made.
  3. Add an endpoint GET /api/history that uses lRange to return the last 5 logs for the current user.
  4. Verify using redis-cli by running LRANGE logs:user:user_123 0 -1 after making a few requests.

Common Pitfalls

  • Forgetting to Trim: Without LTRIM, your list will grow indefinitely, eventually consuming all available RAM. Always pair a push with a trim when the log size must be fixed.
  • Performance at Scale: While LPUSH and LTRIM are $O(1)$, remember that if you store massive JSON blobs, you might hit network bandwidth limits. Keep your log entries lean (timestamps, short action descriptions, and IDs).
  • Key Expiration: If you want these logs to disappear after a period of inactivity, don't forget to set a TTL on the list using EXPIRE.

FAQ

Can I use RPUSH instead of LPUSH? Yes, but then your newest items will be at the end of the list. You would then need to use LTRIM differently to keep the last $N$ items. LPUSH is standard for "newest-first" feeds.

Is this a replacement for a database audit log? No. This is for recent activity (e.g., a "Last 10 Actions" widget). For permanent audit trails, you should write to a persistent store, similar to how one might handle auditing changes in a CMS.

What happens if I set LTRIM to index 0, 0? The list will be truncated to exactly one element—the most recent one.

Recap

By combining LPUSH and LTRIM, we created a self-maintaining, fixed-size log buffer. This approach is highly efficient for real-time activity tracking, offloading the burden of sorting and cleanup from your primary database.

Up next: Set Operations: Intersection and Union

Similar Posts