-
Notifications
You must be signed in to change notification settings - Fork 360
[DI] Process log template #5548
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
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Overall package sizeSelf size: 9.33 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | @datadog/libdatadog | 0.5.1 | 29.73 MB | 29.73 MB | | @datadog/native-appsec | 8.5.2 | 19.33 MB | 19.34 MB | | @datadog/native-iast-taint-tracking | 3.3.1 | 13.99 MB | 13.99 MB | | @datadog/pprof | 5.7.1 | 9.51 MB | 9.88 MB | | @opentelemetry/core | 1.30.1 | 908.66 kB | 7.16 MB | | protobufjs | 7.4.0 | 2.77 MB | 5.42 MB | | @datadog/wasm-js-rewriter | 4.0.0 | 2.85 MB | 3.58 MB | | @datadog/native-metrics | 3.1.1 | 1.02 MB | 1.43 MB | | @opentelemetry/api | 1.8.0 | 1.21 MB | 1.21 MB | | import-in-the-middle | 1.13.1 | 117.64 kB | 839.26 kB | | source-map | 0.7.4 | 226 kB | 226 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | lru-cache | 7.18.3 | 133.92 kB | 133.92 kB | | pprof-format | 2.1.0 | 111.69 kB | 111.69 kB | | @datadog/sketches-js | 2.1.1 | 109.9 kB | 109.9 kB | | lodash.sortby | 4.7.0 | 75.76 kB | 75.76 kB | | ignore | 5.3.2 | 53.63 kB | 53.63 kB | | istanbul-lib-coverage | 3.2.0 | 29.34 kB | 29.34 kB | | rfdc | 1.4.1 | 27.15 kB | 27.15 kB | | @isaacs/ttlcache | 1.4.1 | 25.2 kB | 25.2 kB | | dc-polyfill | 0.1.8 | 25.08 kB | 25.08 kB | | tlhunter-sorted-set | 0.1.0 | 24.94 kB | 24.94 kB | | shell-quote | 1.8.2 | 23.54 kB | 23.54 kB | | limiter | 1.1.5 | 23.17 kB | 23.17 kB | | retry | 0.13.1 | 18.85 kB | 18.85 kB | | semifies | 1.0.0 | 15.84 kB | 15.84 kB | | jest-docblock | 29.7.0 | 8.99 kB | 12.76 kB | | crypto-randomuuid | 1.0.0 | 11.18 kB | 11.18 kB | | ttl-set | 1.0.0 | 4.61 kB | 9.69 kB | | path-to-regexp | 0.1.12 | 6.6 kB | 6.6 kB | | koalas | 1.0.2 | 6.47 kB | 6.47 kB | | module-details-from-path | 1.0.3 | 4.47 kB | 4.47 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #5548 +/- ##
==========================================
- Coverage 79.29% 79.28% -0.01%
==========================================
Files 513 513
Lines 23342 23342
==========================================
- Hits 18508 18507 -1
- Misses 4834 4835 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Datadog ReportBranch report: ✅ 0 Failed, 920 Passed, 0 Skipped, 8m 9.5s Total Time |
BenchmarksBenchmark execution time: 2025-04-11 13:23:13 Comparing candidate commit aa6c85b in PR branch Found 1 performance improvements and 0 performance regressions! Performance is the same for 946 metrics, 16 unstable metrics. scenario:startup-with-tracer-22
|
8a66ab4 to
6a1e418
Compare
shatzi
left a comment
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.
Awesome progress.
I suggest we fix the spec on how errors are handled before commit.
| expression: probe.condition, | ||
| returnByValue: true | ||
| }) | ||
| if (result.value !== true) continue |
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.
can we have a condition that fails with exception?
the spec should generate an evaluation errors every X minutes - so the user can fix those errors.
I think at the current state failed condition simply stop all probes from been trigger, is that right?
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.
Yes, that's an oversight from me when I created the condition logic. We should report any errors that might occur here. I'll do that in a follow up PR since it's nothing to do with log templates (I just moved this code around)
da3c9d4 to
457f6fc
Compare
b143b67 to
e13fed4
Compare
BridgeAR
left a comment
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.
Just a few suggestions for more performant code. The main aspect is likely the for await.
| assert.strictEqual(diagnostics.exception.message, 'Cannot compile expression: original dsl') | ||
| done() | ||
| } else if (diagnostics.status === 'INSTALLED') { | ||
| assert.fail('Should not install when condition cannot be compiled') |
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.
I guess assert.fail() is used to break out of the loop and to not call done multiple times with an error?
I might be wrong but if this fails, I would expect the test to time out and the error to be an unhandled rejection. So instead, maybe this could use the async await pattern that I recently suggested? :)
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.
assert.fail will immediately end the test without having to call done or waiting for a timeout. I added it here as an escape hatch in case somehow diagnostics.status was unexpectedly INSTALLED which should never happen in this test.
It has technically nothing to do with this PR, but while I was working on other tests during the development in this PR I noticed I could make this improvement (those other tests was a copy of this test, but has since been refactored, so that's why this single change remains in this PR)
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.
I guess the test runner does some magic in this case to detect it? Or is this code sync?
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.
No the code is async. Yeah, I assume it does so as well. I just tested it to make sure.
| const { result } = await session.post('Debugger.evaluateOnCallFrame', { | ||
| callFrameId: params.callFrames[0].callFrameId, | ||
| expression: probe.condition, | ||
| returnByValue: true | ||
| }) |
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.
This could use a similar pattern that I recently implemented it in the collector. All following lines have to be executed in the then of the promise.
That is however only allowed in case the order of the probes and of the expressions does not matter.
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.
Good idea. I'll do that in a later PR as the condition evaluation has nothing to do with this PR. This diff just appeared in this PR because I moved the code a few lines down from where it was before and added the TODO's so that I could remember to deal with this later.
The log template is now evaluated in the context of the line the probe is instrumenting.
Co-authored-by: Ruben Bridgewater <ruben@bridgewater.de>
df0d279 to
aa6c85b
Compare
BridgeAR
left a comment
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.
LGTM
Evaluate the log message template in the context of the line the probe is instrumenting. If there's more than one probe on a specific file/line/column, all evaluations happen in a single `Debugger.evaluateOnCallFrame` call, as to minimise the number of roundtrips over the Chrome DevTools Protocol, and hence, minimise the time the inspected thread is paused. The `util.inspect` method is used to stringify the results of the code evaluations (expect for strings which are just returned as is). Options are passed to `util.inspect` to keep the returned string small, and to align as much as possible with the same implementation in the other tracers. The total length of the full message is also truncated at 8KiB. The only two major differences between this implementation and the implementations in the other tracers are: - If any of the objects traversed by `util.inspect` contains the magic `[Symbol.toStringTag]` getter, it will get executed. This is technically a side-effect, which we don't like, but in this specific case, it has been considered tolerable, as anyone including such a function, will do so with the express intent of modifying the string representation of instances of a given class. - The RFC for how message templates are evaluated, states that any objects with fields, should have their field count capped at 5 in when stringified. This is not possible to do with `util.inspect`, so for now all fields are included in the string representation. This is an ok compromise for an MVP.
Evaluate the log message template in the context of the line the probe is instrumenting. If there's more than one probe on a specific file/line/column, all evaluations happen in a single `Debugger.evaluateOnCallFrame` call, as to minimise the number of roundtrips over the Chrome DevTools Protocol, and hence, minimise the time the inspected thread is paused. The `util.inspect` method is used to stringify the results of the code evaluations (expect for strings which are just returned as is). Options are passed to `util.inspect` to keep the returned string small, and to align as much as possible with the same implementation in the other tracers. The total length of the full message is also truncated at 8KiB. The only two major differences between this implementation and the implementations in the other tracers are: - If any of the objects traversed by `util.inspect` contains the magic `[Symbol.toStringTag]` getter, it will get executed. This is technically a side-effect, which we don't like, but in this specific case, it has been considered tolerable, as anyone including such a function, will do so with the express intent of modifying the string representation of instances of a given class. - The RFC for how message templates are evaluated, states that any objects with fields, should have their field count capped at 5 in when stringified. This is not possible to do with `util.inspect`, so for now all fields are included in the string representation. This is an ok compromise for an MVP.
Evaluate the log message template in the context of the line the probe is instrumenting. If there's more than one probe on a specific file/line/column, all evaluations happen in a single `Debugger.evaluateOnCallFrame` call, as to minimise the number of roundtrips over the Chrome DevTools Protocol, and hence, minimise the time the inspected thread is paused. The `util.inspect` method is used to stringify the results of the code evaluations (expect for strings which are just returned as is). Options are passed to `util.inspect` to keep the returned string small, and to align as much as possible with the same implementation in the other tracers. The total length of the full message is also truncated at 8KiB. The only two major differences between this implementation and the implementations in the other tracers are: - If any of the objects traversed by `util.inspect` contains the magic `[Symbol.toStringTag]` getter, it will get executed. This is technically a side-effect, which we don't like, but in this specific case, it has been considered tolerable, as anyone including such a function, will do so with the express intent of modifying the string representation of instances of a given class. - The RFC for how message templates are evaluated, states that any objects with fields, should have their field count capped at 5 in when stringified. This is not possible to do with `util.inspect`, so for now all fields are included in the string representation. This is an ok compromise for an MVP.

What does this PR do?
Evaluate the log message template in the context of the line the probe is instrumenting.
Data structure
The message template for a log probe is provided with the rest of the probe over Remote Config, and is found in the two probe fields
messageandsegments. Previously, we just usedmessageas-is, which is the raw string that the user entered in the UI. However, the backend parses this string and turns it into an AST which is stored in thesegmentsarray. This PR switches to use this AST if any of the segments requires evaluation. If not, the rawmessageis still just used as-is. Themessageproperty andsegmentsarray has the following format:The AST in the
jsonproperty has the same format as the DI probe conditions already supported by this tracer, so the compiler used to compile probe conditions is reused to compile these segments in this PR.Performance optimizations
If there's more than one probe on a specific file/line/column, all evaluations happen in a single
Debugger.evaluateOnCallFramecall, as to minimise the number of roundtrips over the Chrome DevTools Protocol, and hence, minimise the time the inspected thread is paused.Stringification method
The
util.inspectmethod is used to stringify the results of the code evaluations (expect for strings which are just returned as is). The following options are passed toutil.inspectto keep the returned string small, and to align as much as possible with the same implementation in the other tracers.The total length of the full message is also truncated at 8KiB.
Motivation
Feature parity with the other tracers.
Additional Notes
The only two major differences between this implementation and the implementations in the other tracers are;
util.inspectcontains the magic[Symbol.toStringTag]getter, it will get executed. This is technically a side-effect, which we don't like, but in this specific case, it has been considered tolerable, as anyone including such a function, will do so with the express intent of modifying the string representation of instances of a given class.util.inspect, so for now all fields are included in the string representation. This is an ok compromise for an MVP.