@@ -9,6 +9,7 @@ import { readFileSync, existsSync } from "node:fs";
99import { fileURLToPath } from "node:url" ;
1010import { dirname , join } from "node:path" ;
1111import { corpusMaxMtime , rankKnowledgeForQuery } from "./knowledge-rank.mjs" ;
12+ import { hashActorValue , recordSearchUsage } from "./usage-log.mjs" ;
1213
1314const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
1415const PORT = parseInt ( process . env . SEARCH_HTTP_PORT || process . env . QWEN_HTTP_PORT || "8790" , 10 ) ;
@@ -158,6 +159,21 @@ function resolveTier(req) {
158159 return "free" ;
159160}
160161
162+ function searchUsageActor ( req ) {
163+ const auth = req . headers . authorization ;
164+ if ( typeof auth === "string" && auth . startsWith ( "Bearer " ) ) {
165+ const token = auth . slice ( 7 ) . trim ( ) ;
166+ if ( token ) {
167+ return { actorType : "api_token" , actorKey : hashActorValue ( token ) } ;
168+ }
169+ }
170+ if ( isPublicFacingRequest ( req ) ) {
171+ return { actorType : "anonymous_ip" , actorKey : hashActorValue ( clientFacingIp ( req ) ) } ;
172+ }
173+ const ra = req . socket . remoteAddress || "internal" ;
174+ return { actorType : "internal" , actorKey : hashActorValue ( `dock:${ ra } ` ) } ;
175+ }
176+
161177function checkRateLimit ( tier , keyId ) {
162178 const t = TIERS [ tier ] ;
163179 if ( ! t ) return { ok : true } ;
@@ -405,11 +421,21 @@ async function handleRequest(req, res) {
405421 return ;
406422 }
407423
424+ const rateTier = tier === "basic" ? "basic" : "free" ;
425+ const usageActor = searchUsageActor ( req ) ;
426+
408427 if ( isPublicFacingRequest ( req ) ) {
409- const rateTier = tier === "basic" ? "basic" : "free" ;
410428 const keyId = `${ rateTier } :${ tier === "basic" ? "token" : clientFacingIp ( req ) } ` ;
411429 const rl = checkRateLimit ( rateTier , keyId ) ;
412430 if ( ! rl . ok ) {
431+ recordSearchUsage ( {
432+ eventType : "search_rate_limit" ,
433+ actorType : usageActor . actorType ,
434+ actorKey : usageActor . actorKey ,
435+ tier : rateTier ,
436+ status : 429 ,
437+ meta : { reason : rl . reason } ,
438+ } ) ;
413439 sendError ( res , 429 , "rate_limit" , `Rate limit exceeded (${ rl . reason } )` , {
414440 tier : rateTier ,
415441 limit : rl . limit ,
@@ -437,9 +463,25 @@ async function handleRequest(req, res) {
437463
438464 try {
439465 const result = await runSearchQuery ( query ) ;
466+ recordSearchUsage ( {
467+ eventType : "search_query" ,
468+ actorType : usageActor . actorType ,
469+ actorKey : usageActor . actorKey ,
470+ tier : rateTier ,
471+ status : 200 ,
472+ meta : { cache_hit : result . meta ?. cache_hit === true } ,
473+ } ) ;
440474 sendJson ( res , 200 , result ) ;
441475 } catch ( err ) {
442476 const message = err instanceof Error ? err . message : String ( err ) ;
477+ recordSearchUsage ( {
478+ eventType : "search_error" ,
479+ actorType : usageActor . actorType ,
480+ actorKey : usageActor . actorKey ,
481+ tier : rateTier ,
482+ status : 502 ,
483+ meta : { message : message . slice ( 0 , 500 ) } ,
484+ } ) ;
443485 sendError ( res , 502 , "search_failed" , message ) ;
444486 }
445487 return ;
0 commit comments