Repository: unkeyed/unkey
Pull request #3196: refactor: add proper logging to key delete
Author: login: ogzhanolguncu
Created: 2025-04-25T12:18:54Z | Merged: 2025-04-25T14:41:49Z

## What does this PR do?

This PR adds some proper logging to key deletion tRPC endpoint and audit insertion.

*If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists*

<!-- If there isn't an issue for this PR, please re-review our Contributing Guide and create an issue -->

## Type of change

<!-- Please mark the relevant points by using [x] -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Chore (refactoring code, technical debt, workflow improvements)
- [x] Enhancement (small improvements)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration -->

- Test A
- Test B

## Checklist

<!-- We're starting to get more and more contributions. Please help us making this efficient for all of us and go through this checklist. Please tick off what you did  -->

### Required

- [ ] Filled out the "How to test" section in this PR
- [ ] Read [Contributing Guide](./CONTRIBUTING.md)
- [ ] Self-reviewed my own code
- [ ] Commented on my code in hard-to-understand areas
- [ ] Ran `pnpm build`
- [ ] Ran `pnpm fmt`
- [ ] Checked for warnings, there are none
- [ ] Removed all `console.logs`
- [ ] Merged the latest changes from main onto my branch with `git pull origin main`
- [ ] My changes don't cause any responsiveness issues

### Appreciated

- [ ] If a UI change was made: Added a screen recording or screenshots to this PR
- [ ] Updated the Unkey Docs if changes were necessary


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
	- Improved validation when deleting keys, ensuring at least one key ID must be provided.
	- Enhanced error messages for missing keys or workspaces during deletion.

- **New Features**
	- Added detailed runtime logging for audit log insertions and key deletion actions, improving transparency and traceability for these operations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/apps/dashboard/lib/audit.ts b/apps/dashboard/lib/audit.ts
index 7d51fd508f..453af817fc 100644
--- a/apps/dashboard/lib/audit.ts
+++ b/apps/dashboard/lib/audit.ts
@@ -102,11 +102,30 @@ export async function insertAuditLogs(
   const logs = Array.isArray(logOrLogs) ? logOrLogs : [logOrLogs];
 
   if (logs.length === 0) {
+    console.info("No audit logs to insert");
     return Promise.resolve();
   }
 
+  console.info({
+    message: "Inserting audit logs",
+    count: logs.length,
+    events: logs.map((log) => log.event),
+    workspaceIds: [...new Set(logs.map((log) => log.workspaceId))],
+  });
+
   for (const log of logs) {
     const auditLogId = newId("auditLog");
+
+    console.info({
+      message: "Inserting audit log entry",
+      auditLogId,
+      workspaceId: log.workspaceId,
+      event: log.event,
+      actorType: log.actor.type,
+      actorId: log.actor.id,
+      resourceCount: log.resources.length,
+    });
+
     await db.insert(schema.auditLog).values({
       id: auditLogId,
       workspaceId: log.workspaceId,
@@ -122,7 +141,15 @@ export async function insertAuditLogs(
       actorName: log.actor.name,
       actorMeta: log.actor.meta,
     });
+
     if (log.resources.length > 0) {
+      console.info({
+        message: "Inserting audit log resources",
+        auditLogId,
+        resourceCount: log.resources.length,
+        resourceTypes: log.resources.map((r) => r.type),
+      });
+
       await db.insert(schema.auditLogTarget).values(
         log.resources.map((r) => ({
           workspaceId: log.workspaceId,
@@ -138,4 +165,9 @@ export async function insertAuditLogs(
       );
     }
   }
+
+  console.info({
+    message: "Successfully inserted all audit logs",
+    count: logs.length,
+  });
 }
diff --git a/apps/dashboard/lib/trpc/routers/key/delete.ts b/apps/dashboard/lib/trpc/routers/key/delete.ts
index 8fface1682..2da49c03f9 100644
--- a/apps/dashboard/lib/trpc/routers/key/delete.ts
+++ b/apps/dashboard/lib/trpc/routers/key/delete.ts
@@ -9,78 +9,176 @@ export const deleteKeys = t.procedure
   .use(requireWorkspace)
   .input(
     z.object({
-      keyIds: z.array(z.string()),
+      keyIds: z.array(z.string()).min(1, "At least one key ID must be provided"),
     }),
   )
   .mutation(async ({ ctx, input }) => {
-    const workspace = await db.query.workspaces
-      .findFirst({
+    const { keyIds } = input;
+    const userId = ctx.user.id;
+    const tenantId = ctx.tenant.id;
+
+    console.info({
+      message: "Attempting to delete keys",
+      userId,
+      tenantId,
+      keyCount: keyIds.length,
+      keyIds,
+    });
+
+    if (keyIds.length === 0) {
+      console.warn({
+        message: "No key IDs provided for deletion",
+        userId,
+        tenantId,
+      });
+      throw new TRPCError({
+        code: "BAD_REQUEST",
+        message: "No keys were provided for deletion",
+      });
+    }
+
+    try {
+      const workspace = await db.query.workspaces.findFirst({
         where: (table, { and, eq, isNull }) =>
-          and(eq(table.orgId, ctx.tenant.id), isNull(table.deletedAtM)),
+          and(eq(table.orgId, tenantId), isNull(table.deletedAtM)),
         with: {
           keys: {
             where: (table, { and, inArray, isNull }) =>
-              and(isNull(table.deletedAtM), inArray(table.id, input.keyIds)),
+              and(isNull(table.deletedAtM), inArray(table.id, keyIds)),
             columns: {
               id: true,
             },
           },
         },
-      })
-      .catch((_err) => {
+      });
+
+      if (!workspace) {
+        console.error({
+          message: "Workspace not found",
+          userId,
+          tenantId,
+        });
         throw new TRPCError({
-          code: "INTERNAL_SERVER_ERROR",
-          message:
-            "We were unable to delete this key. Please try again or contact support@unkey.dev.",
+          code: "NOT_FOUND",
+          message: "Workspace not found. Please verify your workspace selection and try again.",
         });
-      });
-    if (!workspace) {
-      throw new TRPCError({
-        code: "NOT_FOUND",
-        message:
-          "We are unable to find the correct workspace. Please try again or contact support@unkey.dev.",
-      });
-    }
+      }
 
-    await db
-      .transaction(async (tx) => {
-        await tx
-          .update(schema.keys)
-          .set({ deletedAtM: Date.now() })
-          .where(
-            and(
-              eq(schema.keys.workspaceId, workspace.id),
-              inArray(
-                schema.keys.id,
-                workspace.keys.map((k) => k.id),
-              ),
-            ),
-          );
-        insertAuditLogs(
-          tx,
-          workspace.keys.map((key) => ({
+      // Check if keys were found in the workspace
+      if (workspace.keys.length === 0) {
+        console.warn({
+          message: "No keys found for deletion",
+          userId,
+          tenantId,
+          keyIds,
+        });
+        throw new TRPCError({
+          code: "NOT_FOUND",
+          message: "None of the specified keys could be found in your workspace.",
+        });
+      }
+
+      try {
+        await db.transaction(async (tx) => {
+          console.info({
+            message: "Starting key deletion transaction",
+            userId,
+            tenantId,
             workspaceId: workspace.id,
-            actor: { type: "user", id: ctx.user.id },
-            event: "key.delete",
-            description: `Deleted ${key.id}`,
-            resources: [
-              {
-                type: "key",
-                id: key.id,
+            keysToDelete: workspace.keys.map((k) => k.id),
+          });
+
+          await tx
+            .update(schema.keys)
+            .set({ deletedAtM: Date.now() })
+            .where(
+              and(
+                eq(schema.keys.workspaceId, workspace.id),
+                inArray(
+                  schema.keys.id,
+                  workspace.keys.map((k) => k.id),
+                ),
+              ),
+            );
+
+          await insertAuditLogs(
+            tx,
+            workspace.keys.map((key) => ({
+              workspaceId: workspace.id,
+              actor: { type: "user", id: userId },
+              event: "key.delete",
+              description: `Deleted key ${key.id}`,
+              resources: [
+                {
+                  type: "key",
+                  id: key.id,
+                },
+              ],
+              context: {
+                location: ctx.audit.location,
+                userAgent: ctx.audit.userAgent,
               },
-            ],
-            context: {
-              location: ctx.audit.location,
-              userAgent: ctx.audit.userAgent,
-            },
-          })),
-        );
-      })
-      .catch((err) => {
-        console.error(err);
-        throw new TRPCError({
-          code: "INTERNAL_SERVER_ERROR",
-          message: "We are unable to delete the key. Please try again or contact support@unkey.dev",
+            })),
+          );
+        });
+      } catch (txErr) {
+        console.error({
+          message: "Transaction failed during key deletion",
+          userId,
+          tenantId,
+          workspaceId: workspace.id,
+          error: txErr instanceof Error ? txErr.message : String(txErr),
+          stack: txErr instanceof Error ? txErr.stack : undefined,
         });
+
+        // Check for specific error types
+        const errorMessage = String(txErr);
+        if (errorMessage.includes("Forbidden")) {
+          throw new TRPCError({
+            code: "FORBIDDEN",
+            message:
+              "Permission denied. The system doesn't have sufficient access to complete this operation.",
+          });
+        }
+        if (errorMessage.includes("auditLog")) {
+          throw new TRPCError({
+            code: "INTERNAL_SERVER_ERROR",
+            message: "Unable to create audit logs. Please contact support@unkey.dev.",
+          });
+        }
+        throw txErr; // Re-throw to be caught by outer catch
+      }
+
+      console.info({
+        message: "Keys deleted successfully",
+        userId,
+        tenantId,
+        keyCount: workspace.keys.length,
+        deletedKeyIds: workspace.keys.map((k) => k.id),
+      });
+
+      return {
+        deletedKeyIds: workspace.keys.map((k) => k.id),
+        totalDeleted: workspace.keys.length,
+      };
+    } catch (err) {
+      if (err instanceof TRPCError) {
+        // Re-throw if it's already a TRPC error
+        throw err;
+      }
+
+      console.error({
+        message: "Error during key deletion",
+        userId,
+        tenantId,
+        keyIds,
+        error: err instanceof Error ? err.message : String(err),
+        stack: err instanceof Error ? err.stack : undefined,
       });
+
+      throw new TRPCError({
+        code: "INTERNAL_SERVER_ERROR",
+        message: "Failed to delete keys. Our team has been notified of this issue.",
+      });
+    }
   });


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>apps/dashboard/lib/audit.ts (1)</summary><blockquote>

`109-128`: **Reduce verbosity and avoid leaking identifiers in info-level logs**

1. These `console.info` statements emit full `workspaceIds`, `auditLogId`s and raw `resources`, which might be considered sensitive metadata or breach GDPR/PII guidelines if they end up in shared log collectors.
2. High-volume endpoints (e.g. bulk key deletion) will generate thousands of lines, potentially throttling STDOUT and slowing the event-loop.

Consider:
* Downgrading most of these to `logger.debug` / `trace`.
* Masking or hashing IDs, or logging counts instead of full arrays when the array size is > N.
* Wrapping the whole block with `logger.isLevelEnabled("debug")` if the underlying logger supports it.

Example:

```diff
-  console.info({
+  logger.debug({
     message: "Inserting audit logs",
-    events: logs.map((log) => log.event),
-    workspaceIds: [...new Set(logs.map((log) => log.workspaceId))],
+    eventCount: logs.length,
   });
```




Also applies to: 146-151, 169-172

</blockquote></details>
<details>
<summary>apps/dashboard/lib/trpc/routers/key/delete.ts (1)</summary><blockquote>

`28-38`: **Duplicate empty-array guard is unreachable**

`z.array(...).min(1)` already rejects empty arrays, so this runtime check will never execute. Removing it reduces noise and keeps the control-flow lean.

```diff
-    if (keyIds.length === 0) {
-      console.warn({ ... });
-      throw new TRPCError({
-        code: "BAD_REQUEST",
-        message: "No keys were provided for deletion",
-      });
-    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between fa2710415cd1422f20710688d37b26bbd34cb6d0 and 8ff054cc23cbf100f3df73486f387f2d2ea34733.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `apps/dashboard/lib/audit.ts` (3 hunks)
* `apps/dashboard/lib/trpc/routers/key/delete.ts` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (14)</summary>

* GitHub Check: Test Packages / Test ./apps/dashboard
* GitHub Check: Test Packages / Test ./packages/rbac
* GitHub Check: Test Packages / Test ./packages/nextjs
* GitHub Check: Test Packages / Test ./packages/hono
* GitHub Check: Test Packages / Test ./packages/cache
* GitHub Check: Test Packages / Test ./packages/api
* GitHub Check: Test Packages / Test ./internal/clickhouse
* GitHub Check: Test Packages / Test ./internal/resend
* GitHub Check: Test Packages / Test ./internal/keys
* GitHub Check: Test Packages / Test ./internal/id
* GitHub Check: Test Packages / Test ./internal/hash
* GitHub Check: Test Packages / Test ./internal/billing
* GitHub Check: autofix
* GitHub Check: Analyze (javascript-typescript)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>apps/dashboard/lib/trpc/routers/key/delete.ts (1)</summary>

`12-13`: **Good input hardening with `min(1)`**

Requiring at least one key ID at the schema level eliminates empty-payload edge cases before they hit business logic.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T12:23:30Z
user: login: ogzhanolguncu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T14:15:10Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T14:16:28Z
user: login: perkinsjr
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-25T14:40:30Z

Review comments:
diff_hunk: @@ -102,11 +102,30 @@ export async function insertAuditLogs(
   const logs = Array.isArray(logOrLogs) ? logOrLogs : [logOrLogs];
 
   if (logs.length === 0) {
+    console.info("No audit logs to insert");
     return Promise.resolve();
   }
 
path: apps/dashboard/lib/audit.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Prefer a structured logger over `console.*`**

Directly using `console.info` in library code makes it hard to control log levels, redact sensitive data, or aggregate logs across environments. The rest of the code-base already relies on our `logger` utility (e.g. `@/lib/logger`) for structured JSON output – switching to it here keeps behaviour consistent and avoids losing context in production.

```diff
-  if (logs.length === 0) {
-    console.info("No audit logs to insert");
+  if (logs.length === 0) {
+    logger.debug({ message: "No audit logs to insert" });
   }
```

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-25T12:23:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,78 +9,176 @@ export const deleteKeys = t.procedure
   .use(requireWorkspace)
   .input(
     z.object({
-      keyIds: z.array(z.string()),
+      keyIds: z.array(z.string()).min(1, "At least one key ID must be provided"),
     }),
   )
   .mutation(async ({ ctx, input }) => {
-    const workspace = await db.query.workspaces
-      .findFirst({
+    const { keyIds } = input;
+    const userId = ctx.user.id;
+    const tenantId = ctx.tenant.id;
+
+    console.info({
+      message: "Attempting to delete keys",
+      userId,
+      tenantId,
+      keyCount: keyIds.length,
+      keyIds,
+    });
+
+    if (keyIds.length === 0) {
+      console.warn({
+        message: "No key IDs provided for deletion",
+        userId,
+        tenantId,
+      });
+      throw new TRPCError({
+        code: "BAD_REQUEST",
+        message: "No keys were provided for deletion",
+      });
+    }
+
+    try {
+      const workspace = await db.query.workspaces.findFirst({
         where: (table, { and, eq, isNull }) =>
-          and(eq(table.orgId, ctx.tenant.id), isNull(table.deletedAtM)),
+          and(eq(table.orgId, tenantId), isNull(table.deletedAtM)),
         with: {
           keys: {
             where: (table, { and, inArray, isNull }) =>
-              and(isNull(table.deletedAtM), inArray(table.id, input.keyIds)),
+              and(isNull(table.deletedAtM), inArray(table.id, keyIds)),
             columns: {
               id: true,
             },
           },
         },
-      })
-      .catch((_err) => {
+      });
+
+      if (!workspace) {
+        console.error({
+          message: "Workspace not found",
+          userId,
+          tenantId,
+        });
         throw new TRPCError({
-          code: "INTERNAL_SERVER_ERROR",
-          message:
-            "We were unable to delete this key. Please try again or contact support@unkey.dev.",
+          code: "NOT_FOUND",
+          message: "Workspace not found. Please verify your workspace selection and try again.",
         });
-      });
-    if (!workspace) {
-      throw new TRPCError({
-        code: "NOT_FOUND",
-        message:
-          "We are unable to find the correct workspace. Please try again or contact support@unkey.dev.",
-      });
-    }
+      }
 
-    await db
-      .transaction(async (tx) => {
-        await tx
-          .update(schema.keys)
-          .set({ deletedAtM: Date.now() })
-          .where(
-            and(
-              eq(schema.keys.workspaceId, workspace.id),
-              inArray(
-                schema.keys.id,
-                workspace.keys.map((k) => k.id),
-              ),
-            ),
-          );
-        insertAuditLogs(
-          tx,
-          workspace.keys.map((key) => ({
+      // Check if keys were found in the workspace
+      if (workspace.keys.length === 0) {
+        console.warn({
+          message: "No keys found for deletion",
+          userId,
+          tenantId,
+          keyIds,
+        });
+        throw new TRPCError({
+          code: "NOT_FOUND",
+          message: "None of the specified keys could be found in your workspace.",
+        });
+      }
+
+      try {
+        await db.transaction(async (tx) => {
+          console.info({
+            message: "Starting key deletion transaction",
+            userId,
+            tenantId,
             workspaceId: workspace.id,
-            actor: { type: "user", id: ctx.user.id },
-            event: "key.delete",
-            description: `Deleted ${key.id}`,
-            resources: [
-              {
-                type: "key",
-                id: key.id,
+            keysToDelete: workspace.keys.map((k) => k.id),
+          });
+
+          await tx
+            .update(schema.keys)
+            .set({ deletedAtM: Date.now() })
+            .where(
+              and(
+                eq(schema.keys.workspaceId, workspace.id),
+                inArray(
+                  schema.keys.id,
+                  workspace.keys.map((k) => k.id),
+                ),
+              ),
+            );
+
+          await insertAuditLogs(
+            tx,
+            workspace.keys.map((key) => ({
+              workspaceId: workspace.id,
+              actor: { type: "user", id: userId },
+              event: "key.delete",
+              description: `Deleted key ${key.id}`,
+              resources: [
+                {
+                  type: "key",
+                  id: key.id,
+                },
+              ],
+              context: {
+                location: ctx.audit.location,
+                userAgent: ctx.audit.userAgent,
               },
-            ],
-            context: {
-              location: ctx.audit.location,
-              userAgent: ctx.audit.userAgent,
-            },
-          })),
-        );
-      })
-      .catch((err) => {
-        console.error(err);
-        throw new TRPCError({
-          code: "INTERNAL_SERVER_ERROR",
-          message: "We are unable to delete the key. Please try again or contact support@unkey.dev",
+            })),
+          );
+        });
+      } catch (txErr) {
+        console.error({
+          message: "Transaction failed during key deletion",
+          userId,
+          tenantId,
+          workspaceId: workspace.id,
+          error: txErr instanceof Error ? txErr.message : String(txErr),
+          stack: txErr instanceof Error ? txErr.stack : undefined,
         });
+
+        // Check for specific error types
+        const errorMessage = String(txErr);
+        if (errorMessage.includes("Forbidden")) {
+          throw new TRPCError({
+            code: "FORBIDDEN",
+            message:
+              "Permission denied. The system doesn't have sufficient access to complete this operation.",
+          });
+        }
+        if (errorMessage.includes("auditLog")) {
+          throw new TRPCError({
+            code: "INTERNAL_SERVER_ERROR",
+            message: "Unable to create audit logs. Please contact support@unkey.dev.",
+          });
+        }
+        throw txErr; // Re-throw to be caught by outer catch
path: apps/dashboard/lib/trpc/routers/key/delete.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Fragile string-match error handling**

Parsing `txErr` via `String(txErr).includes("Forbidden")` / `"auditLog"` couples behaviour to message wording. A future change in the underlying driver or our own error messages could silently break this branch.

Recommendations:
* Throw typed errors from lower layers (`new PermissionError()`, `new AuditLogError()`) and `instanceof` them here.
* Alternatively, inspect vendor-specific error codes (`err.code`) if available.

```diff
- if (errorMessage.includes("Forbidden")) {
+ if (txErr instanceof PermissionError) {
```

---

🏁 Script executed:

```shell
#!/bin/bash
set -e

echo "---- Context around txErr in delete.ts (lines 120–180) ----"
sed -n '120,180p' apps/dashboard/lib/trpc/routers/key/delete.ts || true

echo
echo "---- Searching for custom PermissionError definitions ----"
rg "class PermissionError" -n .

echo
echo "---- Searching for custom AuditLogError definitions ----"
rg "class AuditLogError" -n .

echo
echo "---- Searching for any txErr.code usage ----"
rg "txErr\.code" -n .
```

Length of output: 2212

---

🏁 Script executed:

```shell
#!/bin/bash
set -e

echo "---- Top imports in delete.ts ----"
sed -n '1,50p' apps/dashboard/lib/trpc/routers/key/delete.ts || true

echo
echo "---- Searching for “prisma” references in delete.ts ----"
rg -n "prisma" -n apps/dashboard/lib/trpc/routers/key/delete.ts || true

echo
echo "---- Searching for “$transaction” usage in delete.ts ----"
rg -n "\$transaction" -n apps/dashboard/lib/trpc/routers/key/delete.ts || true
```

Length of output: 1950

---



</details>

**Use Typed Errors or Vendor Error Codes Instead of String Matches**

The current `catch (txErr)` block in  
`apps/dashboard/lib/trpc/routers/key/delete.ts` (around lines 135–149)  
relies on `String(txErr).includes("…")`, which is brittle. Any change to error messages will silently break these branches.  

Recommendations:  
• Replace the `errorMessage.includes` checks with either:  
  – Vendor-specific error checks (e.g. for Prisma:  
    `txErr instanceof Prisma.PrismaClientKnownRequestError && txErr.code === 'P2003'`)  
  – Custom error classes (e.g. throw `new AuditLogError()` inside `insertAuditLogs`, then `if (txErr instanceof AuditLogError)`)  

Example diff:  
```diff
- const errorMessage = String(txErr);
- if (errorMessage.includes("Forbidden")) {
-   throw new TRPCError({
-     code: "FORBIDDEN",
-     message: "Permission denied…",
-   });
- }
- if (errorMessage.includes("auditLog")) {
-   throw new TRPCError({
-     code: "INTERNAL_SERVER_ERROR",
-     message: "Unable to create audit logs…",
-   });
- }
+ // Handle DB permission errors via vendor error code
+ if (
+   txErr instanceof Prisma.PrismaClientKnownRequestError &&
+   txErr.code === "P2003"
+ ) {
+   throw new TRPCError({
+     code: "FORBIDDEN",
+     message: "Permission denied. The system doesn't have sufficient access…",
+   });
+ }
+ // Handle audit‐log failures via a custom error
+ if (txErr instanceof AuditLogError) {
+   throw new TRPCError({
+     code: "INTERNAL_SERVER_ERROR",
+     message: "Unable to create audit logs. Please contact support@unkey.dev.",
+   });
+ }
```  
• In `lib/audit.ts`, wrap your audit‐log insertion:  
```ts
export class AuditLogError extends Error {}

export async function insertAuditLogs(...) {
  try {
    // …
  } catch (e) {
    throw new AuditLogError(e.message);
  }
}
```  
This removes fragile string matching and makes your error handling robust.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T12:23:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,11 +102,30 @@ export async function insertAuditLogs(
   const logs = Array.isArray(logOrLogs) ? logOrLogs : [logOrLogs];
 
   if (logs.length === 0) {
+    console.info("No audit logs to insert");
     return Promise.resolve();
   }
 
path: apps/dashboard/lib/audit.ts
user: login: ogzhanolguncu
body: We don't have that utility anymore
created_at: 2025-04-25T14:15:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,11 +102,30 @@ export async function insertAuditLogs(
   const logs = Array.isArray(logOrLogs) ? logOrLogs : [logOrLogs];
 
   if (logs.length === 0) {
+    console.info("No audit logs to insert");
     return Promise.resolve();
   }
 
path: apps/dashboard/lib/audit.ts
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
# First, let's search for any potential logging utility files or modules
echo "Searching for logger files..."
fd -t f 'logger' -E "*.log"

# Check if there's any import pattern for a logger utility
echo -e "\nSearching for logger imports..."
rg "import.*logger" -A 2 -B 2

# Let's look for console.* usage patterns to see what's standard
echo -e "\nChecking console.* usage patterns..."
rg "console\.(log|info|error|warn|debug)" --stats

# Also search for any logging setup/configuration
echo -e "\nSearching for logging configuration..."
rg "logging" -t ts -t js
```

Length of output: 34262

---



</details>

@ogzhanolguncu Thanks for pointing that out. I've searched through the codebase and confirmed there's no structured logger utility being used in the dashboard app. Direct `console.*` methods appear to be the standard approach throughout this part of the codebase.

The current implementation with `console.info` is appropriate and consistent with the rest of the codebase. Thanks for the correction!

---

<details>
<summary>🧬 Code Graph Analysis Results</summary>

### Relevant snippets for `apps/dashboard/lib/audit.ts`

---

**File:** `internal/schema/src/auditlog.ts`  
**Lines:** 3-55  
**Summary:**  
This snippet exports a Zod enum `unkeyAuditLogEvents` which enumerates all valid audit log event strings. These event strings are used in the `audit.ts` file to type the `event` property of audit logs. The enum includes events like `"workspace.create"`, `"key.update"`, `"ratelimitNamespace.delete"`, `"webhook.create"`, `"identity.update"`, and others. This enum is imported and used in `apps/dashboard/lib/audit.ts` to type the `event` field in the `UnkeyAuditLog` type.

---

**File:** `internal/id/src/generate.ts`  
**Lines:** 33-52  
**Summary:**  
This snippet defines the function `newId<TPrefix extends keyof typeof prefixes>(prefix: TPrefix)` which generates a new unique ID string with a given prefix. It uses a custom epoch and encodes a 12-byte buffer into a base58 string. This function is used in `apps/dashboard/lib/audit.ts` to generate new audit log IDs (`newId("auditLog")`).

---

**File:** `internal/clickhouse/src/logs.ts`  
**Lines:** 24-38  
**Summary:**  
Defines a Zod schema `log` for structured logging data including fields like `request_id`, `time`, `workspace_id`, `host`, `method`, `path`, `request_headers`, `response_status`, `error`, and `service_latency`. While this is a logging schema, it is not directly referenced or imported in `apps/dashboard/lib/audit.ts`. It may be contextually related to logging but not directly relevant to audit log insertion or schema.

---

These snippets provide context on the audit log event types and ID generation utility used in the `audit.ts` file. No other utilities or functions from the provided snippets appear directly referenced or relevant to the current file.

</details>



---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3196
File: apps/dashboard/lib/audit.ts:105-108
Timestamp: 2025-04-25T14:16:27.140Z
Learning: The dashboard app in the Unkey codebase uses direct console.* methods (console.info, console.error, etc.) for logging rather than a structured logger utility.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer><details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
# First, let's search for any potential logging utility files or modules
echo "Searching for logger files..."
fd -t f 'logger' -E "*.log"

# Check if there's any import pattern for a logger utility
echo -e "\nSearching for logger imports..."
rg "import.*logger" -A 2 -B 2

# Let's look for console.* usage patterns to see what's standard
echo -e "\nChecking console.* usage patterns..."
rg "console\.(log|info|error|warn|debug)" --stats

# Also search for any logging setup/configuration
echo -e "\nSearching for logging configuration..."
rg "logging" -t ts -t js
```

Length of output: 34262

---



</details>

@ogzhanolguncu Thanks for pointing that out. I've searched through the codebase and confirmed there's no structured logger utility being used in the dashboard app. Direct `console.*` methods appear to be the standard approach throughout this part of the codebase.

The current implementation with `console.info` is appropriate and consistent with the rest of the codebase. Thanks for the correction!

---

<details>
<summary>🧬 Code Graph Analysis Results</summary>

### Relevant snippets for `apps/dashboard/lib/audit.ts`

---

**File:** `internal/schema/src/auditlog.ts`  
**Lines:** 3-55  
**Summary:**  
This snippet exports a Zod enum `unkeyAuditLogEvents` which enumerates all valid audit log event strings. These event strings are used in the `audit.ts` file to type the `event` property of audit logs. The enum includes events like `"workspace.create"`, `"key.update"`, `"ratelimitNamespace.delete"`, `"webhook.create"`, `"identity.update"`, and others. This enum is imported and used in `apps/dashboard/lib/audit.ts` to type the `event` field in the `UnkeyAuditLog` type.

---

**File:** `internal/id/src/generate.ts`  
**Lines:** 33-52  
**Summary:**  
This snippet defines the function `newId<TPrefix extends keyof typeof prefixes>(prefix: TPrefix)` which generates a new unique ID string with a given prefix. It uses a custom epoch and encodes a 12-byte buffer into a base58 string. This function is used in `apps/dashboard/lib/audit.ts` to generate new audit log IDs (`newId("auditLog")`).

---

**File:** `internal/clickhouse/src/logs.ts`  
**Lines:** 24-38  
**Summary:**  
Defines a Zod schema `log` for structured logging data including fields like `request_id`, `time`, `workspace_id`, `host`, `method`, `path`, `request_headers`, `response_status`, `error`, and `service_latency`. While this is a logging schema, it is not directly referenced or imported in `apps/dashboard/lib/audit.ts`. It may be contextually related to logging but not directly relevant to audit log insertion or schema.

---

These snippets provide context on the audit log event types and ID generation utility used in the `audit.ts` file. No other utilities or functions from the provided snippets appear directly referenced or relevant to the current file.

</details>



---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3196
File: apps/dashboard/lib/audit.ts:105-108
Timestamp: 2025-04-25T14:16:27.140Z
Learning: The dashboard app in the Unkey codebase uses direct console.* methods (console.info, console.error, etc.) for logging rather than a structured logger utility.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-25T14:16:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-04-25T12:18:58Z
body: ###  ⚠️  No Changeset found

Latest commit: 8bd6c041b7d48c69dd0d8ab002b6348ef778bf1d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/unkeyed/unkey/new/key-deletion-logs?filename=.changeset/ninety-cooks-smoke.md&value=---%0A%22%40unkey%2Fdashboard%22%3A%20patch%0A---%0A%0Arefactor%3A%20add%20proper%20logging%20to%20key%20delete%0A)


author_association: NONE
user: login: vercel[bot]
created_at: 2025-04-25T12:18:59Z
body: [vc]: #CPa5r5cTO6ZF6ldpMlHwdkW5Jd2lwebEmyGGf0XijhQ=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJkYXNoYm9hcmQiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9kYXNoYm9hcmQiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vdW5rZXkvZGFzaGJvYXJkL0gyNm1SYUZnWVk5dlZQQjZlTWdTWnR3cFhQUFUiLCJwcmV2aWV3VXJsIjoiZGFzaGJvYXJkLWdpdC1rZXktZGVsZXRpb24tbG9ncy11bmtleS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImRhc2hib2FyZC1naXQta2V5LWRlbGV0aW9uLWxvZ3MtdW5rZXkudmVyY2VsLmFwcCJ9fSx7Im5hbWUiOiJlbmdpbmVlcmluZyIsInJvb3REaXJlY3RvcnkiOiJhcHBzL2VuZ2luZWVyaW5nIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImVuZ2luZWVyaW5nLWdpdC1rZXktZGVsZXRpb24tbG9ncy11bmtleS52ZXJjZWwuYXBwIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS91bmtleS9lbmdpbmVlcmluZy9KQk5uN0x5djdVTVB4UjFEc0d1UUoyYUxmNWpjIiwicHJldmlld1VybCI6ImVuZ2luZWVyaW5nLWdpdC1rZXktZGVsZXRpb24tbG9ncy11bmtleS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **dashboard** | ✅ Ready ([Inspect](https://vercel.com/unkey/dashboard/H26mRaFgYY9vVPB6eMgSZtwpXPPU)) | [Visit Preview](https://dashboard-git-key-deletion-logs-unkey.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/dashboard-git-key-deletion-logs-unkey.vercel.app?via=pr-comment-feedback-link) | Apr 25, 2025 2:24pm |
| **engineering** | ✅ Ready ([Inspect](https://vercel.com/unkey/engineering/JBNn7Lyv7UMPxR1DsGuQJ2aLf5jc)) | [Visit Preview](https://engineering-git-key-deletion-logs-unkey.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/engineering-git-key-deletion-logs-unkey.vercel.app?via=pr-comment-feedback-link) | Apr 25, 2025 2:24pm |



author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-25T12:19:01Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

This update introduces extensive logging enhancements and stricter input validation to two core modules. The `insertAuditLogs` function in the audit library now provides detailed runtime logs at various stages of the audit log insertion process. In the key deletion router, the mutation procedure enforces a non-empty array of key IDs, adds comprehensive logging throughout the deletion workflow, clarifies error messages, and improves error handling for both workspace and key lookup failures. No public API signatures are changed, except for stricter input schema validation.

## Changes

| File(s)                                               | Change Summary                                                                                                                                                                                                                                                                                                                                                              |
|-------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| apps/dashboard/lib/audit.ts                           | Enhanced `insertAuditLogs` with detailed logging at each stage: when no logs are provided, before insertion (with counts and details), per-entry insert, per-resource insert, and on completion. No changes to function signature or control flow.                                                                                                                          |
| apps/dashboard/lib/trpc/routers/key/delete.ts         | Refined input schema to require at least one key ID. Added detailed logging at all stages: input validation, workspace/key lookup, transaction start/end, and error handling. Improved error messages for workspace/key not found. Wrapped key deletion in a transaction with explicit logging. Enhanced audit log insertion and error handling for permission/audit failures. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant deleteKeys Procedure
    participant DB
    participant AuditLog

    Client->>deleteKeys Procedure: Request to delete keys (with keyIds)
    deleteKeys Procedure->>deleteKeys Procedure: Validate keyIds (must not be empty)
    deleteKeys Procedure->>DB: Find workspace by tenantId and userId
    alt Workspace not found
        deleteKeys Procedure-->>Client: Throw NOT_FOUND error
    else Workspace found
        deleteKeys Procedure->>DB: Find keys by keyIds in workspace
        alt No keys found
            deleteKeys Procedure-->>Client: Throw NOT_FOUND error
        else Keys found
            deleteKeys Procedure->>DB: Begin transaction
            deleteKeys Procedure->>DB: Mark keys as deleted
            deleteKeys Procedure->>AuditLog: Insert audit logs for deleted keys
            DB-->>deleteKeys Procedure: Commit transaction
            deleteKeys Procedure-->>Client: Respond with success
        end
    end
    Note over deleteKeys Procedure: Logs at each stage and on errors
```

## Possibly related PRs

- unkeyed/unkey#2843: Modifies the same `insertAuditLogs` function by changing its signature and moving workspace fetching, making it code-level related to the logging enhancements in this PR.

## Suggested labels

`Bug`

## Suggested reviewers

- perkinsjr
- mcstepp

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=unkeyed/unkey&utm_content=3196):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-25T12:19:07Z
body: Thank you for following the naming conventions for pull request titles! 🙏
<!-- Sticky Pull Request Commentpr-title-lint-error -->
author_association: CONTRIBUTOR
user: login: perkinsjr
created_at: 2025-04-25T14:13:45Z
body: Can you resolve Rabbit comments first :) cc @ogzhanolguncu 
author_association: MEMBER