Add a debug option for the list code owners REST endpoints

Users frequently ask why a user is or isn't suggested as a code owner.
The new debug option on the list code owners REST endpoint includes
debug logs into the response that help to answer exactly this question.

We already have the CheckCodeOwner REST endpoint which exposes similar
debug logs, but only for a single user. With the new option this
information can be retrieved for all relevant users at once.

Since debug logs may contain sensitive information this option is only
available for users that have the 'Check Code Owner' global capability,
which is also required by the CheckCodeOwner REST endpoint.

Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I183907365dab02732ff67732a9d09ae95eb10459
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwners.java
index 655ef66..a2079b8 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwners.java
@@ -49,6 +49,7 @@
     private Long seed;
     private Boolean resolveAllUsers;
     private Boolean highestScoreOnly;
+    private Boolean debug;
 
     /**
      * Lists the code owners for the given path.
@@ -128,6 +129,18 @@
     }
 
     /**
+     * Sets whether debug logs should be included into the response.
+     *
+     * <p>Requires the 'Check Code Owner' global capability.
+     *
+     * @param debug whether debug logs should be included into the response
+     */
+    public QueryRequest withDebug(boolean debug) {
+      this.debug = debug;
+      return this;
+    }
+
+    /**
      * Sets the branch revision from which the code owner configs should be read.
      *
      * <p>Not supported for querying code owners for a path in a change.
@@ -166,6 +179,11 @@
       return Optional.ofNullable(highestScoreOnly);
     }
 
+    /** Whether debug logs should be included into the response. */
+    public Optional<Boolean> getDebug() {
+      return Optional.ofNullable(debug);
+    }
+
     /** Returns the branch revision from which the code owner configs should be read. */
     public Optional<String> getRevision() {
       return Optional.ofNullable(revision);
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnersInfo.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnersInfo.java
index 2d3a84c..d699029 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnersInfo.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnersInfo.java
@@ -33,4 +33,11 @@
    * <p>Not set if {@code false}.
    */
   public Boolean ownedByAllUsers;
+
+  /**
+   * Debug logs that may help to understand why a user is or isn't suggested as a code owner.
+   *
+   * <p>Only set if requested via {@code --debug}.
+   */
+  public List<String> debugLogs;
 }
diff --git a/java/com/google/gerrit/plugins/codeowners/api/impl/CodeOwnersInBranchImpl.java b/java/com/google/gerrit/plugins/codeowners/api/impl/CodeOwnersInBranchImpl.java
index 107fa61..fff0b9c 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/impl/CodeOwnersInBranchImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/impl/CodeOwnersInBranchImpl.java
@@ -60,6 +60,7 @@
           getSeed().ifPresent(getCodeOwners::setSeed);
           getResolveAllUsers().ifPresent(getCodeOwners::setResolveAllUsers);
           getHighestScoreOnly().ifPresent(getCodeOwners::setHighestScoreOnly);
+          getDebug().ifPresent(getCodeOwners::setDebug);
           getRevision().ifPresent(getCodeOwners::setRevision);
           CodeOwnersInBranchCollection.PathResource pathInBranchResource =
               codeOwnersInBranchCollection.parse(
diff --git a/java/com/google/gerrit/plugins/codeowners/api/impl/CodeOwnersInChangeImpl.java b/java/com/google/gerrit/plugins/codeowners/api/impl/CodeOwnersInChangeImpl.java
index b185172..97a5715 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/impl/CodeOwnersInChangeImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/impl/CodeOwnersInChangeImpl.java
@@ -65,6 +65,7 @@
           getSeed().ifPresent(getCodeOwners::setSeed);
           getResolveAllUsers().ifPresent(getCodeOwners::setResolveAllUsers);
           getHighestScoreOnly().ifPresent(getCodeOwners::setHighestScoreOnly);
+          getDebug().ifPresent(getCodeOwners::setDebug);
           CodeOwnersInChangeCollection.PathResource pathInChangeResource =
               codeOwnersInChangeCollection.parse(
                   revisionResource, IdString.fromDecoded(path.toString()));
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
index 11b9b6b..8272bb6 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
@@ -222,6 +222,9 @@
           "resolve code owners for %s from code owner config %s", path, codeOwnerConfig.key());
 
       List<String> messages = new ArrayList<>();
+      messages.add(
+          String.format(
+              "resolve code owners for %s from code owner config %s", path, codeOwnerConfig.key()));
 
       // Create a code owner config builder to create the resolved code owner config (= code owner
       // config that is scoped to the path and which has imports resolved)
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
index d1e6cd2..3ad00e8 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
@@ -79,6 +79,7 @@
   private final Accounts accounts;
   private final AccountControl.Factory accountControlFactory;
   private final PermissionBackend permissionBackend;
+  private final CheckCodeOwnerCapability checkCodeOwnerCapability;
   private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
   private final CodeOwnerConfigHierarchy codeOwnerConfigHierarchy;
   private final Provider<CodeOwnerResolver> codeOwnerResolver;
@@ -90,6 +91,7 @@
   private Optional<Long> seed = Optional.empty();
   private boolean resolveAllUsers;
   private boolean highestScoreOnly;
+  private boolean debug;
 
   @Option(
       name = "-o",
@@ -138,11 +140,21 @@
     this.highestScoreOnly = highestScoreOnly;
   }
 
+  @Option(
+      name = "--debug",
+      usage =
+          "whether debug logs should be included into the response"
+              + " (requires the 'Check Code Owner' global capability)")
+  public void setDebug(boolean debug) {
+    this.debug = debug;
+  }
+
   protected AbstractGetCodeOwnersForPath(
       AccountVisibility accountVisibility,
       Accounts accounts,
       AccountControl.Factory accountControlFactory,
       PermissionBackend permissionBackend,
+      CheckCodeOwnerCapability checkCodeOwnerCapability,
       CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
       CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
       Provider<CodeOwnerResolver> codeOwnerResolver,
@@ -151,6 +163,7 @@
     this.accounts = accounts;
     this.accountControlFactory = accountControlFactory;
     this.permissionBackend = permissionBackend;
+    this.checkCodeOwnerCapability = checkCodeOwnerCapability;
     this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
     this.codeOwnerConfigHierarchy = codeOwnerConfigHierarchy;
     this.codeOwnerResolver = codeOwnerResolver;
@@ -164,6 +177,10 @@
     parseHexOptions();
     validateLimit();
 
+    if (debug) {
+      permissionBackend.currentUser().check(checkCodeOwnerCapability.getPermission());
+    }
+
     if (!seed.isPresent()) {
       seed = getDefaultSeed(rsrc);
     }
@@ -180,6 +197,7 @@
 
     Set<CodeOwner> codeOwners = new HashSet<>();
     AtomicBoolean ownedByAllUsers = new AtomicBoolean(false);
+    List<String> debugLogs = new ArrayList<>();
     codeOwnerConfigHierarchy.visit(
         rsrc.getBranch(),
         rsrc.getRevision(),
@@ -187,6 +205,11 @@
         codeOwnerConfig -> {
           CodeOwnerResolverResult pathCodeOwners =
               codeOwnerResolver.get().resolvePathCodeOwners(codeOwnerConfig, rsrc.getPath());
+
+          if (debug) {
+            debugLogs.addAll(pathCodeOwners.messages());
+          }
+
           codeOwners.addAll(filterCodeOwners(rsrc, pathCodeOwners.codeOwners()));
 
           int distance =
@@ -224,6 +247,12 @@
 
     if (codeOwners.size() < limit || !ownedByAllUsers.get()) {
       CodeOwnerResolverResult globalCodeOwners = getGlobalCodeOwners(rsrc.getBranch().project());
+
+      if (debug) {
+        debugLogs.add("resolve global code owners");
+        debugLogs.addAll(globalCodeOwners.messages());
+      }
+
       globalCodeOwners
           .codeOwners()
           .forEach(
@@ -261,6 +290,7 @@
     codeOwnersInfo.codeOwners =
         codeOwnerJsonFactory.create(getFillOptions()).format(sortedAndLimitedCodeOwners);
     codeOwnersInfo.ownedByAllUsers = ownedByAllUsers.get() ? true : null;
+    codeOwnersInfo.debugLogs = debug ? debugLogs : null;
     return Response.ok(codeOwnersInfo);
   }
 
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java
index e4099c6..8a9b85c 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java
@@ -74,6 +74,7 @@
       Accounts accounts,
       AccountControl.Factory accountControlFactory,
       PermissionBackend permissionBackend,
+      CheckCodeOwnerCapability checkCodeOwnerCapability,
       CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
       CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
       Provider<CodeOwnerResolver> codeOwnerResolver,
@@ -84,6 +85,7 @@
         accounts,
         accountControlFactory,
         permissionBackend,
+        checkCodeOwnerCapability,
         codeOwnersPluginConfiguration,
         codeOwnerConfigHierarchy,
         codeOwnerResolver,
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
index e6a2f0d..3180fd1 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
@@ -62,6 +62,7 @@
       Accounts accounts,
       AccountControl.Factory accountControlFactory,
       PermissionBackend permissionBackend,
+      CheckCodeOwnerCapability checkCodeOwnerCapability,
       CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
       CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
       Provider<CodeOwnerResolver> codeOwnerResolver,
@@ -72,6 +73,7 @@
         accounts,
         accountControlFactory,
         permissionBackend,
+        checkCodeOwnerCapability,
         codeOwnersPluginConfiguration,
         codeOwnerConfigHierarchy,
         codeOwnerResolver,
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnersInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnersInfoSubject.java
index 28ea59e..11433e2 100644
--- a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnersInfoSubject.java
+++ b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnersInfoSubject.java
@@ -20,6 +20,7 @@
 
 import com.google.common.truth.BooleanSubject;
 import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IterableSubject;
 import com.google.common.truth.Subject;
 import com.google.gerrit.plugins.codeowners.api.CodeOwnerInfo;
 import com.google.gerrit.plugins.codeowners.api.CodeOwnersInfo;
@@ -58,6 +59,16 @@
     return check("ownedByAllUsers").that(codeOwnersInfo().ownedByAllUsers);
   }
 
+  public void hasDebugLogsThatContainAllOf(String... expectedMessages) {
+    for (String expectedMessage : expectedMessages) {
+      check("debugLogs").that(codeOwnersInfo().debugLogs).contains(expectedMessage);
+    }
+  }
+
+  public IterableSubject hasDebugLogsThat() {
+    return check("debugLogs").that(codeOwnersInfo().debugLogs);
+  }
+
   private CodeOwnersInfo codeOwnersInfo() {
     isNotNull();
     return codeOwnersInfo;
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
index b2d421d..2688b07 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.client.ListAccountsOption;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -45,8 +46,13 @@
 import com.google.gerrit.plugins.codeowners.acceptance.testsuite.TestPathExpressions;
 import com.google.gerrit.plugins.codeowners.api.CodeOwners;
 import com.google.gerrit.plugins.codeowners.api.CodeOwnersInfo;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImportMode;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigReference;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerReference;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerResolver;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerSet;
+import com.google.gerrit.plugins.codeowners.restapi.CheckCodeOwnerCapability;
 import com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnersForPathInBranch;
 import com.google.inject.Inject;
 import java.util.List;
@@ -167,6 +173,7 @@
         .comparingElementsUsing(hasAccountName())
         .containsExactly(null, null, null);
     assertThat(codeOwnersInfo).hasOwnedByAllUsersThat().isNull();
+    assertThat(codeOwnersInfo).hasDebugLogsThat().isNull();
   }
 
   @Test
@@ -1464,4 +1471,133 @@
             "/foo/bar/baz.md");
     assertThat(codeOwnersInfo).hasCodeOwnersThat().isEmpty();
   }
+
+  @Test
+  public void debugRequireCallerToBeAdminOrHaveTheCheckCodeOwnerCapability() throws Exception {
+    requestScopeOperations.setApiUser(user.id());
+    AuthException authException =
+        assertThrows(
+            AuthException.class,
+            () ->
+                queryCodeOwners(
+                    getCodeOwnersApi().query().withDebug(/* debug= */ true), "/foo/bar/baz.md"));
+    assertThat(authException)
+        .hasMessageThat()
+        .isEqualTo(
+            String.format("%s for plugin code-owners not permitted", CheckCodeOwnerCapability.ID));
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "global.owner@example.com")
+  public void getCodeOwnersWithDebug_byAdmin() throws Exception {
+    testGetCodeOwnersWithDebug();
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "global.owner@example.com")
+  public void getCodeOwnersWithDebug_byUserThatHasTheCheckCodeOwnerCapability() throws Exception {
+    projectOperations
+        .allProjectsForUpdate()
+        .add(allowCapability("code-owners-" + CheckCodeOwnerCapability.ID).group(REGISTERED_USERS))
+        .update();
+
+    requestScopeOperations.setApiUser(user.id());
+    testGetCodeOwnersWithDebug();
+  }
+
+  private void testGetCodeOwnersWithDebug() throws Exception {
+    TestAccount globalOwner =
+        accountCreator.create("global_owner", "global.owner@example.com", "Global Owner", null);
+
+    skipTestIfImportsNotSupportedByCodeOwnersBackend();
+
+    TestAccount user2 = accountCreator.user2();
+
+    CodeOwnerConfig.Key rootKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/")
+            .addCodeOwnerEmail(admin.email())
+            .create();
+
+    String nonExistingEmail = "non-existing@example.com";
+    CodeOwnerConfig.Key fooKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/foo/")
+            .addCodeOwnerEmail(nonExistingEmail)
+            .create();
+
+    Project.NameKey nonExistingProject = Project.nameKey("non-existing");
+    CodeOwnerConfigReference nonResolvableCodeOwnerConfigReference =
+        CodeOwnerConfigReference.builder(
+                CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
+                codeOwnerConfigOperations
+                    .codeOwnerConfig(CodeOwnerConfig.Key.create(nonExistingProject, "master", "/"))
+                    .getFilePath())
+            .setProject(nonExistingProject)
+            .build();
+
+    CodeOwnerConfig.Key fooBarKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/foo/bar/")
+            .addImport(nonResolvableCodeOwnerConfigReference)
+            .addCodeOwnerSet(
+                CodeOwnerSet.builder()
+                    .addPathExpression(testPathExpressions.matchFileType("md"))
+                    .addCodeOwnerEmail(user.email())
+                    .build())
+            .addCodeOwnerSet(
+                CodeOwnerSet.builder()
+                    .addPathExpression(testPathExpressions.matchFileType("txt"))
+                    .addCodeOwnerEmail(user2.email())
+                    .build())
+            .create();
+
+    String path = "/foo/bar/baz.md";
+    CodeOwnersInfo codeOwnersInfo =
+        queryCodeOwners(getCodeOwnersApi().query().withDebug(/* debug= */ true), path);
+    assertThat(codeOwnersInfo)
+        .hasCodeOwnersThat()
+        .comparingElementsUsing(hasAccountId())
+        .containsExactly(user.id(), admin.id(), globalOwner.id())
+        .inOrder();
+    assertThat(codeOwnersInfo)
+        .hasDebugLogsThatContainAllOf(
+            String.format("resolve code owners for %s from code owner config %s", path, fooBarKey),
+            "per-file code owner set with path expressions [*.md] matches",
+            String.format(
+                "The import of %s:master:/%s in %s:master:/foo/bar/%s cannot be resolved:"
+                    + " project %s not found",
+                nonExistingProject.get(),
+                getCodeOwnerConfigFileName(),
+                project.get(),
+                getCodeOwnerConfigFileName(),
+                nonExistingProject.get()),
+            String.format(
+                "resolving code owner reference %s", CodeOwnerReference.create(user.email())),
+            String.format("resolved to account %d", user.id().get()),
+            String.format("resolve code owners for %s from code owner config %s", path, fooKey),
+            String.format(
+                "resolving code owner reference %s", CodeOwnerReference.create(nonExistingEmail)),
+            String.format(
+                "cannot resolve code owner email %s: no account with this email exists",
+                nonExistingEmail),
+            String.format("resolve code owners for %s from code owner config %s", path, rootKey),
+            String.format(
+                "resolving code owner reference %s", CodeOwnerReference.create(admin.email())),
+            String.format("resolved to account %d", admin.id().get()),
+            "resolve global code owners",
+            String.format(
+                "resolving code owner reference %s",
+                CodeOwnerReference.create(globalOwner.email())),
+            String.format("resolved to account %d", globalOwner.id().get()));
+  }
 }
diff --git a/resources/Documentation/config-faqs.md b/resources/Documentation/config-faqs.md
index 7396934..ab88bb7 100644
--- a/resources/Documentation/config-faqs.md
+++ b/resources/Documentation/config-faqs.md
@@ -4,6 +4,7 @@
 * [How to check if the code owners functionality is enabled for a project or branch](#checkIfEnabled)
 * [How to avoid issues with code owner config files](#avoidIssuesWithCodeOwnerConfigs)
 * [How to investigate issues with code owner config files](#investigateIssuesWithCodeOwnerConfigs)
+* [How to investigate issues with the code owner suggestion](#investigateIssuesWithCodeOwnerSuggestion)
 * [How to define default code owners](#defineDefaultCodeOwners)
 * [How to setup code owner overrides](#setupOverrides)
 * [What's the best place to keep the global plugin
@@ -86,6 +87,7 @@
 
 Since code owner config files are part of the source code, any issues with them
 should be investigated and fixed by the project owners and host administrators.
+
 To do this they can:
 
 * Check the code owner config files for issues by calling the [Check Code Owner
@@ -101,6 +103,39 @@
 Also see [above](#avoidIssuesWithCodeOwnerConfigs) how to avoid issues with code
 owner config files in the first place.
 
+## <a id="investigateIssuesWithCodeOwnerSuggestion">How to investigate issues with the code owner suggestion
+
+If the code owners config suggestion is not working as expected, this is either
+caused by:
+
+* issues in the code owner config files
+* user permissions
+* account visibility
+* account states
+* a bug in the @PLUGIN@ plugin
+
+Issues with code owner config files, user permissions, account visibility and
+account states should be investigated and fixed by the project owners and host
+administrators.
+
+To do this they can:
+
+* Use the `--debug` option of the [List Code
+  Owners](rest-api.html#list-code-owners-for-path-in-branch) REST endpoints to
+  get debug logs included into the response.
+* Check the code owner config files for issues by calling the [Check Code Owner
+  Config File REST endpoint](rest-api.html#check-code-owner-config-files)
+* Check the code ownership of a user for a certain path by calling the [Check
+  Code Owner REST endpoint](rest-api.html#check-code-owner) (requires the caller
+  to be host administrator or have the [Check Code Owner
+  capability](rest-api.html#checkCodeOwner))
+
+Bugs with the @PLUGIN@ plugin should be filed as issues for the Gerrit team, but
+only after other causes have been excluded.
+
+Also see [above](#avoidIssuesWithCodeOwnerConfigs) how to avoid issues with code
+owner config files in the first place.
+
 ## <a id="defineDefaultCodeOwners">How to define default code owners
 
 [Default code owners](backend-find-owners.html#defaultCodeOwnerConfiguration)
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index 2cfa2f7..be963ff 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -462,6 +462,7 @@
 | `seed`       | optional | Seed, as a long value, that should be used to shuffle code owners that have the same score. Can be used to make the sort order stable across several requests, e.g. to get the same set of random code owners for different file paths that have the same code owners. Important: the sort order is only stable if the requests use the same seed **and** the same limit. In addition, the sort order is not guaranteed to be stable if new accounts are created in between the requests, or if the account visibility is changed.
 | `resolve-all-users` | optional | Whether code ownerships that are assigned to all users should be resolved to random users. If not set, `false` by default. Also see the [sorting example](#sortingExample) below to see how this parameter affects the returned code owners.
 | `highest-score-only` | optional | Whether only code owners with the highest score should be returned. If not set, `false` by default.
+| `debug`      | optional | Whether debug logs should be included into the response. Requires the [Check Code Owner](#checkCodeOwner) global capability.
 | `revision`   | optional | Revision from which the code owner configs should be read as commit SHA1. Can be used to read historic code owners from this branch, but imports from other branches or repositories as well as default and global code owners from `refs/meta/config` are still read from the current revisions. If not specified the code owner configs are read from the HEAD revision of the branch. Not supported for getting code owners for a path in a change.
 
 As a response a [CodeOwnersInfo](#code-owners-info) entity is returned that
@@ -959,6 +960,7 @@
 | ------------- | -------- | ----------- |
 | `code_owners` |          | List of code owners as [CodeOwnerInfo](#code-owner-info) entities. The code owners are sorted by a score that is computed from mutliple [scoring factors](#scoringFactors).
 | `owned_by_all_users` | optional | Whether the path is owned by all users. Not set if `false`.
+| `debug_logs`  | optional | Debug logs that may help to understand why a user is or isn't suggested as a code owner. Only set if requested via `--debug`.
 
 ### <a id="file-code-owner-status-info"> FileCodeOwnerStatusInfo
 The `FileCodeOwnerStatusInfo` entity describes the code owner statuses for a
@@ -1039,7 +1041,8 @@
 ### <a id="checkCodeOwner">Check Code Owner
 
 Global capability that allows a user to call the [Check Code
-Owner](#check-code-owner) REST endpoint.
+Owner](#check-code-owner) REST endpoint and use the `--debug` option of the
+[List Code Owners](#list-code-owners-for-path-in-branch) REST endpoints.
 
 Assigning this capability allows users to inspect code ownerships. This may
 reveal accounts and secondary emails to the user that the user cannot see