pino
Version:
super fast, all natural json logger
469 lines (393 loc) • 11.7 kB
text/typescript
import { IncomingMessage, ServerResponse } from "http";
import { Socket } from "net";
import { expectError, expectType } from 'tsd';
import P, { LoggerOptions, pino } from "../../";
import Logger = P.Logger;
const log = pino();
const info = log.info;
const error = log.error;
info("hello world");
error("this is at error level");
info("the answer is %d", 42);
info({ obj: 42 }, "hello world");
info({ obj: 42, b: 2 }, "hello world");
info({ obj: { aa: "bbb" } }, "another");
setImmediate(info, "after setImmediate");
error(new Error("an error"));
const writeSym = pino.symbols.writeSym;
const testUniqSymbol = {
[pino.symbols.needsMetadataGsym]: true,
}[pino.symbols.needsMetadataGsym];
const log2: P.Logger = pino({
name: "myapp",
safe: true,
serializers: {
req: pino.stdSerializers.req,
res: pino.stdSerializers.res,
err: pino.stdSerializers.err,
},
});
pino({
write(o) {},
});
pino({
mixin() {
return { customName: "unknown", customId: 111 };
},
});
pino({
mixin: () => ({ customName: "unknown", customId: 111 }),
});
pino({
mixin: (context: object) => ({ customName: "unknown", customId: 111 }),
});
pino({
mixin: (context: object, level: number) => ({ customName: "unknown", customId: 111 }),
});
pino({
redact: { paths: [], censor: "SECRET" },
});
pino({
redact: { paths: [], censor: () => "SECRET" },
});
pino({
redact: { paths: [], censor: (value) => value },
});
pino({
redact: { paths: [], censor: (value, path) => path.join() },
});
pino({
depthLimit: 1
});
pino({
edgeLimit: 1
});
pino({
browser: {
write(o) {},
},
});
pino({
browser: {
write: {
info(o) {},
error(o) {},
},
serialize: true,
asObject: true,
transmit: {
level: "fatal",
send: (level, logEvent) => {
level;
logEvent.bindings;
logEvent.level;
logEvent.ts;
logEvent.messages;
},
},
disabled: false
},
});
pino({}, undefined);
pino({ base: null });
if ("pino" in log) console.log(`pino version: ${log.pino}`);
expectType<void>(log.flush());
log.flush((err?: Error) => undefined);
log.child({ a: "property" }).info("hello child!");
log.level = "error";
log.info("nope");
const child = log.child({ foo: "bar" });
child.info("nope again");
child.level = "info";
child.info("hooray");
log.info("nope nope nope");
log.child({ foo: "bar" }, { level: "debug" }).debug("debug!");
child.bindings();
const customSerializers = {
test() {
return "this is my serializer";
},
};
pino().child({}, { serializers: customSerializers }).info({ test: "should not show up" });
const child2 = log.child({ father: true });
const childChild = child2.child({ baby: true });
const childRedacted = pino().child({}, { redact: ["path"] })
childRedacted.info({
msg: "logged with redacted properties",
path: "Not shown",
});
const childAnotherRedacted = pino().child({}, {
redact: {
paths: ["anotherPath"],
censor: "Not the log you\re looking for",
}
})
childAnotherRedacted.info({
msg: "another logged with redacted properties",
anotherPath: "Not shown",
});
log.level = "info";
if (log.levelVal === 30) {
console.log("logger level is `info`");
}
const listener = (lvl: any, val: any, prevLvl: any, prevVal: any) => {
console.log(lvl, val, prevLvl, prevVal);
};
log.on("level-change", (lvl, val, prevLvl, prevVal, logger) => {
console.log(lvl, val, prevLvl, prevVal);
});
log.level = "trace";
log.removeListener("level-change", listener);
log.level = "info";
pino.levels.values.error === 50;
pino.levels.labels[50] === "error";
const logstderr: pino.Logger = pino(process.stderr);
logstderr.error("on stderr instead of stdout");
log.useLevelLabels = true;
log.info("lol");
log.level === "info";
const isEnabled: boolean = log.isLevelEnabled("info");
const redacted = pino({
redact: ["path"],
});
redacted.info({
msg: "logged with redacted properties",
path: "Not shown",
});
const anotherRedacted = pino({
redact: {
paths: ["anotherPath"],
censor: "Not the log you\re looking for",
},
});
anotherRedacted.info({
msg: "another logged with redacted properties",
anotherPath: "Not shown",
});
const withTimeFn = pino({
timestamp: pino.stdTimeFunctions.isoTime,
});
const withNestedKey = pino({
nestedKey: "payload",
});
const withHooks = pino({
hooks: {
logMethod(args, method, level) {
expectType<pino.Logger>(this);
return method.apply(this, args);
},
streamWrite(s) {
expectType<string>(s);
return s.replaceAll('secret-key', 'xxx');
},
},
});
// Properties/types imported from pino-std-serializers
const wrappedErrSerializer = pino.stdSerializers.wrapErrorSerializer((err: pino.SerializedError) => {
return { ...err, newProp: "foo" };
});
const wrappedReqSerializer = pino.stdSerializers.wrapRequestSerializer((req: pino.SerializedRequest) => {
return { ...req, newProp: "foo" };
});
const wrappedResSerializer = pino.stdSerializers.wrapResponseSerializer((res: pino.SerializedResponse) => {
return { ...res, newProp: "foo" };
});
const socket = new Socket();
const incomingMessage = new IncomingMessage(socket);
const serverResponse = new ServerResponse(incomingMessage);
const mappedHttpRequest: { req: pino.SerializedRequest } = pino.stdSerializers.mapHttpRequest(incomingMessage);
const mappedHttpResponse: { res: pino.SerializedResponse } = pino.stdSerializers.mapHttpResponse(serverResponse);
const serializedErr: pino.SerializedError = pino.stdSerializers.err(new Error());
const serializedReq: pino.SerializedRequest = pino.stdSerializers.req(incomingMessage);
const serializedRes: pino.SerializedResponse = pino.stdSerializers.res(serverResponse);
/**
* Destination static method
*/
const destinationViaDefaultArgs = pino.destination();
const destinationViaStrFileDescriptor = pino.destination("/log/path");
const destinationViaNumFileDescriptor = pino.destination(2);
const destinationViaStream = pino.destination(process.stdout);
const destinationViaOptionsObject = pino.destination({ dest: "/log/path", sync: false });
pino(destinationViaDefaultArgs);
pino({ name: "my-logger" }, destinationViaDefaultArgs);
pino(destinationViaStrFileDescriptor);
pino({ name: "my-logger" }, destinationViaStrFileDescriptor);
pino(destinationViaNumFileDescriptor);
pino({ name: "my-logger" }, destinationViaNumFileDescriptor);
pino(destinationViaStream);
pino({ name: "my-logger" }, destinationViaStream);
pino(destinationViaOptionsObject);
pino({ name: "my-logger" }, destinationViaOptionsObject);
try {
throw new Error('Some error')
} catch (err) {
log.error(err)
}
interface StrictShape {
activity: string;
err?: unknown;
}
info<StrictShape>({
activity: "Required property",
});
const logLine: pino.LogDescriptor = {
level: 20,
msg: "A log message",
time: new Date().getTime(),
aCustomProperty: true,
};
interface CustomLogger extends pino.Logger {
customMethod(msg: string, ...args: unknown[]): void;
}
const serializerFunc: pino.SerializerFn = () => {}
const writeFunc: pino.WriteFn = () => {}
interface CustomBaseLogger extends pino.BaseLogger {
child(): CustomBaseLogger
}
const customBaseLogger: CustomBaseLogger = {
level: 'info',
fatal() {},
error() {},
warn() {},
info() {},
debug() {},
trace() {},
silent() {},
child() { return this }
}
// custom levels
const log3 = pino({ customLevels: { myLevel: 100 } })
expectError(log3.log())
log3.level = 'myLevel'
log3.myLevel('')
log3.child({}).myLevel('')
log3.on('level-change', (lvl, val, prevLvl, prevVal, instance) => {
instance.myLevel('foo');
});
const clog3 = log3.child({}, { customLevels: { childLevel: 120 } })
// child inherit parent
clog3.myLevel('')
// child itself
clog3.childLevel('')
const cclog3 = clog3.child({}, { customLevels: { childLevel2: 130 } })
// child inherit root
cclog3.myLevel('')
// child inherit parent
cclog3.childLevel('')
// child itself
cclog3.childLevel2('')
const ccclog3 = clog3.child({})
expectError(ccclog3.nonLevel(''))
const withChildCallback = pino({
onChild: (child: Logger) => {}
})
withChildCallback.onChild = (child: Logger) => {}
pino({
crlf: true,
});
const customLevels = { foo: 99, bar: 42 }
const customLevelLogger = pino({ customLevels });
type CustomLevelLogger = typeof customLevelLogger
type CustomLevelLoggerLevels = pino.Level | keyof typeof customLevels
const fn = (logger: Pick<CustomLevelLogger, CustomLevelLoggerLevels>) => {}
const customLevelChildLogger = customLevelLogger.child({ name: "child" })
fn(customLevelChildLogger); // missing foo typing
// unknown option
expectError(
pino({
hello: 'world'
})
);
// unknown option
expectError(
pino({
hello: 'world',
customLevels: {
'log': 30
}
})
);
function dangerous () {
throw Error('foo')
}
try {
dangerous()
} catch (err) {
log.error(err)
}
try {
dangerous()
} catch (err) {
log.error({ err })
}
const bLogger = pino({
customLevels: {
log: 5,
},
level: 'log',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
},
},
});
expectType<Logger<'log'>>(pino({
customLevels: {
log: 5,
},
level: 'log',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
},
},
}))
const parentLogger1 = pino({
customLevels: { myLevel: 90 },
onChild: (child) => { const a = child.myLevel; }
}, process.stdout)
parentLogger1.onChild = (child) => { child.myLevel(''); }
const childLogger1 = parentLogger1.child({});
childLogger1.myLevel('');
expectError(childLogger1.doesntExist(''));
const parentLogger2 = pino({}, process.stdin);
expectError(parentLogger2.onChild = (child) => { const b = child.doesntExist; });
const childLogger2 = parentLogger2.child({});
expectError(childLogger2.doesntExist);
expectError(pino({
onChild: (child) => { const a = child.doesntExist; }
}, process.stdout));
const pinoWithoutLevelsSorting = pino({});
const pinoWithDescSortingLevels = pino({ levelComparison: 'DESC' });
const pinoWithAscSortingLevels = pino({ levelComparison: 'ASC' });
const pinoWithCustomSortingLevels = pino({ levelComparison: () => false });
// with wrong level comparison direction
expectError(pino({ levelComparison: 'SOME'}), process.stdout);
// with wrong level comparison type
expectError(pino({ levelComparison: 123}), process.stdout);
// with wrong custom level comparison return type
expectError(pino({ levelComparison: () => null }), process.stdout);
expectError(pino({ levelComparison: () => 1 }), process.stdout);
expectError(pino({ levelComparison: () => 'string' }), process.stdout);
const customLevelsOnlyOpts = {
useOnlyCustomLevels: true,
customLevels: {
customDebug: 10,
info: 20, // to make sure the default names are also available for override
customNetwork: 30,
customError: 40,
},
level: 'customDebug',
} satisfies LoggerOptions;
const loggerWithCustomLevelOnly = pino(customLevelsOnlyOpts);
loggerWithCustomLevelOnly.customDebug('test3')
loggerWithCustomLevelOnly.info('test4')
loggerWithCustomLevelOnly.customError('test5')
loggerWithCustomLevelOnly.customNetwork('test6')
expectError(loggerWithCustomLevelOnly.fatal('test'));
expectError(loggerWithCustomLevelOnly.error('test'));
expectError(loggerWithCustomLevelOnly.warn('test'));
expectError(loggerWithCustomLevelOnly.debug('test'));
expectError(loggerWithCustomLevelOnly.trace('test'));