Skip to content

Commit 3016cc2

Browse files
authored
feat: capture x-client header for analytics (#468)
### Context We recently added the ability to pass custom headers to identify which Storacha client uses the Upload API (see: storacha/upload-service#215). Now we need to capture the custom header for analytics. ### Changes - Updated the `ucan-invocation-handler` to log additional information if the `X-Client` custom header is present. - The logs are structured in JSON format that's easy to query in CloudWatch Logs. - The `clientId` field will contain the actual value of the `X-Client` header which indicates the name and version of the Storacha Client + service/project it is integrated to, e.g: `Storacha/X (js; MCP/Y)` - where `X` = major version of the Storacha Client, and `Y` = major version of the service/project integration. - The `requestId` allows us to correlate this specific log entry with other logs from the same request, which helps debug issues. - Then we build analytics based on CloudWatch Logs Insights using some query like this: ``` fields @timestamp, clientId | filter message = "Client request" | stats count(*) as requestCount by clientId | sort requestCount desc ``` Related to: storacha/mcp-storage-server#18
1 parent 03c4281 commit 3016cc2

File tree

1 file changed

+17
-3
lines changed

1 file changed

+17
-3
lines changed

upload-api/functions/ucan-invocation-router.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,20 @@ export const knownWebDIDs = {
118118
* @param {import('aws-lambda').APIGatewayProxyEventV2} request
119119
*/
120120
export async function ucanInvocationRouter(request) {
121+
try {
122+
// Capture X-Client custom header for analytics
123+
const clientId = Object.entries(request.headers)
124+
.find(([key]) => key.toLowerCase() === 'x-client')?.[1] ?? 'Storacha/?'
125+
console.log(JSON.stringify({
126+
message: 'Client request',
127+
clientId,
128+
requestId: request.requestContext?.requestId || 'unknown',
129+
timestamp: new Date().toISOString()
130+
}))
131+
} catch (error) {
132+
console.error(error)
133+
}
134+
121135
const {
122136
storeTableName,
123137
storeBucketName,
@@ -205,7 +219,7 @@ export async function ucanInvocationRouter(request) {
205219
credentials: {
206220
accessKeyId: carparkBucketAccessKeyId,
207221
secretAccessKey: carparkBucketSecretAccessKey,
208-
},
222+
},
209223
}),
210224
createBlobsStorage(AWS_REGION, storeBucketName),
211225
)
@@ -267,7 +281,7 @@ export async function ucanInvocationRouter(request) {
267281
const storageProviderTable = createStorageProviderTable(AWS_REGION, storageProviderTableName, options)
268282
const routingService = createRoutingService(storageProviderTable, serviceSigner)
269283

270-
284+
271285
let audience // accept invocations addressed to any alias
272286
const proofs = [] // accept attestations issued by any alias
273287
if (UPLOAD_API_ALIAS) {
@@ -380,7 +394,7 @@ export const fromLambdaRequest = (request) => ({
380394
body: Buffer.from(request.body || '', 'base64'),
381395
})
382396

383-
function getLambdaEnv () {
397+
function getLambdaEnv() {
384398
return {
385399
storeTableName: mustGetEnv('STORE_TABLE_NAME'),
386400
storeBucketName: mustGetEnv('STORE_BUCKET_NAME'),

0 commit comments

Comments
 (0)