New Plugin: Log Deprecated Notices

A few weeks ago I was testing some plugins and realized it was about time I write an update to Stephen Rider’s Log Deprecated Calls plugin. I don’t like reinventing the wheel, but there were a few reasons I wanted to start anew.

I spent quite a bit of time early in the WordPress 3.0 development cycle improving our deprecated notices. We added reporting of deprecated arguments and I tediously went back through the SVN history to find the version numbers for every deprecated function, both when the function was introduced and when it was deprecated. We also formally deprecated a number of functions, such as the really old widget API — e.g. use wp_register_sidebar_widget(), not register_sidebar_widget() — and the old escaping aliases — e.g. use esc_attr() instead of attribute_escape().

So, I wanted to make sure there was a plugin to fully leverage all of this. Stephen indicated he wanted to update his plugin to handle arguments (originally released for WordPress 2.6), but that he was also working on integrating it into his own plugin framework. I wanted to scrap the extra table and leverage a custom post type for both the storage and the UI, and see how far that would get me.

The results were interesting. I’ve made some pretty interesting customizations to allow me to leverage the custom post type UI, and the plugin is scattered with notes on possible enhancements for WordPress 3.1 that can help a lot of plugin developers, which I’ll revisit when we start development in September. In hindsight I think leveraging the UI was a wildly successful experiment, even with the hacks I employed to make it work to my liking, because I didn’t have to worry about any overhead.

Feedback and bug reports are encouraged. The plugin is available in the WordPress Plugin Directory. Hereโ€™s the source. The current version is 0.1-RC1.

Features



You’ll find the Deprecated Calls screen under the Tools menu.

  • Backtraces. The plugin tracks down exactly where the function was called from, the argument was used, or the file was included. It also provides special context and handling for unique situations: deprecated functions used as a hook callback, usage of user levels instead of capabilities, unregistered settings, and other quirky things that WordPress reports.
  • Filters. Filter notices by date, by type of notice, even by version. Oh, and you can search too.
  • Logical counts. Each unique log entry is only recorded once. After that, it keeps a count. Keeps the log neat and organized, and keeps the database size at O(1) levels instead of O(n).
  • Muting. Is a notice a known issue? Mute it and you won’t be bothered by it again. There are also bulk actions for muting and deleting notices.
  • Unread count. New unmuted notices since last time you visited the Deprecated Calls screen? You’ll see a bubble on the submenu item with the unread count.
Unread count.

What’s to Come

I’ve paused development while I work on other projects, but the next big step will be identifying plugins that cause notices. I think the ability to filter notices by a file or plugin will be an important drill-down. I’m also going to experiment with adding an unobtrusive note on the plugins page next to plugins throwing notices.

I’d like to offer better multisite support, though I’m not entirely sure what that entails. Since plugins are network-wide, then I imagine the log should be network-wide as well (at least for plugins activated for the network), but the logs are stored in posts tables, so I’ll have to think about that some more. (Let me know if you have any ideas.)

I want to consider offering the ability to auto-purge the log. Since the plugin only stores unique notices, and keeps a running tally, then the database won’t grow in size, assuming it’s the same notices being triggered over and over. And the log is paginated, searchable, and filterable. So I’m not convinced it is a necessary feature.

The plugin is arguably okay for a production environment, as it uses the API, thus the few queries it makes from the frontend are mostly cached. (It’s designed for a development environment, however.) To improve performance further (especially cutting down on queries to update counts), I will probably delay logging operations until the shutdown hook, and do it all at once.

It would be great to hear how individuals use it in WordPress plugin development and site management — that will help drive new features. Contact me with suggestions.

The Making Of

As I said, I employed some very interesting techniques to get what I wanted, helpfully noted in the source code with the phrase “cheap hack.” I wouldn’t encourage some of these in your own plugins because there isn’t a guarantee it’ll work in the future. Do as I say, not as I do? Pretty much, but that’s only because I either write or read every commit, so I can adjust the plugin quite quickly to ensure compatibility. Some of these hacks, however, are simply inventive ways to fully leverage a custom post type.

As I wrote above, I made a list of improvements for core. In hindsight, I wished I didn’t use _deprecated_argument() in dubious ways, because each scenario needed special handling. I expect to introduce _deprecated_message() in 3.1. (Also in store — deprecated notices for hooks? Maybe.)

Here’s some of the more clever hacks and tricks I used:

  • The “Muted” state is actually the Trash. The trash post status excludes it from the main list of notices, allowed me to use the “Trash” action link and “Move to Trash” bulk action, and allowed me to use the easy undo/untrash functionality. Then I performed some renaming: the Trash became “Muted,” and “Mute” as a verb, and ” Move to Trash” became “Mute.” Even the “Empty Trash” button simply became “Clear Log,” and with two small hacks that was made visible and functional when viewing the unmuted entries too.
  • Custom post type in a submenu. While this is something we definitely plan to do in 3.1, it is currently unsupported. No problem. I just unset the top-level menu and add it myself to the Tools menu.
  • Permissions. I wanted to show administrators the UI, but no one needed to edit or add anything. So, I utilized the activate_plugins permission, and simply disabled the edit and add new screens. The “Add New” button is hidden through CSS (as is a few other things). Introducing an add_posts capability would help, but really only for custom post types, so I’m not sure how to make this better.
  • Querying the filters. I wrote some nifty code to handle querying by more than one key/value postmeta pair.
  • Searching. The details of each notice are stored in postmeta, but they’re only used for querying when applying filters, not to actually display the log. Instead, the description of the notice (and the alternative) is generated when the notice is logged, and stored in post_content. This allows searching, and prevents querying hundreds of postmeta rows to display the log. But since I have the postmeta values, I can rely on them, and even rebuild the post_content field on a future upgrade. (I do have a note in the plugin that maybe this isn’t the best idea, but it works well so far.)
  • Bulk actions. I have high hopes for a core bulk actions API in 3.1, but for now, I remove and rename via a little JavaScript.

You’ll have to review the code yourself if you want to discover some of the hacks I’d rather not mention. (Just don’t use them! ๐Ÿ™‚ )

Published by

Andrew Nacin

Lead developer of WordPress, living in Washington, D.C. Follow me on Twitter.

8 thoughts on “New Plugin: Log Deprecated Notices”

  1. I like your UI improvements, for the most part: Changing it to a table was something I had in mind — definitely easier to read. “Count” and “Last Used” are also nice improvements I might borrow. I also intend to ease up on table size by making it so there’s only one entry for any given call (rather than an entry for every time it’s called, which is inefficient.)

    I actually have a newer unreleased version that has several under-the-hood changes, but nothing as significant as what you’ve done here with custom post types. I can email it to you directly if you’re interested.

    For the multisite support, I think your hooked function could just call the wp global and log it with the call?

    I tried doing an auto-purge in my version (you probably noticed it in the code), but I couldn’t get the cron stuff to work, and moved on to other priorities. The code is still there though.

    Question: It’s seems as though you have to work kind of hard to shoehorn this into the Custom Post Types interface. What are your reasons for doing so — caching mainly? (I do like you idea of saving actual logging for Shutdown hook — a good idea.)

    1. For the multisite support, I think your hooked function could just call the wp global and log it with the call?

      A network administrator shouldn’t need to consult the logs on X number of sites. The administrator should be able to see all of that in one place, especially since control over themes and plugins are centralized and out of the hands of the individual site administrators. The problem is, the notices would all be logged to different posts tables. I think I can get around that by inserting the post directly into the main site’s posts table, but then I’m reinventing the wheel a bit with my own version of wp_insert_post(). (I wouldn’t want to switch_to_blog() because that’s too intensive.) Then again, I might end up with direct queries anyway if I’m going to defer until the shutdown hook.

      Question: Itโ€™s seems as though you have to work kind of hard to shoehorn this into the Custom Post Types interface. What are your reasons for doing so โ€” caching mainly?

      Great question, one I somewhat tried to address in pieces throughout my post. Caching wasn’t quite the overhead I was talking about. It was ultimately an experiment that started with a very simple UI which grew more advanced as I started adding and refining features. When I started, a custom post type made far more sense. But even now, even with all of the shoehorning, I think it still does. Let me explain.

      One thing I did identify was a lack of ways and hooks to cleanly modify the UI, and that is something I absolutely want to address in a future version. But I was saving myself tons of code for every small hack I wrote. The overhead I’m referring to is everything I am able to seamlessly leverage without even thinking much about it —

      • The log shows up. I don’t need to do anything; core queried it for me.
      • The Muted state is simply the Trash, renamed. That’s it. With that, I was able to leverage action links, trash/untrash (i.e. Undo), and their suppression from the main log. The ‘Empty Trash’ button was simply renamed to ‘Clear Log.’ (However, I did implement two tiny hacks to make that button A) appear, and B) work, on the main log.)
      • Pagination just works. So does the ability to adjust the number shown per page, via the ‘Screen Options’ tab.
      • The search works. Just like that.
      • The filters were dead simple to write. I’m hooking into an existing query instead of rolling my own, and it’s oh-so-elegant.
      • With a few short lines of JavaScript, I removed the ‘Edit’ bulk action, added a ‘Delete’ one, and renamed ‘Trash’ to ‘Mute.’ And WordPress handled all of that server-side for me, helpfully bulk-muting and bulk-deleting posts without me needing to write anything.
      • Permissions were a bit funky, since I didn’t want anyone to be able to add or edit any posts as it was just a read-only log, but they still needed to be able to edit_posts to view the UI (which normally allows them to add posts). Nothing a little CSS display: none and some wp_die() calls couldn’t fix.
      • Relying on core UI components also goes a long way to seamlessly integrate the plugin into core. An administrator not told this is a plugin would have a hard time determining that it is (which is a good goal for most plugin authors to follow).

      So, I would have needed to write from scratch the querying (including searching, filtering, and pagination), mute/unmute (w/ Undo), and the UI (including bulk actions, search, and filters). It was an interesting experience and I hope to make core a bit more flexible based on what I’ve learned, but this showed off some nice potential, and even now, the benefits already outweighed the costs.

  2. “An administrator not told this is a plugin would have a hard time determining that it is (which is a good goal for most plugin authors to follow).”

    That’s my Golden Rule of UI design. ๐Ÿ™‚

    Nothing like actually writing plugins to see changes that are needed in Core. One of the things I think would be good for WP 3.1 would be making the user capabilities a bit more refined. Turn the existing ones into supersets of more specific capabilities, so plugin authors can either test against the “broad” capabilities or delve down the specific ones. (I’ll probably be dropping a line to wp-hackers about this sooner or later.)

    1. Yup! Right now it doesn’t discriminate. It simply identifies where the deprecated call came from, and would track it back to plugins, mu-plugins, themes, drop-ins, what have you.

      In a future version, I plan for it to actually identify the specific plugin or theme, and allow you to filter on that.

      The WordPress.org theme reviewers team is aware of the plugin and some of them have already begun to use it. Hopefully I’ll get some more suggestions from them on how things can be improved.

  3. WOW! Fantastic plugin. I pretty much only develop Themes, but this sure is useful for finding code I may have misused.

    Your plugin did seem to break when clearing the logs due to the SEO Ultimate plugin I have installed (probably an error more with their plugin since most of the errors your plugin found were from SEO Ultimate).

    Here’s one of the error messages I received: Fatal error: Call to undefined function wp_verify_nonce() in /PATH/wp-content/plugins/seo-ultimate/plugin/class.seo-ultimate.php on line 1440

Comments are closed.