-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix logtail/logtail-js#19 Maximum call stack size exceeded #29
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot @boly38! This looks great and makes sense.
I left some comments but most of them are fairly small nitpicks.
I'll also try to check this out and run the test. (As you've noticed we don't have CI setup just yet)
Test looks great ✅ |
Reviewed-by: Andrej Hoos <andrej.hoos@gmail.com> Reviewed-by: gyfis <gyfis@seznam.cz>
contributing on this package: my small return of experience: in option.
cd packages/types && npm install && npm run build
cd ../core && npm install && npm link ../types && npm run build
cd ../node && npm link ../core && npm link ../types && npm run build
cd ../../ && npm run test there is maybe a shortest way to contribute.
|
@boly38 Thanks for the feedback! We have Lerna to manage the multiple packages, however last time it I tried to get it working, I wasn't able to. From what I understand Lerna is supposed to create symlinks in each package's To run individual jest tests, for checking the test you wrote I just run Hope that helps you get going on this PR, but I would completely understand if you don't want to bother - in that case I'm happy to take this PR over 🙂 We definitely should spend some more time to get the Lerna setup working, because as I noted, the current setup is not great. Thanks! |
Thanks for your tips : I just fixed base test (I think). I will squash those 3 commit and re-push, then give you the hand on this PR. |
Closes: 19 Thanks: Yago Tomé <yagotome@gmail.com> code review from PR logtail#29 Reviewed-by: Andrej Hoos <andrej.hoos@gmail.com> Reviewed-by: gyfis <gyfis@seznam.cz> fix ./node_modules/.bin/jest packages/core/src/base.test.ts
} else if (typeof value === "object" && maxDepth < 1) { | ||
if (this._options.contextObjectMaxDepthWarn) { | ||
console.warn(`[Logtail] Max depth of ${this._options.contextObjectMaxDepth} reached when serializing logs. Please do not use circular references and excessive object depth in your logs.`); | ||
} | ||
return value.toString();// result in "[object Object]" : we refused to serialize deeper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi there! I'm the OP of this issue, thanks a lot for following up on it.
I'm keen to share some thoughts with you guys here. I appreciate there are other reasons you need to control max depth in the context object, as the underlying serialization scheme (msgpack) also has its own limitation, as pointed out by @boly38 in this comment. However, I wanted to call out that the original problem (#19) could be handled differently, in a more accurate way. That's how I believe the problem should be tackled:
- Keep track of all object references (including array) seen at the function call stack -- a WeakSet could be used for that.
- If you visit the same object again, then you refuse to serialize it.
Just a draft snippet to make it clearer:
private sanitizeForEncoding(value: any): any {
const dfsPath = new WeakSet(); // note the weakset to track a dfs path (objects seen in the current call stack)
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
return value;
} else if (value instanceof Date) {
return value.toISOString();
} else if (Array.isArray(value)) {
return value.map((item) => this.sanitizeForEncoding(item));
} else if (typeof value === "object") {
const logClone: { [key: string]: any } = {};
Object.entries(value).forEach((item: object) => {
const key = item[0];
const value = item[1];
if (dfsPath.has(value)) { // here we detected a cycle
logClone[key] = value.toString();// result in "[object Object]" : we refused to serialize deeper
} else {
const result = this.sanitizeForEncoding(value);
if (result !== undefined){
logClone[key] = result;
}
}
});
dfsPath.delete(value); // the value` object is leaving the call stack and will not be part of a cycle if saw again in a different dfs path
return logClone;
} else {
return undefined;
}
}
That way, circular references would be stopped as soon as detected, instead of serializing them until they reach maximum call depth. What do you guys think? cc @adikus
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my quick feedback :
- ℹ️ need to append dfsPath as arg for recursive call but understand the idea of weakSet & recursion ( with doc example in addition )
- ➕ elegent design, and fix the circular ref
- ➖ don't fix an object without circular ref but really deep (can be unrealistic I agree)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I like the idea! I'll try to incorporate this in.
Moving this to #34 |
Fix #19 Maximum call stack size exceeded
Closes: 19
Thanks: Yago Tomé yagotome@gmail.com
this PR is my first typescript contribution so be indulgent. I take all comments. 🙏