Use your local development environment to do breakpoint debugging on your remote server(s)
This document is mostly about the nuts-and-bolts of establishing communications between your local development environment and the remote servers. It’s not a HOWTO use your IDE, it’s about how to diagnose network or service issues that are specific to the Platform hosting environment.
Assumptions
You will need:
- A local copy of your project site, with all code files available
- The
platform
CLI utility installed and configured to connect to the hosting API - An IDE such as PHPStorm with integrated XDebug support.
- XDebug extensions installed and activated on your environments
Conceptual overview
Before getting started, it’s helpful to understand what happens at every step in an xdebugging process.
You normally don’t have to worry about some of these layers, but if any one of them goes wrong, nothing will work, so this may help to narrow in on what does and doesn’t work, when you are setting up for the first time.
Once all the server configurations are in place, and your IDE and the tunnels are set up to listen, what happens is this:
- You initiate an xdebug session from your browser, by requesting
http://your.site?XDEBUG_SESSION_START=yourxdebugkey
- This request is routed to the outer (cache) layer of the hosting platform, where the presence of the
XDEBUG_SESSION_START
key should tell the system not to cache it. - The request next passes to the load-balancing router, which forwards it to one of three web heads.
- It reaches the (nginx) webserver, which also recognises the
XDEBUG_SESSION_START
key, and so passes it to a dedicated xdebug-enabled php service for processing.
This is to avoid the performance cost of running xdebug resident or on-demand inside a production PHP service. - PHP script execution begins, and this starts sending stack trace messages to a socket on that server (
/run/platform/${PROJECTID}${SUFFIX}/xdebug.sock
). - Your development environment has a tunnel open to each of the three web heads, which listens to that remote socket(s), and relays these messages to a local socket (port 9000) on your development machine.
- Your IDE is configured to listen to that port, so the incoming message triggers IDE debugging.
- Your IDE is configured to map the paths of php code files on the server to your local project folders, so it’s able to show you where in the local code, the remote process is currently stepping through.
- You can use your IDE to step through the execution stack, evaluate state, or run to breakpoints. This sends messages back over the tunnel to the running process, which then executes on the server.
- Eventually, execution and page build completes, and the response is sent back from the server, and your browser displays the page.
- For subsequent browser requests, an
XDEBUG_SESSION
cookie should have been set, and should provide the same effect as theXDEBUG_SESSION_START
parameter for subsequent requests.
That is what is supposed to happen when all is well.
The routing, tunneling and the multi-head delegation of requests are the quirks specific to this hosting environment that you may need to know. Other tutorials on how to xdebug Magento or remote breakpoint debugging with PHPStorm should be referred to for deeper HOWTOs.
For reference, the config settings on the server that make this happen can be inspected on the server at
/etc/platform/$USER/php-fpm.xdebug.conf
/etc/platform/$USER/php.xdebug.ini
Activity logs are kept separate from the usual access logs, and are seen at
/var/log/platform/$USER/xdebug.access.log
/var/log/platform/$USER/php5-fpm-xdebug.log
Remember, you are sometimes talking to three different servers at once.
A Dedicated (Previously known as “Pro”, “Platform Enterprise” or “PE”) site with integrated deployment management has several web heads in the cluster.
This makes connecting to “the server” indeterminate, as any one of three may be the server for a request, so keep this in mind as we go forwards.
Getting started
Getting the server configured
For a “Dedicated” cluster, you need to have requested to have xdebug enabled via a support ticket to Platform.sh.
This may already be done for you, so please check before raising another ticket.
They will have set xdebug_enable: true
on your project, and provided you with a unique xdebug_key
to use to initiate the session.
Your xdebug_key
is usually different between your production
and your staging
environments. Take note of which you are using.
If xdebug has already been enabled, a record of your key may have been helpfully left in a text file
/mnt/shared/
for your reference.If it’s not there, you can usually retrieve the xdebug key with a command like:
platform ssh --environment=staging 'grep -A 3 XDebug /etc/platform/$USER/nginx.conf'
# XDebug Configuration ## map "$cookie_xdebug_session$arg_xdebug_session_start$arg_xdebug_session_stop" $php_backend { "Gd6QdPZaqnnSet32" "unix:///run/platform/myproject_stg/php5-xdebug.sock";
= Your
XDEBUGKEY=Gd6QdPZaqnnSet32
If you can’t find that key in your
nginx.conf
file either, then xdebug is probably not yet enabled for you, and you should raise the request.
Local environment
Assume any debugging should be happening on staging in the first case.
PROJECTID=[projectID from ticket]
BRANCH='staging'
Or if you already have the project cloned locally:
PROJECTID=$(platform project:info id)
HOSTNAME=$(platform environment:info edge_hostname)
XDebug talking to multiple webheads
To listen to multiple possible sources of incoming xdebug connections,
XDebug communication from all three heads need to be tunneled back to a port (9000) on our local environment.
Most public XDebug tutorials won’t allow for this multiple-head issue.
Open tunnels to the servers
Here’s a small script that sets up 3 simultaneous tunnels:
# Set these:
PROJECTID="xxxxxxxxxxxxx"
XDEBUGKEY="yyyyyyyyyyyyy"
# Optionally change these:
BRANCH="staging"; # or 'master'
PORT=9000
# These are the per-instance configurations you may need to change.
# Review these
URL=$( platform --project=${PROJECTID} --environment=${BRANCH} route:get 'https://{default}/' --property=url )
[ $BRANCH = 'staging' ] && SUFFIX='_stg' || SUFFIX=''
SOCKETPATH="/run/platform/${PROJECTID}${SUFFIX}/xdebug.sock"
WEBHEADS=( $(platform --project=${PROJECTID} --environment=${BRANCH} ssh --pipe --all) )
# These are settings to be used for setting up the tunnels
# Now open the tunnels
for WEBHEAD in $WEBHEADS ; do
echo "Will listen to xdebug on webhead ${WEBHEAD}"
echo "Clearing old xdebug socket on instance."
ssh ${WEBHEAD} "rm ${SOCKETPATH}"
echo "Opening port forwarding to xdebug socket. Listening on port ${PORT} in the background."
ssh -R ${SOCKETPATH}:localhost:${PORT} -N ${WEBHEAD} &
done
At this point, the remote server(s) should be sending you messages back down that tunnel.
You next need to connect a listener on your end to do something with that info.
To close the tunnels
kill $(jobs -p)
If using
zsh
, then “jobs -p
” doesn’t work as expected. Instead, “kill %1 %2 %3 %4
” may work.
In my experience, the ssh tunnels time out on their own in about 10 minutes if idle.
Timeout, server ssh.platform.cloud not responding.
[1] Exit 255 ssh -R ${SOCKETPATH}:localhost:${PORT} -N ${WEBHEAD}
To start debugging.
Launch your browser with the key in the URL
open "${URL}?XDEBUG_SESSION_START=${XDEBUGKEY}"
Diagnostic: Ensure the request is hitting the server(s) correctly
When that key is used (and is correct) during a browser session, transactions will be getting logged in xdebug.access.log
on the server(s).
You can check activity in these files to ensure that the request is even getting through - that it’s not being cached, and that the parameter is being passed through the router without being stripped.
The following command will summarize the most recent xdebug.access.log
from all webheads at once.
WEBHEADS=$(platform ssh --project=$PROJECTID --environment=staging --pipe --all)
echo $WEBHEADS | xargs -I% ssh % 'tail /var/log/platform/$USER/xdebug.access.log'
Reasons the XDebug session may not be getting logged in xdebug.access.log
- You have three web heads, the load balancer may be sending your request to any one of them. You need to check all logs on all instances at once, not just one.
- Your $XDEBUGKEY is wrong. Double-check against the value in
/etc/platform/$USER/nginx.conf
. - The URL you used was for a different branch than the one you are looking at
- The outside cache layer is intercepting the request. You can check if it’s cached using
wget -I
- The inside router is not recognising or honoring the
XDEBUGKEY
- Nginx is performing a redirect or an access denied before the request can be routed to php. You’ll need your IP to be whitelisted if using HTTP access controls.
- The xdebug php service (
site-$USER-xdebug-php
) is not running or responding. You should see it in the server process list (ps -axf
).
Investigating
Things don’t always go smoothly, so here is a process of elimination to ensure that all things are set up as expected.
To verify that xdebug configs have been deployed on the host(s)
You can see the settings on the servers in /etc/platform/${PROJECTID}${SUFFIX}/php.xdebug.ini
,
looking like
xdebug.remote_host = unix:///run/platform/xxxxxxxxxxxxx_stg/xdebug.sock
To verify that xdebug is being loaded by PHP
You may be able to check out a phpinfo diagnostic from within your web application and confirm xdebug is running.
In Drupal this can be found underneath reports
.
Don’t verify using php -m
command
Note: Running basic diagnostics like commandline
php -i
on the server
may not show that xdebug is enabled as php can be configured to use different settings
for commandline than it does for web requests. The filesphp-fpm.conf
,php-cli.conf
, andphp-fpm.xdebug.conf
(deployed into in the appsetc
folder) are different in that way, and are each used depending upon context of the request.
To verify that the xdebug process is active
xdebug.access.log
The xdebug.conf
tells us that the logs are at /var/log/platform/$USER/xdebug.access.log
Tailing that log should show some current activity when a browser session activates xdebug with the xdebug key.
When connected to an instance,
tail -f /var/log/platform/${USER}/xdebug.access.log
Or called directly from your environment:
echo tail /var/log/platform/${PROJECTID}${SUFFIX}/xdebug.access.log | platform --project=${PROJECTID} --environment=${BRANCH} ssh
echo tail /var/log/platform/${PROJECTID}${SUFFIX}/error.log | platform --project=${PROJECTID} --environment=${BRANCH} ssh
Beware, the requests will actually happen on more than one instance, so you will only see some of the requests. Use tmux or similar tools to watch them all simultaneously.
Gotcha if using non-standard project names
Most dedicated hosting plans name your docroot after your project ID. Such as qazqaz234qaz
or qazqaz234qaz_stg
. Thus is the assumption used in the tunnel script that is configured to listen to the xdebug socket /run/platform/qazqaz234qaz/php5-xdebug.sock
. However, if you are using a non-standard or legacy docroot name, some of these paths need to be updated. The socket may instead be something like /run/platform/shoppingsite/php5-xdebug.sock
.
Diagnosing if the cache layer is interfering
If your outer cache layer (eg Fastly) is returning a previously cached version of the page, you will seem to be getting debuggable transactions at first, but later requests will fail to debug, sometimes unpredictably.
Use curl -I "${URL}?XDEBUG_SESSION_START=${XDEBUGKEY}"
a couple of times in a row, and look for x-cache: HIT, HIT
in the response.
If this is happening, you need to bypass the cache. This may be possible by disabling your cache, or configuring it to use the presence of the XDEBUG_SESSION_START
argument to prevent caching - using the cache management configurations if you have access to them.
To verify that the socket is open for communication with the debugger client
After the client sets up a tunnel from their development side, the socket file mentioned in the configs
should be seen on each of the servers.
ls -la /run/platform/${USER}/xdebug.sock
You should check to see that the last-modified date on it is recent - that reflects the last time the socket was set up.
Beware timezones on the server are likely to be quite different to your own!
Compare the date against the server time!echo $(( $(date +%s) - $(stat -c%Y "/run/platform/${USER}/xdebug.sock") )) seconds old.`
Note: Don’t get distracted by
php5-xdebug.sock
seen in the same directory, that’s an internal socket used for communication between php-fpm and nginx
To verify that messages are being sent down the pipe to the debugger client
Listen for a bit
When the tunnels are active, port 9000 on the developers machine is a window into the xdebug process.
If it seems that xdebug is not firing at all, on the server you may sniff what’s happening on port 9000 with something like
As a very basic test, running
netcat --listen --local-port 9000
on the developer machine, and then visiting the website with the XDEBUG_SESSION_START
key in the URL (or the XDEBUG_CONFIG
set in a CLI environment) should result in the first raw xdebug message being shown on your console.
It’s an XML packet in the DBGp protocol.
Doing this will stall the server, as you will not be able to respond with the expected sort of acknowledgements (just exit out)
but if you get any sort of initial packet sent to that port, it shows that something xdebug is happening, and you need to work on the tunnels.
It seems that unless you acknowledge that first message appropriately, no subsequent ones will be sent, and the server will hang there until you kill one end of the conversation, so there is a limit to what can be done without a real debugging tool, but this may at least prove that messages are getting through to the developers desktop.
Dephpugger is a quick CLI tool for this, but you probably want to just go straight to using a real IDE.
Using an IDE to listen to xdebug messages
With something like PHPStorm, you can just ‘start listening’ to port 9000 and when the first message arrives from the server, the wizard will ask you to match the incoming request (eg /app/${PROJECTID}_stg/index.php
)
to a local file to begin breakpoint debugging. If you don’t have a local checkout of the project, well, you need to go get one to proceed now.
XDebug on cli
Note that xdebugging on the CLI does NOT log into the access log (not even the
xdebug.access.log
which is for web requests)
so looking for clues there will not help.
You can trigger xdebug behaviour on the CLI
- using a custom
php.ini
, - by setting an environment variable
export XDEBUG_CONFIG="remote_enable=1"
- or by specifying everything up front in the commandline arguments
…though all methods ALSO need XDEBUG_CONFIG
to at least be set to SOMETHING.
To test XDebug is working in a snippet
As a single command is the most straightforward for testing, XDebug can be triggered minimally with:
# On the host:
SOCKETPATH="unix:///run/platform/${USER}/xdebug.sock"
export XDEBUG_CONFIG="remote_enable=1 remote_host=$SOCKETPATH"
php \
-dzend_extension=xdebug.so \
index.php
If you have a listener open on port 9000 on your local dev, it’ll start getting messages.
I haven’t been able to find a way to get logs of these transactions, so it’s up to you to be listening correctly.
To use the php.xdebug.ini
To work as designed however, a php.xdebug.ini
has been provided.
To use that, you should invoke php, source the special ini
, and also must set XDEBUG_CONFIG
to non-null in your session.
PHPXDEBUG=/etc/platform/${USER}/php.xdebug.ini
export XDEBUG_CONFIG=true
php -c $PHPXDEBUG index.php
… and stuff should be coming down the socket.
Interesting snippets:
PHPXDEBUG=/etc/platform/$USER/php.xdebug.ini
php -c $PHPXDEBUG -r ‘echo(ini_get(“xdebug.idekey”));’
Go and trace your project
The real fun begins after the tunnels are set up and the XDebug communications are happening. Other tutorials from your IDE will probably be more helpful than can be covered here.