vscode-php-debug icon indicating copy to clipboard operation
vscode-php-debug copied to clipboard

Support filtering incoming xdebug connections by parameter

Open christhomas opened this issue 4 years ago • 6 comments

PHP version: 8.0 Xdebug version: 3.x Adapter version: 1.14.9

I've got a request to be able to filter incoming connections by a parameter (env param I guess), because I've got a larger set of services running side by side, all with xdebug enabled, all executing multiple times per request. This causes problems with the plugin because it can't find how to map the requests to the filesystem.

The setup is that I've got multiple PHP docker containers, all with xdebug enabled, all running simultaneously. When I run a frontend that makes a request, it'll ping pong through these services and each service will execute and open xdebug connections to the same port on my local machine, which means my vscode with the debugger running, will receive many xdebug connection attempts from as many as 20 docker containers all wanting to interact with xdebug.

If I've got debugging running on one of those projects, that debugger will receive 20 something incoming requests and it will be able to map only one of them to the current project, the other 19 will throw errors about path mappings being wrong, files being missing and unable to debug, etc, etc. So in my vscode launch.json, I need a way to filter these connections so only 1 of those 20 incoming connections will be debugged. The rest can safely be ignored.

It is possible to reconfigure the containers, stop+rebuild, enable xdebug, etc, etc. But this is really painful, time consuming, and from experience, error-prone. I'd much rather be able to run 20 containers all with xdebug, but filter the 1 connection that I want by an environment variable than have to play this game of whack-a-mole, trying to enable/disable xdebug based on my requirements.

PHPStorm does this by using a env var "PHP_IDE_CONFIG=serverName=thenameofmyproject" and then in each debug configuration you configure the server name to use with which configuration.

Did I explain this well? I think being able to filter incoming connections by such a parameter would be a really good feature to add and allow me to use this plugin with larger projects. What is your opinion of that?

christhomas avatar Mar 12 '21 17:03 christhomas

Hi @christhomas!

Sorry for not getting back sooner. If I understand the issue, you have a bunch of containers, where the code is residing in the same location, but outside of containers its separated. That's a typical enough setup. You want an option to scope what part of the project (eg which container) starts debugging.

Without changing anything in the extension right now, here is what I think are your options now:

  1. Use the new https://xdebug.org/docs/all_settings#trigger_value in each container to explicitly target what part of your system starts Xdebug. You'll probably want to use a Xdebug browser helper, but I'm not sure how friendly this is. In this scenario you will still need to adjust your launch.json configuration to fix path mappings each time you switch between debugging containers and you can't do multiple ones at the same time.
  2. Use a different port for each project. Each container should use a different Xdebug port. In you launch.json (I assume you have one big vscode project for all of this) just copy the "Listen For Xdebug" part any number of times and change the port. Each block can have their own path mappings. Then you can choose what parts of the project you want to debug by starting that specific vscode debug configuration. You can even run more than one.

As per your request, I do think it makes sense, but I'll try to investigate how PHPStorm does it first.

Let me know if the suggested approach works for you.

zobo avatar Mar 18 '21 08:03 zobo

To explain the setup a bit better. Imagine you have a group of 20 microservices programmed in PHP.

They'd use similar libraries and probably use the same framework (lets assume lumen), all with vendor folders with lots of similar, identical files and folders layouts.

Yet they are all completely separate projects. Running as separate docker containers.

All with xdebug enabled. All programmed when xdebug is enabled, to call back to the host machine for debugging using whatever debugger is enabled.

In this case, vscode with this extension is listening.

Now with that setup, you'd hit one of the endpoints maybe using postman or even curl and from that, you'd expect that endpoint to maybe perform subrequests to the other microservices to do various actions such as auth, maybe check data from other services, etc. Then the endpoint you hit would respond with the final result of the entire "cloud" of subrequests and calls with a json result.

In this scenario, maybe all of those services would attempt to call back to the host machine to debug. All 20 of them. You'd need to debug one project at a time, or at least have a way for the debugger to know what service made the debugging request. So you'd need a way to identify which debugging request was which.

I think this extension only allows one project or configuration to be enabled at a time. But that one debugger would receive 20 requests to do debugging and some files would mistakenly map to existing files (cause remember, they all use the same libraries, but from the wrong containers), so you'd have a mess of "which container did the request to debug and which container is currently being debugger". You'd have no way to tell which is which.

In PHPStorm, you can configure this variable I mentioned above "PHP_IDE_CONFIG=serverName=thenameofmyproject" and this comes through with each debugging request. Then each xdebug configuration can be setup with a variable "serverName" with the value "thenameofmyproject" and PHPStorm knows which xdebug configuration to use with each debugging request. It maps them from request->configuration and then if there are any breakpoints in the project setup, it knows from the configuration the path mappings to the files on your host machine how to know which location in the filesystem relates to each debugging breakpoint.

I'm not an expert on xdebug, vscode, or phpstorm, but from my understanding, this is how it knows how to correctly map each debugging request to each debugging configuration and behave accordingly.

The idea you gave would work, but it's very cumbersome and laborious, to the point where it's not really a good solution as with larger setups like what I've got, it just makes more sense to use a tool which can handle the setup rather than manually configuring each debugging setup you need on demand and toggle values, restart containers, etc, etc. It becomes very very tricky and fiddly.

I don't know how advanced this plugin is in terms of being able to run one or more configurations simultaneously like phpstorm does. But a good first step to solving this problem would be "is the xdebug request having an env var which matches the variable configured into this xdebug configuration". So when I hit the debug button in vscode and it reads the configuration. It'll only debug incoming requests which match the configuration and discard those which don't match.

Then I could debug one service at a time at least by just starting and stopping the debugger with different configurations.

That would be an amazing start. It would solve my immediate problem of having 20 requests and vscode php debug trying to debug ALL of them, but confusing which path mappings are what, then ending up with a mess of "I don't know what I'm debugging actually, but it's probably the wrong one".

I know this comment is long, but I'm trying to write down all my thoughts on how I currently have to work and how this plugin might be able to fix this issue. Does it make sense so far?

christhomas avatar Mar 19 '21 10:03 christhomas

Hi.

I think I have a pretty good picture of what your setup might be, I just have a few questions.

  1. Do you have one large checkout and see all source at once in one VSCode instance? Or do you have (up to) 20 VSCode instances open at the same time, each with its own root folder?

  2. Can you explain what you mean by "use a tool which can handle the setup"? Are you using PHPStrom to spin up Docker instances? I understand there's some tooling that helps you create the right PHPStorm configurations AND correctly configures your Docker instances so that all this works together.

  3. Do you have separate Dockerfile definitions for each service, or do you use one generic image and start up different containers and use volume mounts and .env files for configuration?

I know the DBGp specification and as far as I know there's only two ways, officially, how a debugger engine (Xdebug) can identify itself to the IDE. First is the TCP Port it connects to, the other is the IDE KEY present in the packet. One can abuse other functions, but I'd rather not..

I don't currently have access to PHPStorm. Do you think you could get me a xdebug.log showing this sort of behavior?

As for the extension. First it's good to understand the VSCode debug architecture. You create configurations in your .vscode/launch.json on the roof of you VSCode project. There can be any number of configurations, and not all need to be for type: php. Those that are, use this plugin - debug adapter, to be precise. There can be multiple php debug adapters running, the limitation is, that they cannot listen on the same TCP Port. Just a reminder, Xdebug (PHP) actually TCP connects to the debug adapter (IDE).

One solution is to define different listening ports, or to use a dbgpproxy. I have a task to implement that part, but sadly am not yet finished with it.

In any case, if you can get me that missing info (your product structure, your docker setup, any configuration orchestrator) I can give you a very specific setup that should work for you - without changing a line of the implementation of this plugin - and without you changing anything after initial setup.

I'd also very much like to see how PHPStorm communicates with their debug client implementation - xdebug.log.

zobo avatar Mar 19 '21 13:03 zobo

  1. All projects are in their own separate git repository, but either:
  • opened the parent directory of all the projects in vscode
  • use a workspace and add the individual projects I want to group together and save that as a single workspace

But in any configuration, all projects are not part of one git checkout or git repository. They are separated.

  1. Docker runs all the containers, but phpstorm isn't in control of that and we use the command line. You can run them from the command line and view them in phpstorm because all phpstorm is doing is communicating with the local docker server. There is a user interface which lets you run containers and configure them inside phpstorm, etc. But I don't think it's relevant to how the containers are debugged. We define our containers using a docker-compose.yml

  2. I'm not sure where you're going with this question. Maybe you're thinking of a "super container" which is a term I've heard before for a container which runs multiple services inside it, but this is an anti-pattern that should be avoided and we don't use it. We use the recommended one-process-one-container approach

I'm wondering whether the environment variables of a process that is connecting back to xdebug via the dbgp protocol, are available to the IDE. Because in our containers, all we need to do is set that variable I gave above. This is enough for PHPStorm to somehow map that xdebug connection to the correct configuration. So on my host machine where PHPStorm is running, it's getting that information despite it existing ONLY inside the env vars of a docker container. The phpfpm server has those variables and xdebug extension installed into that container, is being loaded by phpfpm and I'm thinking that it's sending all the env vars back to the PHPStorm xdebug session and it's parsing them out and mapping it that way.

christhomas avatar Mar 19 '21 13:03 christhomas

I know what you mean about ports and things. PHPStorm must be creating a singular xdebug listener/service on the port configured. Then accepting all incoming connections and diverting them to running configurations.

So perhaps when you activate one configuration, it runs the dbgp listener on the port for incoming connections. Then accepting the connection if it matches the configuration which is active.

But if you activate a second configuration, it just adds it to the list of active configurations and the same dbgp is being used to accept any connections. But now there are two activate configurations. Then it loops through each configuration to see which one it matches and either accepts or rejects it based on the two active configurations.

To activate a third, I guess it's the same thing. Just added one more configuration and then looping through them.

christhomas avatar Mar 19 '21 14:03 christhomas

Sorry about question 3. I got lost in details and haven't asked the right way. What I wanted to ask is: Where do you define PHP_IDE_CONFIG and I think you answered that later - you set it in docker-compose.yml (or .env). The reason I'm asking this is I want to come up with a configuration that requires as small a compromise as possible.

Regarding PHPStorm, I also believe they do it like this. This is however problematic in our case due to VSCode architecture:

  1. VSCode, and this extension, use the Debug Adapter Protocol. When you start F5 in VSCode an instance of DAP is created (code of this plugin) and one segment of the configuration from .vscode/launch.json is passed to this DAP instance. This is the ONLY context this code has.
  2. There are ways around this. This extension can carry more code parts. One being the DAP code and other the SVCode extension code. Technically we could implement the listening socket in the VSCode extension part, build a UI for it (if needed) and then spawn a DAP session - passing the active socket to it. At this point the needed configuration selection would be done.
  3. However! It's not only VSCode that uses this DAP implementation. There are other IDEs that could, can and do use it, so we must take great care not to depend on some VSCode specific behaviour.

One way to implement this could be to extend the current launch configuration and add a map of IDEKEY/path_maps. But I'm hesitant to add so much complexity.

  "configurations": [
    {
      "name": "...",
      "pathMappingsIde": {
         "project1": { "/code": "${workspaceRoot}/project1" },
      }
...

That is why I'm concentrating on ports and ide keys.

Currently I would do it like this. Filenames are made up and everything is relative to ${workspaceRoot}:

You have project1, project2, project3. Each has their own docker-compose.yml.

.vscode/launch.json

  "configurations": [
    {
      "name": "Listen for Xdebug - project1",
      "type": "php", "request": "launch",
      "pathMappings": { "/code": "${workspaceRoot}/project1" },
      "port": 9011
    },
    {
      "name": "Listen for Xdebug - project2",
      "type": "php", "request": "launch",
      "pathMappings": { "/code": "${workspaceRoot}/project2" },
      "port": 9012
    },
    {
      "name": "Listen for Xdebug - project3",
      "type": "php", "request": "launch",
      "pathMappings": { "/code": "${workspaceRoot}/project3" },
      "port": 9013
    },

In your docker-compose add following env next to PHP_IDE_CONFIG for project1 - XDEBUG_CONFIG=client_port=9011 ... for project2 - XDEBUG_CONFIG=client_port=9012 etc

(Note: I'm using Xdebug 3.)

Now you have an option to separately start debugging for each project or have multiple running at once.

If you for some reason dislike the ports solution, you could go with dbgp proxy. Difference there is that each running configuration will open a random port and register that one with its IDE KEY with dbgp proxy. There you'll also need to add an ENV XDEBUG_CONFIG=idekey=project1 for each project.

I can't implement the proposed solution as it would either break compatibility with other IDEs or introduce complexity by re-implementing configuration management that should already be in the IDE.

I really hope I understood all your pain points. The proposed solution, as far as I can see, shouldn't require any extra effort aside from added the vscode configuration and the ENVs. What I do see is that if you want to debug all of the projects always at the same time, you'd need to select-start, select-start, select-start all configuration. That's very panful. However I think that could also be solved by some macro magic. I have not explored that avenue. If you are interested in debugging one or two projects you can simply use vscode command > Debug: Select and Start Debugging....

zobo avatar Mar 19 '21 14:03 zobo