Skip to content

Conversation

@megatnt1122
Copy link
Collaborator

@megatnt1122 megatnt1122 commented Dec 2, 2025

Ticket

#1522

Description

Logging improvements to topic rounter

Tasks

  • - A description of the PR has been provided, and a diagram included if it is a new feature.
  • - Formatter has been run
  • - CHANGELOG comment has been added
  • - Labels have been assigned to the pr
  • - A reviwer has been added
  • - A user has been assigned to work on the pr
  • - If new feature a unit test has been added

Summary by Sourcery

Add structured logging around topic router endpoints and introduce basic integration tests for topic routing.

Enhancements:

  • Instrument topic list, view, and search endpoints with standardized request lifecycle logging including client and correlation identifiers.

Tests:

  • Add Foxx integration tests for topic_router covering /view, /search, and /list/topics behavior, and wire these into the CMake Foxx test suite.

@megatnt1122 megatnt1122 added Type: Refactor Imlplementation change, same functionality Priority: Low Lower priority work. Component: Foxx Foxx Arango micro services. labels Dec 2, 2025
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 2, 2025

Reviewer's Guide

Adds structured request logging around topic router endpoints and introduces basic integration tests for the topic Foxx service, wiring them into the CMake Foxx test suite.

Sequence diagram for topic router logging on list topics endpoint

sequenceDiagram
    actor Client
    participant TopicRouter as TopicRouter_list_topics
    participant GLib as GLib_support
    participant Logger as Logger
    participant DB as ArangoDB
    participant ErrorHandler as ErrorHandler

    Client->>TopicRouter: GET /topic/list/topics?client=clientId
    TopicRouter->>GLib: getUserFromClientID(clientId)
    GLib-->>TopicRouter: client

    TopicRouter->>Logger: logRequestStarted(clientId, correlationId, GET, topic/list/topics, Started, List topics)

    TopicRouter->>DB: execute list topics query
    DB-->>TopicRouter: result

    alt success
        TopicRouter-->>Client: 200 OK with result
        TopicRouter->>Logger: logRequestSuccess(clientId, correlationId, GET, topic/list/topics, Success, List topics, result)
    else failure
        TopicRouter->>Logger: logRequestFailure(clientId, correlationId, GET, topic/list/topics, Failure, List topics, result, error)
        TopicRouter->>ErrorHandler: handleException(error, response)
        ErrorHandler-->>Client: error response
    end
Loading

Class diagram for topic router logging integration

classDiagram
    class TopicRouter {
        +get_list_topics(req, res)
        +get_view(req, res)
        +get_search(req, res)
    }

    class Logger {
        +logRequestStarted(client, correlationId, httpVerb, routePath, status, description)
        +logRequestSuccess(client, correlationId, httpVerb, routePath, status, description, extra)
        +logRequestFailure(client, correlationId, httpVerb, routePath, status, description, extra, error)
    }

    class GLib_support {
        +getUserFromClientID(clientId)
        +handleException(error, res)
    }

    class ArangoDB_t {
        +exists(id)
        +document(id)
        +query(queryString, params)
    }

    TopicRouter --> Logger : uses
    TopicRouter --> GLib_support : uses
    TopicRouter --> ArangoDB_t : uses
Loading

File-Level Changes

Change Details Files
Instrument topic router endpoints with structured request logging using a shared logger and base path.
  • Import shared logger module and define a common basePath for topic routes.
  • Resolve client from client query parameter at the start of each handler for inclusion in log context.
  • Add logRequestStarted calls at the beginning of list, view, and search handlers with HTTP verb, route path, correlation ID, and description.
  • Add logRequestSuccess calls after successful responses including result/topic payloads in the extra field.
  • Add logRequestFailure calls in catch blocks, including error and last known result/topic payloads before delegating to existing exception handling.
core/database/foxx/api/topic_router.js
Register topic router Foxx tests in the CMake test harness.
  • Add a foxx_topic_router test target invoking test_foxx.sh with the unit_topic_router tag.
  • Ensure foxx_topic_router test has the Foxx fixtures required for execution.
core/database/CMakeLists.txt
Add integration-style Foxx tests for topic router endpoints to validate routes and basic behavior.
  • Create topic_router.test.js test suite using Mocha/Chai and @arangodb/request against the /topic endpoints.
  • Set up and tear down user (u) and topic (t) collections before/after tests, truncating or creating as needed.
  • Test /topic/view returns 200 when a valid user and topic exist and are referenced via query params.
  • Test /topic/search by creating a topicview ArangoSearch view, inserting a topic, forcing view refresh, and asserting the endpoint returns 200.
  • Test /topic/list/topics returns only top-level topics by inspecting paging metadata and returned topic IDs.
core/database/foxx/tests/topic_router.test.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `core/database/foxx/api/topic_router.js:12` </location>
<code_context>
 const g_lib = require("./support");
+const logger = require("./lib/logger");

+const basePath = "topic";
 module.exports = router;

</code_context>

<issue_to_address>
**question:** Consider whether `basePath` should include the leading slash to keep logged route paths consistent with actual routes.

Right now the routes are defined as `/list/topics`, `/view`, `/search`, but logged as `topic/list/topics` (no leading slash). If any tooling or consumers expect paths to start with `/` or to match the Foxx route definitions, this mismatch could cause confusion in logs and dashboards. Consider standardising this to a leading-slash form (e.g. `"/topic"`) or logging the full route path exactly as defined.
</issue_to_address>

### Comment 2
<location> `core/database/foxx/api/topic_router.js:19` </location>
<code_context>

 router
     .get("/list/topics", function (req, res) {
+        const client = g_lib.getUserFromClientID(req.queryParams.client);
         try {
+            logger.logRequestStarted({
</code_context>

<issue_to_address>
**issue (bug_risk):** Client lookup happening outside the `try` block can prevent error handling and logging from running if it throws.

In this handler (and in the `/view` and `/search` handlers), `g_lib.getUserFromClientID` is called before the `try`. If it throws (e.g., malformed client ID, DB error), `logRequestStarted`, `logRequestFailure`, and `g_lib.handleException` are all skipped and the exception escapes unhandled. Please move this lookup into the existing `try` block or wrap it in its own `try/catch` so these failures are logged and mapped to proper HTTP responses.
</issue_to_address>

### Comment 3
<location> `core/database/foxx/api/topic_router.js:21-28` </location>
<code_context>
     .get("/list/topics", function (req, res) {
+        const client = g_lib.getUserFromClientID(req.queryParams.client);
         try {
+            logger.logRequestStarted({
+                client: client?._id,
+                correlationId: req.headers["x-correlation-id"],
+                httpVerb: "GET",
+                routePath: basePath + "/list/topics",
+                status: "Started",
+                description: "List topics",
+            });
+
             var qry,
</code_context>

<issue_to_address>
**suggestion (bug_risk):** `logRequestStarted` in `/search` is outside the `try`, unlike the other handlers, which may lead to inconsistent error handling.

In `/search`, `logRequestStarted` runs before the `try`, unlike `/list/topics` and `/view` where it’s inside. If this call throws (e.g., due to serialization, transport, or config issues), the request can fail before the main logic runs and `logRequestFailure`/`handleException` won’t be invoked. To align behavior and avoid unhandled failures, move `logRequestStarted` into the `try` or wrap it in its own small `try/catch`.

Suggested implementation:

```javascript
        try {
            logger.logRequestStarted({
                client: client?._id,
                correlationId: req.headers["x-correlation-id"],
                httpVerb: "POST",
                routePath: basePath + "/search",
                status: "Started",
                description: "Search topics",
            });
        } catch (logErr) {
            // Prevent logging failures from breaking the request handler.
            // Adjust this to use your central error logger if available.
            console.error("Failed to log request start for /search", logErr);
        }

```

Because I can’t see the full `/search` handler, you should:

1. Ensure this `logger.logRequestStarted` block is located at the start of the `/search` route handler, before the main `try` that wraps the business logic (if one exists).
2. If your codebase has a centralized logging/monitoring utility for internal errors (e.g. `logger.logInternalError` or similar), replace `console.error` in the `catch` with that, to stay consistent with the rest of the file.
3. Optionally, if the `/search` handler already has a top-level `try { ... } catch (err) { ... }`, you can alternatively move `logger.logRequestStarted` inside that main `try` block instead of wrapping it in its own `try/catch`, to match exactly how `/list/topics` and `/view` are structured.
</issue_to_address>

### Comment 4
<location> `core/database/foxx/api/topic_router.js:73-80` </location>
<code_context>
             });

             res.send(result);
+            logger.logRequestSuccess({
+                client: client?._id,
+                correlationId: req.headers["x-correlation-id"],
+                httpVerb: "GET",
+                routePath: basePath + "/list/topics",
+                status: "Success",
+                description: "List topics",
+                extra: result,
+            });
         } catch (e) {
</code_context>

<issue_to_address>
**🚨 suggestion (security):** Logging full `result`/`topic` objects in `extra` may have performance and data-exposure implications.

Across these handlers, `extra` is set to the full `result`/`topic` for both success and failure. For large or sensitive payloads (e.g. topic lists, embedded content, user fields), this can bloat logs and expose data unnecessarily. Consider logging only key identifiers/summary fields or a redacted subset instead, particularly for `/list/topics` and `/search` where responses can be large.

Suggested implementation:

```javascript
            logger.logRequestSuccess({
                client: client?._id,
                correlationId: req.headers["x-correlation-id"],
                httpVerb: "GET",
                routePath: basePath + "/list/topics",
                status: "Success",
                description: "List topics",
                // Only log summary info to avoid large/sensitive payloads in logs
                extra: {
                    topicCount: Array.isArray(result) ? result.length : undefined,
                },
            });

```

```javascript
            logger.logRequestFailure({
                client: client?._id,
                correlationId: req.headers["x-correlation-id"],
                httpVerb: "GET",
                routePath: basePath + "/list/topics",
                status: "Failure",
                description: "List topics",
                // Only log summary info to avoid large/sensitive payloads in logs
                extra: {
                    topicCount: Array.isArray(result) ? result.length : undefined,
                },
                error: e,
            });

```

To fully implement your comment across the codebase, you likely need to:
1. Apply the same pattern to other topic-related handlers (e.g. `/search`, `/list`, detail endpoints) where `extra` currently logs full `result`/`topic` payloads.
2. Consider extracting a small helper, e.g. `summarizeResultForLog(result)` or `summarizeTopicForLog(topic)`, that returns only identifiers and counts (IDs, names, counts) and use that helper consistently in all `logger.logRequestSuccess` / `logger.logRequestFailure` calls.
3. For search endpoints, log summary fields such as `{ query, resultCount }` instead of full result arrays or documents, to avoid performance and data-exposure issues.
</issue_to_address>

### Comment 5
<location> `core/database/foxx/tests/topic_router.test.js:36-45` </location>
<code_context>
+        });
+    });
+
+    it("should successfully run the list route", () => {
+        db.u.save({
+            _key: "fakeUser",
+            _id: "u/fakeUser",
+            name: "fake user",
+            name_first: "fake",
+            name_last: "user",
+            is_admin: true,
+            max_coll: 50,
+            max_proj: 10,
+            max_sav_qry: 20,
+            email: "[email protected]",
+        });
+
+        db.t.save({
+            _key: "10",
+        });
+
+        // arrange
+        // TODO: make encoded query params less hard coded
+        const request_string = `${topic_base_url}/view?client=u/fakeUser&id=10`;
+        // act
+        const response = request.get(request_string);
+        // assert
+        expect(response.status).to.equal(200);
</code_context>

<issue_to_address>
**issue (testing):** Test name and URL under test are inconsistent

The test name mentions the list route, but it actually hits `/view` (`/topic/view?client=u/fakeUser&id=10`). This is confusing for future readers and coverage assumptions. Please either rename the test to reflect `/view` or change the URL to the intended list route (e.g. `/list/topics`).
</issue_to_address>

### Comment 6
<location> `core/database/foxx/tests/topic_router.test.js:62-60` </location>
<code_context>
+    it("should successfully run the search route", () => {
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding search edge-case tests (no matches / empty phrase) to better cover `/search` behavior

Right now we only assert the happy-path match. Given the logic in this route (tokenization, phrase parsing, etc.), please add at least one edge-case test, e.g. a search with no results and/or an empty/whitespace-only phrase (if valid). This helps verify router behavior and logging under less ideal inputs.

Suggested implementation:

```javascript
    it("should run the search route for happy-path and edge cases", () => {
        // Create user
        db.u.save({
            _key: "fakeUser",
            is_admin: true,
        });

        // Create ArangoSearch View (if missing)
        if (!db._view("topicview")) {
            db._createView("topicview", "arangosearch", {
                links: {

```

Because only the beginning of the test body is visible, you’ll need to extend the body of this `it` block where the actual `/search` request is currently made. After the existing “happy-path” search assertion, add at least:

1. A “no matches” search:
   - Issue a GET to `/search` with a phrase that you know will not match any seeded topic (e.g. `__no_such_phrase__`).
   - Assert `response.status` is 200 (or the expected status) and that the response body contains an empty result set (e.g. `expect(response.json.results).to.have.length(0)` or equivalent for your schema).
   - Optionally assert that any expected logging/metadata fields are present.

2. An empty/whitespace phrase search (if the route allows it):
   - Issue a GET to `/search` with `phrase=""` or `"   "`.
   - Assert the status code and body semantics that your router defines for this case (e.g. 200 with empty results, or a 400 validation error).
   - Add explicit assertions for body shape so regressions in input validation or tokenization are caught.

Place these additional calls after your current “happy-path” search request and keep using the existing `request`/`topic_base_url` helpers so the tests stay consistent with the rest of the file.
</issue_to_address>

### Comment 7
<location> `core/database/foxx/tests/topic_router.test.js:56-58` </location>
<code_context>
+
+        // arrange
+        // TODO: make encoded query params less hard coded
+        const request_string = `${topic_base_url}/view?client=u/fakeUser&id=10`;
+        // act
+        const response = request.get(request_string);
+        // assert
+        expect(response.status).to.equal(200);
</code_context>

<issue_to_address>
**suggestion (testing):** No test for behavior when `client` is missing or invalid in the query

These changes now rely on `g_lib.getUserFromClientID(req.queryParams.client)` in all three routes, and all current tests pass a valid `client` query (with `/search` using a slightly different shape). Please add at least one test where `client` is missing and one where it’s malformed, to confirm the router’s behavior in those cases (e.g., logging a null client vs. returning an explicit error) and to validate the robustness of the new logging lookup.

Suggested implementation:

```javascript
        db.t.save({
            _key: "10",
        });

        // arrange
        // TODO: make encoded query params less hard coded
        const request_string = `${topic_base_url}/view?client=u/fakeUser&id=10`;
        // act
        const response = request.get(request_string);
        // assert
        expect(response.status).to.equal(200);
    });
    it("should handle missing client query param on view route", () => {
        // Create user
        db.u.save({
            _key: "fakeUser",
            is_admin: true,
        });

        // Create topic
        db.t.save({
            _key: "11",
        });

        // arrange: omit client query param entirely
        const request_string = `${topic_base_url}/view?id=11`;

        // act
        const response = request.get(request_string);

        // assert
        // Expect the router to reject requests without a client identifier
        expect(response.status).to.equal(400);
    });
    it("should handle malformed client query param on view route", () => {
        // Create user
        db.u.save({
            _key: "fakeUser",
            is_admin: true,
        });

        // Create topic
        db.t.save({
            _key: "12",
        });

        // arrange: pass a malformed client (does not match expected encoding/shape)
        const malformedClient = "not/a/valid/client";
        const request_string = `${topic_base_url}/view?client=${encodeURIComponent(
            malformedClient
        )}&id=12`;

        // act
        const response = request.get(request_string);

        // assert
        // Expect the router to detect and reject malformed client identifiers
        expect(response.status).to.equal(400);
    });
    it("should successfully run the search route", () => {
        // Create user
        db.u.save({
            _key: "fakeUser",
            is_admin: true,
        });

        // Create ArangoSearch View (if missing)
        if (!db._view("topicview")) {

```

1. If the actual router behavior for missing or malformed `client` is different (e.g. returns `200` but logs a null client, or uses a different status like `422`), update the `expect(response.status).to.equal(400);` assertions in the two new tests to match the real behavior.
2. If the router returns a JSON error payload (e.g. `{ error: "invalid client" }`), add additional expectations on `response.json` or `response.body` (depending on the existing test conventions in this file) to assert the error shape/message.
3. If topics with keys `"11"` and `"12"` conflict with other tests’ fixtures, adjust the `_key` values in the new tests to unused ones that are consistent with the rest of the suite.
</issue_to_address>

### Comment 8
<location> `core/database/foxx/tests/topic_router.test.js:133` </location>
<code_context>
        const paging = body[body.length - 1].paging;

</code_context>

<issue_to_address>
**suggestion (code-quality):** Prefer object destructuring when accessing and using properties. ([`use-object-destructuring`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/JavaScript/Default-Rules/use-object-destructuring))

```suggestion
        const {paging} = body[body.length - 1];
```

<br/><details><summary>Explanation</summary>Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the [Airbnb Javascript Style Guide](https://airbnb.io/javascript/#destructuring--object)
</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

const g_lib = require("./support");
const logger = require("./lib/logger");

const basePath = "topic";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Consider whether basePath should include the leading slash to keep logged route paths consistent with actual routes.

Right now the routes are defined as /list/topics, /view, /search, but logged as topic/list/topics (no leading slash). If any tooling or consumers expect paths to start with / or to match the Foxx route definitions, this mismatch could cause confusion in logs and dashboards. Consider standardising this to a leading-slash form (e.g. "/topic") or logging the full route path exactly as defined.

Comment on lines +36 to +45
it("should successfully run the list route", () => {
db.u.save({
_key: "fakeUser",
_id: "u/fakeUser",
name: "fake user",
name_first: "fake",
name_last: "user",
is_admin: true,
max_coll: 50,
max_proj: 10,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Test name and URL under test are inconsistent

The test name mentions the list route, but it actually hits /view (/topic/view?client=u/fakeUser&id=10). This is confusing for future readers and coverage assumptions. Please either rename the test to reflect /view or change the URL to the intended list route (e.g. /list/topics).

Copy link
Collaborator

@JoshuaSBrown JoshuaSBrown left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some progress, keep it up.

@JoshuaSBrown
Copy link
Collaborator

Looking at the log output.

image

When passing to topic search, make sure that you add a descriptive field to the Extra part for the key.

Bad

Extra:

Good

Extra: { items_found: }

That way when it displays in the logs it is clear what is being returned.

Also we need to remove _rev and _key from the topic view content. topics should contain the following, creator and title attributes according to what I am seeing in the support.js file.

           topic = obj.db.t.save(
                {
                    title: topic,
                    creator: a_owner_id,
                    coll_cnt: 1,
                },
                {
                    returnNew: true,
                },
            );

Copy link
Collaborator

@JoshuaSBrown JoshuaSBrown left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure you look at what is being logged.

@JoshuaSBrown JoshuaSBrown removed their assignment Dec 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Component: Foxx Foxx Arango micro services. Priority: Low Lower priority work. Type: Refactor Imlplementation change, same functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants