ClockWork

Clockwork is a browser extension, providing tools for debugging and profiling your PHP applications

Install

Install the Clockwork library via Composer

 

$ composer require itsgoingd/clockwork

 

Publish config

 

vendor:publish

 

Clockwork comes with a clock() helper function, which provides an easy way to add records to the Clockwork log and events to the timeline.

Usage

To interact with the data collected by Clockwork, you will need to

Config ( clockwork.php)

'enable' => env('CLOCKWORK_ENABLE', null),
'features' => [
  'database' => [
    'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', true),
  ],
  'routes' => [
    'enabled' => env('CLOCKWORK_ROUTES_ENABLED', true)
  ],
  'views' => [
    'enabled' => env('CLOCKWORK_VIEWS_ENABLED', true)
  ]
],
'web' => env('CLOCKWORK_WEB', true),
'storage' => env('CLOCKWORK_STORAGE', 'sql'),
'storage_files_path' => env('CLOCKWORK_STORAGE_FILES_PATH', storage_path('clockwork')),
'storage_expiration' => env('CLOCKWORK_STORAGE_EXPIRATION', 60 * 2),
'authentication' => env('CLOCKWORK_AUTHENTICATION', false),
'authentication_password' => env('CLOCKWORK_AUTHENTICATION_PASSWORD', 'VerySecretPassword'),
'filter_uris' => [
  '/horizon/.*', // Laravel Horizon requests
],
'register_helpers' => env('CLOCKWORK_REGISTER_HELPERS', true),

Config (clockwork.php)

<?php

return [

	/*
	|--------------------------------------------------------------------------
	| Enable Clockwork
	|--------------------------------------------------------------------------
	|
	| You can explicitly enable or disable Clockwork here. When enabled, special
	| headers for communication with the Clockwork Chrome extension will be
	| included in your application responses and requests data will be available
	| at /__clockwork url.
	| When set to null, Clockwork behavior is controlled by app.debug setting.
	| Default: null
	|
	*/

	'enable' => env('CLOCKWORK_ENABLE', null),

	/*
	|--------------------------------------------------------------------------
	| Features
	|--------------------------------------------------------------------------
	|
	| You can enable or disable various Clockwork features here. Some features
	| accept additional configuration (eg. slow query threshold for database).
	|
	*/

	'features' => [

		// Cache usage stats and cache queries including results
		'cache' => [
			'enabled' => env('CLOCKWORK_CACHE_ENABLED', true),

			// Collect cache queries including results (high performance impact with a high number of queries)
			'collect_queries' => env('CLOCKWORK_CACHE_QUERIES', false)
		],

		// Database usage stats and queries
		'database' => [
			'enabled' => env('CLOCKWORK_DATABASE_ENABLED', true),

			// Collect database queries (high performance impact with a very high number of queries)
			'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', false),

			// Query execution time threshold in miliseconds after which the query will be marked as slow
			'slow_threshold' => env('CLOCKWORK_DATABASE_SLOW_THRESHOLD'),

			// Collect only slow database queries
			'slow_only' => env('CLOCKWORK_DATABASE_SLOW_ONLY', false),

			// Detect and report duplicate (N+1) queries
			'detect_duplicate_queries' => env('CLOCKWORK_DATABASE_DETECT_DUPLICATE_QUERIES', false)
		],

		// Sent emails
		'emails' => [
			'enabled' => env('CLOCKWORK_EMAILS_ENABLED', true),
		],

		// Dispatched events
		'events' => [
			'enabled' => env('CLOCKWORK_EVENTS_ENABLED', true),

			// Ignored events (framework events are ignored by default)
			'ignored_events' => [
				// App\Events\UserRegistered::class,
				// 'user.registered'
			],
		],

		// Laravel log (you can still log directly to Clockwork with laravel log disabled)
		'log' => [
			'enabled' => env('CLOCKWORK_LOG_ENABLED', true)
		],

		// Dispatched queue jobs
		'queue' => [
			'enabled' => env('CLOCKWORK_QUEUE_ENABLED', true)
		],

		// Redis commands
		'redis' => [
			'enabled' => env('CLOCKWORK_REDIS_ENABLED', true)
		],

		// Routes list
		'routes' => [
			'enabled' => env('CLOCKWORK_ROUTES_ENABLED', true)
		],

		// Rendered views including passed data (high performance impact with large amount of data passed to views)
		'views' => [
			'enabled' => env('CLOCKWORK_VIEWS_ENABLED', false)
		]

	],

	/*
	|--------------------------------------------------------------------------
	| Enable web UI
	|--------------------------------------------------------------------------
	|
	| Enable or disable the Clockwork web UI available at  http://your.app/__clockwork.
	| You can also set whether to use the dark theme by default.
	| Default: true
	|
	*/

	'web' => env('CLOCKWORK_WEB', true),

	'web_dark_theme' => env('CLOCKWORK_WEB_DARK_THEME', true),

	/*
	|--------------------------------------------------------------------------
	| Enable data collection, when Clockwork is disabled
	|--------------------------------------------------------------------------
	|
	| This setting controls, whether data about application requests will be
	| recorded even when Clockwork is disabled (useful for later analysis).
	| Default: false
	|
	*/

	'collect_data_always' => env('CLOCKWORK_COLLECT_DATA_ALWAYS', false),

	/*
	|--------------------------------------------------------------------------
	| Metadata storage
	|--------------------------------------------------------------------------
	|
	| You can configure how are the metadata collected by Clockwork stored.
	| Valid options are: files or sql.
	| Files storage stores the metadata in one-per-request files in a specified
	| directory.
	| Sql storage stores the metadata as rows in a sql database. You can specify
	| the database by name if defined in database.php or by path to Sqlite
	| database. Database table will be automatically created.
	| Sql storage requires PDO.
	|
	*/

	'storage' => env('CLOCKWORK_STORAGE', 'sql'),

	'storage_files_path' => env('CLOCKWORK_STORAGE_FILES_PATH', storage_path('clockwork')),

	// Compress the metadata files using gzip, trading a little bit of performance for lower disk usage
	'storage_files_compress' => env('CLOCKWORK_STORAGE_FILES_COMPRESS', false),

	'storage_sql_database' => env('CLOCKWORK_STORAGE_SQL_DATABASE', storage_path('clockwork.sqlite')),
	'storage_sql_table'    => env('CLOCKWORK_STORAGE_SQL_TABLE', 'clockwork'),

	/*
	|--------------------------------------------------------------------------
	| Metadata expiration
	|--------------------------------------------------------------------------
	|
	| Maximum lifetime of the metadata in minutes, metadata for older requests
	| will automatically be deleted when storing new requests.
	| When set to false, metadata will never be deleted.
	| Default: 1 week
	|
	*/

	'storage_expiration' => env('CLOCKWORK_STORAGE_EXPIRATION', 60 * 2),

	/*
	|--------------------------------------------------------------------------
	| Authentication
	|--------------------------------------------------------------------------
	|
	| Clockwork can be configured to require authentication before allowing
	/ access to the collected data. This is recommended when the application
	/ is publicly accessible, as the metadata might contain sensitive information.
	/ Setting to "true" enables authentication with a single password set below,
	/ "false" disables authentication.
	/ You can also pass a class name of a custom authentication implementation.
	/ Default: false
	|
	*/

	'authentication' => env('CLOCKWORK_AUTHENTICATION', false),

	'authentication_password' => env('CLOCKWORK_AUTHENTICATION_PASSWORD', 'VerySecretPassword'),

	/*
	|--------------------------------------------------------------------------
	| Disable data collection for certain URIs
	|--------------------------------------------------------------------------
	|
	| You can disable data collection for specific URIs by adding matching
	| regular expressions here.
	|
	*/

	'filter_uris' => [
		'/horizon/.*', // Laravel Horizon requests
		'/telescope/.*', // Laravel Telescope requests
        '/admin/voyager-assets*'
	],

	/*
	|--------------------------------------------------------------------------
	| Enable collecting of stack traces
	|--------------------------------------------------------------------------
	|
	| This setting controls, whether log messages and certain data sources, like
	/ the database or cache data sources, should collect stack traces.
	/ You might want to disable this if you are collecting 100s of queries or
	/ log messages, as the stack traces can considerably increase the metadata size.
	/ You can force collecting of stack trace for a single log call by passing
	/ [ 'trace' => true ] as $context.
	| Default: true
	|
	*/

	'stack_traces' => [
		// Enable or disable collecting of stack traces, when disabled only caller file and line number is collected
		'enabled' => env('CLOCKWORK_STACK_TRACES_ENABLED', true),

		// List of vendor names to skip when determining caller, common vendor are automatically added
		'skip_vendors' => [
			// 'phpunit'
		],

		// List of namespaces to skip when determining caller
		'skip_namespaces' => [
			// 'Laravel'
		],

		// List of class names to skip when determining caller
		'skip_classes' => [
			// App\CustomLog::class
		],

		// Limit of frames to be collected
		'limit' => env('CLOCKWORK_STACK_TRACES_LIMIT', 10)
	],

	/*
	|--------------------------------------------------------------------------
	| Serialization
	|--------------------------------------------------------------------------
	|
	| Configure how Clockwork serializes the collected data.
	| Depth limits how many levels of multi-level arrays and objects have
	| extended serialization (rest uses simple serialization).
	| Blackbox allows you to specify classes which contents should be never
	| serialized (eg. a service container class).
	| Lowering depth limit and adding classes to blackbox lowers the memory
	| usage and processing time.
	|
	*/

	'serialization_depth' => env('CLOCKWORK_SERIALIZATION_DEPTH', 10),

	'serialization_blackbox' => [
		\Illuminate\Container\Container::class,
		\Illuminate\Foundation\Application::class,
		\Laravel\Lumen\Application::class
	],

	/*
	|--------------------------------------------------------------------------
	| Register helpers
	|--------------------------------------------------------------------------
	|
	| This setting controls whether the "clock" helper function will be registered. You can use the "clock" function to
	| quickly log something to Clockwork or access the Clockwork instance.
	|
	*/

	'register_helpers' => env('CLOCKWORK_REGISTER_HELPERS', true),

	/*
	|--------------------------------------------------------------------------
	| Send Headers for AJAX request
	|--------------------------------------------------------------------------
	|
	| When trying to collect data the AJAX method can sometimes fail if it is
	| missing required headers. For example, an API might require a version
	| number using Accept headers to route the HTTP request to the correct
	| codebase.
	|
	*/

	'headers' => [
		// 'Accept' => 'application/vnd.com.whatever.v1+json',
	],

	/*
	|--------------------------------------------------------------------------
	| Server-Timing
	|--------------------------------------------------------------------------
	|
	| Clockwork supports the W3C Server Timing specification, which allows for
	/ collecting a simple performance metrics in a cross-browser way. Eg. in
	/ Chrome, your app, database and timeline event timings will be shown
	/ in the Dev Tools network tab.
	/ This setting specifies the max number of timeline events that will be sent.
	| When set to false, Server-Timing headers will not be set.
	| Default: 10
	|
	*/

	'server_timing' => env('CLOCKWORK_SERVER_TIMING', 10)

];
  • request data - headers, GET and POST data, cookies
  • performance metrics
  • log messages
  • database queries
  • cache usage
  • application events
  • session data
  • sent emails
  • rendered views

Collected data

Collecting data about your application runtime is the core functionality of Clockwork. Include:

Some of the data is not collected by default as it may considerably increase the metadata size:

  • cache queries (cache stats are still collected)
  • rendered views
  • application routes

Request, Performance metric

Database

'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', true),

'slow_only' => env('CLOCKWORK_DATABASE_SLOW_ONLY', false),

'detect_duplicate_queries' => env('CLOCKWORK_DATABASE_DETECT_DUPLICATE_QUERIES', true)

Database

Events

Routes

Views

Cache

Logging

// All data logged using the Laravel log methods will also appear in the Clockwork
Log::debug('An informational message.');


// Logging data to Clockwork can be done using the helper function, which even supports logging multiple values at once
clock(User::first(), auth()->user(), $username);


// If you want to specify a log level, you can use the long-form call
clock()->info("User {$username} logged in!");


// The clock() helper function *returns it's first argument*, so you can easily add inline debugging statements to your code:
User::create( clock($request->all()) );

// You can use the *trace* option to make Clockwork explicitly collect stack trace for a particular message.
clock()->info("Trace this message!", [ 'trace' => true ])

Logging

clock($posts);
dump($posts);

vs

Timeline

// To add a custom event to the timeline, you'll need to start an event with an unique name and description first

clock()->startEvent('mybox-api-call', "Very slow dumb API");


// After executing the tracked block of code, you can end the event, using it's unique name.

clock()->endEvent('mybox-api-call')

Timeline

Userdata

Show specific data in a new tab:

 

$cart = clock()->userData('cart') ->title('Cart');

$cart->counters([
  'Products' => 3,
  'Value' => '949.80€' ]);  

$cart->table('Products', [
  [ 'Product' => 'iPad Pro 10.5" 256G Silver', 'Price' => '849 €' ],
  [ 'Product' => 'Smart Cover iPad Pro 10.5 White', 'Price' => '61.90 €' ],
  [ 'Product' => 'Apple Lightning to USB 3 Camera Adapter', 'Price' => '38.90 €' ] ]); 

 

Userdata

Simple log:

 Instead of logging the cart contents and looking for them in the log tab, we can make a new "Cart" tab which will show some stats and the cart contents:

Xdebug profiler

Setup (Linux)

Install the Xdebug extension via PECL (part of your PHP installation):

zend_extension="/usr/local/php/modules/xdebug.so"

xdebug.profiler_enable_trigger=1

Enable the PHP extension in your php.ini  (location of php.ini  and the xdebug extension path will depend on your operating system):

$ pecl install xdebug

Xdebug profiler

Setup (Windows)

Download php_xdebug-2.7.2-7.2-vc15-x86_64.dll

 

Move the downloaded file to
 
d:\ospanel\modules\php\PHP-7.2-x64\ext

 

Update php.ini and change the line

zend_extension = d:\ospanel\modules\php\PHP-7.2-x64\ext\php_xdebug-2.7.2-7.2-vc15-x86_64.dll

 

Restart the webserver

Xdebug profiler

Setup (Docker)

Dockerfile

FROM php:7.2-fpm

RUN apt-get update && apt-get install -y wget git unzip \
    && pecl install xdebug-2.7.1 \
    && docker-php-ext-enable xdebug

ADD ./php.ini /usr/local/etc/php/php.ini

RUN wget https://getcomposer.org/installer -O - -q \
    | php -- --install-dir=/bin --filename=composer --quiet

WORKDIR /var/www
max_execution_time = 1000
max_input_time = 1000

php.ini

Xdebug profiler

Setup (Docker)

docker-compose.yml

version: '3'
services:
  php-fpm:
    # ...
    environment:
      XDEBUG_CONFIG: "remote_host=192.168.220.1 remote_enable=1 profiler_enable_trigger=1"
      PHP_IDE_CONFIG: "serverName=Docker"
    networks:
      - internal
  nginx:
    # ...
    networks:
      - internal
networks:
  internal:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.220.0/28

Xdebug profiler

Setup (Docker)

docker-compose.yml

version: '3'
services:
  php-fpm:
    build:
      context: docker/php-fpm
    volumes:
      - ./:/var/www
    environment:
      XDEBUG_CONFIG: "remote_host=192.168.220.1 remote_enable=1 profiler_enable_trigger=1"
      PHP_IDE_CONFIG: "serverName=Docker"
    networks:
      - internal
  nginx:
    build:
      context: docker/nginx
    volumes:
      - ./:/var/www
    ports:
      - "80:80"
    depends_on:
      - php-fpm
    networks:
      - internal
networks:
  internal:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.220.0/28

It is still not recommended to run Clockwork in production or environments that might contain sensitive customer data!

!!!

Bonus

config / debugbar.php

'clockwork' => true

The Debugbar can emulate the Clockwork headers, so you can use the Chrome Extension, without the server-side code. It uses Debugbar collectors instead.

Clockwork (Laravel)

By TAlex

Clockwork (Laravel)

  • 1,765