Skip to content

Commit 020437c

Browse files
[core.logging] Fix calculating payload bytes for arrays and zlib streams. (#92100) (#93284)
Co-authored-by: Luke Elmers <luke.elmers@elastic.co>
1 parent 26fa8d4 commit 020437c

File tree

4 files changed

+208
-85
lines changed

4 files changed

+208
-85
lines changed

packages/kbn-legacy-logging/src/utils/get_payload_size.test.ts

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
* Side Public License, v 1.
77
*/
88

9-
import { createGunzip } from 'zlib';
109
import mockFs from 'mock-fs';
1110
import { createReadStream } from 'fs';
11+
import { PassThrough } from 'stream';
12+
import { createGzip, createGunzip } from 'zlib';
1213

1314
import { getResponsePayloadBytes } from './get_payload_size';
1415

@@ -27,38 +28,74 @@ describe('getPayloadSize', () => {
2728
});
2829
});
2930

30-
describe('handles fs streams', () => {
31+
describe('handles streams', () => {
3132
afterEach(() => mockFs.restore());
3233

33-
test('with ascii characters', async () => {
34-
mockFs({ 'test.txt': 'heya' });
35-
const readStream = createReadStream('test.txt');
36-
37-
let data = '';
38-
for await (const chunk of readStream) {
39-
data += chunk;
40-
}
41-
42-
const result = getResponsePayloadBytes(readStream);
43-
expect(result).toBe(Buffer.byteLength(data));
44-
});
45-
46-
test('with special characters', async () => {
47-
mockFs({ 'test.txt': '¡hola!' });
48-
const readStream = createReadStream('test.txt');
49-
50-
let data = '';
51-
for await (const chunk of readStream) {
52-
data += chunk;
53-
}
54-
55-
const result = getResponsePayloadBytes(readStream);
56-
expect(result).toBe(Buffer.byteLength(data));
34+
test('ignores streams that are not fs or zlib streams', async () => {
35+
const result = getResponsePayloadBytes(new PassThrough());
36+
expect(result).toBe(undefined);
5737
});
5838

59-
test('ignores streams that are not instances of ReadStream', async () => {
60-
const result = getResponsePayloadBytes(createGunzip());
61-
expect(result).toBe(undefined);
39+
describe('fs streams', () => {
40+
test('with ascii characters', async () => {
41+
mockFs({ 'test.txt': 'heya' });
42+
const readStream = createReadStream('test.txt');
43+
44+
let data = '';
45+
for await (const chunk of readStream) {
46+
data += chunk;
47+
}
48+
49+
const result = getResponsePayloadBytes(readStream);
50+
expect(result).toBe(Buffer.byteLength(data));
51+
});
52+
53+
test('with special characters', async () => {
54+
mockFs({ 'test.txt': '¡hola!' });
55+
const readStream = createReadStream('test.txt');
56+
57+
let data = '';
58+
for await (const chunk of readStream) {
59+
data += chunk;
60+
}
61+
62+
const result = getResponsePayloadBytes(readStream);
63+
expect(result).toBe(Buffer.byteLength(data));
64+
});
65+
66+
describe('zlib streams', () => {
67+
test('with ascii characters', async () => {
68+
mockFs({ 'test.txt': 'heya' });
69+
const readStream = createReadStream('test.txt');
70+
const source = readStream.pipe(createGzip()).pipe(createGunzip());
71+
72+
let data = '';
73+
for await (const chunk of source) {
74+
data += chunk;
75+
}
76+
77+
const result = getResponsePayloadBytes(source);
78+
79+
expect(data).toBe('heya');
80+
expect(result).toBe(source.bytesWritten);
81+
});
82+
83+
test('with special characters', async () => {
84+
mockFs({ 'test.txt': '¡hola!' });
85+
const readStream = createReadStream('test.txt');
86+
const source = readStream.pipe(createGzip()).pipe(createGunzip());
87+
88+
let data = '';
89+
for await (const chunk of source) {
90+
data += chunk;
91+
}
92+
93+
const result = getResponsePayloadBytes(source);
94+
95+
expect(data).toBe('¡hola!');
96+
expect(result).toBe(source.bytesWritten);
97+
});
98+
});
6299
});
63100
});
64101

@@ -79,8 +116,17 @@ describe('getPayloadSize', () => {
79116
expect(result).toBe(JSON.stringify(payload).length);
80117
});
81118

119+
test('when source is array object', () => {
120+
const payload = [{ message: 'hey' }, { message: 'ya' }];
121+
const result = getResponsePayloadBytes(payload);
122+
expect(result).toBe(JSON.stringify(payload).length);
123+
});
124+
82125
test('returns undefined when source is not plain object', () => {
83-
const result = getResponsePayloadBytes([1, 2, 3]);
126+
class TestClass {
127+
constructor() {}
128+
}
129+
const result = getResponsePayloadBytes(new TestClass());
84130
expect(result).toBe(undefined);
85131
});
86132
});

packages/kbn-legacy-logging/src/utils/get_payload_size.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@
88

99
import { isPlainObject } from 'lodash';
1010
import { ReadStream } from 'fs';
11+
import { Zlib } from 'zlib';
1112
import type { ResponseObject } from '@hapi/hapi';
1213

1314
const isBuffer = (obj: unknown): obj is Buffer => Buffer.isBuffer(obj);
1415
const isFsReadStream = (obj: unknown): obj is ReadStream =>
1516
typeof obj === 'object' && obj !== null && 'bytesRead' in obj && obj instanceof ReadStream;
17+
const isZlibStream = (obj: unknown): obj is Zlib => {
18+
return typeof obj === 'object' && obj !== null && 'bytesWritten' in obj;
19+
};
1620
const isString = (obj: unknown): obj is string => typeof obj === 'string';
1721

1822
/**
@@ -51,11 +55,15 @@ export function getResponsePayloadBytes(
5155
return payload.bytesRead;
5256
}
5357

58+
if (isZlibStream(payload)) {
59+
return payload.bytesWritten;
60+
}
61+
5462
if (isString(payload)) {
5563
return Buffer.byteLength(payload);
5664
}
5765

58-
if (isPlainObject(payload)) {
66+
if (isPlainObject(payload) || Array.isArray(payload)) {
5967
return Buffer.byteLength(JSON.stringify(payload));
6068
}
6169

src/core/server/http/logging/get_payload_size.test.ts

Lines changed: 103 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
* Side Public License, v 1.
77
*/
88

9-
import { createGunzip } from 'zlib';
109
import type { Request } from '@hapi/hapi';
1110
import Boom from '@hapi/boom';
1211

1312
import mockFs from 'mock-fs';
1413
import { createReadStream } from 'fs';
14+
import { PassThrough } from 'stream';
15+
import { createGunzip, createGzip } from 'zlib';
1516

1617
import { loggerMock, MockedLogger } from '../../logging/logger.mock';
1718

@@ -55,59 +56,107 @@ describe('getPayloadSize', () => {
5556
});
5657
});
5758

58-
describe('handles fs streams', () => {
59+
describe('handles streams', () => {
5960
afterEach(() => mockFs.restore());
6061

61-
test('with ascii characters', async () => {
62-
mockFs({ 'test.txt': 'heya' });
63-
const source = createReadStream('test.txt');
64-
65-
let data = '';
66-
for await (const chunk of source) {
67-
data += chunk;
68-
}
69-
62+
test('ignores streams that are not fs or zlib streams', async () => {
7063
const result = getResponsePayloadBytes(
7164
{
7265
variety: 'stream',
73-
source,
66+
source: new PassThrough(),
7467
} as Response,
7568
logger
7669
);
7770

78-
expect(result).toBe(Buffer.byteLength(data));
71+
expect(result).toBe(undefined);
7972
});
8073

81-
test('with special characters', async () => {
82-
mockFs({ 'test.txt': '¡hola!' });
83-
const source = createReadStream('test.txt');
74+
describe('fs streams', () => {
75+
test('with ascii characters', async () => {
76+
mockFs({ 'test.txt': 'heya' });
77+
const source = createReadStream('test.txt');
8478

85-
let data = '';
86-
for await (const chunk of source) {
87-
data += chunk;
88-
}
79+
let data = '';
80+
for await (const chunk of source) {
81+
data += chunk;
82+
}
8983

90-
const result = getResponsePayloadBytes(
91-
{
92-
variety: 'stream',
93-
source,
94-
} as Response,
95-
logger
96-
);
84+
const result = getResponsePayloadBytes(
85+
{
86+
variety: 'stream',
87+
source,
88+
} as Response,
89+
logger
90+
);
91+
92+
expect(result).toBe(Buffer.byteLength(data));
93+
});
94+
95+
test('with special characters', async () => {
96+
mockFs({ 'test.txt': '¡hola!' });
97+
const source = createReadStream('test.txt');
98+
99+
let data = '';
100+
for await (const chunk of source) {
101+
data += chunk;
102+
}
103+
104+
const result = getResponsePayloadBytes(
105+
{
106+
variety: 'stream',
107+
source,
108+
} as Response,
109+
logger
110+
);
97111

98-
expect(result).toBe(Buffer.byteLength(data));
112+
expect(result).toBe(Buffer.byteLength(data));
113+
});
99114
});
100115

101-
test('ignores streams that are not instances of ReadStream', async () => {
102-
const result = getResponsePayloadBytes(
103-
{
104-
variety: 'stream',
105-
source: createGunzip(),
106-
} as Response,
107-
logger
108-
);
116+
describe('zlib streams', () => {
117+
test('with ascii characters', async () => {
118+
mockFs({ 'test.txt': 'heya' });
119+
const readStream = createReadStream('test.txt');
120+
const source = readStream.pipe(createGzip()).pipe(createGunzip());
109121

110-
expect(result).toBe(undefined);
122+
let data = '';
123+
for await (const chunk of source) {
124+
data += chunk;
125+
}
126+
127+
const result = getResponsePayloadBytes(
128+
{
129+
variety: 'stream',
130+
source,
131+
} as Response,
132+
logger
133+
);
134+
135+
expect(data).toBe('heya');
136+
expect(result).toBe(source.bytesWritten);
137+
});
138+
139+
test('with special characters', async () => {
140+
mockFs({ 'test.txt': '¡hola!' });
141+
const readStream = createReadStream('test.txt');
142+
const source = readStream.pipe(createGzip()).pipe(createGunzip());
143+
144+
let data = '';
145+
for await (const chunk of source) {
146+
data += chunk;
147+
}
148+
149+
const result = getResponsePayloadBytes(
150+
{
151+
variety: 'stream',
152+
source,
153+
} as Response,
154+
logger
155+
);
156+
157+
expect(data).toBe('¡hola!');
158+
expect(result).toBe(source.bytesWritten);
159+
});
111160
});
112161
});
113162

@@ -134,7 +183,7 @@ describe('getPayloadSize', () => {
134183
expect(result).toBe(7);
135184
});
136185

137-
test('when source is object', () => {
186+
test('when source is plain object', () => {
138187
const payload = { message: 'heya' };
139188
const result = getResponsePayloadBytes(
140189
{
@@ -146,11 +195,26 @@ describe('getPayloadSize', () => {
146195
expect(result).toBe(JSON.stringify(payload).length);
147196
});
148197

198+
test('when source is array object', () => {
199+
const payload = [{ message: 'hey' }, { message: 'ya' }];
200+
const result = getResponsePayloadBytes(
201+
{
202+
variety: 'plain',
203+
source: payload,
204+
} as Response,
205+
logger
206+
);
207+
expect(result).toBe(JSON.stringify(payload).length);
208+
});
209+
149210
test('returns undefined when source is not a plain object', () => {
211+
class TestClass {
212+
constructor() {}
213+
}
150214
const result = getResponsePayloadBytes(
151215
{
152216
variety: 'plain',
153-
source: [1, 2, 3],
217+
source: new TestClass(),
154218
} as Response,
155219
logger
156220
);

0 commit comments

Comments
 (0)