Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit83fa3b4

Browse files
committed
Reduce computation by size estimating on raw JSON requests
1 parent4d93456 commit83fa3b4

File tree

7 files changed

+167
-97
lines changed

7 files changed

+167
-97
lines changed

‎src/api/coderApi.ts‎

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import{typeAxiosInstance}from"axios";
1+
import{
2+
typeAxiosResponseHeaders,
3+
typeAxiosInstance,
4+
typeAxiosHeaders,
5+
typeAxiosResponseTransformer,
6+
}from"axios";
27
import{Api}from"coder/site/src/api/api";
38
import{
49
typeGetInboxNotificationResponse,
@@ -23,6 +28,7 @@ import {
2328
typeRequestConfigWithMeta,
2429
HttpClientLogLevel,
2530
}from"../logging/types";
31+
import{serializeValue,sizeOf}from"../logging/utils";
2632
import{WsLogger}from"../logging/wsLogger";
2733
import{
2834
OneWayWebSocket,
@@ -207,7 +213,24 @@ function addLoggingInterceptors(client: AxiosInstance, logger: Logger) {
207213
(config)=>{
208214
constconfigWithMeta=configasRequestConfigWithMeta;
209215
configWithMeta.metadata=createRequestMeta();
210-
logRequest(logger,configWithMeta,getLogLevel());
216+
217+
config.transformRequest=[
218+
...wrapRequestTransform(
219+
config.transformRequest||client.defaults.transformRequest||[],
220+
configWithMeta,
221+
),
222+
(data)=>{
223+
// Log after setting the raw request size
224+
logRequest(logger,configWithMeta,getLogLevel());
225+
returndata;
226+
},
227+
];
228+
229+
config.transformResponse=wrapResponseTransform(
230+
config.transformResponse||client.defaults.transformResponse||[],
231+
configWithMeta,
232+
);
233+
211234
returnconfig;
212235
},
213236
(error:unknown)=>{
@@ -228,6 +251,66 @@ function addLoggingInterceptors(client: AxiosInstance, logger: Logger) {
228251
);
229252
}
230253

254+
functionwrapRequestTransform(
255+
transformer:AxiosResponseTransformer|AxiosResponseTransformer[],
256+
config:RequestConfigWithMeta,
257+
):AxiosResponseTransformer[]{
258+
return[
259+
(data:unknown,headers:AxiosHeaders)=>{
260+
consttransformerArray=Array.isArray(transformer)
261+
?transformer
262+
:[transformer];
263+
264+
// Transform the request first then estimate the size
265+
constresult=transformerArray.reduce(
266+
(d,fn)=>fn.call(config,d,headers),
267+
data,
268+
);
269+
270+
config.rawRequestSize=getSize(config.headers,result);
271+
272+
returnresult;
273+
},
274+
];
275+
}
276+
277+
functionwrapResponseTransform(
278+
transformer:AxiosResponseTransformer|AxiosResponseTransformer[],
279+
config:RequestConfigWithMeta,
280+
):AxiosResponseTransformer[]{
281+
return[
282+
(data:unknown,headers:AxiosResponseHeaders,status?:number)=>{
283+
// estimate the size before transforming the response
284+
config.rawResponseSize=getSize(headers,data);
285+
286+
consttransformerArray=Array.isArray(transformer)
287+
?transformer
288+
:[transformer];
289+
290+
returntransformerArray.reduce(
291+
(d,fn)=>fn.call(config,d,headers,status),
292+
data,
293+
);
294+
},
295+
];
296+
}
297+
298+
functiongetSize(headers:AxiosHeaders,data:unknown):number|undefined{
299+
constcontentLength=headers["content-length"];
300+
if(contentLength!==undefined){
301+
returnparseInt(contentLength,10);
302+
}
303+
304+
constsize=sizeOf(data);
305+
if(size!==undefined){
306+
returnsize;
307+
}
308+
309+
// Fallback
310+
conststringified=serializeValue(data);
311+
returnstringified===null ?undefined :Buffer.byteLength(stringified);
312+
}
313+
231314
functiongetLogLevel():HttpClientLogLevel{
232315
constlogLevelStr=vscode.workspace
233316
.getConfiguration()

‎src/logging/formatters.ts‎

Lines changed: 5 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
importutilfrom"node:util";
21
importprettyBytesfrom"pretty-bytes";
32

4-
import{sizeOf}from"./utils";
3+
import{serializeValue}from"./utils";
54

65
importtype{AxiosRequestConfig}from"axios";
76

@@ -21,38 +20,11 @@ export function formatTime(ms: number): string {
2120
}
2221

2322
exportfunctionformatMethod(method:string|undefined):string{
24-
return(method ?method :"GET").toUpperCase();
23+
returnmethod?.toUpperCase()||"GET";
2524
}
2625

27-
/**
28-
* Formats content-length for display. Returns the header value if available,
29-
* otherwise estimates size by serializing the data body (prefixed with ~).
30-
*/
31-
exportfunctionformatContentLength(
32-
headers:Record<string,unknown>,
33-
data:unknown,
34-
):string{
35-
constlen=headers["content-length"];
36-
if(len&&typeoflen==="string"){
37-
constbytes=parseInt(len,10);
38-
returnisNaN(bytes) ?"(? B)" :`(${prettyBytes(bytes)})`;
39-
}
40-
41-
// Estimate from data if no header
42-
constsize=sizeOf(data);
43-
if(size!==undefined){
44-
return`(${prettyBytes(size)})`;
45-
}
46-
47-
if(typeofdata==="object"){
48-
conststringified=safeStringify(data);
49-
if(stringified!==null){
50-
constbytes=Buffer.byteLength(stringified,"utf8");
51-
return`(~${prettyBytes(bytes)})`;
52-
}
53-
}
54-
55-
return"(? B)";
26+
exportfunctionformatSize(size:number|undefined):string{
27+
returnsize===undefined ?"(? B)" :`(${prettyBytes(size)})`;
5628
}
5729

5830
exportfunctionformatUri(config:AxiosRequestConfig|undefined):string{
@@ -75,25 +47,8 @@ export function formatHeaders(headers: Record<string, unknown>): string {
7547

7648
exportfunctionformatBody(body:unknown):string{
7749
if(body){
78-
returnsafeStringify(body)??"<invalid body>";
50+
returnserializeValue(body)??"<invalid body>";
7951
}else{
8052
return"<no body>";
8153
}
8254
}
83-
84-
functionsafeStringify(data:unknown):string|null{
85-
try{
86-
returnutil.inspect(data,{
87-
showHidden:false,
88-
depth:Infinity,
89-
maxArrayLength:Infinity,
90-
maxStringLength:Infinity,
91-
breakLength:Infinity,
92-
compact:true,
93-
getters:false,// avoid side-effects
94-
});
95-
}catch{
96-
// Should rarely happen but just in case
97-
returnnull;
98-
}
99-
}

‎src/logging/httpLogger.ts‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { getErrorDetail } from "../error";
55

66
import{
77
formatBody,
8-
formatContentLength,
98
formatHeaders,
109
formatMethod,
10+
formatSize,
1111
formatTime,
1212
formatUri,
1313
}from"./formatters";
@@ -42,11 +42,10 @@ export function logRequest(
4242
return;
4343
}
4444

45-
const{ requestId, method, url}=parseConfig(config);
46-
constlen=formatContentLength(config.headers,config.data);
45+
const{ requestId, method, url, requestSize}=parseConfig(config);
4746

4847
constmsg=[
49-
`→${shortId(requestId)}${method}${url}${len}`,
48+
`→${shortId(requestId)}${method}${url}${requestSize}`,
5049
...buildExtraLogs(config.headers,config.data,logLevel),
5150
];
5251
logger.trace(msg.join("\n"));
@@ -64,11 +63,12 @@ export function logResponse(
6463
return;
6564
}
6665

67-
const{ requestId, method, url, time}=parseConfig(response.config);
68-
constlen=formatContentLength(response.headers,response.data);
66+
const{ requestId, method, url, time, responseSize}=parseConfig(
67+
response.config,
68+
);
6969

7070
constmsg=[
71-
`←${shortId(requestId)}${response.status}${method}${url}${len}${time}`,
71+
`←${shortId(requestId)}${response.status}${method}${url}${responseSize}${time}`,
7272
...buildExtraLogs(response.headers,response.data,logLevel),
7373
];
7474
logger.trace(msg.join("\n"));
@@ -150,12 +150,16 @@ function parseConfig(config: RequestConfigWithMeta | undefined): {
150150
method:string;
151151
url:string;
152152
time:string;
153+
requestSize:string;
154+
responseSize:string;
153155
}{
154156
constmeta=config?.metadata;
155157
return{
156158
requestId:meta?.requestId||"unknown",
157159
method:formatMethod(config?.method),
158160
url:formatUri(config),
159161
time:meta ?formatTime(Date.now()-meta.startedAt) :"?ms",
162+
requestSize:formatSize(config?.rawRequestSize),
163+
responseSize:formatSize(config?.rawResponseSize),
160164
};
161165
}

‎src/logging/types.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ export interface RequestMeta {
1414

1515
exporttypeRequestConfigWithMeta=InternalAxiosRequestConfig&{
1616
metadata?:RequestMeta;
17+
rawRequestSize?:number;
18+
rawResponseSize?:number;
1719
};

‎src/logging/utils.ts‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import{Buffer}from"node:buffer";
22
importcryptofrom"node:crypto";
3+
importutilfrom"node:util";
34

45
exportfunctionshortId(id:string):string{
56
returnid.slice(0,8);
67
}
78

9+
exportfunctioncreateRequestId():string{
10+
returncrypto.randomUUID().replace(/-/g,"");
11+
}
12+
813
/**
914
* Returns the byte size of the data if it can be determined from the data's intrinsic properties,
1015
* otherwise returns undefined (e.g., for plain objects and arrays that would require serialization).
@@ -13,7 +18,10 @@ export function sizeOf(data: unknown): number | undefined {
1318
if(data===null||data===undefined){
1419
return0;
1520
}
16-
if(typeofdata==="number"||typeofdata==="boolean"){
21+
if(typeofdata==="boolean"){
22+
return4;
23+
}
24+
if(typeofdata==="number"){
1725
return8;
1826
}
1927
if(typeofdata==="string"||typeofdata==="bigint"){
@@ -36,6 +44,19 @@ export function sizeOf(data: unknown): number | undefined {
3644
returnundefined;
3745
}
3846

39-
exportfunctioncreateRequestId():string{
40-
returncrypto.randomUUID().replace(/-/g,"");
47+
exportfunctionserializeValue(data:unknown):string|null{
48+
try{
49+
returnutil.inspect(data,{
50+
showHidden:false,
51+
depth:Infinity,
52+
maxArrayLength:Infinity,
53+
maxStringLength:Infinity,
54+
breakLength:Infinity,
55+
compact:true,
56+
getters:false,// avoid side-effects
57+
});
58+
}catch{
59+
// Should rarely happen but just in case
60+
returnnull;
61+
}
4162
}

‎test/unit/logging/formatters.test.ts‎

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { describe, expect, it } from "vitest";
22

33
import{
44
formatBody,
5-
formatContentLength,
65
formatHeaders,
76
formatMethod,
7+
formatSize,
88
formatTime,
99
formatUri,
1010
}from"@/logging/formatters";
@@ -34,40 +34,14 @@ describe("Logging formatters", () => {
3434
});
3535
});
3636

37-
describe("formatContentLength",()=>{
38-
it("uses content-length header when available",()=>{
39-
constresult=formatContentLength({"content-length":"1024"},null);
40-
expect(result).toContain("1.02 kB");
37+
describe("formatSize",()=>{
38+
it("formats byte sizes using pretty-bytes",()=>{
39+
expect(formatSize(1024)).toContain("1.02 kB");
40+
expect(formatSize(0)).toBe("(0 B)");
4141
});
4242

43-
it("handles invalid content-length header",()=>{
44-
constresult=formatContentLength({"content-length":"invalid"},null);
45-
expect(result).toContain("?");
46-
});
47-
48-
it.each([
49-
["hello",5],
50-
[Buffer.from("test"),4],
51-
[123,8],
52-
[false,8],
53-
[BigInt(1234),4],
54-
[null,0],
55-
[undefined,0],
56-
])("calculates size for %s",(data:unknown,bytes:number)=>{
57-
constresult=formatContentLength({},data);
58-
expect(result).toContain(`${bytes} B`);
59-
});
60-
61-
it("estimates size for objects",()=>{
62-
constresult=formatContentLength({},{foo:"bar"});
63-
expect(result).toMatch(/~\d+/);
64-
});
65-
66-
it("handles circular references safely",()=>{
67-
constcircular:Record<string,unknown>={a:1};
68-
circular.self=circular;
69-
constresult=formatContentLength({},circular);
70-
expect(result).toMatch(/~\d+/);
43+
it("returns placeholder for undefined",()=>{
44+
expect(formatSize(undefined)).toBe("(? B)");
7145
});
7246
});
7347

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp