Step-by-Step Guide to building a custom automated performance diagnostic log block for Gutenberg using REST API custom routes
Defining the Performance Diagnostic Log Block
This guide details the construction of a custom Gutenberg block designed to capture and display performance diagnostic logs directly within the WordPress editor. This empowers content creators and administrators to embed real-time performance metrics or historical diagnostic data into their pages and posts, offering immediate context for performance-related content. We will leverage WordPress’s REST API to create custom routes for data retrieval and manipulation, ensuring a robust and scalable solution.
Backend: Custom REST API Endpoint for Log Retrieval
The core of our solution lies in a custom REST API endpoint that will serve performance log data. This endpoint will be accessible from the frontend and will be queried by our Gutenberg block. We’ll implement this using PHP within your WordPress theme’s `functions.php` file or a custom plugin.
First, we register a new REST API route. This route will be responsible for fetching log data. For demonstration purposes, we’ll simulate fetching log entries. In a production environment, this would involve querying a custom log table, a dedicated logging service, or parsing log files.
Registering the REST API Route
We use the `rest_api_init` action hook to register our custom endpoint. The route will be `/myplugin/v1/performance-logs`. We’ll define a callback function that handles the GET request.
add_action( 'rest_api_init', function () {
register_rest_route( 'myplugin/v1', '/performance-logs', array(
'methods' => 'GET',
'callback' => 'myplugin_get_performance_logs',
'permission_callback' => function () {
// Ensure only authenticated users with sufficient capabilities can access.
// Adjust 'edit_posts' capability as needed.
return current_user_can( 'edit_posts' );
}
) );
} );
Implementing the Callback Function
The `myplugin_get_performance_logs` function will be responsible for fetching and formatting the log data. For this example, we’ll return a hardcoded array of log entries. In a real-world scenario, this function would interact with your logging mechanism.
function myplugin_get_performance_logs( WP_REST_Request $request ) {
// Simulate fetching performance log data.
// In a real application, this would query a database, log file, or external service.
$logs = array(
array(
'timestamp' => current_time( 'mysql' ),
'level' => 'INFO',
'message' => 'Page load time: 1.2s',
'details' => array(
'dns_lookup' => '0.1s',
'tcp_connect' => '0.2s',
'ssl_handshake' => '0.3s',
'ttfb' => '0.6s',
'dom_interactive' => '0.9s',
'dom_complete' => '1.2s'
)
),
array(
'timestamp' => date( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) ),
'level' => 'WARNING',
'message' => 'High database query count detected.',
'details' => array(
'query_count' => 150,
'slow_queries' => 5
)
),
array(
'timestamp' => date( 'Y-m-d H:i:s', strtotime( '-10 minutes' ) ),
'level' => 'ERROR',
'message' => 'External API request timed out.',
'details' => array(
'api_endpoint' => 'https://api.example.com/data',
'timeout' => '5s'
)
)
);
// You might want to add pagination or filtering based on $request parameters.
// For example: $page = $request->get_param( 'page' ) ?: 1;
$response = new WP_REST_Response( $logs, 200 );
$response->header( 'Content-Type', 'application/json' );
return $response;
}
// Ensure the function is defined before it's called.
// This is typically handled by WordPress's loading order if placed in functions.php or a plugin.
Frontend: Gutenberg Block Development
Now, we’ll develop the Gutenberg block. This involves creating JavaScript files for the block’s editor interface and its frontend rendering. We’ll use the `@wordpress/scripts` package for building our JavaScript assets.
Project Setup and Dependencies
Navigate to your theme’s directory or your plugin’s directory. If you don’t have a `package.json` file, create one:
npm init -y
Install the necessary WordPress scripts package:
npm install @wordpress/scripts --save-dev
Add build scripts to your `package.json`:
{
"name": "my-performance-block",
"version": "1.0.0",
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
},
"devDependencies": {
"@wordpress/scripts": "^26.0.0"
}
}
Block Registration (`block.json`)
Create a `block.json` file in your block’s directory (e.g., `wp-content/themes/your-theme/blocks/performance-log/block.json`). This file describes your block to WordPress.
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "myplugin/performance-log",
"version": "0.1.0",
"title": "Performance Diagnostic Log",
"category": "widgets",
"icon": "performance",
"description": "Displays performance diagnostic logs.",
"attributes": {
"logCount": {
"type": "number",
"default": 5
}
},
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"render": "file:./render.php"
}
Editor JavaScript (`src/index.js`)
This file registers the block and defines its behavior in the editor. We’ll use `registerBlockType` from `@wordpress/blocks` and `apiFetch` from `@wordpress/api-fetch` to get our log data.
import { registerBlockType } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import { apiFetch } from '@wordpress/api-fetch';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl } from '@wordpress/components';
import { Fragment } from '@wordpress/element';
import './style.scss'; // For frontend styles
import './editor.scss'; // For editor-specific styles
const blockName = 'myplugin/performance-log';
registerBlockType( blockName, {
apiVersion: 3,
title: 'Performance Diagnostic Log',
icon: 'performance',
category: 'widgets',
description: 'Displays performance diagnostic logs.',
attributes: {
logCount: {
type: 'number',
default: 5,
},
},
edit: ( { attributes, setAttributes } ) => {
const { logCount } = attributes;
// Fetch logs using apiFetch
const logs = useSelect( ( select ) => {
return select( 'core' ).getEntityRecords( 'root', 'myplugin/v1/performance-logs', { per_page: logCount } );
}, [ logCount ] );
const isLoading = useSelect( ( select ) => {
return select( 'core' ).isResolving( 'core', 'getEntityRecords', [ 'root', 'myplugin/v1/performance-logs', { per_page: logCount } ] );
}, [ logCount ] );
const errorMessage = useSelect( ( select ) => {
return select( 'core' ).get andError( 'core', 'getEntityRecords', [ 'root', 'myplugin/v1/performance-logs', { per_page: logCount } ] );
}, [ logCount ] );
const renderLogEntry = ( log, index ) => (
<li key={ index }>
<strong>[{ log.timestamp }] { log.level}:</strong> { log.message }
{ log.details && Object.keys( log.details ).length > 0 && (
<ul>
{ Object.entries( log.details ).map( ( [key, value] ) => (
<li key={ key }>{ key }: { typeof value === 'object' ? JSON.stringify( value ) : value }</li>
) ) }
</ul>
) }
</li>
);
return (
<>
<InspectorControls>
<PanelBody title="Log Settings">
<RangeControl
label="Number of Logs to Display"
value={ logCount }
onChange={ ( newCount ) => setAttributes( { logCount: newCount } ) }
min={ 1 }
max={ 10 }
/>
</PanelBody>
</InspectorControls>
<div className="wp-block-myplugin-performance-log">
<h3>Performance Logs (Editor Preview)</h3>
{ isLoading && <p>Loading logs...</p> }
{ errorMessage && <p style={{ color: 'red' }}>Error loading logs: { errorMessage.message }</p> }
{ !isLoading && !errorMessage && logs && logs.length > 0 && (
<ul>
{ logs.slice( 0, logCount ).map( renderLogEntry ) }
</ul>
) }
{ !isLoading && !errorMessage && ( !logs || logs.length === 0 ) && <p>No logs found.</p> }
</div>
</>
);
},
save: () => {
// The frontend rendering will be handled by PHP (render.php)
// This function can return null or a placeholder if server-side rendering is used.
return null;
},
} );
Editor Styles (`src/editor.scss`)
Basic styling for the block in the editor.
.wp-block-myplugin-performance-log {
border: 1px solid #ccc;
padding: 15px;
background-color: #f9f9f9;
margin-bottom: 1em;
h3 {
margin-top: 0;
color: #333;
}
ul {
list-style: disc inside;
padding-left: 20px;
}
li {
margin-bottom: 0.5em;
}
}
Frontend Styles (`src/style.scss`)
Styles that will be applied to the block on the frontend.
.wp-block-myplugin-performance-log {
border: 1px solid #e0e0e0;
padding: 20px;
background-color: #ffffff;
margin-bottom: 2em;
font-family: sans-serif;
font-size: 0.9em;
h3 {
margin-top: 0;
color: #555;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin-bottom: 15px;
}
ul {
list-style: none;
padding-left: 0;
}
li {
margin-bottom: 0.8em;
padding-left: 15px;
position: relative;
&:before {
content: '•';
color: #0073aa;
font-weight: bold;
display: inline-block;
width: 1em;
margin-left: -1em;
position: absolute;
left: 0;
}
}
strong {
color: #333;
}
.log-level-INFO { color: green; }
.log-level-WARNING { color: orange; }
.log-level-ERROR { color: red; }
.log-details {
margin-top: 5px;
padding-left: 20px;
font-size: 0.95em;
color: #666;
border-left: 2px solid #f0f0f0;
}
}
Server-Side Rendering (`render.php`)
Since we’re fetching data from the REST API, server-side rendering (SSR) is a good approach for the frontend. This ensures the data is available immediately on page load, improving perceived performance and SEO. The `render.php` file defined in `block.json` will be executed on the server.
<?php
/**
* Server-side rendering for the Performance Diagnostic Log block.
*
* @package MyPlugin
*/
// Ensure this file is only included within the WordPress environment.
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
// Get attributes passed from the block.
$log_count = isset( $attributes['logCount'] ) ? (int) $attributes['logCount'] : 5;
// Fetch logs via REST API.
// Note: In a real-world scenario, you might want to cache this response
// or use a more direct method if performance is critical and the API is internal.
$request_url = rest_url( 'myplugin/v1/performance-logs' );
$response = wp_remote_get( $request_url );
$logs = array();
if ( ! is_wp_error( $response ) && $response['response']['code'] === 200 ) {
$body = wp_remote_retrieve_body( $response );
$logs_data = json_decode( $body, true );
if ( is_array( $logs_data ) ) {
$logs = array_slice( $logs_data, 0, $log_count );
}
}
?>
<div class="wp-block-myplugin-performance-log">
<h3>Performance Logs</h3>
<?php if ( ! empty( $logs ) ) : ?>
<ul>
<?php foreach ( $logs as $log ) : ?>
<li class="log-entry log-level-<?php echo esc_attr( strtoupper( $log['level'] ?? 'UNKNOWN' ) ); ?>">
<strong>[<?php echo esc_html( $log['timestamp'] ?? '' ); ?>] <?php echo esc_html( $log['level'] ?? '' ); ?>:</strong> <?php echo esc_html( $log['message'] ?? '' ); ?>
<?php if ( ! empty( $log['details'] ) && is_array( $log['details'] ) ) : ?>
<div class="log-details">
<strong>Details:</strong>
<ul>
<?php foreach ( $log['details'] as $key => $value ) : ?>
<li><?php echo esc_html( $key ); ?>: <?php echo esc_html( is_array( $value ) ? json_encode( $value ) : $value ); ?></li>
</?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php else : ?>
<p>No performance logs available or an error occurred fetching them.</p>
<?php endif; ?>
</div>
Building the Assets
Run the build command in your terminal from the directory containing `package.json`:
npm run build
This will compile your SCSS files and JavaScript into production-ready assets, typically placed in a `build` directory. You’ll need to enqueue these assets in your theme’s `functions.php` or plugin file.
Enqueuing Block Assets
Register and enqueue the block’s assets. This is crucial for the block to function correctly both in the editor and on the frontend.
function myplugin_register_performance_log_block() {
// Register the block. The 'editorScript' and 'editorStyle' from block.json
// handle the editor-side assets. We need to enqueue the frontend styles.
// Enqueue frontend styles if they are not already handled by block.json's 'style' property.
// The 'style' property in block.json usually handles this automatically.
// If you need more control or have separate frontend JS, you'd enqueue them here.
// Example of enqueuing a separate frontend JS file if needed:
// wp_enqueue_script(
// 'myplugin-performance-log-frontend',
// plugins_url( 'build/frontend.js', __FILE__ ), // Adjust path as necessary
// array( 'wp-element', 'wp-api-fetch' ),
// filemtime( plugin_dir_path( __FILE__ ) . 'build/frontend.js' )
// );
// Ensure the block is registered. This is often done implicitly by WordPress
// when it finds a block.json file, but explicit registration can be done.
register_block_type( __DIR__ . '/blocks/performance-log' ); // Assuming block.json is here
}
add_action( 'init', 'myplugin_register_performance_log_block' );
Testing and Deployment
After implementing the backend API route and the Gutenberg block, you can test the functionality. Add the “Performance Diagnostic Log” block to a post or page in the WordPress editor. You should see a preview of the logs. Publish the post/page and view it on the frontend to confirm the logs are displayed correctly.
For production deployment:
- Ensure the REST API route is accessible and secured appropriately.
- The `npm run build` command should be part of your deployment pipeline.
- The compiled assets (JavaScript and CSS) must be deployed to the server.
- Consider caching strategies for the REST API endpoint if it becomes a performance bottleneck itself.
Advanced Considerations
Security: The `permission_callback` in `register_rest_route` is crucial. Ensure it aligns with your security requirements. For sensitive data, consider more granular capabilities or role-based access.
Performance of the API: If your log data source is slow, the REST API endpoint can become a bottleneck. Implement caching (e.g., using `wp_cache_set` and `wp_cache_get`) or optimize your data retrieval queries.
Error Handling: Enhance error handling in both the PHP callback and the JavaScript `edit` function to provide more informative feedback to the user.
Data Visualization: For more complex performance data, consider integrating charting libraries (e.g., Chart.js) within the block’s editor or frontend rendering to visualize trends.
Log Management: For production systems, a dedicated logging solution (like ELK stack, Splunk, or cloud-based services) is recommended over simple file or database logging. Your REST API would then interface with this system.