Enabling debugging in GraphQL APIs

When developing for GraphQL APIs, you might want to get detailed information about a specific request, to help you understand what is going on in more detail.

The GraphQL API supports multiple debugging modes that will return additional data when enabled. These settings should only be used during development, not on production systems.

Enabling performance data

You can enable performance data by adding either of the following lines into your config.php, above the final line that includes setuplib.php (only one or the other is required):

define('MDL_PERF', true);

$CFG->perfdebug = 15;  // Any number greater than 7 will trigger the inclusion of performance data

Types of performance data

Performance data

When enabled, each response will include additional performance data with the request. This provides information such as execution time, memory usage, number of database queries, caching information, etc., to help you understand your query's performance implications. The performance data is located within the extensions property in a property called performance_data:

{
  "data": {
    "core_course": {
      "id": "2",
      "fullname": "Course 1",
      "category": {
        "id": "1",
        "name": "Miscellaneous"
      }
    }
  },
  "extensions": {
    "performance_data": {
      "core": {
        "cache": { ... },
        "db": {
          "reads": 13,
          "writes": 3,
          "time": 0.081470000000000001
        },
        "filters": { ... },
        "includecount": 633,
        "logwrites": 0,
        "memory": {
          "total": 19839072,
          "growth": 19405176,
          "peak": 28621824
        },
        "posix_times": {
          "ticks": 57,
          "utime": 25,
          "stime": 11,
          "cutime": 0,
          "cstime": 0
        },
        "realtime": 0.56207599999999991,
        "server_load": "1.38",
        "session": {
          "size": "11.6KB",
          "handler": "core\\session\\file"
        },
        "strings": { ... }
      }
    }
  }
}

Complexity data - external API only

When performance data is enabled, the external API will include additional information about the query complexity in the response. This data is not shown for other endpoint types.

This provides information on the calculated complexity of the query, as defined by the rate-limiting code. This information is used to apply restrictions to external API usage in complexity points per minute. So, this data can be used to understand the cost of a specific query. The complexity data is located within the extensions property in a property called complexity_data:

{
	"data": { ... }
    "extensions": {
        "complexity_data": {
            "query_complexity": 10
        },
        "performance_data": { ... }
    }
}

See API rate limiting and query complexity for more information on how complexity is calculated and used.

Levels of debugging

Different API endpoints handle debugging levels in different ways.

External endpoint

You can control the level of debugging output displayed in the response by setting the response_debug totara_api setting in your config.php as follows:

// Determines the amount of information returned by an API response when an error occurs.
// - ERROR_RESPONSE_LEVEL_NONE (0)
// - ERROR_RESPONSE_LEVEL_NORMAL (1)
// - ERROR_RESPONSE_LEVEL_DEVELOPER (2)
$CFG->forced_plugin_settings['totara_api'] = array(
    'response_debug' =>  1
);

The default is normal (1), which displays basic debugging information about what went wrong via the debugMessage property:

{
  "errors": [
    {
      "debugMessage": "This is the specific error message",
      "message": "Internal server error",
      "extensions": {
        "category": "internal"
      },
      "locations": [
        {
          "line": 4,
          "column": 7
        }
      ],
      "path": [
        "core_user_users",
        "items",
        0,
        "id"
      ]
    }
  ]
}

Setting to none (0) removes the debugMessage property and just gives a generic error:

{
  "errors": [
    {
      "message": "Internal server error",
      "extensions": {
        "category": "internal"
      },
      "locations": [
        {
          "line": 4,
          "column": 7
        }
      ],
      "path": [
        "core_user_users",
        "items",
        0,
        "id"
      ]
    }
  ]
}

while developer (2) includes detailed stack trace and line numbers to the output:

{
  "errors": [
    {
      "debugMessage": "This is the specific error message",
      "message": "Internal server error",
      "extensions": {
        "category": "internal"
      },
      "locations": [
        {
          "line": 4,
          "column": 7
        }
      ],
      "path": [
        "core_user_users",
        "items",
        0,
        "id"
      ],
      "trace": [
        {
          "file": "/var/www/totara/src/main/server/totara/webapi/classes/default_resolver.php",
          "line": 91,
          "call": "core\\webapi\\resolver\\type\\user::resolve('id', instance of core\\entity\\user, array(1), instance of core\\webapi\\execution_context)"
        },
        {
          "file": "/var/www/totara/src/main/server/totara/oauth2/classes/webapi/middleware/client_rate_limit.php",
          "line": 50,
          "call": "totara_webapi\\default_resolver::totara_webapi\\{closure}(instance of core\\webapi\\resolver\\payload)"
        },
        {
          "file": "/var/www/totara/src/main/server/totara/webapi/classes/default_resolver.php",
          "line": 160,
          "call": "totara_oauth2\\webapi\\middleware\\client_rate_limit::handle(instance of core\\webapi\\resolver\\payload, instance of Closure)"
        },
		...
      ]
    }
  ]
}

Note that the responses above describe the behaviour for normal exceptions. The API also supports the concept of 'client aware' exceptions which intentionally provide more information in non-debugging mode. See Implementing GraphQL services for more details about the different exception types.

Other endpoints (AJAX, mobile, dev)

The behaviour of other endpoints depends on the $CFG->debug config.php setting. When set to a value of E_ALL or higher, GraphQL will be in debugging mode.

In debugging mode, responses will include a full-stack trace:

[
    {
        "message": "this is an error",
        "extensions": {
            "category": "example category"
        },
        "locations": [
            {
                "line": 9,
                "column": 11
            }
        ],
        "path": [
            "clients",
            "items",
            1,
            "service_account",
            "user",
            "id"
        ],
        "trace": [
            {
                "file": "/var/www/totara/src/main/server/totara/webapi/classes/default_resolver.php",
                "line": 91,
                "call": "core\\webapi\\resolver\\type\\user::resolve('id', instance of core\\entity\\user, array(0), instance of core\\webapi\\execution_context)"
            },
            {
                "file": "/var/www/totara/src/main/server/totara/webapi/classes/default_resolver.php",
                "line": 119,
                "call": "totara_webapi\\default_resolver::totara_webapi\\{closure}(instance of core\\webapi\\resolver\\payload)"
            },
            ...
            {
                "file": "/var/www/totara/src/main/server/totara/webapi/ajax.php",
                "line": 62,
                "call": "totara_webapi\\controllers\\api_controller::process('graphql_request')"
            }
        ]
    }
]

When not in debugging mode, the stack trace will be excluded:

[
    {
        "message": "this is an error",
        "extensions": {
            "category": "example category"
        },
        "locations": [
            {
                "line": 9,
                "column": 11
            }
        ],
        "path": [
            "clients",
            "items",
            0,
            "service_account",
            "user",
            "id"
        ]
    }
]

Enabling introspection

Introspection is only enabled by default on the developer API. It is not available for the AJAX and mobile APIs at all, and must be enabled via the enable_introspection admin setting on the external API (accessed via Quick-access menu > Development > API > API settings).

When disabled (the default), any introspection queries will fail with the error:

GraphQL introspection isn't enabled.