Laptop on a log

WooCommerce Function of the Week: wc_get_logger

If you are or aim to be an advanced WooCommerce developer, this week's WooCommerce function will help you immensely.

And — spoiler alert — that's not because of this function's syntax (which is beyond my own understanding), but due to the world of possibility it opens when it's called from inside a plugin or code snippet.

Logging in software development means keeping a file log of events for troubleshooting and health tracking purposes. Go to WordPress Dashboard > WooCommerce > Status > Logs and take a look at the dropdown: you'll see dozens if not hundreds of log entries generated by either WooCommerce itself or some of your active WooCommerce extensions.

You can do that too, with wc_get_logger. The wc_get_logger function lets you create your own event log. You might want one for troubleshooting something under development or to give your clients a handy report of what a plugin or code snippet did on any given day.

Not sure why and how you should use wc_get_logger? Here's more context, some case studies, and a code example. Enjoy!

Function source code and description

File woocommerce\includes\wc-core-functions.php, line 1952 (as of WooCommerce 6.0.0) is where wc_get_logger resides. Let's take a look at the code:

/**
 * Get a shared logger instance.
 *
 * Use the woocommerce_logging_class filter to change the logging class. You may provide one of the following:
 *     - a class name which will be instantiated as `new $class` with no arguments
 *     - an instance which will be used directly as the logger
 * In either case, the class or instance *must* implement WC_Logger_Interface.
 *
 * @see WC_Logger_Interface
 *
 * @return WC_Logger
 */
function wc_get_logger() {
	static $logger = null;

	$class = apply_filters( 'woocommerce_logging_class', 'WC_Logger' );

	if ( null !== $logger && is_string( $class ) && is_a( $logger, $class ) ) {
		return $logger;
	}

	$implements = class_implements( $class );

	if ( is_array( $implements ) && in_array( 'WC_Logger_Interface', $implements, true ) ) {
		$logger = is_object( $class ) ? $class : new $class();
	} else {
		wc_doing_it_wrong(
			__FUNCTION__,
			sprintf(
				/* translators: 1: class name 2: woocommerce_logging_class 3: WC_Logger_Interface */
				__( 'The class %1$s provided by %2$s filter must implement %3$s.', 'woocommerce' ),
				'<code>' . esc_html( is_object( $class ) ? get_class( $class ) : $class ) . '</code>',
				'<code>woocommerce_logging_class</code>',
				'<code>WC_Logger_Interface</code>'
			),
			'3.0'
		);

		$logger = is_a( $logger, 'WC_Logger' ) ? $logger : new WC_Logger();
	}

	return $logger;
}

Now, as I already mentioned, and unlike last week where we studied wc_get_customer_total_spent syntax line by line, this time we're not going to worry about the way the function is written — instead we'll focus on how it's used.

So, let's do a quick file search for wc_get_logger and see if we can find something interesting. Take a look at woocommerce\includes\class-woocommerce.php line 237:

/**
 * Ensures fatal errors are logged so they can be picked up in the status report.
 *
 * @since 3.2.0
 */
public function log_errors() {
	$error = error_get_last();
	if ( $error && in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ), true ) ) {
		$logger = wc_get_logger();
		$logger->critical(
			/* translators: 1: error message 2: file name and path 3: line number */
			sprintf( __( '%1$s in %2$s on line %3$s', 'woocommerce' ), $error['message'], $error['file'], $error['line'] ) . PHP_EOL,
			array(
				'source' => 'fatal-errors',
			)
		);
		do_action( 'woocommerce_shutdown_error', $error );
	}
}

You can see wc_get_logger “gives access” to the WooCommerce logging system, which is then written in case there is a Fatal Error (error 5xx) in WordPress. This log is super helpful if you want to keep an eye on your WordPress website without enabling WP_DEBUG or logging in to your FTP looking for an error log file. (The errors are also very well described. They include the functions that triggered them, hooks involved, references, and lots of information that a normal debugger won't give you.)

Here's a fatal error log. It comes with a date and time, the error description, the plugin/function that triggered it, and the line number where the code broke. It's always good to know when and where things go wrong!

So this is what we're looking at today: creating our own custom log within the WooCommerce Status admin dashboard.

Case studies

Why on earth should you “log stuff” in the first place?

Well, let's say you're developing a new WooCommerce extension or coding a PHP snippet. Maybe you want to know “what's inside a PHP variable” because a calculation you're doing with it isn't coming out as expected.

No problem! Invoke wc_get_logger, and then print the variable content inside the log. Why not use WP_DEBUG instead? Well, to be honest, they are equivalent — so pick whatever suits you better.

Another scenario: you've connected your WooCommerce products with an external API, which returns daily prices and stock quantities.

As you can see, wc_get_logger is a nice way to invoke the logging system to print previous and new prices or stock inventory every time the API is called. You never know, something could have gone wrong.

Finally, the store manager may want to be kept up to date with manual operations (e.g. a product title update) or customer events such as failed orders, products added to their cart, products purchased together (so they can build better product recommendations), and so on.

Anything is loggable!

Rodolfo Melogli

Let's build our custom event log

Alright, time to write some code. On top of wc_get_logger, you also need to know about the different types of log entry levels:

emergency|alert|critical|error|warning|notice|info|debug

Right after you invoke wc_get_logger by assigning it to a variable, you can then add an entry to your custom log by defining one of those levels. In the example above we saw this:

$logger = wc_get_logger();
$logger->critical(...);

/* Example:

2022-02-16T00:53:34+00:00 CRITICAL syntax error, unexpected 'limit' (T_STRING), expecting ']' in .../wp-content/plugins/code-snippets/php/admin-menus/class-edit-menu.php(221) : eval()'d code on line 15

*/

So basically it defines that “CRITICAL” string at the beginning of the log entry. (It's not much, but it could be useful if you want to classify log entries based on their severity level.)

Together with that, we also need to study the rest of the syntax for writing a log entry e.g.:

$logger->info( wc_print_r( $order, true ), array( 'source' => 'failed-orders' ) );

We already covered the “->info” part a second ago — inside the parentheses you also find the log entry content and the log name. Here's a quicker way to describe the log writing function:

$logger->LOG_ENTRY_LEVEL( LOG_ENTRY_CONTENT, array( 'source' => LOG_NAME ) );

All you have to define are:

  • LOG_ENTRY_LEVEL (emergency|alert|critical|error|warning|notice|info|debug)
  • LOG_ENTRY_CONTENT (a string to describe the event)
  • LOG_NAME (the log name slug e.g. ‘fatal-errors'; you can make this up, and actually I tend to use slugs that begin with the letter ‘a', such as ‘aaa-my-custom-log' so they show first in the dropdown as it's sorted alphabetically)

That's it!

We can now code a specific case scenario: let's create a log called “aaa-product-url-changes” and create a log entry each time someone or something changes a WooCommerce product slug (URL).

First of all, we need to identify the “product has changed” WordPress hook so that we can fire the wc_get_logged function. That would be the post_updated action.

Second, let's see if there were slug changes ("$post->post_name").

And third, let's write the log entry.

Here's the full snippet:

/**
 * @snippet       Log Product URL changes
 * @how-to        Get CustomizeWoo.com FREE
 * @author        Rodolfo Melogli
 * @compatible    WooCommerce 6
 * @donate $9     https://businessbloomer.com/bloomer-armada/
 */
  
add_action( 'post_updated', 'bbloomer_log_product_slug_changes_wc_status', 10, 3 );
 
function bbloomer_log_product_slug_changes_wc_status( $post_ID, $post_after, $post_before ) {
 
	// BAIL IF NOT A PRODUCT
	if ( 'product' !== $post_before->post_type ) return;
	
	// BAIL IF SLUG DIDNT CHANGE
	if ( $post_after->post_name == $post_before->post_name ) return;

	// LOAD THE WC LOGGER
	$logger = wc_get_logger();

	// LOG SLUGS TO CUSTOM LOG
	$logger->info( 'Product ID ' . $post_ID . ' slug changed from "' . $post_before->post_name . '" to "' . $post_after->post_name . '"', array( 'source' => 'aaa-product-url-changes' ) );
 
}

Here's a screenshot of the original post — important if I change a product permalink from sprite-foam-yoga-brick  to sprite-foam-yoga-brick-2022:

Cool, ha?

On Business Bloomer, I coded two more WooCommerce case studies in case you're interested: a failed order log and a product price update log. Enjoy!

Further Questions

How long is the log kept for? Is there a log entry limit? How can you disallow certain user roles from viewing a given log?

Lots of possible questions there — let's chat via the comments and #woocommerce in Post Status Slack!

Post Status

You — and your whole team can Join Post Status too!

Build your network. Learn with others. Find your next job — or your next hire. Read the Post Status newsletter. ✉️ Listen to podcasts. 🎙️ Follow @Post_Status. 🐦

Similar Posts

One Comment

Comments are closed.