Skip to content

Commit dac9a61

Browse files
authored
feat: add request tracing and documentation (#495)
* feat: add request tracing and documentation - Introduced Request Tracing feature to track and debug API requests using trace IDs. - Updated documentation in advanced features, client configuration, and troubleshooting sections to include usage examples and details about trace ID formats. - Added support for automatic timestamp (`client-request-unixmsec`) in requests. - Enhanced gRPC client to handle trace IDs in metadata. - Updated tests to validate trace ID functionality in various API calls. Signed-off-by: ryjiang <jiangruiyi@gmail.com> * refactor: improve client request ID handling in metadata - Extracted client request ID logic into a separate function for better readability and maintainability. - Updated the `promisify` and `extractRequestMetadata` functions to utilize the new `getClientRequestId` function. - Removed redundant `Math.floor()` call in `currentTimeMs` function. Signed-off-by: ryjiang <jiangruiyi@gmail.com> --------- Signed-off-by: ryjiang <jiangruiyi@gmail.com>
1 parent 9cf5537 commit dac9a61

File tree

13 files changed

+529
-28
lines changed

13 files changed

+529
-28
lines changed

docs/content/advanced/advanced-features.mdx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,103 @@ const client = new MilvusClient({
124124
});
125125
```
126126

127+
## Request Tracing (TraceID)
128+
129+
Track and trace individual requests using trace IDs for debugging and monitoring purposes.
130+
131+
### Overview
132+
133+
The SDK automatically adds a timestamp (`client-request-unixmsec`) to every request. You can optionally provide a `client_request_id` (or `client-request-id`) to track specific requests across your application.
134+
135+
### Automatic Timestamp
136+
137+
Every request automatically includes a `client-request-unixmsec` timestamp:
138+
139+
```javascript
140+
// This request will have client-request-unixmsec automatically added
141+
await client.createCollection({
142+
collection_name: 'my_collection',
143+
fields: [...],
144+
// Timestamp is added automatically, no configuration needed
145+
});
146+
```
147+
148+
### Using TraceID
149+
150+
Add a trace ID to track specific requests:
151+
152+
```javascript
153+
// Recommended: use client_request_id (JavaScript/TypeScript convention)
154+
const traceId = `trace-${Date.now()}`;
155+
await client.createCollection({
156+
collection_name: 'my_collection',
157+
fields: [...],
158+
client_request_id: traceId,
159+
});
160+
161+
// Alternative: use client-request-id (compatible with pymilvus)
162+
await client.createCollection({
163+
collection_name: 'my_collection',
164+
fields: [...],
165+
'client-request-id': traceId,
166+
});
167+
```
168+
169+
**Note**: If both formats are provided, `client_request_id` takes priority (JavaScript/TypeScript convention).
170+
171+
### Supported Operations
172+
173+
All API methods that extend `GrpcTimeOut` support trace IDs:
174+
175+
```javascript
176+
// Insert with trace ID
177+
await client.insert({
178+
collection_name: 'my_collection',
179+
data: [
180+
{ id: 1, vector: [0.1, 0.2, 0.3] },
181+
],
182+
client_request_id: 'insert-trace-123',
183+
});
184+
185+
// Search with trace ID
186+
await client.search({
187+
collection_name: 'my_collection',
188+
vector: [0.1, 0.2, 0.3],
189+
limit: 10,
190+
client_request_id: 'search-trace-456',
191+
});
192+
193+
// Query with trace ID
194+
await client.query({
195+
collection_name: 'my_collection',
196+
expr: 'id > 100',
197+
client_request_id: 'query-trace-789',
198+
});
199+
200+
// All other operations support trace IDs
201+
await client.createIndex({
202+
collection_name: 'my_collection',
203+
field_name: 'vector',
204+
index_type: 'IVF_FLAT',
205+
client_request_id: 'index-trace-101',
206+
});
207+
```
208+
209+
### Use Cases
210+
211+
1. **Request Tracking**: Track requests across distributed systems
212+
2. **Debugging**: Identify specific requests in logs
213+
3. **Performance Monitoring**: Correlate trace IDs with performance metrics
214+
4. **Error Investigation**: Trace errors back to specific requests
215+
216+
### Implementation Details
217+
218+
- Trace IDs are passed through gRPC metadata
219+
- Both `client_request_id` and `client-request-id` formats are supported
220+
- Trace IDs are automatically extracted from request parameters
221+
- The timestamp `client-request-unixmsec` is automatically added by the interceptor
222+
- No need to modify API method implementations
223+
127224
## Iterator Patterns
128225

129226
### Query Iterator

docs/content/core-concepts/client-configuration.mdx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,34 @@ interface ClientConfig {
286286
}
287287
```
288288

289+
## Request Tracing
290+
291+
All API requests support optional trace IDs for tracking and debugging:
292+
293+
```javascript
294+
// Add trace ID to any request
295+
await client.createCollection({
296+
collection_name: 'my_collection',
297+
fields: [...],
298+
client_request_id: 'trace-id-123', // Optional trace ID
299+
});
300+
301+
// Alternative format (compatible with pymilvus)
302+
await client.createCollection({
303+
collection_name: 'my_collection',
304+
fields: [...],
305+
'client-request-id': 'trace-id-123',
306+
});
307+
```
308+
309+
**Features:**
310+
- Automatic timestamp: Every request includes `client-request-unixmsec`
311+
- Trace ID support: Add `client_request_id` or `client-request-id` to track requests
312+
- Global support: All API methods automatically support trace IDs
313+
- Priority: `client_request_id` takes priority over `client-request-id` (JavaScript convention)
314+
315+
For more details, see [Request Tracing](../advanced/advanced-features#request-tracing-traceid) in Advanced Features.
316+
289317
## Next Steps
290318

291319
- Learn about [Data Types & Schemas](./data-types-schemas)

docs/content/reference/troubleshooting.mdx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,28 @@ const client = new MilvusClient({
255255
});
256256
```
257257

258+
### Use TraceID for Request Tracking
259+
260+
Add trace IDs to track specific requests:
261+
262+
```javascript
263+
// Add trace ID to track requests
264+
await client.createCollection({
265+
collection_name: 'my_collection',
266+
fields: [...],
267+
client_request_id: 'debug-trace-123', // Track this specific request
268+
});
269+
270+
// All operations support trace IDs
271+
await client.insert({
272+
collection_name: 'my_collection',
273+
data: [...],
274+
client_request_id: 'insert-trace-456',
275+
});
276+
```
277+
278+
This helps correlate logs and errors with specific requests. See [Request Tracing](../advanced/advanced-features#request-tracing-traceid) for more details.
279+
258280
### Check Collection Schema
259281

260282
```javascript

milvus/const/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ export enum METADATA {
22
DATABASE = 'dbname',
33
AUTH = 'authorization',
44
CLIENT_ID = 'identifier',
5+
CLIENT_REQUEST_ID = 'client-request-id',
6+
CLIENT_REQUEST_UNIXMSEC = 'client-request-unixmsec',
57
}
68

79
export enum CONNECT_STATUS {

milvus/grpc/Collection.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ export class Collection extends Database {
201201
}
202202

203203
// Call the promisify function to create the collection.
204+
// traceid will be automatically extracted from data by promisify
204205
const createPromise = await promisify(
205206
this.channelPool,
206207
'CreateCollection',

milvus/grpc/GrpcClient.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
getRetryInterceptor,
1919
getMetaInterceptor,
2020
getTraceInterceptor,
21+
getRequestMetadataInterceptor,
2122
ErrorCode,
2223
DEFAULT_DB,
2324
METADATA,
@@ -100,6 +101,9 @@ export class GRPCClient extends User {
100101
// interceptors
101102
const interceptors = [metaInterceptor];
102103

104+
// add request metadata interceptor (adds client-request-unixmsec)
105+
interceptors.push(getRequestMetadataInterceptor());
106+
103107
// add trace if necessary
104108
if (this.config.trace) {
105109
// add trace interceptor

milvus/types/Common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export interface StringArrayId {
3232
}
3333
export interface GrpcTimeOut {
3434
timeout?: number;
35+
client_request_id?: string; // optional, trace id for request tracking
36+
'client-request-id'?: string; // optional, trace id for request tracking (alternative format)
3537
}
3638
export type PrivilegesTypes =
3739
| CollectionPrivileges

milvus/utils/Function.ts

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,77 @@
11
import {
22
KeyValuePair,
3-
DataType,
4-
ERROR_REASONS,
53
FieldSchema,
64
DataTypeStringEnum,
75
DEFAULT_MIN_INT64,
86
SparseFloatVector,
97
FieldData,
10-
FieldType,
8+
METADATA,
119
} from '../';
1210
import { Pool } from 'generic-pool';
11+
import { Metadata } from '@grpc/grpc-js';
1312

1413
/**
15-
* Promisify a function call with optional timeout
16-
* @param obj - The object containing the target function
14+
* Promisify a function call with optional timeout and metadata
15+
* @param pool - The pool of gRPC clients
1716
* @param target - The name of the target function to call
18-
* @param params - The parameters to pass to the target function
17+
* @param params - The parameters to pass to the target function (may contain client_request_id or client-request-id)
1918
* @param timeout - Optional timeout in milliseconds
19+
* @param requestMetadata - Optional metadata to include in the request (e.g., client-request-id). If not provided, will be extracted from params automatically.
2020
* @returns A Promise that resolves with the result of the target function call
2121
*/
2222
export async function promisify(
2323
pool: Pool<any>,
2424
target: string,
2525
params: any,
26-
timeout: number
26+
timeout: number,
27+
requestMetadata?: { 'client-request-id'?: string; client_request_id?: string }
2728
): Promise<any> {
2829
// Calculate the deadline for the function call
2930
const t = timeout === 0 ? 1000 * 60 * 60 * 24 : timeout;
3031

3132
// get client
3233
const client = await pool.acquire();
3334

35+
// Extract traceid from params if requestMetadata is not explicitly provided
36+
let finalRequestMetadata = requestMetadata;
37+
if (!finalRequestMetadata && params) {
38+
finalRequestMetadata = extractRequestMetadata(params);
39+
}
40+
41+
// Create metadata object if traceid is found
42+
const metadata = finalRequestMetadata ? new Metadata() : undefined;
43+
44+
if (metadata && finalRequestMetadata) {
45+
// Support both client_request_id and client-request-id (for compatibility)
46+
// Priority: client_request_id > client-request-id (JavaScript/TypeScript convention)
47+
const clientRequestId = getClientRequestId(finalRequestMetadata);
48+
if (clientRequestId) {
49+
// Convert to string to prevent runtime errors if non-string value is passed
50+
metadata.add(METADATA.CLIENT_REQUEST_ID, String(clientRequestId));
51+
}
52+
}
53+
3454
// Create a new Promise that wraps the target function call
3555
return new Promise((resolve, reject) => {
3656
try {
37-
// Call the target function with the provided parameters and deadline
38-
client[target](
39-
params,
40-
{ deadline: new Date(Date.now() + t) },
41-
(err: any, result: any) => {
42-
if (err) {
43-
// If there was an error, reject the Promise with the error
44-
reject(err);
45-
} else {
46-
// Otherwise, resolve the Promise with the result
47-
resolve(result);
48-
}
49-
if (client) {
50-
pool.release(client);
51-
}
57+
// Call the target function with the provided parameters, deadline, and metadata
58+
const callOptions: any = { deadline: new Date(Date.now() + t) };
59+
if (metadata) {
60+
callOptions.metadata = metadata;
61+
}
62+
63+
client[target](params, callOptions, (err: any, result: any) => {
64+
if (err) {
65+
// If there was an error, reject the Promise with the error
66+
reject(err);
67+
} else {
68+
// Otherwise, resolve the Promise with the result
69+
resolve(result);
5270
}
53-
);
71+
if (client) {
72+
pool.release(client);
73+
}
74+
});
5475
} catch (e: any) {
5576
reject(e);
5677
if (client) {
@@ -139,3 +160,40 @@ export const getValidDataArray = (data: FieldData[], length: number) => {
139160
return data[i] !== undefined && data[i] !== null;
140161
});
141162
};
163+
164+
/**
165+
* Extracts client request ID from metadata object with priority handling.
166+
* Priority: client_request_id > client-request-id (JavaScript/TypeScript convention)
167+
* @param metadata - Metadata object that may contain traceid
168+
* @returns Client request ID as string or undefined if not found
169+
*/
170+
const getClientRequestId = (metadata?: {
171+
'client-request-id'?: string;
172+
client_request_id?: string;
173+
}): string | undefined => {
174+
if (!metadata) {
175+
return undefined;
176+
}
177+
// Priority: client_request_id > client-request-id (JavaScript/TypeScript convention)
178+
return metadata.client_request_id || metadata['client-request-id'];
179+
};
180+
181+
/**
182+
* Extracts request metadata (traceid) from request data.
183+
* Supports both client_request_id and client-request-id formats.
184+
* Priority: client_request_id > client-request-id (JavaScript/TypeScript convention)
185+
* @param data - Request data that may contain traceid
186+
* @returns Request metadata object or undefined if no traceid provided
187+
*/
188+
export const extractRequestMetadata = (
189+
data: any
190+
):
191+
| {
192+
'client-request-id': string;
193+
}
194+
| undefined => {
195+
const clientRequestId = getClientRequestId(data);
196+
return clientRequestId
197+
? { 'client-request-id': String(clientRequestId) }
198+
: undefined;
199+
};

milvus/utils/Grpc.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface Carrier {
1919
traceparent?: string;
2020
tracestate?: string;
2121
}
22-
import { DEFAULT_DB } from '../const';
22+
import { DEFAULT_DB, METADATA } from '../const';
2323
import sdkInfo from '../../sdk.json';
2424
import milvusProtoJson from '../proto-json/milvus';
2525
import { INamespace } from 'protobufjs';
@@ -256,6 +256,36 @@ export const getRetryInterceptor = ({
256256
return new InterceptingCall(nextCall(options), requester);
257257
};
258258

259+
/**
260+
* Returns current time in milliseconds as a string.
261+
* @returns Current time in milliseconds as a string.
262+
*/
263+
const currentTimeMs = (): string => {
264+
// Date.now() already returns an integer, so Math.floor() is redundant
265+
return String(Date.now());
266+
};
267+
268+
/**
269+
* Returns a gRPC interceptor function that adds request-level metadata to outgoing requests.
270+
* This interceptor automatically adds client-request-unixmsec timestamp to every request.
271+
* The client-request-id should be passed via promisify's requestMetadata parameter.
272+
*/
273+
export const getRequestMetadataInterceptor = () => {
274+
return function (options: any, nextCall: any) {
275+
// Create a new InterceptingCall object with nextCall(options) as its first parameter.
276+
return new InterceptingCall(nextCall(options), {
277+
// Define the start method of the InterceptingCall object.
278+
start: function (metadata, listener, next) {
279+
// Always add client-request-unixmsec timestamp
280+
metadata.add(METADATA.CLIENT_REQUEST_UNIXMSEC, currentTimeMs());
281+
282+
// Call next(metadata, listener) to continue the call with the modified metadata.
283+
next(metadata, listener);
284+
},
285+
});
286+
};
287+
};
288+
259289
/**
260290
* Returns a gRPC interceptor function that adds trace context to outgoing requests.
261291
*/

0 commit comments

Comments
 (0)