Skip to content

Add audit routing fields to patient and SQL logs - #3267

Queued
jerome-ng wants to merge 4 commits into
developfrom
jerome-ng/audit-log-json-format-develop
Queued

Add audit routing fields to patient and SQL logs#3267
jerome-ng wants to merge 4 commits into
developfrom
jerome-ng/audit-log-json-format-develop

Conversation

@jerome-ng

@jerome-ng jerome-ng commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Patient-access and CDM SQL audit events now include three top-level fields used to identify audit records and their source:

{"log-type":"audit","audit-log-type":"access","service-name":"analytics-svc"}

Both logger types use access, including failed SQL executions. All existing payload field names, values, and nesting are preserved. Audit enablement flags, NDJSON file output, and the AUDIT_LOG_TO_CONSOLE destination switch behave as before.

Example JSON

These events were captured from authenticated requests to this PR branch on the local demo database. Only personId and actor.id have been replaced with example values. JSON is formatted here for readability; the console emitted each event as a single JSON line.

The raw-audit-... values are correlation labels explicitly supplied in the test requests' x-req-correlation-id headers. They are not generated UUIDs; the logger preserves the incoming correlation ID.

Live scenario HTTP Patient-access records SQL records
Patient ID and gender name 200 1 5
Patient ID only 200 0 5
Empty patient list 200 0 5
Invalid negative limit 500 0 4 (one failed)
CDM version lookup 200 0 2
Patient access
{
  "action": "read",
  "occurredAt": "2026-09-07T03:04:05.795Z",
  "personId": "example-patient",
  "accessChannel": "D2E Pt Ls",
  "successful": true,
  "attributes": [
    "patient.attributes.Gender_concept_name"
  ],
  "configs": {
    "cohortBuilder": {
      "id": "4fce3cb7-32bf-4b46-8cba-32e4f77a14dd",
      "version": "A"
    },
    "cdm": {
      "id": "e10f83a0-ade9-4a33-90ae-cf760813943c",
      "version": "3"
    }
  },
  "log-type": "audit",
  "audit-log-type": "access",
  "service-name": "analytics-svc",
  "schemaVersion": 1,
  "eventType": "patient.access",
  "actor": {
    "type": "user",
    "id": "example-user"
  }
}
SQL query for patient access
{
  "log-type": "audit",
  "audit-log-type": "access",
  "service-name": "analytics-svc",
  "schemaVersion": 1,
  "eventType": "cdm.sql",
  "occurredAt": "2026-09-07T03:04:05.792Z",
  "actor": {
    "type": "user",
    "id": "example-user"
  },
  "request": {
    "method": "GET",
    "path": "/analytics-svc/api/services/patient",
    "correlationId": "raw-audit-develop-patient-access",
    "query": {
      "mriquery": "eJyNkU1LAzEQhv+KzDkr3W60bW5qUbyoiHiRIrPJpAZ2kzWZLUrZ/y5ZP4oW0Vs+3nnyzGQLBhkT8aUBBZU+OjqeLmxhJ1gVktAW81JSMZGLekE4NdKUIECHpxB5SdZ5xy54UFvQwVu3XiLjbjcypdVU6XpWVNPaFrKWx8Vc11hUU5J2NsNSGjMyc8U9xTQC4QQGARqjSR88Js+gHvbW1q1vkJ9AQYfs8oUA5xOj13TVtzVFUJPd0eXyW9JjS6DgFJPTB6N9jqJmtyFQFptEAvi1y6Fz1zDFM4zZF5mjq3umH36rr/hpCA2hPwue0XmKICB0ubOrZe4NzSb73LmW3sGgfN80w9+A61v4R+rzmZdsmLXWPUZDBhTHngQ0rnUMqhQQrE3E45R0aPrWp19ne7hr+7BzeQ4hmqwOICDRM6jJIP4uvSBvKD7q4DV1/Dh+wj6qHFbD8AYkrd8/",
      "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1"
    },
    "params": {},
    "body": {}
  },
  "database": {
    "engine": "duckdb",
    "dialect": "postgresql",
    "code": "demo_database",
    "schema": "demo_cdm"
  },
  "operation": "executeQuery",
  "sql": "WITH  patientDEF AS (SELECT P0.\"GENDER_CONCEPT_ID\" AS \"06242c43\", P0.\"person_id\" AS \"fff367b7\"  FROM memory.main.\"MRI_PLUGIN_3050ba0523eac439f1e99f612a8e5bfb79f952de6c186295\" P0  GROUP BY P0.\"GENDER_CONCEPT_ID\", P0.\"person_id\")  SELECT * FROM patientDEF",
  "parameterCount": 0,
  "successful": true,
  "durationMs": 5.908,
  "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1",
  "configs": {
    "cohortBuilder": {
      "id": "4fce3cb7-32bf-4b46-8cba-32e4f77a14dd",
      "version": "A"
    },
    "cdm": {
      "id": "e10f83a0-ade9-4a33-90ae-cf760813943c",
      "version": "3"
    }
  }
}
SQL query for ID-only access
{
  "log-type": "audit",
  "audit-log-type": "access",
  "service-name": "analytics-svc",
  "schemaVersion": 1,
  "eventType": "cdm.sql",
  "occurredAt": "2026-09-07T03:04:06.429Z",
  "actor": {
    "type": "user",
    "id": "example-user"
  },
  "request": {
    "method": "GET",
    "path": "/analytics-svc/api/services/patient",
    "correlationId": "raw-audit-develop-patient-id-only",
    "query": {
      "mriquery": "eJyNkMtOwzAQRX8FzTqp0sZ9eUdbIXVTEEJsEIuJPW4tJXawJxWoyr8jh0cFCMHOj+vjc+cEGhkj8VaDhFJNp7PJ0uSmwDIXhCZfjAXlhVhWS8KJFnoMGSh/8IE3ZKyzbL0DeQLlnbH7DTKedwNTGEWlquZ5OalMLioxyxeqwryckDDzOY6F1gMzvbinEAcgXEKfgcKg4zuPyTHIhx9rY/c3yAeQ0CLbdJGBdZHRKdp1TUUBZHE+2m6+JB02BBJWGK26GOxTFBXbI4E0WEfKgF/aFLqyNVNYY0i+yBxs1TF983v8jK+8rwnd2jtG6yhABr5NzXab1A31Mfnc2YbewCBdV9f934DrW/hH6uOb52SYtPYdBk0aJIeOMqhtYxnkOANvTCQepqR83TUu/jrb0bn2qLVpDj7opA6QQaQnkEX/2PevdvXE8A==",
      "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1"
    },
    "params": {},
    "body": {}
  },
  "database": {
    "engine": "duckdb",
    "dialect": "postgresql",
    "code": "demo_database",
    "schema": "demo_cdm"
  },
  "operation": "executeQuery",
  "sql": "WITH  patientDEF AS (SELECT P0.\"person_id\" AS \"057de8c4\"  FROM memory.main.\"MRI_PLUGIN_999e970e53fa09fce0b8b290f95ecaa378c04a75b6eb6d83\" P0  GROUP BY P0.\"person_id\")  SELECT * FROM patientDEF",
  "parameterCount": 0,
  "successful": true,
  "durationMs": 2.854,
  "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1",
  "configs": {
    "cohortBuilder": {
      "id": "4fce3cb7-32bf-4b46-8cba-32e4f77a14dd",
      "version": "A"
    },
    "cdm": {
      "id": "e10f83a0-ade9-4a33-90ae-cf760813943c",
      "version": "3"
    }
  }
}
SQL query for an empty patient list
{
  "log-type": "audit",
  "audit-log-type": "access",
  "service-name": "analytics-svc",
  "schemaVersion": 1,
  "eventType": "cdm.sql",
  "occurredAt": "2026-09-07T03:04:06.788Z",
  "actor": {
    "type": "user",
    "id": "example-user"
  },
  "request": {
    "method": "GET",
    "path": "/analytics-svc/api/services/patient",
    "correlationId": "raw-audit-develop-empty-patient-list",
    "query": {
      "mriquery": "eJyNkU9LxDAQxb+KzDmVdhtdNzfdRfGiIuJFRKbJZDfQJjWZiiL97pL6Z9FFNKdJ8vLLezOvYJAxEZ8bUFDrg4PD2cIWtsS6kIS2OKokFaVcNAvCmZGmAgE6bELkFVnnHbvgQb2CDt669QoZt7uJKa2mWjfzop41tpCNPCyOdINFPSNp53OspDETM7+4pZgmIBzDKEBjNOmDx+QZ1N1Obd36CnkDCnpkly8EOJ8YvaaLoWsogiq3R+erb0qPHYGCE0xO703usxQ1uycCZbFNJIBf+iw6dS1TXGLMfpE5umZg+uHv/kt+EkJL6JfBMzpPEQSEPie7WOVsaJ6ynxvX0TsYlB/advwbcHkN/1B9fvOcHWZb6wGjIQOK40ACWtc5BlUJCNYmymX5sfI02qHz6dce72/j7/cu9yNEkyMACEj0CKocxd9Pz8gbig86eE09P0zD2EVV4/04vgFpBODA",
      "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1"
    },
    "params": {},
    "body": {}
  },
  "database": {
    "engine": "duckdb",
    "dialect": "postgresql",
    "code": "demo_database",
    "schema": "demo_cdm"
  },
  "operation": "executeQuery",
  "sql": "WITH  patientDEF AS (SELECT P0.\"GENDER_CONCEPT_ID\" AS \"f420b384\", P0.\"person_id\" AS \"4ff8237e\"  FROM memory.main.\"MRI_PLUGIN_b766b9b83a45588a847f73b8c8d35c32d1529d4f24cdfcf3\" P0  GROUP BY P0.\"GENDER_CONCEPT_ID\", P0.\"person_id\")  SELECT * FROM patientDEF",
  "parameterCount": 0,
  "successful": true,
  "durationMs": 1.435,
  "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1",
  "configs": {
    "cohortBuilder": {
      "id": "4fce3cb7-32bf-4b46-8cba-32e4f77a14dd",
      "version": "A"
    },
    "cdm": {
      "id": "e10f83a0-ade9-4a33-90ae-cf760813943c",
      "version": "3"
    }
  }
}
Failed SQL execution (invalid negative limit)
{
  "log-type": "audit",
  "audit-log-type": "access",
  "service-name": "analytics-svc",
  "schemaVersion": 1,
  "eventType": "cdm.sql",
  "occurredAt": "2026-09-07T03:04:07.148Z",
  "actor": {
    "type": "user",
    "id": "example-user"
  },
  "request": {
    "method": "GET",
    "path": "/analytics-svc/api/services/patient",
    "correlationId": "raw-audit-develop-invalid-negative-limit",
    "query": {
      "mriquery": "eJyNkU1LxDAQhv+KzDmR7Ta6bm7qonhREfEiItNksgbapCZTUaT/XVI/Fl1Eb/l458kzk1ewyJiJzyxoqM3e3v586aSbYS0VoZMHlSI5U8tmSTi3ylYgwMSHmHhFzgfPPgbQr2BicH69QsbNbmIqZ6g2zULW88ZJ1ah9eWAalPWclFsssFLWTsxScUMpT0A4hFGAwWTzB48pMOjbrbXz60vkB9DQI/tyIcCHzBgMnQ9dQwn0bHN0tvqWDNgRaDjC7M3OZF+iaNg/EWiHbSYB/NKX0IlvmdIxpuKLzMk3A9MPv7uv+FGMLWE4joHRB0ogIPals/NV6Q3tU/G59h29g0GHoW3HvwEXV/CP1Oczz8WwaK0HTJYsaE4DCWh95xm0rARE5zLxNCYT26EL+dfh7m763u19GURMtrgDCMj0CHo2ir9LTylYSvcmBkM930+/sI2qxrtxfAPpPN9s",
      "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1"
    },
    "params": {},
    "body": {}
  },
  "database": {
    "engine": "duckdb",
    "dialect": "postgresql",
    "code": "demo_database",
    "schema": "demo_cdm"
  },
  "operation": "executeUpdate",
  "sql": "INSERT INTO memory.main.\"MRI_PLUGIN_80f1d59662a1207515107ac20fd6daf89575dd598bb52fb9\"\n                \n                SELECT \"pTable\".*\n                        FROM (\n                            SELECT \"62118ca8\" FROM (SELECT P0.\"person_id\" AS \"62118ca8\"  FROM $$SCHEMA$$.\"person\" P0  GROUP BY P0.\"person_id\" LIMIT -1 OFFSET 0) AS alias2\n                        ) AS alias1\n                JOIN $$SCHEMA$$.\"person\" \"pTable\"\n            ON \"62118ca8\" = \"pTable\".\"person_id\"",
  "parameterCount": 2,
  "successful": false,
  "durationMs": 23.417,
  "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1",
  "configs": {
    "cohortBuilder": {
      "id": "4fce3cb7-32bf-4b46-8cba-32e4f77a14dd",
      "version": "A"
    },
    "cdm": {
      "id": "e10f83a0-ade9-4a33-90ae-cf760813943c",
      "version": "3"
    }
  },
  "error": {
    "name": "Error"
  }
}
SQL query for CDM version
{
  "log-type": "audit",
  "audit-log-type": "access",
  "service-name": "analytics-svc",
  "schemaVersion": 1,
  "eventType": "cdm.sql",
  "occurredAt": "2026-09-07T03:04:07.408Z",
  "actor": {
    "type": "user",
    "id": "example-user"
  },
  "request": {
    "method": "GET",
    "path": "/analytics-svc/api/services/alpdb/cdmversion",
    "correlationId": "raw-audit-develop-cdm-version",
    "query": {
      "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1"
    },
    "params": {},
    "body": {}
  },
  "database": {
    "engine": "duckdb",
    "dialect": "postgresql",
    "code": "demo_database",
    "schema": "demo_cdm"
  },
  "operation": "executeQuery",
  "sql": "SELECT CDM_VERSION FROM _3c55629f_f0a3_4eaf_814e_049b9ea2d4d1.demo_cdm.CDM_SOURCE",
  "parameterCount": 0,
  "successful": true,
  "durationMs": 1.963,
  "datasetId": "3c55629f-f0a3-4eaf-814e-049b9ea2d4d1",
  "configs": {
    "cohortBuilder": {
      "id": "4fce3cb7-32bf-4b46-8cba-32e4f77a14dd",
      "version": "A"
    },
    "cdm": {
      "id": "e10f83a0-ade9-4a33-90ae-cf760813943c",
      "version": "3"
    }
  }
}

Validation

  • 31 focused audit tests passed in Docker with Deno 2.7.14, following the existing --no-check convention.
  • Tests cover console and file output, successful and failed SQL, one JSON record per line, and preservation of existing payloads.
  • All five scenarios above were exercised through authenticated APIs against the local demo database with this branch loaded in Trex. No database or audit callbacks were mocked.
  • Captured stdout events parsed directly as standalone JSON, with all three required metadata fields. The invalid-limit request produced a real failed SQL event.
  • ID-only and empty-result requests emitted no patient-access event, consistent with existing behavior.
  • git diff --check passed. The original local runtime and audit settings were restored after validation.
  • Azure collector ingestion has not been verified.

Merge Checklist

  • Automated tests
  • Manual API scenarios on the demo database
  • Documentation
  • Verified local deployed environment
  • Reviewed the diff for scope and preservation of existing behavior

Companion PR: #3268.

@jerome-ng jerome-ng changed the title Fix patient and SQL audit log JSON format Add audit routing fields to patient and SQL logs Sep 7, 2026
@jerome-ng
jerome-ng marked this pull request as ready for review September 7, 2026 02:21
Copilot AI balanced review requested due to automatic review settings September 7, 2026 02:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The focused implementation is consistent across both audit paths and adequately covered by regression tests.

Pull request overview

Adds standardized routing metadata to analytics audit events while preserving existing payloads and output behavior.

Changes:

  • Adds routing fields to patient-access and CDM SQL events.
  • Adds console/file format regression tests.
  • Documents audit configuration and output format.
File summaries
File Description
README.md Links to audit documentation.
plugins/functions/analytics-svc/README.md Documents audit fields, configuration, and tests.
plugins/functions/analytics-svc/src/utils/AuditEventWriter.ts Adds patient-event routing fields.
plugins/functions/analytics-svc/src/utils/CdmSqlAuditLogger.ts Adds SQL-event routing fields.
plugins/functions/analytics-svc/src/utils/AuditLogFormat_test.ts Verifies payload and NDJSON preservation.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@jerome-ng
jerome-ng requested a review from mwaiyee September 7, 2026 05:20
@jerome-ng
jerome-ng enabled auto-merge September 10, 2026 06:29
@jerome-ng
jerome-ng added this pull request to the merge queue Sep 10, 2026
Any commits made after this event will not be merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants