Quick review of Zend_Log

I was asked to do a quick review of Zend_Log the other day to see what I thought of it. Was it good enough to use instead of trying to roll your own or relying on the outdated Log package in PEAR? I should qualify "outdated". By that I mean it uses PEAR_Error and jumps through hoops to work in PHP4. That's a pretty limiting definition of outdated, I know, but that's the one I'm working from right now.

Back on topic. The short answer to is Zend_Log good (enough): yes; the long answer, no. First off, it does what it needs to do without too much fuss. It's documentation is excellent, it has no outside dependencies, and plenty of flex points. All in all a well put together package.

But... It has a few fatal flaws in my opinion. First off, there is redundancy within the code. The one I noticed the most was filtering. When you call the log() method, it runs through all of its filters. A filter can effectively veto a log entry. Nice functionality to have, but only if you're using it. To me that functionality seems like something that should happen at the writer level, however. Open up the base writer object, and sure enough it does. So you have two levels of filtering, something that's probably not going to be used by many users.

I would have created a Zend_Log_Writer_Filterable object that would add filtering in if you needed it. To filter, you instantiate a Filterable type and one of the options passed into it would be the end writer you want to use, stream, db, etc. This gives you the functionality that could be very useful in certain cases, but keeps it from causing performance hits against the main class.

Second up, the way writers themselves are handled. If I'm looking for speed, I want to talk directly to the object that's going to be writing the data. In the case of Zend_Log, the writers aren't accessible directly, and if they were instantiated directly their API is different from Zend_Log.

I put together a simple, some might say simplistic, test (source) to see what kind of effect a straight double-callback would cause. To make it complete, I compared it with iterating over an array with just one value to see what kind of hit the code would take for the array being in place.

The results were as I expected, though a little more dramatic than I thought they would be. The double-call - calling a method that only calls another - causes a hit of more than 100% when compared to calling that method directly. If you take that, and add a foreach() over an array with one value, it takes a hit of another 50%. To me, that means that Zend_Log::log() is 200% slower than it could be if I could access the writer directly.

The flexibility is good, but this code puts it above performance. Having multiple loggers and filters in the base class is kitchen sink syndrome. In the cases where it's needed, special writers could have stepped in to fill the void.

In conclusion, I think the Zend_Log code does too much. In my opinion, it should serve as a Factory for creating new loggers, and I would give it the ability to work as a container for any writer if it was instantiated directly.

Is the code good enough? Yeah, if you're in a pinch or don't really care about milliseconds. If you're looking for a class with a very narrow - possibly even limiting - scope that's as fast as it can be, this one isn't it.

Update: I just ran the same test code above on a server with Zend Platform installed. The single and double callbacks are basically the same (faster, but still a similar difference). The multi-callbacks however ran much slower in comparison. The double was 215% slower than the straight single, and the multi was 242% slower than the double. That means that running an opcode cache with an array when a single value could have been used causes a huge performance hit. Granted, huge is getting into thousandths of a second, but they all add up.