Skip to content

Commit

Permalink
test(transform): split transform and adding a basic test
Browse files Browse the repository at this point in the history
  • Loading branch information
fmauNeko committed Sep 6, 2024
1 parent eaa6cf3 commit 09b322f
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 42 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepare": "husky",
"test": "npm run test:functional && npm run test:types",
"test:functional": "jest --runInBand",
"test": "npm run test:unit && npm run test:types",
"test:unit": "jest --runInBand",
"test:types": "tsc -p tsconfig.dist.json --noEmit"
},
"keywords": [],
Expand Down
45 changes: 6 additions & 39 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,18 @@
import { ecsFormat } from '@elastic/ecs-pino-format';
import { pipeline, Transform } from 'node:stream';
import { pipeline } from 'node:stream';
import build from 'pino-abstract-transport';
import { deserializeError } from 'serialize-error';
import { getTransform } from './transform';
import { PinoTransportEcsOptions } from './types';

export default async function pinoTransportEcs(options: PinoTransportEcsOptions) {
export default async function pinoTransportEcs(options?: PinoTransportEcsOptions) {
const pinoConfigEcs = ecsFormat(options);

return build(
(source) => {
const myTransportStream = new Transform({
autoDestroy: true,
objectMode: true,
transform(line, enc, cb) {
if (options.additionalBindings) {
line = { ...line, ...options.additionalBindings };
}
const transformStream = getTransform(source, pinoConfigEcs, options);

if (pinoConfigEcs.messageKey && source.messageKey !== pinoConfigEcs.messageKey) {
line = { ...line, [pinoConfigEcs.messageKey]: line[source.messageKey] };
delete line[source.messageKey];
}

line['@timestamp'] = new Date(line.time).toISOString();
delete line.time;

line = {
...line,
...pinoConfigEcs.formatters!.level!(source.levels.labels[line.level], line.level),
};

delete line.level;

if (line.err) {
line.err = deserializeError(line.err);
}

line = pinoConfigEcs.formatters!.bindings!(line);
line = pinoConfigEcs.formatters!.log!(line);

this.push(`${JSON.stringify(line)}\n`);
cb();
},
});

pipeline(source, myTransportStream, () => {});
return myTransportStream;
pipeline(source, transformStream, () => {});
return transformStream;
},
{
enablePipelining: true,
Expand Down
45 changes: 45 additions & 0 deletions src/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Transform } from 'node:stream';
import { LoggerOptions } from 'pino';
import build, { type PinoConfig } from 'pino-abstract-transport';
import { deserializeError } from 'serialize-error';
import { PinoTransportEcsOptions } from './types';

export const getTransform = (
source: Transform & build.OnUnknown & PinoConfig,
pinoConfigEcs: LoggerOptions,
options?: PinoTransportEcsOptions,
) =>
new Transform({
autoDestroy: true,
objectMode: true,
transform(line, enc, cb) {
if (options?.additionalBindings) {
line = { ...line, ...options.additionalBindings };
}

if (pinoConfigEcs.messageKey && source.messageKey !== pinoConfigEcs.messageKey) {
line = { ...line, [pinoConfigEcs.messageKey]: line[source.messageKey] };
delete line[source.messageKey];
}

line['@timestamp'] = new Date(line.time).toISOString();
delete line.time;

line = {
...line,
...pinoConfigEcs.formatters!.level!(source.levels.labels[line.level], line.level),
};

delete line.level;

if (line.err) {
line.err = deserializeError(line.err);
}

line = pinoConfigEcs.formatters!.bindings!(line);
line = pinoConfigEcs.formatters!.log!(line);

this.push(`${JSON.stringify(line)}\n`);
cb();
},
});
2 changes: 1 addition & 1 deletion src/types/overrides.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ declare module 'pino-abstract-transport' {
expectPinoConfig?: boolean;
};

type PinoConfig = {
export type PinoConfig = {
errorKey: string;
messageKey: string;
levels: {
Expand Down
156 changes: 156 additions & 0 deletions tests/fixtures/testLogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
export const testLogs: Record<string, any>[] = [
{
level: 30,
time: 1725632753065,
pid: 492854,
hostname: 'izanami',
msg: 'hello world',
},
{
level: 50,
time: 1725632753065,
pid: 492854,
hostname: 'izanami',
msg: 'this is at error level',
},
{
level: 30,
time: 1725632753065,
pid: 492854,
hostname: 'izanami',
msg: 'the answer is 42',
},
{
level: 30,
time: 1725632753065,
pid: 492854,
hostname: 'izanami',
obj: 42,
msg: 'hello world',
},
{
level: 30,
time: 1725632753065,
pid: 492854,
hostname: 'izanami',
obj: 42,
b: 2,
msg: 'hello world',
},
{
level: 30,
time: 1725632753065,
pid: 492854,
hostname: 'izanami',
nested: {
obj: 42,
},
msg: 'nested',
},
{
level: 40,
time: 1725632753065,
pid: 492854,
hostname: 'izanami',
msg: 'WARNING!',
},
{
level: 50,
time: 1725632753065,
pid: 492854,
hostname: 'izanami',
err: {
type: 'Error',
message: 'an error',
stack:
'Error: an error\n at Object.<anonymous> (/home/kuzzle/pino-transport-ecs/tests/application/app.ts:34:14)\n at Module._compile (node:internal/modules/cjs/loader:1358:14)\n at Module.m._compile (/home/kuzzle/pino-transport-ecs/node_modules/ts-node/src/index.ts:1618:23)\n at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)\n at Object.require.extensions.<computed> [as .ts] (/home/kuzzle/pino-transport-ecs/node_modules/ts-node/src/index.ts:1621:12)\n at Module.load (node:internal/modules/cjs/loader:1208:32)\n at Function.Module._load (node:internal/modules/cjs/loader:1024:12)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)\n at node:internal/main/run_main_module:28:49',
},
msg: 'an error',
},
{
level: 30,
time: 1725632753069,
pid: 492854,
hostname: 'izanami',
a: 'property',
msg: 'hello child!',
},
{
level: 30,
time: 1725632753069,
pid: 492854,
hostname: 'izanami',
a: 'property',
another: 'property',
msg: 'hello baby..',
},
{
level: 20,
time: 1725632753070,
pid: 492854,
hostname: 'izanami',
msg: 'this is a debug statement',
},
{
level: 20,
time: 1725632753070,
pid: 492854,
hostname: 'izanami',
another: 'property',
msg: 'this is a debug statement via child',
},
{
level: 10,
time: 1725632753070,
pid: 492854,
hostname: 'izanami',
msg: 'this is a trace statement',
},
{
level: 20,
time: 1725632753070,
pid: 492854,
hostname: 'izanami',
msg: 'this is a "debug" statement with "',
},
{
level: 30,
time: 1725632753070,
pid: 492854,
hostname: 'izanami',
err: {
type: 'Error',
message: 'kaboom',
stack:
'Error: kaboom\n at Object.<anonymous> (/home/kuzzle/pino-transport-ecs/tests/application/app.ts:53:13)\n at Module._compile (node:internal/modules/cjs/loader:1358:14)\n at Module.m._compile (/home/kuzzle/pino-transport-ecs/node_modules/ts-node/src/index.ts:1618:23)\n at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)\n at Object.require.extensions.<computed> [as .ts] (/home/kuzzle/pino-transport-ecs/node_modules/ts-node/src/index.ts:1621:12)\n at Module.load (node:internal/modules/cjs/loader:1208:32)\n at Function.Module._load (node:internal/modules/cjs/loader:1024:12)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)\n at node:internal/main/run_main_module:28:49',
},
msg: 'kaboom',
},
{
level: 30,
time: 1725632753070,
pid: 492854,
hostname: 'izanami',
msg: null,
},
{
level: 30,
time: 1725632753070,
pid: 492854,
hostname: 'izanami',
err: {
type: 'Error',
message: 'kaboom',
stack:
'Error: kaboom\n at Object.<anonymous> (/home/kuzzle/pino-transport-ecs/tests/application/app.ts:56:13)\n at Module._compile (node:internal/modules/cjs/loader:1358:14)\n at Module.m._compile (/home/kuzzle/pino-transport-ecs/node_modules/ts-node/src/index.ts:1618:23)\n at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)\n at Object.require.extensions.<computed> [as .ts] (/home/kuzzle/pino-transport-ecs/node_modules/ts-node/src/index.ts:1621:12)\n at Module.load (node:internal/modules/cjs/loader:1208:32)\n at Function.Module._load (node:internal/modules/cjs/loader:1024:12)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)\n at node:internal/main/run_main_module:28:49',
},
msg: 'with',
},
{
level: 30,
time: 1725632753074,
pid: 492854,
hostname: 'izanami',
msg: 'after setImmediate',
},
];
47 changes: 47 additions & 0 deletions tests/unit/transform.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import ecsFormat from '@elastic/ecs-pino-format';
import { Readable, Transform } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import build, { PinoConfig } from 'pino-abstract-transport';
import { getTransform } from '../../src/transform';
import { testLogs } from '../fixtures/testLogs';

describe('Transform', () => {
it('should convert all the examples messages without error', async () => {
const testLogsStream: Transform & build.OnUnknown & PinoConfig = Readable.from(
testLogs,
) as Transform & build.OnUnknown & PinoConfig;

testLogsStream.levels = {
labels: {
10: 'trace',
20: 'debug',
30: 'info',
40: 'warn',
50: 'error',
60: 'fatal',
},
values: {
trace: 10,
debug: 20,
info: 30,
warn: 40,
error: 50,
fatal: 60,
},
};
testLogsStream.messageKey = 'msg';
testLogsStream.errorKey = 'err';

const pinoConfigEcs = ecsFormat();
const transformStream = getTransform(testLogsStream, pinoConfigEcs, {});

const pipelinePromise = pipeline(testLogsStream, transformStream);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
for await (const line of transformStream) {
// void data
}

await expect(pipelinePromise).resolves.toBeUndefined();
});
});

0 comments on commit 09b322f

Please sign in to comment.