Add gRPC recorder request filtering logic to make it more generic and configurable (#6070)

Add gRPC recorder request filtering logic to make it more generic and configurable #6070
This commit is contained in:
Jose Castelli
2025-09-09 19:01:32 +02:00
committed by GitHub
parent 592e1a6d49
commit 9b750783c9
7 changed files with 114 additions and 18 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add gRPC recorder request filtering logic to make it more generic and configurable
+13 -13
View File
@@ -1,14 +1,14 @@
// HubSpot Tracking Code for Cline Documentation
(function() {
// Check if HubSpot script is already loaded to prevent duplicates
if (!document.getElementById('hs-script-loader')) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.id = 'hs-script-loader';
script.async = true;
script.src = 'https://js-na2.hs-scripts.com/243656267.js';
// Append the script to the document head
document.head.appendChild(script);
}
})();
;(() => {
// Check if HubSpot script is already loaded to prevent duplicates
if (!document.getElementById("hs-script-loader")) {
var script = document.createElement("script")
script.type = "text/javascript"
script.id = "hs-script-loader"
script.async = true
script.src = "https://js-na2.hs-scripts.com/243656267.js"
// Append the script to the document head
document.head.appendChild(script)
}
})()
@@ -1,3 +1,4 @@
import { GrpcRequestFilter } from "@core/controller/grpc-recorder/types"
import { GrpcRecorder, GrpcRecorderNoops, IRecorder } from "@/core/controller/grpc-recorder/grpc-recorder"
import { LogFileHandler, LogFileHandlerNoops } from "@/core/controller/grpc-recorder/log-file-handler"
@@ -11,6 +12,7 @@ import { LogFileHandler, LogFileHandlerNoops } from "@/core/controller/grpc-reco
export class GrpcRecorderBuilder {
private fileHandler: LogFileHandler | null = null
private enabled: boolean = true
private filters: GrpcRequestFilter[] = []
public withLogFileHandler(handler: LogFileHandler): this {
this.fileHandler = handler
@@ -22,12 +24,40 @@ export class GrpcRecorderBuilder {
return this
}
public withFilters(...filters: GrpcRequestFilter[]): this {
this.filters.push(...filters)
return this
}
public build(): IRecorder {
if (!this.enabled) {
return new GrpcRecorderNoops()
}
let filters: GrpcRequestFilter[] = filtersFromEnv()
if (this.filters.length > 0) {
filters = filters.concat(this.filters)
}
const handler = this.fileHandler ?? new LogFileHandlerNoops()
return new GrpcRecorder(handler)
return new GrpcRecorder(handler, filters)
}
}
function filtersFromEnv(): GrpcRequestFilter[] {
const filters: GrpcRequestFilter[] = []
if (process.env.GRPC_RECORDER_TESTS_FILTERS_ENABLED === "true") {
filters.push(...testFilters())
}
return filters
}
function testFilters(): GrpcRequestFilter[] {
/*
* Ignores streaming messages and unwanted services messages
* that record more than expected.
*/
return [(req) => req.is_streaming, (req) => ["cline.UiService", "cline.McpService", "cline.WebService"].includes(req.service)]
}
@@ -7,7 +7,10 @@ describe("grpc-recorder", () => {
let recorder: IRecorder
before(async () => {
recorder = GrpcRecorder.builder().enableIf(true).build()
recorder = GrpcRecorder.builder()
.withFilters((req: GrpcRequest) => req.service === "the-unwanted-service")
.enableIf(true)
.build()
})
describe("GrpcRecorder", () => {
@@ -73,7 +76,7 @@ describe("grpc-recorder", () => {
recorder.recordRequest(us.request)
let sessionLog = recorder.getSessionLog()
expect(sessionLog.entries).length(index + 1)
expect(sessionLog.entries).length(index + 1, `unexpected request_id: ${us.request.request_id}`)
expect(sessionLog.entries[index]).to.include({
service: us.request.service,
@@ -101,6 +104,48 @@ describe("grpc-recorder", () => {
completedRequests: 2,
errorRequests: 1,
})
recorder.recordRequest({
service: "the-unwanted-service",
method: "the-method",
message: "the-message",
request_id: "request-id-1",
is_streaming: false,
})
// we expect to filter out this unwanted request
expect(sessionLog.entries).length(3)
})
it("using default filtering should filter out unwanted requests", async () => {
const customRecorder = GrpcRecorder.builder()
.withFilters(
(req) => req.is_streaming,
(req) => ["cline.UiService", "cline.McpService", "cline.WebService"].includes(req.service),
)
.enableIf(true)
.build()
const unwantedServices = ["cline.UiService", "cline.McpService", "cline.WebService"]
unwantedServices.forEach((us) => {
customRecorder.recordRequest({
service: us,
method: "the-method",
message: "the-message",
request_id: "request-id-1",
is_streaming: false,
})
})
let sessionLog = customRecorder.getSessionLog()
expect(sessionLog.entries).length(0)
customRecorder.recordRequest({
service: "streaming-request",
method: "the-method",
message: "the-message",
request_id: "request-id-1",
is_streaming: true,
})
sessionLog = customRecorder.getSessionLog()
expect(sessionLog.entries).length(0)
})
})
})
@@ -2,7 +2,7 @@ import { GrpcResponse } from "@shared/ExtensionMessage"
import { GrpcRequest } from "@shared/WebviewMessage"
import { GrpcRecorderBuilder } from "@/core/controller/grpc-recorder/grpc-recorder.builder"
import { ILogFileHandler } from "@/core/controller/grpc-recorder/log-file-handler"
import { GrpcLogEntry, GrpcSessionLog, SessionStats } from "@/core/controller/grpc-recorder/types"
import { GrpcLogEntry, GrpcRequestFilter, GrpcSessionLog, SessionStats } from "@/core/controller/grpc-recorder/types"
export class GrpcRecorderNoops implements IRecorder {
recordRequest(_request: GrpcRequest): void {}
@@ -36,7 +36,10 @@ export class GrpcRecorder implements IRecorder {
private sessionLog: GrpcSessionLog
private pendingRequests: Map<string, { entry: GrpcLogEntry; startTime: number }> = new Map()
constructor(private fileHandler: ILogFileHandler) {
constructor(
private fileHandler: ILogFileHandler,
private requestFilters: GrpcRequestFilter[] = [],
) {
this.sessionLog = {
startTime: new Date().toISOString(),
entries: [],
@@ -61,6 +64,10 @@ export class GrpcRecorder implements IRecorder {
* @param request - The incoming gRPC request.
*/
public recordRequest(request: GrpcRequest): void {
if (this.shouldFilter(request)) {
return
}
const entry: GrpcLogEntry = {
requestId: request.request_id,
service: request.service,
@@ -176,4 +183,8 @@ export class GrpcRecorder implements IRecorder {
errorRequests,
}
}
private shouldFilter(request: GrpcRequest): boolean {
return this.requestFilters.some((filter) => filter(request))
}
}
@@ -1,3 +1,7 @@
import { GrpcRequest } from "@/shared/WebviewMessage"
export type GrpcRequestFilter = (request: GrpcRequest) => boolean
export interface GrpcLogEntry {
requestId: string
service: string
+1
View File
@@ -238,6 +238,7 @@ export const e2e = test
CLINE_ENVIRONMENT: "local",
GRPC_RECORDER_FILE_NAME: E2ETestHelper.generateTestFileName(testInfo.title, testInfo.project.name),
// GRPC_RECORDER_ENABLED: "true",
// GRPC_RECORDER_TESTS_FILTERS_ENABLED: "true"
// IS_DEV: "true",
// DEV_WORKSPACE_FOLDER: E2ETestHelper.CODEBASE_ROOT_DIR,
},