View on GitHub

Resources for users and developers of Dicoogle

Debugging in Dicoogle

This section provides a few guidelines and approaches to debugging solutions in Dicoogle.

Logging

Dicoogle uses slf4j for logging the software’s internal functioning, backed by log4j2. Plugin developers are recommended to use slf4j in their plugins as well. It is already included in the dicoogle SDK package, so no additional dependencies are required.

The most common usage is a class-level logger instance, which is created like this:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyAmazingPlugin implements JettyPluginInterface {
    private static final Logger logger = LoggerFactory.getLogger(MyAmazingPlugin.class);

    // ...
}

You may then use the logger object like this:

logger.warn("Could not find query provider {}", providerName);

Please see the slf4j user manual for more information. The FAQ also provides excellent tips on how to use (and how not to use) the API. In particular:

  • Avoid performing concatenations in the logged text (i.e. do not write logger.info("Status: " + status);); use template matching instead (e.g. logger.info("Status: {}", status);).
  • Do not call toString() on the template arguments, as this is done automatically and only when needed.
  • Restrict ERROR level log instructions to situations where something critical occurred in the application, often associated to bugs in the software, and that should be attended by an administrator. Less critical issues should be logged with the WARNING level or lower.
  • Logging lines for fine-grained debugging purposes should be at either level DEBUG or TRACE.

When seeking to debug a particular plugin, it is useful to configure the logging framework to output messages of lower levels. You can configure Dicoogle to show these messages with a custom log4j2 configuration file, such as the one below.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <!-- This appender describes the console's standard output -->
        <Console name="STDOUT" target="SYSTEM_OUT">
            <PatternLayout pattern="%-5p %C{2} (%F:%L) - %m%n"/>
        </Console>
        <!-- This appender describes a file logging strategy which writes the latest
            lines to "dicoogle.log" and keeps previous logs in "dicoogle-#.log" files,
            rolling to a new file as the server restarts or the file becomes too large.
         -->
        <RollingRandomAccessFile name="Rolling" fileName="dicoogle.log" filePattern="dicoogle-%i.log" >
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} | %-5p [%t] (%F:%L) - %m%n"/>
            <Policies>
                <OnStartupTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="2.0 MB"/>
            </Policies>
        </RollingRandomAccessFile>
    </Appenders>
    <Loggers>
        <Root level="debug">
            <AppenderRef ref="STDOUT" level="info" />
            <AppenderRef ref="Rolling" level="info" />
        </Root>
        <Logger name="pt.ua.dicoogle" additivity="false">
            <AppenderRef ref="STDOUT" level="info" />
            <AppenderRef ref="Rolling" level="debug" />
        </Logger>
        <Logger name="org.eclipse.jetty" additivity="false">
            <AppenderRef ref="STDOUT" level="warn" />
            <AppenderRef ref="Rolling" level="info" />
        </Logger>

        <!-- specific configuration for your plugin. Use the project's
          class hierarchy root. We put `additivity="false"` to prevent
          other loggers from repeating the same lines.
        -->
        <Logger name="pt.ua.dicoogle.my.plugin" additivity="false">
            <!-- print everything except TRACE to console output -->
            <AppenderRef ref="STDOUT" level="debug" />
            <!-- and print everything to .log files -->
            <AppenderRef ref="Rolling" level="trace" />
        </Logger>
    </Loggers>
</Configuration>

The JVM variable log4j.configurationFile should then be defined when running Dicoogle, as thus:

java -Dlog4j.configurationFile=log4j2.xml -jar "dicoogle.jar" -s

Running a debugger

When trying to fix bugs in the plugin, sometimes just adding more prints is not practical, nor very helpful. Using a debugger to step through the code can be more effective at understanding the current behaviour of the software, including what’s wrong with it. A small tutorial follows, we will show the necessary steps to debug Dicoogle plugins using Visual Studio Code or, alternatively, IntelliJ IDEA. Although IDEs are not the same, they will usually involve a very similar process to the shown below.

Fetching all source repositories

First, we need the source code for both Dicoogle and the plugins that we wish to debug. Using a command-line Git client, fetching the source code for Dicoogle is simple:

git clone https://github.com/bioinformatics-ua/dicoogle.git

In this situation, you may consider checking out a released version to ensure compatibility:

git checkout 3.0.2

The same source code can be downloaded from GitHub in the Releases page. For the plugins, we suppose that you already have their respective source code. Nevertheless, if you wish to debug one of the publicly available plugins, we provide the source code on demand via the Downloads page.

Visual Studio Code

Preparing the IDE

You can download Visual Studio Code for free from the official website. Please ensure that you have the latest stable version. In order to debug Java programs, we also need the “Java Extension Pack”, which can be installed directly from the IDE, in the Extensions Marketplace. Please install this extension pack and reload Visual Studio Code afterwards.

The Java Extension Pack from the Extension Marketplace.

Preparing the workspace

Before we start using Visual Studio Code, let’s create a new folder similar to our “DicoogleDir” folder, with the following hierarchy:

 dicoogle-run-debug
 .
 ├── Plugins
 |   ├── ...
 |   ├── nifti-plugin.jar
 |   └── list-plugin.jar
 └── storage                (optional)
     ├── «my-dicom-data»
     └── ...

Basically, we are preparing a working directory for debugging purposes, containing only the necessary plugins (don’t forget the plugins!). If some data is needed, include it as well. We also do not need dicoogle.jar because we’ll be using the source code directly to build and run the respective class files.

Open the source code’s root folder with Visual Studio Code. Then, include the root folders of each plugin by adding them to your workspace (see the screenshot below). In this example, we will load two plugins that extend Dicoogle with web services: dicoogle-nifti and dicoogle-list.

Adding to workspace

At this point we should have multiple folders in the Explorer tab (you can press Ctrl+Shift+E if it’s not visible). Consider saving the workspace file to our new “dicoogle-run-debug” folder. Next, open the Debug tab (Ctrl+Shift+D) and set up a new launch configuration for “dicoogle” (the core project):

New launch configuration

At the top, when requested to choose the type of program, choose Java:

Choose Java as the base configuration

Visual Studio Code will automatically prepare us most of the configuration required. We still need to perform a few changes:

  • Only the “Debug (Launch)-Main<dicoogle>” and “Debug (Attach)” configurations are required. Other stray configurations can be removed from the JSON object.
  • We need to change the cwd field of the first configuration to the folder that we created. This is the working directory that the debugger will assume when running Dicoogle. Without it, the debugger will use the root source code folder, and the intended plugins will not be loaded.
  • We can also add the “-s” flag to the command line arguments to prevent Dicoogle from opening the browser.
    {
        "type": "java",
        "name": "Debug (Launch)-Main<dicoogle>",
        "request": "launch",
        "cwd": «PATH TO DICOOGLE DIR»,
        "console": "internalConsole",
        "stopOnEntry": false,
        "mainClass": "pt.ua.dicoogle.Main",
        "projectName": "dicoogle",
        "args": "-s"
    },

The launch.json file should be something similar to the following:

launch.json

Note that The IDE should be able to know where to find the plugins’ source code, because they are living in our workspace. However, in some cases, it may also be required to specify the source paths of each plugin. This may also be required in other IDEs. See the screenshot below for an example.

launch.json with sourcePaths

Using the debugger

To make sure that everything is properly set up, let’s add a breakpoint or two in our code. In one the plugin set classes, add a breakpoint by clicking on the left side of a code line. A red dot should appear where you clicked.

Adding a breakpoint

We can now press the “Start Debugging” button on the Debug tab to run Dicoogle through the debugger. After a few moments, the program should stop at the given breakpoints.

Finally!

It is now possible to slowly step through the code and observe the state of your plugin. More information on debugging with Visual Studio Code is available at the official website here.

IntelliJ IDEA

Preparing the IDE

You can download IntelliJ IDEA Community version for free from the official website, compatible with Windows, macOS and Linux.

Preparing the workspace

Before we start using IntelliJ IDEA, let’s create a new folder similar to our “DicoogleDir” folder, with the following hierarchy:

 dicoogle-run-debug
 .
 ├── Plugins
 |   ├── ...
 |   ├── nifti-plugin.jar
 |   └── list-plugin.jar
 ├── storage                (optional)
 |   ├── «my-dicom-data»
 |   └── ...
 └── dicoogle.jar

Basically, we are preparing a working directory for debugging purposes, containing only the necessary plugins (don’t forget all the plugins!). If some data is needed, include it as well. Open the source code’s root folder with IntelliJ IDEA.

Opening source folder

Next, we will configure the Remote Debug in IntelliJ. On the top right corner, open “Add Configuration”. From the “+” icon, choose “Remote”, as shown in the screenshot below. You can use the default settings from the IntelliJ IDEA Remote run/debug template.

Add remote configuration

Using the debugger

At this point, all the required settings are completed. To start debugging, start Dicoogle with the flag -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005. Example:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -j dicoogle.jar -s

To make sure that everything is properly set up, let’s add a breakpoint or two in our code. In one the plugin set classes, add a breakpoint by clicking on the left side of a code line. A red dot should appear where you clicked.

Adding a breakpoint

We can now press the Debug button on top right corner (or by clicking F5) to start debugging. After a few moments, the program should stop at the given breakpoints.

Click debug In this example, we added some breakpoints in pt.ua.dicoogle.server.web.servlets.accounts.LoginServlet > doPost(), which means that the program should stop when you try to login in Dicoogle on the browser:

Finally!

It is now possible to slowly step through the code and observe the state of your plugin.

Next