Skip to content

Conversation

@downfa11
Copy link
Contributor

@downfa11 downfa11 commented Oct 24, 2025

What this PR does / why we need it:

This PR adds webhook error logging functionality to help users debug webhook integration issues in PaaS environments.

When webhook servers return errors, the system now:

  1. Stores webhook details in DB
  2. Provides endpoints to query webhook logs by project (both types 'auth' and 'event')

This enables dashboard users to troubleshoot webhook failures without requiring direct server access.

Which issue(s) this PR fixes:

Fixes #1540

Special notes for your reviewer:

  • Response body is truncated to 10KB to prevent excessive storage usage
  • Logs are queryable by project ID and webhook type (auth/event)
  • Admin API follows protocol pattern used in api/yorkie/v1/admin.proto

Does this PR introduce a user-facing change?:


Additional documentation:

Added `ListWebhookLogs` endpoint to query webhook error logs by project

Checklist:

  • Added relevant tests or not required
  • Addressed and resolved all CodeRabbit review comments
  • Didn't break anything

Summary by CodeRabbit

  • New Features

    • Admin API to list webhook logs with pagination and webhook-type filtering.
    • Webhook attempts are persisted (request/response payloads, status codes, error messages, timestamps).
    • Webhook client now returns raw response payloads; manager persists failed/non-OK attempts to the DB.
  • Documentation

    • OpenAPI updated to expose the new webhook logs endpoint and models.
  • Tests

    • Added coverage for webhook log creation, listing, filtering, and benchmarks.
  • Chores

    • Storage collections/indexes added for webhook logs; backend init order corrected.

@coderabbitai
Copy link

coderabbitai bot commented Oct 24, 2025

Walkthrough

Adds webhook logging end-to-end: new WebhookLog types and converters, protobuf + OpenAPI schemas and ListWebhookLogs RPC, DB persistence (Mongo + in-memory) with indexes, webhook client change to return raw response body, wiring to persist failed/non-OK webhook attempts, and tests/bench updates.

Changes

Cohort / File(s) Summary
API Types & Converters
api/types/webhook_log.go, api/converter/to_pb.go
Add WebhookLogInfo with DeepCopy() and converters ToWebhookLog / ToWebhookLogs mapping to protobuf WebhookLog.
Proto & OpenAPI
api/yorkie/v1/admin.proto, api/docs/yorkie/v1/admin.openapi.yaml
Add WebhookLog, ListWebhookLogsRequest, ListWebhookLogsResponse, and AdminService.ListWebhookLogs RPC; update OpenAPI schemas, request/response bodies and refs.
Generated Connect Glue
api/yorkie/v1/v1connect/admin.connect.go
Wire AdminServiceListWebhookLogsProcedure across client, server handler, and unimplemented stubs (client method, server handler case, unimplemented handler).
Database Interface
server/backend/database/database.go
Add CreateWebhookLog(ctx, *WebhookLogInfo) error and ListWebhookLogs(ctx, projectID, webhookType, pageSize, offset) ([]*WebhookLogInfo, error) to Database.
In-memory DB
server/backend/database/memory/indexes.go, server/backend/database/memory/database.go
Declare tblWebhookLogs, add webhooklogs schema and indexes; implement CreateWebhookLog (generate ID, truncate response to 10KB) and ListWebhookLogs (filter, sort desc by CreatedAt, paging, return deep copies).
MongoDB Implementation & Indexes
server/backend/database/mongo/indexes.go, server/backend/database/mongo/client.go, server/backend/database/mongo/client_test.go
Add ColWebhookLogs collection and indexes (compound + TTL 30d); implement CreateWebhookLog (truncate, set CreatedAt, insert) and ListWebhookLogs (filter by project/type, pagination, sort); add tests.
Webhook Client
pkg/webhook/client.go, pkg/webhook/client_test.go
Extend Client.Send signature to return responseBody []byte as an additional return; read and propagate raw response body; update tests to expect extra value.
Webhook Manager & Backend Init
server/backend/webhook/manager.go, server/backend/backend.go
Add db database.Database field and parameter to NewManager; update SendWebhook to accept projectID and db; record webhook attempts to DB on non-OK/error; initialize manager after DB creation.
Auth Webhook & RPC Handler
server/rpc/auth/webhook.go, server/rpc/admin_server.go
Capture responseBody from webhook client; create WebhookLogInfo for failures/non-OK responses and call CreateWebhookLog; add ListWebhookLogs RPC handler resolving project, querying DB, converting logs and returning nextPageToken.
Tests / Testcases / Benchmarks
server/rpc/testcases/testcases.go, server/rpc/server_test.go, server/backend/database/mongo/client_test.go, test/bench/webhook_bench_test.go
Add RunAdminListWebhookLogsTest and server subtest; add Mongo tests for webhook logs; update tests/benchmarks to pass new manager/db params and accommodate Send extra return value.
OpenAPI Formatting (cosmetic)
api/docs/yorkie/v1/cluster.openapi.yaml, api/docs/yorkie/v1/resources.openapi.yaml, api/docs/yorkie/v1/yorkie.openapi.yaml
Wide cosmetic/formatting changes: convert refs to single-quoted strings and normalize indentation/quoting; no semantic schema changes besides admin additions.

Sequence Diagram(s)

sequenceDiagram
    participant AdminClient as Admin Client
    participant AdminServer as AdminServer
    participant DB as Database
    participant Conv as Converter

    AdminClient->>AdminServer: ListWebhookLogs(req{projectName,pageSize,pageToken,webhookType})
    AdminServer->>AdminServer: resolve projectID from caller
    AdminServer->>DB: ListWebhookLogs(projectID, webhookType, pageSize, offset)
    DB->>DB: query, filter, sort by CreatedAt desc, apply offset/pageSize
    DB-->>AdminServer: []*WebhookLogInfo
    AdminServer->>Conv: ToWebhookLogs(logs)
    Conv-->>AdminServer: []*api.WebhookLog
    AdminServer->>AdminServer: generate nextPageToken(last.ID)
    AdminServer-->>AdminClient: ListWebhookLogsResponse{logs, nextPageToken}
Loading
sequenceDiagram
    participant Caller as Auth/Webhook Caller
    participant Client as pkg/webhook.Client
    participant Remote as Remote Webhook Server
    participant DB as Database

    Caller->>Client: Send(request)
    Client->>Remote: HTTP POST (body)
    Remote-->>Client: HTTP response (status, body) or error
    alt status == 200 && no error
        Client-->>Caller: (resp, 200, responseBody, nil)
        Caller->>Caller: normal processing
    else non-200 or error
        Client-->>Caller: (nil, status, responseBody, error)
        Caller->>Caller: build WebhookLogInfo{projectID,url,reqBody,status,responseBody,errorMessage,createdAt}
        Caller->>DB: CreateWebhookLog(logInfo)
        DB-->>Caller: ack / error
        Caller->>Caller: continue (DB write errors logged/swallowed)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement 🌟

Suggested reviewers

  • hackerwins
  • blurfx

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request contains out-of-scope changes unrelated to webhook error logging. The file api/yorkie/v1/v1connect/yorkie.connect.go includes additions for a Broadcast RPC procedure, client method, handler interface, and HTTP routing—functionality that is separate from and unrelated to the webhook logging feature. Additionally, several OpenAPI documentation files (cluster.openapi.yaml, resources.openapi.yaml, yorkie.openapi.yaml) were modified primarily for formatting and quote style consistency, which while minor, are not directly connected to the webhook logging objectives outlined in issue #1540. Remove the Broadcast RPC additions from api/yorkie/v1/v1connect/yorkie.connect.go as this feature is unrelated to webhook error logging and should be addressed in a separate pull request. Consider either reverting the OpenAPI formatting changes or addressing them in a dedicated formatting PR to keep this changeset focused on the webhook logging feature described in issue #1540.
Docstring Coverage ⚠️ Warning Docstring coverage is 56.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Add webhook error logging in Dashboard" directly and accurately reflects the primary objective of this changeset. The changes introduce comprehensive webhook error logging functionality including database storage for webhook logs, a new ListWebhookLogs RPC endpoint, and modifications to capture webhook response data internally. The title is concise, specific, and clearly communicates the main purpose to developers reviewing the repository history.
Linked Issues Check ✅ Passed The pull request successfully addresses the core requirements from issue #1540. It implements internal webhook error logging by capturing response bodies in the webhook client, storing logs in the database via new CreateWebhookLog and ListWebhookLogs methods, truncating response bodies to 10KB to limit storage, and providing a ListWebhookLogs RPC endpoint for the Dashboard to query logs by project and webhook type. The implementation follows the protocol pattern established in the admin API and does not expose webhook response bodies to external clients, maintaining the security posture requested in the issue.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef77d1 and d7891e6.

⛔ Files ignored due to path filters (2)
  • api/yorkie/v1/admin.pb.go is excluded by !**/*.pb.go
  • api/yorkie/v1/yorkie.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (8)
  • api/converter/to_pb.go (1 hunks)
  • api/docs/yorkie/v1/admin.openapi.yaml (78 hunks)
  • api/docs/yorkie/v1/cluster.openapi.yaml (14 hunks)
  • api/docs/yorkie/v1/resources.openapi.yaml (78 hunks)
  • api/docs/yorkie/v1/yorkie.openapi.yaml (59 hunks)
  • api/yorkie/v1/admin.proto (3 hunks)
  • api/yorkie/v1/v1connect/yorkie.connect.go (9 hunks)
  • server/backend/database/mongo/indexes.go (3 hunks)
✅ Files skipped from review due to trivial changes (2)
  • api/docs/yorkie/v1/yorkie.openapi.yaml
  • api/docs/yorkie/v1/cluster.openapi.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/backend/database/mongo/indexes.go
  • api/converter/to_pb.go
🧰 Additional context used
🧬 Code graph analysis (1)
api/yorkie/v1/v1connect/yorkie.connect.go (2)
api/yorkie/v1/yorkie.pb.go (6)
  • BroadcastRequest (849-858)
  • BroadcastRequest (873-873)
  • BroadcastRequest (888-890)
  • BroadcastResponse (920-924)
  • BroadcastResponse (939-939)
  • BroadcastResponse (954-956)
pkg/document/document.go (2)
  • BroadcastRequest (86-89)
  • New (157-173)
🪛 Checkov (3.2.334)
api/docs/yorkie/v1/resources.openapi.yaml

[medium] 237-245: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

api/docs/yorkie/v1/admin.openapi.yaml

[medium] 1018-1026: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: complex-test
  • GitHub Check: load-test
  • GitHub Check: bench
  • GitHub Check: build
🔇 Additional comments (8)
api/docs/yorkie/v1/resources.openapi.yaml (1)

1-2231: LGTM! Cosmetic formatting changes.

The changes convert double quotes to single quotes for $ref, enum values, and descriptions throughout the OpenAPI spec. These are purely stylistic with no functional impact on the API contract.

api/yorkie/v1/v1connect/yorkie.connect.go (1)

72-400: LGTM! Generated Connect RPC wiring for Broadcast.

The generated code correctly adds Broadcast RPC support across the Connect service layer:

  • Procedure constant, client/handler interfaces, struct fields, initialization, and HTTP routing all follow the established pattern.
  • Implementation is consistent with existing RPCs like WatchDocument and AttachPresence.
api/yorkie/v1/admin.proto (3)

21-21: LGTM! Timestamp import and ListWebhookLogs RPC.

The import of google/protobuf/timestamp.proto is needed for the WebhookLog.created_at field, and the RPC definition follows the standard AdminService pattern.

Also applies to: 62-62


295-300: LGTM! Well-structured ListWebhookLogsRequest.

The request message includes appropriate pagination fields (page_size, page_token) and filtering by project_name and webhook_type. The inline comment clarifies the expected webhook type values.


302-316: LGTM! Complete webhook log message definitions.

  • ListWebhookLogsResponse follows the standard pagination pattern with next_page_token.
  • WebhookLog captures all essential webhook execution details: ID, type, URL, request/response bodies (as bytes for binary safety), status code, error message, and timestamp.
api/docs/yorkie/v1/admin.openapi.yaml (3)

3-11: LGTM! Cosmetic formatting improvements.

The changes standardize quote styles (double → single) across $ref, enum values, and descriptions, consistent with the formatting applied in resources.openapi.yaml.

Also applies to: 17-324, 331-804, 808-3312


217-228: LGTM! ListWebhookLogs endpoint wiring.

The endpoint follows the standard AdminService pattern with proper request/response body refs and support for both application/json and application/proto content types.

Also applies to: 480-488, 724-732


1739-1783: LGTM! Request and response schemas.

  • ListWebhookLogsRequest includes pagination (pageSize, pageToken) and filtering fields (projectName, webhookType) matching the proto definition.
  • ListWebhookLogsResponse follows the standard pagination pattern with logs array and nextPageToken.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/webhook/client.go (1)

105-123: Critical: Response body read twice, breaking JSON unmarshaling.

The response body is read on line 105, exhausting the reader. The subsequent io.ReadAll(resp.Body) on line 118 will return an empty slice because HTTP response bodies can only be read once. This causes JSON unmarshaling on line 123 to operate on an empty body instead of the actual response, breaking all non-int response types.

Apply this diff to use the already-read responseBody:

 		responseBody, err = io.ReadAll(resp.Body)
 		if err != nil {
 			return resp.StatusCode, fmt.Errorf("read response body: %w", err)
 		}
 
 		if !isExpectedStatus(resp.StatusCode) {
 			return resp.StatusCode, ErrUnexpectedStatusCode
 		}
 
 		if _, ok := any(res).(int); ok {
 			return resp.StatusCode, nil
 		}
 
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return resp.StatusCode, fmt.Errorf("send webhook: %w", err)
-		}
-
-		if err := json.Unmarshal(body, &res); err != nil {
+		if err := json.Unmarshal(responseBody, &res); err != nil {
 			logger := logging.From(ctx)
 			if logger != nil {
-				logger.Errorf("send webhook url=%s, status=%d, body=%s", url, resp.StatusCode, string(body))
+				logger.Errorf("send webhook url=%s, status=%d, body=%s", url, resp.StatusCode, string(responseBody))
 			}
 
 			return resp.StatusCode, ErrInvalidJSONResponse
 		}
pkg/webhook/client_test.go (1)

292-292: Critical: Missing return value in function call.

The call to unreachableClient.Send only destructures 3 return values, but the function now returns 4 values (including responseBody). This will cause a compilation error.

Apply this diff to fix the call:

-		resp, statusCode, err := unreachableClient.Send(context.Background(), invalidJSONServer.URL, "", body, options)
+		resp, statusCode, _, err := unreachableClient.Send(context.Background(), invalidJSONServer.URL, "", body, options)
🧹 Nitpick comments (10)
server/backend/database/memory/indexes.go (2)

313-335: Add compound index to support webhook_type filtering.

ListWebhookLogs filters by project and webhook_type; add an index on (ProjectID, WebhookType, CreatedAt) to avoid scans in memory backend.

 		tblWebhookLogs: {
 			Name: tblWebhookLogs,
 			Indexes: map[string]*memdb.IndexSchema{
 				"id": {
 					Name:    "id",
 					Unique:  true,
 					Indexer: &memdb.StringFieldIndex{Field: "ID"},
 				},
 				"project_id": {
 					Name:    "project_id",
 					Indexer: &memdb.StringFieldIndex{Field: "ProjectID"},
 				},
 				"project_id_created_at": {
 					Name: "project_id_created_at",
 					Indexer: &memdb.CompoundIndex{
 						Indexes: []memdb.Indexer{
 							&memdb.StringFieldIndex{Field: "ProjectID"},
 							&memdb.TimeFieldIndex{Field: "CreatedAt"},
 						},
 					},
 				},
+				"project_id_type_created_at": {
+					Name: "project_id_type_created_at",
+					Indexer: &memdb.CompoundIndex{
+						Indexes: []memdb.Indexer{
+							&memdb.StringFieldIndex{Field: "ProjectID"},
+							&memdb.StringFieldIndex{Field: "WebhookType"},
+							&memdb.TimeFieldIndex{Field: "CreatedAt"},
+						},
+					},
+				},
 			},
 		},

31-31: Naming consistency nit.

"webhookLogs" breaks the lowercase naming pattern used elsewhere (e.g., clusternodes, documents). Consider "webhooklogs" for consistency.

server/rpc/server_test.go (1)

259-261: Add tests for pagination and type filter.

Cover non-empty results, NextPageToken propagation, PageToken consumption, and webhook_type filtering to prevent regressions.

server/backend/backend.go (1)

153-154: Fix variable name typo.

Use eventWebhookManager consistently.

-	eventWebhookManger := webhook.NewManager(pkgwebhook.NewClient[types.EventWebhookRequest, int](), db)
+	eventWebhookManager := webhook.NewManager(pkgwebhook.NewClient[types.EventWebhookRequest, int](), db)
...
-		EventWebhookManager: eventWebhookManger,
+		EventWebhookManager: eventWebhookManager,

Also applies to: 202-204

server/backend/database/mongo/indexes.go (1)

206-219: Consider adding webhook_type to the compound index.

Since ListWebhookLogs filters by webhook_type (optional), including it in the compound index could improve query performance when filtering by type. The current index only covers project_id and created_at.

Consider an index like:

 	}, {
 		name: ColWebhookLogs,
 		indexes: []mongo.IndexModel{{
 			Keys: bson.D{
 				{Key: "project_id", Value: int32(1)},
+				{Key: "webhook_type", Value: int32(1)},
 				{Key: "created_at", Value: int32(-1)}, // desc
 			},
 		}, {

This would efficiently support queries filtering by both project_id and webhook_type, while still allowing queries that filter by project_id alone (index prefix property).

server/rpc/testcases/testcases.go (1)

1606-1655: Consider testing with actual webhook logs.

The current test only verifies the empty logs case and error handling. Consider adding a test case that creates actual webhook logs via the database and then verifies they are returned by ListWebhookLogs, similar to how TestWebhookLogs in client_test.go creates and retrieves logs.

This would provide more comprehensive coverage of the webhook log retrieval flow through the admin RPC layer.

server/backend/database/memory/database.go (1)

2272-2307: Use WebhookLogInfo.DeepCopy() for consistency.

Lines 2289-2299 manually create a copy of the webhook log, but the WebhookLogInfo type already has a DeepCopy() method. Using it would be more consistent with the rest of the codebase (e.g., line 2343 in ListWebhookLogs uses DeepCopy).

-	logCopy := &types.WebhookLogInfo{
-		ID:           webhookLog.ID,
-		ProjectID:    webhookLog.ProjectID,
-		WebhookType:  webhookLog.WebhookType,
-		WebhookURL:   webhookLog.WebhookURL,
-		RequestBody:  webhookLog.RequestBody,
-		StatusCode:   webhookLog.StatusCode,
-		ResponseBody: responseBody,
-		ErrorMessage: webhookLog.ErrorMessage,
-		CreatedAt:    webhookLog.CreatedAt,
-	}
+	logCopy := webhookLog.DeepCopy()
+	logCopy.ResponseBody = responseBody

Note: You'll need to truncate after the DeepCopy to ensure the stored copy has the truncated response body.

server/backend/database/mongo/client.go (1)

2387-2432: ListWebhookLogs: map ObjectID to hex; guard page size/offset

  • Map _id ObjectID to hex string.
  • Clamp pageSize and sanitize offset to avoid negative/oversized queries.

Apply:

 func (c *Client) ListWebhookLogs(
@@
 ) ([]*types.WebhookLogInfo, error) {
 	filter := bson.M{"project_id": projectID}
 
 	if webhookType != "" {
 		filter["webhook_type"] = webhookType
 	}
 
+	// Safety bounds
+	if pageSize <= 0 {
+		pageSize = 50
+	} else if pageSize > 100 {
+		pageSize = 100
+	}
+	if offset < 0 {
+		offset = 0
+	}
+
 	opts := options.Find().
 		SetSort(bson.D{{Key: "created_at", Value: -1}}).
 		SetLimit(int64(pageSize)).
 		SetSkip(int64(offset))
@@
 	infos := make([]*types.WebhookLogInfo, len(docs))
 	for i, doc := range docs {
 		infos[i] = &types.WebhookLogInfo{
-			ID:           types.ID(doc.ID),
+			ID:           types.ID(doc.ID.Hex()),
 			ProjectID:    doc.ProjectID,
 			WebhookType:  doc.WebhookType,
 			WebhookURL:   doc.WebhookURL,
 			RequestBody:  doc.RequestBody,
 			StatusCode:   doc.StatusCode,
 			ResponseBody: doc.ResponseBody,
 			ErrorMessage: doc.ErrorMessage,
 			CreatedAt:    doc.CreatedAt,
 		}
 	}
 
 	return infos, nil
 }

Optional: consider redacting secrets in WebhookURL (strip query/basic auth) before persisting to avoid leaking tokens.

api/docs/yorkie/v1/admin.openapi.yaml (1)

1741-1766: Constrain pagination fields

Add reasonable bounds to guide clients and protect the backend.

Apply:

     yorkie.v1.ListWebhookLogsRequest:
@@
         pageSize:
           additionalProperties: false
           description: ""
           title: page_size
           type: integer
+          minimum: 1
+          maximum: 100
@@
         webhookType:
           additionalProperties: false
           description: '"auth", "event", or "" for all'
           title: webhook_type
           type: string
+          enum: [auth, event]
server/backend/webhook/manager.go (1)

106-126: Log non-200 and transport errors; use UTC timestamp

Behavior is correct. Prefer UTC for consistent storage.

Apply:

-			CreatedAt:    time.Now(),
+			CreatedAt:    time.Now().UTC(),

Optional: define a constant for "event" to avoid typos, or reuse an existing enumerated type if present.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b411f4 and ce7be60.

⛔ Files ignored due to path filters (1)
  • api/yorkie/v1/admin.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (19)
  • api/converter/to_pb.go (1 hunks)
  • api/docs/yorkie/v1/admin.openapi.yaml (5 hunks)
  • api/types/webhook_log.go (1 hunks)
  • api/yorkie/v1/admin.proto (3 hunks)
  • api/yorkie/v1/v1connect/admin.connect.go (9 hunks)
  • pkg/webhook/client.go (3 hunks)
  • pkg/webhook/client_test.go (9 hunks)
  • server/backend/backend.go (1 hunks)
  • server/backend/database/database.go (1 hunks)
  • server/backend/database/memory/database.go (1 hunks)
  • server/backend/database/memory/indexes.go (2 hunks)
  • server/backend/database/mongo/client.go (2 hunks)
  • server/backend/database/mongo/client_test.go (2 hunks)
  • server/backend/database/mongo/indexes.go (3 hunks)
  • server/backend/webhook/manager.go (5 hunks)
  • server/rpc/admin_server.go (1 hunks)
  • server/rpc/auth/webhook.go (2 hunks)
  • server/rpc/server_test.go (1 hunks)
  • server/rpc/testcases/testcases.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
api/converter/to_pb.go (3)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
api/yorkie/v1/admin.pb.go (3)
  • WebhookLog (2771-2784)
  • WebhookLog (2799-2799)
  • WebhookLog (2814-2816)
api/types/id.go (1)
  • ID (37-37)
server/backend/backend.go (2)
server/backend/webhook/manager.go (1)
  • NewManager (49-55)
pkg/webhook/client.go (1)
  • NewClient (65-69)
server/backend/database/database.go (2)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
api/types/id.go (1)
  • ID (37-37)
server/rpc/admin_server.go (5)
api/yorkie/v1/admin.pb.go (6)
  • ListWebhookLogsRequest (2645-2654)
  • ListWebhookLogsRequest (2669-2669)
  • ListWebhookLogsRequest (2684-2686)
  • ListWebhookLogsResponse (2716-2723)
  • ListWebhookLogsResponse (2738-2738)
  • ListWebhookLogsResponse (2753-2755)
server/projects/projects.go (1)
  • GetProject (66-78)
api/types/id.go (1)
  • ID (37-37)
api/converter/to_pb.go (1)
  • ToWebhookLogs (739-745)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
server/backend/database/mongo/client_test.go (1)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
server/backend/database/memory/database.go (2)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
api/types/id.go (1)
  • ID (37-37)
server/rpc/server_test.go (1)
server/rpc/testcases/testcases.go (1)
  • RunAdminListWebhookLogsTest (1607-1655)
server/rpc/auth/webhook.go (3)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
api/types/id.go (1)
  • ID (37-37)
server/logging/context.go (1)
  • From (32-43)
server/backend/database/mongo/client.go (3)
api/types/id.go (1)
  • ID (37-37)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
server/backend/database/mongo/indexes.go (1)
  • ColWebhookLogs (48-48)
server/rpc/testcases/testcases.go (7)
api/yorkie/v1/v1connect/admin.connect.go (1)
  • AdminServiceClient (127-154)
admin/auth.go (1)
  • AuthInterceptor (31-33)
test/helper/helper.go (4)
  • TestSlugName (410-439)
  • AdminUser (74-74)
  • AdminPassword (75-75)
  • CreateProject (622-628)
api/yorkie/v1/admin.pb.go (9)
  • LogInRequest (196-203)
  • LogInRequest (218-218)
  • LogInRequest (233-235)
  • CreateProjectRequest (492-498)
  • CreateProjectRequest (513-513)
  • CreateProjectRequest (528-530)
  • ListWebhookLogsRequest (2645-2654)
  • ListWebhookLogsRequest (2669-2669)
  • ListWebhookLogsRequest (2684-2686)
server/rpc/connecthelper/errors.go (1)
  • CodeOf (59-76)
server/backend/database/database.go (1)
  • ErrProjectNotFound (40-40)
api/converter/errors.go (1)
  • ErrorCodeOf (11-27)
api/yorkie/v1/v1connect/admin.connect.go (1)
api/yorkie/v1/admin.pb.go (6)
  • ListWebhookLogsRequest (2645-2654)
  • ListWebhookLogsRequest (2669-2669)
  • ListWebhookLogsRequest (2684-2686)
  • ListWebhookLogsResponse (2716-2723)
  • ListWebhookLogsResponse (2738-2738)
  • ListWebhookLogsResponse (2753-2755)
server/backend/webhook/manager.go (4)
server/backend/database/database.go (1)
  • Database (74-439)
api/types/event_webhook.go (3)
  • EventWebhookRequest (50-53)
  • EventWebhookType (31-31)
  • NewRequestBody (56-70)
api/types/id.go (1)
  • ID (37-37)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
🔇 Additional comments (19)
server/rpc/auth/webhook.go (1)

71-91: Redact RequestBody token and sanitize webhook URL before logging; also use UTC timestamp.

RequestBody currently contains the auth token directly (it's marshaled from req containing the Token field). Additionally, webhook URLs aren't sanitized and could contain credentials. The 10KB ResponseBody truncation is already handled at the database layer, so no duplication needed.

Apply this simpler patch to both webhook.go (lines 71-91) and manager.go (lines 110-126):

 	if err != nil || status != http.StatusOK {
 		errorMessage := ""
 		if err != nil {
 			errorMessage = err.Error()
 		}
+		// Sanitize URL by removing credentials/query
+		safeURL := prj.AuthWebhookURL
+		if u, perr := url.Parse(prj.AuthWebhookURL); perr == nil {
+			u.User = nil
+			u.RawQuery = ""
+			safeURL = u.String()
+		}
 
 		webhookLog := &types.WebhookLogInfo{
 			ProjectID:    prj.ID,
 			WebhookType:  "auth",
-			WebhookURL:   prj.AuthWebhookURL,
-			RequestBody:  body,
+			WebhookURL:   safeURL,
+			RequestBody:  body, // Token already in marshaled form; database layer truncates ResponseBody
-			CreatedAt:    time.Now(),
+			CreatedAt:    time.Now().UTC(),
 		}
 		if logErr := be.DB.CreateWebhookLog(ctx, webhookLog); logErr != nil {

For event webhooks in manager.go, use attr.URL instead of prj.AuthWebhookURL. Add "net/url" import if not present.

For deeper redaction of the Token field itself, consider a pre-marshal approach or database-layer filtering.

api/converter/to_pb.go (1)

724-745: LGTM!

The webhook log converters follow the established patterns in this file and correctly map internal types to protobuf representations.

server/backend/database/mongo/client_test.go (1)

239-283: LGTM!

The webhook logs tests provide good coverage for both creation and retrieval scenarios, including filtering by webhook type. The tests follow the established patterns in this file.

api/types/webhook_log.go (1)

5-40: LGTM!

The WebhookLogInfo type and DeepCopy implementation are well-designed. The DeepCopy correctly handles nil checks and creates deep copies of byte slices, ensuring safe concurrent access.

api/yorkie/v1/admin.proto (2)

62-62: LGTM!

The new ListWebhookLogs RPC follows the established patterns in the AdminService.


295-316: LGTM!

The webhook log messages are well-designed with proper pagination support. The optional webhook_type filter allows querying all logs or filtering by specific type.

server/backend/database/memory/database.go (1)

2309-2351: LGTM!

The ListWebhookLogs implementation correctly handles filtering, pagination, and sorting. The use of DeepCopy ensures safe concurrent access.

server/backend/database/mongo/client.go (1)

2357-2385: CreateWebhookLog: aligns with 10KB truncation; set ID from ObjectID

Truncation logic looks good. With the _id fix, the InsertedID cast remains valid. No further changes needed here.

api/docs/yorkie/v1/admin.openapi.yaml (3)

218-229: Add ListWebhookLogs path: OK

Path wiring and tags look consistent with existing AdminService routes.


481-489: Request body schema reference: OK

RequestBody component correctly references the new request schema.


725-733: Response schema reference: OK

Response component correctly references the new response schema.

api/yorkie/v1/v1connect/admin.connect.go (6)

121-124: Procedure constant added: OK

Constant name and value align with service/method.


153-154: Client interface method: OK

New method signature matches generated request/response types.


291-296: Client initialization: OK

Client constructed with correct procedure path.


454-457: Client method implementation: OK

Delegates to underlying client; consistent with other methods.


620-624: Handler wiring and routing: OK

Unary handler and switch case registered correctly.


788-790: Unimplemented stub: OK

Returns CodeUnimplemented as expected.

server/backend/webhook/manager.go (2)

45-55: Inject DB into Manager: OK

Constructor and struct updated coherently; no issues.


74-77: Immediate call path: OK

Limiter bypass path invokes SendWebhook directly; no double-send.

Signed-off-by: downfa11 <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
server/backend/database/mongo/indexes.go (2)

47-49: Collection naming consistency

All other collections are lowercase; "webhookLogs" is camelCase. Prefer lowercase to match convention and avoid surprises in tooling and queries.

Apply:

-	ColWebhookLogs = "webhookLogs"
+	ColWebhookLogs = "webhooklogs"

206-220: Add index for queries without webhook_type; keep TTL as-is

ListWebhookLogs often filters only by projectID and sorts by created_at desc. The current compound index won’t support that sort when webhook_type is omitted. Add (project_id, created_at:-1).

Apply:

 	{
 		name: ColWebhookLogs,
 		indexes: []mongo.IndexModel{{
 			Keys: bson.D{
 				{Key: "project_id", Value: int32(1)},
 				{Key: "webhook_type", Value: int32(1)},
 				{Key: "created_at", Value: int32(-1)}, // desc
 			},
 		}, {
+			// Support queries that filter only by project_id and sort by created_at desc.
+			Keys: bson.D{
+				{Key: "project_id", Value: int32(1)},
+				{Key: "created_at", Value: int32(-1)}, // desc
+			},
+		}, {
 			Keys: bson.D{
 				{Key: "created_at", Value: int32(1)},
 			},
 			Options: options.Index().SetExpireAfterSeconds(2592000), // TTL: 30day
 		}},
 	},
pkg/webhook/client_test.go (1)

292-297: Optional: also assert responseBody for invalid JSON

Assert that responseBody is non‑empty to guarantee we capture bodies that fail JSON decoding (useful for DB log tests).

server/backend/database/mongo/client.go (2)

2357-2385: Small hardening: default CreatedAt and named limit

Guard zero timestamps and make the 10KB cap a named const for reuse.

Apply:

+const maxRespBodyBytes = 10 * 1024
+
 func (c *Client) CreateWebhookLog(
   ctx context.Context,
   webhookLog *types.WebhookLogInfo,
 ) error {
   responseBody := webhookLog.ResponseBody
-  if len(responseBody) > 10*1024 {
-    responseBody = responseBody[:10*1024] // 10kb
+  if len(responseBody) > maxRespBodyBytes {
+    responseBody = responseBody[:maxRespBodyBytes]
   }
+  if webhookLog.CreatedAt.IsZero() {
+    webhookLog.CreatedAt = gotime.Now().UTC()
+  }

2387-2441: Offset pagination may not scale; prefer seek pagination

Skip/limit with large offsets degrades on big collections. Consider seek pagination using (created_at, _id) tuple and a “cursor/after” token. Align indexes accordingly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce7be60 and 092c270.

📒 Files selected for processing (10)
  • pkg/webhook/client.go (4 hunks)
  • pkg/webhook/client_test.go (10 hunks)
  • server/backend/backend.go (2 hunks)
  • server/backend/database/database.go (1 hunks)
  • server/backend/database/memory/indexes.go (2 hunks)
  • server/backend/database/mongo/client.go (2 hunks)
  • server/backend/database/mongo/indexes.go (3 hunks)
  • server/backend/webhook/manager.go (5 hunks)
  • server/rpc/admin_server.go (2 hunks)
  • test/bench/webhook_bench_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • server/rpc/admin_server.go
  • server/backend/backend.go
  • server/backend/database/database.go
  • server/backend/database/memory/indexes.go
  • pkg/webhook/client.go
🧰 Additional context used
🧬 Code graph analysis (3)
test/bench/webhook_bench_test.go (1)
server/backend/webhook/manager.go (1)
  • NewManager (49-55)
server/backend/database/mongo/client.go (3)
api/types/id.go (1)
  • ID (37-37)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
server/backend/database/mongo/indexes.go (1)
  • ColWebhookLogs (48-48)
server/backend/webhook/manager.go (3)
api/types/event_webhook.go (3)
  • EventWebhookRequest (50-53)
  • EventWebhookType (31-31)
  • NewRequestBody (56-70)
api/types/id.go (1)
  • ID (37-37)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: complex-test
  • GitHub Check: build
  • GitHub Check: bench
  • GitHub Check: load-test
🔇 Additional comments (4)
pkg/webhook/client_test.go (1)

105-105: LGTM on Send signature updates

Tests correctly adapt to (resp, statusCode, responseBody, err). Assertions look sound and improve observability.

Also applies to: 117-117, 129-129, 141-141, 168-168, 185-185, 210-210, 227-227, 254-259, 266-271, 292-292

server/backend/database/mongo/client.go (1)

62-72: Storage model looks good

Using ObjectID with omitempty avoids duplicate key issues and lets Mongo assign IDs.

server/backend/webhook/manager.go (1)

49-55: Event manager changes look good

Constructor wiring, limiter integration, and error/status handling are correct. Logging only on failure/non‑OK is appropriate.

If you plan to log auth‑webhook failures too, ensure the analogous path sets WebhookType: "auth" consistently.

Also applies to: 59-87, 136-143

test/bench/webhook_bench_test.go (1)

1-1: Go version requirement confirmed

The go.mod declares go 1.23.0, which satisfies the Go 1.22+ requirement for range-over-integers syntax. No changes needed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
server/backend/database/mongo/indexes.go (1)

206-220: Indexes are well-designed for the use case.

The compound index efficiently supports queries by project ID and webhook type with reverse chronological ordering (most recent first). The TTL index is correctly configured as a single-field ascending index to automatically expire logs after 30 days.

Minor: Consider adding shard key comment for consistency.

For consistency with other collections in this file (lines 121, 127, 133, 150, etc.), consider annotating project_id as the shard key:

 		indexes: []mongo.IndexModel{{
 			Keys: bson.D{
-				{Key: "project_id", Value: int32(1)},
+				{Key: "project_id", Value: int32(1)}, // shard key
 				{Key: "webhook_type", Value: int32(1)},
 				{Key: "created_at", Value: int32(-1)}, // desc
 			},
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 092c270 and 5ef77d1.

📒 Files selected for processing (5)
  • server/backend/database/memory/database.go (1 hunks)
  • server/backend/database/mongo/client.go (2 hunks)
  • server/backend/database/mongo/indexes.go (3 hunks)
  • server/backend/webhook/manager.go (5 hunks)
  • test/bench/webhook_bench_test.go (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/backend/database/mongo/client.go
🧰 Additional context used
🧬 Code graph analysis (3)
test/bench/webhook_bench_test.go (3)
server/backend/database/memory/database.go (1)
  • New (44-54)
server/backend/backend.go (1)
  • New (87-211)
server/backend/webhook/manager.go (1)
  • NewManager (49-55)
server/backend/webhook/manager.go (5)
server/backend/database/database.go (1)
  • Database (74-439)
pkg/webhook/client.go (1)
  • Client (59-61)
api/types/event_webhook.go (3)
  • EventWebhookRequest (50-53)
  • EventWebhookType (31-31)
  • NewRequestBody (56-70)
api/types/id.go (1)
  • ID (37-37)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
server/backend/database/memory/database.go (2)
api/types/webhook_log.go (1)
  • WebhookLogInfo (5-15)
api/types/id.go (1)
  • ID (37-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: bench
  • GitHub Check: complex-test
  • GitHub Check: build
  • GitHub Check: load-test
🔇 Additional comments (11)
server/backend/database/mongo/indexes.go (2)

47-48: LGTM!

The constant definition and documentation follow the established pattern and naming conventions used throughout the file.


62-62: LGTM!

The addition to the Collections slice is correct.

test/bench/webhook_bench_test.go (3)

33-33: LGTM! Import correctly added for in-memory database support.

The import enables the benchmark to create in-memory database instances, addressing the nil database issue flagged in previous reviews.


106-110: LGTM! In-memory database properly initialized and passed.

The benchmark now creates and passes a non-nil database instance to SendWebhook, resolving the panic risk flagged in the previous review.

Also applies to: 132-133


154-159: LGTM! Consistent in-memory database setup for Manager benchmark.

The setup mirrors the pattern in benchmarkSendWebhook and properly provides a database instance to NewManager, preventing the nil-db panic.

Also applies to: 172-172

server/backend/database/memory/database.go (2)

2272-2298: LGTM! CreateWebhookLog correctly handles ID generation, truncation, and storage.

The method properly:

  • Generates IDs when missing
  • Truncates response bodies to 10KB to limit storage
  • Creates deep copies to prevent mutation
  • Handles transactions with proper error wrapping

2300-2342: LGTM! ListWebhookLogs correctly implements filtering, pagination, and sorting.

The method properly:

  • Filters by project and webhook type
  • Applies offset and page size pagination
  • Returns deep copies for thread safety
  • Sorts by creation time (newest first)

The in-memory sort is acceptable for this implementation's use case (tests and temporary storage).

server/backend/webhook/manager.go (4)

29-29: LGTM! Database field correctly added to Manager.

The db field is properly declared, accepted in NewManager, and initialized, enabling webhook log persistence.

Also applies to: 45-45, 49-49, 53-53


66-67: LGTM! Send method correctly propagates database and project ID.

Both the immediate and debounced execution paths properly pass projectID and db to SendWebhook, maintaining consistency.

Also applies to: 76-84


105-106: LGTM! SendWebhook signature extended to accept database and project ID.

The signature changes enable webhook log persistence for debugging webhook integration failures.


114-135: LGTM! Webhook logging correctly guards against nil database and handles errors.

The implementation properly:

  • Guards against nil db before logging (line 115) — resolving the panic risk from the previous review
  • Creates comprehensive webhook log entries on failures
  • Logs any persistence errors without failing the webhook send
  • Preserves the original error flow

@hackerwins hackerwins force-pushed the main branch 2 times, most recently from 6f9ef37 to df0b450 Compare December 9, 2025 05:37
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.

Improve webhook error visibility by logging webhook response errors in Dashboard

1 participant