Skip to content

Commit 073a7b1

Browse files
committed
Increase performance and add production setting hideLogPositionForProduction
1 parent 306175f commit 073a7b1

File tree

12 files changed

+187
-174
lines changed

12 files changed

+187
-174
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"sourceType": "module"
1111
},
1212
"plugins": ["@typescript-eslint"],
13-
"ignorePatterns": ["dist/*", "*/dist/*", "/examples/*", "tests/*", "*/tests/*", "build.js"],
13+
"ignorePatterns": ["benchmarks/*", "dist/*", "*/dist/*", "/examples/*", "tests/*", "*/tests/*", "build.js"],
1414
"rules": {
1515
"linebreak-style": ["error", "unix"],
1616
"quotes": ["error", "double"],

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,6 @@ bower_components
8383
# Vuepress
8484
.temp
8585
.cache
86+
87+
# Ignore banchmarks for now
88+
/benchmarks/*

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,6 @@ tsconfig*
9999

100100
# Ignore docs
101101
/docs*
102+
103+
# Ignore banchmarks for now
104+
/benchmarks/*

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,16 @@ Every incoming log message runs through a number of steps before being displayed
200200
- **log message** Log message comes in through the `BaseLogger.log()` method
201201
- **mask** If masking is configured, log message gets recursively masked
202202
- **toLogObj** Log message gets transformed into a log object: A default typed log object can be passed to constructor as a second parameter and will be cloned and enriched with the incoming log parameters. Error properties will be handled accordingly. If there is only one log property, and it's an object, both objects (cloned default `logObj` as well as the log property object) will be merged. If there are more than one, they will be put into properties called "0", "1", ... and so on. Alternatively, log message properties can be put into a property with a name configured with the `argumentsArrayName` setting.
203-
- **addMetaToLogObj** Additional meta information, like the source code position of the log will be gathered and added to the `_meta` property or any other one configured with the setting `metaProperty`.
203+
- **addMetaToLogObj** Additional meta information, like date, runtime and source code position of the log will be gathered and added to the `_meta` property or any other one configured with the setting `metaProperty`.
204204
- **format** In case of "pretty" configuration, a log object will be formatted based on the templates configured in settings. Meta will be formatted by the method `_prettyFormatLogObjMeta` and the actual log payload will be formatted by `prettyFormatLogObj`. Both steps can be overwritten with the settings `formatMeta` and `formatLogObj`.
205205
- **transport** Last step is to "transport" a log message to every attached transport from the setting `attachedTransports`. Last step is the actual transport, either JSON (`transportJSON`), formatted (`transportFormatted`) or omitted, if its set to "hidden". Both default transports can also be overwritten by the corresponding setting.
206206

207+
### ❗Performance
208+
209+
By default, `tslog is optimized for the best developer experience and includes some default settings that may impact performance in production environments. `
210+
To ensure optimal performance in production, we recommend modifying these settings, such as `hideLogPositionForProduction`(s. below), as needed.
211+
212+
207213
### Default log level
208214

209215
`tslog` comes with default log level `0: silly`, `1: trace`, `2: debug`, `3: info`, `4: warn`, `5: error`, `6: fatal`.
@@ -405,6 +411,13 @@ const logMsg = logger.silly("Test1", "Test2");
405411

406412
```
407413

414+
415+
#### hideLogPositionForProduction (default: `false`)
416+
417+
By default, `tslog` gathers and includes the log code position in the meta information of a `logObj to improve the developer experience. `
418+
However, this can significantly impact performance and slow down execution times in production.
419+
To improve performance, you can disable this functionality by setting the option `hideLogPositionForProduction` to `true`.
420+
408421
#### Pretty templates and styles (color settings)
409422
Enables you to overwrite the looks of a formatted _"pretty"_ log message by providing a template string.
410423
Following settings are available for styling:
@@ -713,7 +726,7 @@ const logMsg = logger.info("Test");
713726
### RequestID: Mark a request (e.g. HTTP) call with AsyncLocalStorage and `tslog`
714727
>**Node.js 13.10 introduced a new feature called <a href="https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage" target="_blank">AsyncLocalStorage.</a>**<br>
715728
716-
** Keep track of all subsequent calls and promises originated from a single request (e.g. HTTP).**
729+
** Keep track of all subsequent calls and promises originated from a single request (e.g. HTTP).**
717730

718731
In a real world application a call to an API would lead to many logs produced across the entire application.
719732
When debugging it can be quite handy to be able to group all logs based on a unique identifier, e.g. `requestId`.

examples/nodejs/index2.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,11 @@ firstSubLogger.silly("foo bar 1");
5555

5656
const secondSubLogger = firstSubLogger.getSubLogger({ name: "SecondSubLogger" });
5757
secondSubLogger.silly("foo bar 2");
58+
59+
////////////////////////////
60+
61+
const performanceLogger = new Logger({
62+
hideLogPositionForProduction: true,
63+
});
64+
65+
performanceLogger.silly("log without code position information");

src/BaseLogger.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ export class BaseLogger<LogObj> {
2424
parentNames: settings?.parentNames,
2525
minLevel: settings?.minLevel ?? 0,
2626
argumentsArrayName: settings?.argumentsArrayName,
27+
hideLogPositionForProduction: settings?.hideLogPositionForProduction ?? false,
2728
prettyLogTemplate:
2829
settings?.prettyLogTemplate ??
29-
"{{yyyy}}.{{mm}}.{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}}\t{{logLevelName}}\t[{{filePathWithLine}}{{nameWithDelimiterPrefix}}]\t",
30+
"{{yyyy}}.{{mm}}.{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}}\t{{logLevelName}}\t{{filePathWithLine}}{{nameWithDelimiterPrefix}}\t",
3031
prettyErrorTemplate: settings?.prettyErrorTemplate ?? "\n{{errorName}} {{errorMessage}}\nerror stack:\n{{errorStack}}",
3132
prettyErrorStackTemplate: settings?.prettyErrorStackTemplate ?? " • {{fileName}}\t{{method}}\n\t{{filePathWithLine}}",
3233
prettyErrorParentNamesSeparator: settings?.prettyErrorParentNamesSeparator ?? ":",
@@ -175,7 +176,7 @@ export class BaseLogger<LogObj> {
175176
};
176177

177178
const subLogger: BaseLogger<LogObj> = new (this.constructor as new (
178-
childSettings?: ISettingsParam<LogObj>,
179+
subLoggerSettings?: ISettingsParam<LogObj>,
179180
logObj?: LogObj,
180181
stackDepthLevel?: number
181182
) => this)(subLoggerSettings, logObj ?? this.logObj, this.stackDepthLevel);
@@ -238,7 +239,7 @@ export class BaseLogger<LogObj> {
238239
? new Date(source.getTime())
239240
: source && typeof source === "object"
240241
? Object.getOwnPropertyNames(source).reduce((o, prop) => {
241-
Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(source, prop)!);
242+
Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(source, prop) as PropertyDescriptor);
242243
// execute functions or clone
243244
o[prop] =
244245
typeof source[prop] === "function" ? source[prop]() : this._recursiveCloneAndExecuteFunctions((source as { [key: string]: unknown })[prop], seen);
@@ -276,7 +277,14 @@ export class BaseLogger<LogObj> {
276277
private _addMetaToLogObj(logObj: LogObj, logLevelId: number, logLevelName: string): LogObj & ILogObjMeta & ILogObj {
277278
return {
278279
...logObj,
279-
[this.settings.metaProperty]: getMeta(logLevelId, logLevelName, this.stackDepthLevel, this.settings.name, this.settings.parentNames),
280+
[this.settings.metaProperty]: getMeta(
281+
logLevelId,
282+
logLevelName,
283+
this.stackDepthLevel,
284+
this.settings.hideLogPositionForProduction,
285+
this.settings.name,
286+
this.settings.parentNames
287+
),
280288
};
281289
}
282290

@@ -316,9 +324,9 @@ export class BaseLogger<LogObj> {
316324
placeholderValues["rawIsoStr"] = dateInSettingsTimeZone?.toISOString();
317325
placeholderValues["dateIsoStr"] = dateInSettingsTimeZone?.toISOString().replace("T", " ").replace("Z", "");
318326
placeholderValues["logLevelName"] = logObjMeta?.logLevelName;
319-
placeholderValues["fileNameWithLine"] = logObjMeta?.path?.fileNameWithLine;
320-
placeholderValues["filePathWithLine"] = logObjMeta?.path?.filePathWithLine;
321-
placeholderValues["fullFilePath"] = logObjMeta?.path?.fullFilePath;
327+
placeholderValues["fileNameWithLine"] = logObjMeta?.path?.fileNameWithLine ?? "";
328+
placeholderValues["filePathWithLine"] = logObjMeta?.path?.filePathWithLine ?? "";
329+
placeholderValues["fullFilePath"] = logObjMeta?.path?.fullFilePath ?? "";
322330
// name
323331
let parentNamesString = this.settings.parentNames?.join(this.settings.prettyErrorParentNamesSeparator);
324332
parentNamesString = parentNamesString != null && logObjMeta?.name != null ? parentNamesString + this.settings.prettyErrorParentNamesSeparator : undefined;

src/interfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface ISettingsParam<LogObj> {
1414
parentNames?: string[];
1515
minLevel?: number;
1616
argumentsArrayName?: string;
17+
hideLogPositionForProduction?: boolean;
1718
prettyLogTemplate?: string;
1819
prettyErrorTemplate?: string;
1920
prettyErrorStackTemplate?: string;
@@ -69,6 +70,7 @@ export interface ISettings<LogObj> extends ISettingsParam<LogObj> {
6970
parentNames?: string[];
7071
minLevel: number;
7172
argumentsArrayName?: string;
73+
hideLogPositionForProduction: boolean;
7274
prettyLogTemplate: string;
7375
prettyErrorTemplate: string;
7476
prettyErrorStackTemplate: string;

src/runtime/browser/index.ts

Lines changed: 49 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -22,121 +22,71 @@ const meta: IMetaStatic = {
2222
browser: window?.["navigator"].userAgent,
2323
};
2424

25-
export function getMeta(logLevelId: number, logLevelName: string, stackDepthLevel: number, name?: string): IMeta {
26-
return {
27-
...meta,
25+
const pathRegex = /(?:(?:file|https?|global code|[^@]+)@)?(?:file:)?((?:\/[^:/]+){2,})(?::(\d+))?(?::(\d+))?/;
26+
27+
export function getMeta(
28+
logLevelId: number,
29+
logLevelName: string,
30+
stackDepthLevel: number,
31+
hideLogPositionForPerformance: boolean,
32+
name?: string,
33+
parentNames?: string[]
34+
): IMeta {
35+
// faster than spread operator
36+
return Object.assign({}, meta, {
2837
name,
38+
parentNames,
2939
date: new Date(),
3040
logLevelId,
3141
logLevelName,
32-
path: getCallerStackFrame(stackDepthLevel),
33-
};
42+
path: !hideLogPositionForPerformance ? getCallerStackFrame(stackDepthLevel) : undefined,
43+
}) as IMeta;
3444
}
3545

36-
export function getCallerStackFrame(stackDepthLevel: number): IStackFrame {
37-
try {
38-
throw new Error("getStackTrace");
39-
} catch (e: unknown) {
40-
const href = window.location.origin;
41-
42-
const error = e as Error | undefined;
43-
if (error?.stack) {
44-
let fullFilePath: string | undefined = error?.stack
45-
?.split("\n")
46-
?.filter((line: string) => !line.includes("Error: "))
47-
?.[stackDepthLevel]?.replace(/^\s+at\s+/gm, "");
48-
if (fullFilePath?.slice(-1) === ")") {
49-
fullFilePath = fullFilePath?.match(/\(([^)]+)\)/)?.[1];
50-
}
51-
52-
const pathArray = fullFilePath?.includes(":")
53-
? fullFilePath
54-
?.replace("global code@", "")
55-
?.replace("file://", "")
56-
?.replace(href, "")
57-
?.replace(/^\s+at\s+/gm, "")
58-
?.split(":")
59-
: undefined;
60-
61-
// order plays a role, runs from the back: column, line, path
62-
const fileColumn = pathArray?.pop();
63-
const fileLine = pathArray?.pop();
64-
const filePath = pathArray?.pop()?.split("?")?.[0];
65-
const fileName = filePath?.split("/").pop();
66-
const fileNameWithLine = `${fileName}:${fileLine}`;
67-
const filePathWithLine = `${href}${filePath}:${fileLine}`;
68-
const errorStackLine = fullFilePath?.split(" (");
69-
return {
70-
fullFilePath,
71-
fileName,
72-
fileNameWithLine,
73-
fileColumn,
74-
fileLine,
75-
filePath,
76-
filePathWithLine,
77-
method: errorStackLine?.[0],
78-
};
79-
}
80-
81-
return {
82-
fullFilePath: undefined,
83-
fileName: undefined,
84-
fileNameWithLine: undefined,
85-
fileColumn: undefined,
86-
fileLine: undefined,
87-
filePath: undefined,
88-
filePathWithLine: undefined,
89-
method: undefined,
90-
};
91-
}
46+
export function getCallerStackFrame(stackDepthLevel: number, error: Error = Error()): IStackFrame {
47+
return stackLineToStackFrame((error as Error | undefined)?.stack?.split("\n")?.filter((line: string) => !line.includes("Error: "))?.[stackDepthLevel]);
9248
}
9349

9450
export function getErrorTrace(error: Error): IStackFrame[] {
95-
const href = window.location.origin;
9651
return (error as Error)?.stack
9752
?.split("\n")
9853
?.filter((line: string) => !line.includes("Error: "))
9954
?.reduce((result: IStackFrame[], line: string) => {
100-
if (line?.slice(-1) === ")") {
101-
line = line.match(/\(([^)]+)\)/)?.[1] ?? "";
102-
}
103-
line = line.replace(/^\s+at\s+/gm, "");
104-
const errorStackLine = line.split(" (");
105-
const fullFilePath = line?.slice(-1) === ")" ? line?.match(/\(([^)]+)\)/)?.[1] : line;
106-
const pathArray = fullFilePath?.includes(":")
107-
? fullFilePath
108-
?.replace("global code@", "")
109-
?.replace("file://", "")
110-
?.replace(href, "")
111-
?.replace(/^\s+at\s+/gm, "")
112-
?.split(":")
113-
: undefined;
114-
115-
// order plays a role, runs from the back: column, line, path
116-
const fileColumn = pathArray?.pop();
117-
const fileLine = pathArray?.pop();
118-
const filePath = pathArray?.pop()?.split("?")[0];
119-
const fileName = filePath?.split("/")?.pop()?.split("?")[0];
120-
const fileNameWithLine = `${fileName}:${fileLine}`;
121-
const filePathWithLine = `${href}${filePath}:${fileLine}`;
122-
123-
if (filePath != null && filePath.length > 0) {
124-
result.push({
125-
fullFilePath,
126-
fileName,
127-
fileNameWithLine,
128-
fileColumn,
129-
fileLine,
130-
filePath,
131-
filePathWithLine,
132-
method: errorStackLine?.[1] != null ? errorStackLine?.[0] : undefined,
133-
});
134-
}
55+
result.push(stackLineToStackFrame(line));
13556

13657
return result;
13758
}, []) as IStackFrame[];
13859
}
13960

61+
function stackLineToStackFrame(line?: string): IStackFrame {
62+
const href = window.location.origin;
63+
const pathResult: IStackFrame = {
64+
fullFilePath: undefined,
65+
fileName: undefined,
66+
fileNameWithLine: undefined,
67+
fileColumn: undefined,
68+
fileLine: undefined,
69+
filePath: undefined,
70+
filePathWithLine: undefined,
71+
method: undefined,
72+
};
73+
if (line != null) {
74+
const match = line.match(pathRegex);
75+
if (match) {
76+
pathResult.filePath = match[1].replace(/\?.*$/, "");
77+
pathResult.fullFilePath = `${href}${pathResult.filePath}`;
78+
const pathParts = pathResult.filePath.split("/");
79+
pathResult.fileName = pathParts[pathParts.length - 1];
80+
pathResult.fileLine = match[2];
81+
pathResult.fileColumn = match[3];
82+
pathResult.filePathWithLine = `${pathResult.filePath}:${pathResult.fileLine}`;
83+
pathResult.fileNameWithLine = `${pathResult.fileName}:${pathResult.fileLine}`;
84+
}
85+
}
86+
87+
return pathResult;
88+
}
89+
14090
export function isError(e: Error | unknown): boolean {
14191
return e instanceof Error;
14292
}
@@ -174,6 +124,6 @@ export function transportJSON<LogObj>(json: LogObj & ILogObjMeta): void {
174124
console.log(jsonStringifyRecursive(json));
175125
}
176126

177-
export function isBuffer(arg: unknown) {
178-
return undefined;
127+
export function isBuffer(arg?: unknown) {
128+
return arg ? undefined : undefined;
179129
}

0 commit comments

Comments
 (0)