Skip to main content

Command Palette

Search for a command to run...

How I Built a User Activity Log in Symfony That Non-Technical Users Could Actually Read

Updated
4 min read
How I Built a User Activity Log in Symfony That Non-Technical Users Could Actually Read

A few weeks ago, I found myself facing a problem that many developers eventually encounter.

My application already tracked user actions, but the activity log was only useful to developers.

The system recorded events like:

destination_deleted
offer_accepted
requirement_created

Technically, everything worked.

From a user's perspective, however, these entries were meaningless.

An administrator opening the activity page had no idea what actually happened, who performed the action, or why it mattered.

That's when I decided to redesign the activity logging system and focus on making it understandable for real users rather than just developers.

The Problem

Most developers start activity logging by storing action codes.

For example:

$activity->setAction('destination_deleted');

This is great for development because it's easy to filter and analyze.

The downside is that non-technical users don't think in action codes.

An administrator doesn't want to see:

destination_deleted

They want to see:

Sharon deleted the destination "Douala Port".

The difference seems small, but it completely changes the usability of the system.

My First Version

My first implementation stored only:

  • Action

  • User

  • Date

The database looked something like this:

User Action Date
Sharon destination_deleted 2025-07-20
John requirement_created 2025-07-20

It worked.

But every time someone wanted to investigate an issue, they had to ask a developer what the action code meant.

That wasn't sustainable.

The Solution

I decided to keep the technical action code while generating a human-readable message.

Instead of showing:

destination_deleted

The system now displays:

Sharon deleted the destination "Douala Port".

Instead of:

offer_accepted

Users see:

John accepted an offer for transport request #245.

This immediately made the activity log useful for support teams and administrators.

Designing the Activity Entity

The activity entity stores information such as:

class Activity
{
    private ?User $user = null;

    private ?string $action = null;

    private ?string $message = null;

    private ?\DateTimeImmutable $createdAt = null;
}

The key addition was the message field.

The action remains useful for filtering and reporting, while the message provides a clear explanation for humans.

Creating Activities

Whenever an important action occurs, an activity is created.

For example:

$activity = new Activity();

\(activity->setUser(\)user);
$activity->setAction('destination_deleted');
$activity->setMessage(
    sprintf(
        '%s deleted the destination "%s"',
        $user->getFullName(),
        $destination->getName()
    )
);

\(entityManager->persist(\)activity);

Now every activity contains both technical and business-friendly information.

Improving the Admin Interface

The next challenge was displaying activities in EasyAdmin.

I wanted the newest activities to appear first.

Users typically care about what happened recently, not what happened six months ago.

Sorting by creation date solved this:

->setDefaultSort([
    'createdAt' => 'DESC'
])

I also made activities read-only.

Logs should tell a story of what happened.

Allowing administrators to edit history would defeat their purpose.

Lessons Learned

This small feature taught me several important lessons.

1. Build for Humans

Developers understand action codes.

Users do not.

Always think about who will actually consume the information.

2. Keep Technical and Human Data Separate

The action code is useful for filtering.

The message is useful for reading.

Having both gives you flexibility.

3. Activity Logs Become Important Faster Than You Think

At first, logs seem like a minor feature.

Then a user reports a problem.

Suddenly everyone wants to know:

  • Who did it?

  • When did it happen?

  • What exactly changed?

A good activity log answers those questions immediately.

Final Thoughts

One of the most valuable lessons I've learned as a backend developer is that a feature isn't finished when it works.

A feature is finished when the people using it can understand it.

Transforming my activity log from a collection of technical action codes into a readable audit trail took only a small amount of extra work, but it dramatically improved the experience for administrators and support teams.

Sometimes the best improvements aren't new features.

They're making existing features easier for people to understand.

11 views