[APM] Fix for library frames not collapsing (#26827)

* [APM] fixes #26525
- simplified the stackframe grouping algorithm
- add support for `stackframe.exclude_from_grouping`
- made the rendering more tolerant of edge cases

* Made improvements to code readability and added more meaningful test cases
This commit is contained in:
Oliver Gupte 2018-12-11 19:25:24 -08:00 committed by GitHub
parent d01aaef850
commit f1520f855d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 1110 additions and 128 deletions

View file

@ -20,6 +20,26 @@ const LibraryFrameToggle = styled.div`
user-select: none;
`;
interface LibraryStackFrameProps {
codeLanguage?: string;
stackframe: Stackframe;
}
const LibraryStackFrame: React.SFC<LibraryStackFrameProps> = ({
codeLanguage,
stackframe
}) => {
return hasSourceLines(stackframe) ? (
<CodePreview
stackframe={stackframe}
isLibraryFrame
codeLanguage={codeLanguage}
/>
) : (
<FrameHeading stackframe={stackframe} isLibraryFrame />
);
};
interface Props {
visible?: boolean;
stackframes: Stackframe[];
@ -27,12 +47,25 @@ interface Props {
onClick: () => void;
}
export const LibraryFrames: React.SFC<Props> = ({
export const LibraryStackFrames: React.SFC<Props> = ({
visible,
stackframes,
codeLanguage,
onClick
}) => {
if (stackframes.length === 0) {
return null;
}
if (stackframes.length === 1) {
return (
<LibraryStackFrame
codeLanguage={codeLanguage}
stackframe={stackframes[0]}
/>
);
}
return (
<div>
<LibraryFrameToggle>
@ -44,18 +77,13 @@ export const LibraryFrames: React.SFC<Props> = ({
<div>
{visible &&
stackframes.map((stackframe, i) =>
hasSourceLines(stackframe) ? (
<CodePreview
key={i}
stackframe={stackframe}
isLibraryFrame
codeLanguage={codeLanguage}
/>
) : (
<FrameHeading key={i} stackframe={stackframe} isLibraryFrame />
)
)}
stackframes.map((stackframe, i) => (
<LibraryStackFrame
key={i}
codeLanguage={codeLanguage}
stackframe={stackframe}
/>
))}
</div>
</div>
);

View file

@ -0,0 +1,460 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`stactraceUtils getGroupedStackframes should collapse the library frames into a set of grouped stackframes 1`] = `
Array [
Object {
"isLibraryFrame": false,
"stackframes": Array [
Object {
"abs_path": "/app/server/routes.js",
"context": Object {
"post": Array [
" req.body.lines.forEach(function (line) {",
" client.query('SELECT id FROM products WHERE id=$1', [line.id], next())",
],
"pre": Array [
" })",
"",
],
},
"exclude_from_grouping": false,
"filename": "server/routes.js",
"function": "<anonymous>",
"library_frame": false,
"line": Object {
"context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())",
"number": 307,
},
},
],
},
Object {
"isLibraryFrame": true,
"stackframes": Array [
Object {
"abs_path": "/app/node_modules/pg-pool/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/pg-pool/index.js",
"function": "BoundPool.<anonymous>",
"library_frame": true,
"line": Object {
"number": 137,
},
},
Object {
"abs_path": "/app/node_modules/generic-pool/lib/generic-pool.js",
"exclude_from_grouping": false,
"filename": "node_modules/generic-pool/lib/generic-pool.js",
"function": "dispense",
"library_frame": true,
"line": Object {
"number": 310,
},
},
Object {
"abs_path": "/app/node_modules/generic-pool/lib/generic-pool.js",
"exclude_from_grouping": false,
"filename": "node_modules/generic-pool/lib/generic-pool.js",
"function": "acquire",
"library_frame": true,
"line": Object {
"number": 391,
},
},
Object {
"abs_path": "/app/node_modules/pg-pool/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/pg-pool/index.js",
"function": "BoundPool.<anonymous>",
"library_frame": true,
"line": Object {
"number": 111,
},
},
Object {
"abs_path": "/app/node_modules/pg-pool/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/pg-pool/index.js",
"function": "Pool._promiseNoCallback",
"library_frame": true,
"line": Object {
"number": 75,
},
},
Object {
"abs_path": "/app/node_modules/pg-pool/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/pg-pool/index.js",
"function": "Pool.connect",
"library_frame": true,
"line": Object {
"number": 109,
},
},
],
},
Object {
"isLibraryFrame": false,
"stackframes": Array [
Object {
"abs_path": "/app/server/db.js",
"context": Object {
"post": Array [
"}",
"",
],
"pre": Array [
"",
"exports.client = function (cb) {",
],
},
"exclude_from_grouping": false,
"filename": "server/db.js",
"function": "exports.client",
"library_frame": false,
"line": Object {
"context": " pool.connect(cb)",
"number": 11,
},
},
Object {
"abs_path": "/app/server/routes.js",
"context": Object {
"post": Array [
" if (err) return error(err, res)",
"",
],
"pre": Array [
" }",
"",
],
},
"exclude_from_grouping": false,
"filename": "server/routes.js",
"function": "<anonymous>",
"library_frame": false,
"line": Object {
"context": " db.client(function (err, client, done) {",
"number": 248,
},
},
],
},
Object {
"isLibraryFrame": true,
"stackframes": Array [
Object {
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/layer.js",
"function": "handle",
"library_frame": true,
"line": Object {
"number": 95,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/route.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/route.js",
"function": "next",
"library_frame": true,
"line": Object {
"number": 137,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/route.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/route.js",
"function": "dispatch",
"library_frame": true,
"line": Object {
"number": 112,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/layer.js",
"function": "handle",
"library_frame": true,
"line": Object {
"number": 95,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "<anonymous>",
"library_frame": true,
"line": Object {
"number": 281,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "process_params",
"library_frame": true,
"line": Object {
"number": 335,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "next",
"library_frame": true,
"line": Object {
"number": 275,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "handle",
"library_frame": true,
"line": Object {
"number": 174,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "router",
"library_frame": true,
"line": Object {
"number": 47,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/layer.js",
"function": "handle",
"library_frame": true,
"line": Object {
"number": 95,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "trim_prefix",
"library_frame": true,
"line": Object {
"number": 317,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "<anonymous>",
"library_frame": true,
"line": Object {
"number": 284,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "process_params",
"library_frame": true,
"line": Object {
"number": 335,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "next",
"library_frame": true,
"line": Object {
"number": 275,
},
},
],
},
Object {
"isLibraryFrame": false,
"stackframes": Array [
Object {
"abs_path": "/app/server.js",
"context": Object {
"post": Array [
" }",
"",
],
"pre": Array [
"app.use('/api', function (req, res, next) {",
" if (Math.random() > opbeansRedirectProbability) {",
],
},
"exclude_from_grouping": false,
"filename": "server.js",
"function": "<anonymous>",
"library_frame": false,
"line": Object {
"context": " return next()",
"number": 88,
},
},
],
},
Object {
"isLibraryFrame": true,
"stackframes": Array [
Object {
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/layer.js",
"function": "handle",
"library_frame": true,
"line": Object {
"number": 95,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "trim_prefix",
"library_frame": true,
"line": Object {
"number": 317,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "<anonymous>",
"library_frame": true,
"line": Object {
"number": 284,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "process_params",
"library_frame": true,
"line": Object {
"number": 335,
},
},
],
},
Object {
"isLibraryFrame": true,
"stackframes": Array [
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": true,
"filename": "node_modules/express/lib/router/index.js",
"function": "next",
"library_frame": true,
"line": Object {
"number": 275,
},
},
],
},
Object {
"isLibraryFrame": true,
"stackframes": Array [
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "<anonymous>",
"library_frame": true,
"line": Object {
"number": 635,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "next",
"library_frame": true,
"line": Object {
"number": 260,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "handle",
"library_frame": true,
"line": Object {
"number": 174,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "router",
"library_frame": true,
"line": Object {
"number": 47,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/layer.js",
"function": "handle",
"library_frame": true,
"line": Object {
"number": 95,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "trim_prefix",
"library_frame": true,
"line": Object {
"number": 317,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "<anonymous>",
"library_frame": true,
"line": Object {
"number": 284,
},
},
Object {
"abs_path": "/app/node_modules/express/lib/router/index.js",
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"function": "process_params",
"library_frame": true,
"line": Object {
"number": 335,
},
},
],
},
]
`;

View file

@ -5,21 +5,166 @@
*/
import { Stackframe } from '../../../../../typings/APMDoc';
import { getCollapsedLibraryFrames, hasSourceLines } from '../stacktraceUtils';
import { getGroupedStackframes, hasSourceLines } from '../stacktraceUtils';
import stacktracesMock from './stacktraces.json';
const stackframeMockWithSource = stacktracesMock[0];
const stackframeMockWithoutSource = stacktracesMock[1];
describe('stactraceUtils', () => {
describe('getCollapsedLibraryFrames', () => {
it('should collapse the library frames into a set of grouped, nested stackframes', () => {
const result = getCollapsedLibraryFrames(stacktracesMock as Stackframe[]);
expect(result.length).toBe(3);
expect(result[0].libraryFrame).toBe(false);
expect(result[1].libraryFrame).toBe(true);
expect(result[1].stackframes).toHaveLength(2); // two nested stackframes
expect(result[2].libraryFrame).toBe(false);
describe('getGroupedStackframes', () => {
it('should collapse the library frames into a set of grouped stackframes', () => {
const result = getGroupedStackframes(stacktracesMock as Stackframe[]);
expect(result).toMatchSnapshot();
});
it('should group stackframes when `library_frame` is identical and `exclude_from_grouping` is false', () => {
const stackframes = [
{
library_frame: false,
exclude_from_grouping: false,
filename: 'file-a.txt'
},
{
library_frame: false,
exclude_from_grouping: false,
filename: 'file-b.txt'
},
{
library_frame: true,
exclude_from_grouping: false,
filename: 'file-c.txt'
},
{
library_frame: true,
exclude_from_grouping: false,
filename: 'file-d.txt'
}
] as Stackframe[];
const result = getGroupedStackframes(stackframes);
expect(result).toEqual([
{
isLibraryFrame: false,
stackframes: [
{
exclude_from_grouping: false,
filename: 'file-a.txt',
library_frame: false
},
{
exclude_from_grouping: false,
filename: 'file-b.txt',
library_frame: false
}
]
},
{
isLibraryFrame: true,
stackframes: [
{
exclude_from_grouping: false,
filename: 'file-c.txt',
library_frame: true
},
{
exclude_from_grouping: false,
filename: 'file-d.txt',
library_frame: true
}
]
}
]);
});
it('should not group stackframes when `library_frame` is the different', () => {
const stackframes = [
{
library_frame: false,
exclude_from_grouping: false,
filename: 'file-a.txt'
},
{
library_frame: true,
exclude_from_grouping: false,
filename: 'file-b.txt'
}
] as Stackframe[];
const result = getGroupedStackframes(stackframes);
expect(result).toEqual([
{
isLibraryFrame: false,
stackframes: [
{
exclude_from_grouping: false,
filename: 'file-a.txt',
library_frame: false
}
]
},
{
isLibraryFrame: true,
stackframes: [
{
exclude_from_grouping: false,
filename: 'file-b.txt',
library_frame: true
}
]
}
]);
});
it('should not group stackframes when `exclude_from_grouping` is true', () => {
const stackframes = [
{
library_frame: false,
exclude_from_grouping: false,
filename: 'file-a.txt'
},
{
library_frame: false,
exclude_from_grouping: true,
filename: 'file-b.txt'
}
] as Stackframe[];
const result = getGroupedStackframes(stackframes);
expect(result).toEqual([
{
isLibraryFrame: false,
stackframes: [
{
exclude_from_grouping: false,
filename: 'file-a.txt',
library_frame: false
}
]
},
{
isLibraryFrame: false,
stackframes: [
{
exclude_from_grouping: true,
filename: 'file-b.txt',
library_frame: false
}
]
}
]);
});
it('should handle empty stackframes', () => {
const result = getGroupedStackframes([] as Stackframe[]);
expect(result).toHaveLength(0);
});
it('should handle one stackframe', () => {
const result = getGroupedStackframes([
stacktracesMock[0]
] as Stackframe[]);
expect(result).toHaveLength(1);
expect(result[0].stackframes).toHaveLength(1);
});
});

View file

@ -1,67 +1,416 @@
[
{
"library_frame": false,
"exclude_from_grouping": false,
"filename": "server/routes.js",
"abs_path": "/app/server/routes.js",
"line": {
"number": 307,
"context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())"
},
"function": "<anonymous>",
"libraryFrame": false,
"excludeFromGrouping": false,
"context": {
"pre": ["", "app.get('/log-error', function (req, res) {"],
"pre": [
" })",
""
],
"post": [
" if (err) {",
" res.status(500).send('could not capture error: ' + err.message)"
" req.body.lines.forEach(function (line) {",
" client.query('SELECT id FROM products WHERE id=$1', [line.id], next())"
]
},
"line": {
"number": 17,
"context": " apm.captureError(new Error('foo'), function (err) {"
},
"filename": "server/coffee.js",
"absPath": "/app/server/coffee.js"
}
},
{
"function": "get",
"libraryFrame": true,
"excludeFromGrouping": false,
"context": {},
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/pg-pool/index.js",
"abs_path": "/app/node_modules/pg-pool/index.js",
"line": {
"number": 123,
"context": ""
"number": 137
},
"filename": "express/get.js",
"absPath": "/node_modules/express/get.js"
"function": "BoundPool.<anonymous>"
},
{
"function": "use",
"libraryFrame": true,
"excludeFromGrouping": false,
"context": {
"pre": ["", "app.use('/log-error', function (req, res) {"],
"post": [
" if (err) {",
" res.status(500).send('could not capture error: ' + err.message)"
]
},
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/generic-pool/lib/generic-pool.js",
"abs_path": "/app/node_modules/generic-pool/lib/generic-pool.js",
"line": {
"number": 234,
"context": " apm.captureError(new Error('foo'), function (err) {"
"number": 310
},
"filename": "express/use.js",
"absPath": "/node_modules/express/use.js"
"function": "dispense"
},
{
"function": "handleCoffee",
"libraryFrame": false,
"excludeFromGrouping": false,
"context": {
"pre": ["", ""],
"post": [
" reply(getCoffee(req.id));"
]
},
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/generic-pool/lib/generic-pool.js",
"abs_path": "/app/node_modules/generic-pool/lib/generic-pool.js",
"line": {
"number": 45,
"context": " handleCoffee(req => {"
"number": 391
},
"filename": "server/handleCoffee.js",
"absPath": "/app/server/handleCoffee.js"
"function": "acquire"
},
{
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/pg-pool/index.js",
"abs_path": "/app/node_modules/pg-pool/index.js",
"line": {
"number": 111
},
"function": "BoundPool.<anonymous>"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/pg-pool/index.js",
"abs_path": "/app/node_modules/pg-pool/index.js",
"line": {
"number": 75
},
"function": "Pool._promiseNoCallback"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/pg-pool/index.js",
"abs_path": "/app/node_modules/pg-pool/index.js",
"line": {
"number": 109
},
"function": "Pool.connect"
},
{
"library_frame": false,
"exclude_from_grouping": false,
"filename": "server/db.js",
"abs_path": "/app/server/db.js",
"line": {
"number": 11,
"context": " pool.connect(cb)"
},
"function": "exports.client",
"context": {
"pre": [
"",
"exports.client = function (cb) {"
],
"post": [
"}",
""
]
}
},
{
"library_frame": false,
"exclude_from_grouping": false,
"filename": "server/routes.js",
"abs_path": "/app/server/routes.js",
"line": {
"number": 248,
"context": " db.client(function (err, client, done) {"
},
"function": "<anonymous>",
"context": {
"pre": [
" }",
""
],
"post": [
" if (err) return error(err, res)",
""
]
}
},
{
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/express/lib/router/layer.js",
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"line": {
"number": 95
},
"function": "handle"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/route.js",
"abs_path": "/app/node_modules/express/lib/router/route.js",
"line": {
"number": 137
},
"function": "next"
},
{
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/express/lib/router/route.js",
"abs_path": "/app/node_modules/express/lib/router/route.js",
"line": {
"number": 112
},
"function": "dispatch"
},
{
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/express/lib/router/layer.js",
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"line": {
"number": 95
},
"function": "handle"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"abs_path": "/app/node_modules/express/lib/router/index.js",
"filename": "node_modules/express/lib/router/index.js",
"line": {
"number": 281
},
"function": "<anonymous>"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 335
},
"function": "process_params"
},
{
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 275
},
"function": "next"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 174
},
"function": "handle"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 47
},
"function": "router"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/layer.js",
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"line": {
"number": 95
},
"function": "handle"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 317
},
"function": "trim_prefix"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 284
},
"function": "<anonymous>"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"abs_path": "/app/node_modules/express/lib/router/index.js",
"filename": "node_modules/express/lib/router/index.js",
"line": {
"number": 335
},
"function": "process_params"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 275
},
"function": "next"
},
{
"exclude_from_grouping": false,
"library_frame": false,
"filename": "server.js",
"abs_path": "/app/server.js",
"line": {
"number": 88,
"context": " return next()"
},
"function": "<anonymous>",
"context": {
"pre": [
"app.use('/api', function (req, res, next) {",
" if (Math.random() > opbeansRedirectProbability) {"
],
"post": [
" }",
""
]
}
},
{
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/express/lib/router/layer.js",
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"line": {
"number": 95
},
"function": "handle"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 317
},
"function": "trim_prefix"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 284
},
"function": "<anonymous>"
},
{
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 335
},
"function": "process_params"
},
{
"library_frame": true,
"exclude_from_grouping": true,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 275
},
"function": "next"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 635
},
"function": "<anonymous>"
},
{
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 260
},
"function": "next"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 174
},
"function": "handle"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 47
},
"function": "router"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/layer.js",
"abs_path": "/app/node_modules/express/lib/router/layer.js",
"line": {
"number": 95
},
"function": "handle"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 317
},
"function": "trim_prefix"
},
{
"exclude_from_grouping": false,
"library_frame": true,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 284
},
"function": "<anonymous>"
},
{
"library_frame": true,
"exclude_from_grouping": false,
"filename": "node_modules/express/lib/router/index.js",
"abs_path": "/app/node_modules/express/lib/router/index.js",
"line": {
"number": 335
},
"function": "process_params"
}
]

View file

@ -7,34 +7,29 @@
import { EuiTitle } from '@elastic/eui';
import { isEmpty } from 'lodash';
import React, { PureComponent } from 'react';
import { Stackframe } from '../../../../typings/APMDoc';
import { CodePreview } from '../../shared/CodePreview';
import { EmptyMessage } from '../../shared/EmptyMessage';
// @ts-ignore
import { Ellipsis } from '../../shared/Icons';
import { FrameHeading } from './FrameHeading';
import { LibraryFrames } from './LibraryFrames';
import {
getCollapsedLibraryFrames,
hasSourceLines,
StackframeCollapsed
} from './stacktraceUtils';
import { LibraryStackFrames } from './LibraryStackFrames';
import { getGroupedStackframes, hasSourceLines } from './stacktraceUtils';
interface Props {
stackframes?: StackframeCollapsed[];
stackframes?: Stackframe[];
codeLanguage?: string;
}
interface StateLibraryframes {
[i: number]: boolean;
}
interface State {
libraryframes: StateLibraryframes;
visibilityMap: {
[i: number]: boolean;
};
}
export class Stacktrace extends PureComponent<Props, State> {
public state = {
libraryframes: {}
visibilityMap: {}
};
public componentDidMount() {
@ -44,23 +39,23 @@ export class Stacktrace extends PureComponent<Props, State> {
}
const hasAnyAppFrames = this.props.stackframes.some(
frame => !frame.libraryFrame
frame => !frame.library_frame
);
if (!hasAnyAppFrames) {
// If there are no app frames available, always show the only existing group
this.setState({ libraryframes: { 0: true } });
this.setState({ visibilityMap: { 0: true } });
}
}
public toggle = (i: number) =>
this.setState(({ libraryframes }) => {
return { libraryframes: { ...libraryframes, [i]: !libraryframes[i] } };
this.setState(({ visibilityMap }) => {
return { visibilityMap: { ...visibilityMap, [i]: !visibilityMap[i] } };
});
public render() {
const { stackframes = [], codeLanguage } = this.props;
const { libraryframes } = this.state as State;
const { visibilityMap } = this.state as State;
if (isEmpty(stackframes)) {
return <EmptyMessage heading="No stacktrace available." hideSubheading />;
@ -71,30 +66,32 @@ export class Stacktrace extends PureComponent<Props, State> {
<EuiTitle size="xs">
<h3>Stack traces</h3>
</EuiTitle>
{getCollapsedLibraryFrames(stackframes).map((item, i) => {
if (!item.libraryFrame) {
if (hasSourceLines(item)) {
{getGroupedStackframes(stackframes).map(
({ isLibraryFrame, stackframes: groupedStackframes }, i) => {
if (isLibraryFrame) {
return (
<CodePreview
<LibraryStackFrames
key={i}
stackframe={item}
visible={visibilityMap[i]}
stackframes={groupedStackframes}
codeLanguage={codeLanguage}
onClick={() => this.toggle(i)}
/>
);
}
return <FrameHeading key={i} stackframe={item} />;
return groupedStackframes.map((stackframe, idx) =>
hasSourceLines(stackframe) ? (
<CodePreview
key={idx}
stackframe={stackframe}
codeLanguage={codeLanguage}
/>
) : (
<FrameHeading key={idx} stackframe={stackframe} />
)
);
}
return (
<LibraryFrames
key={i}
visible={libraryframes[i]}
stackframes={item.stackframes || []}
codeLanguage={codeLanguage}
onClick={() => this.toggle(i)}
/>
);
})}
)}
</div>
);
}

View file

@ -7,35 +7,38 @@
import { get, isEmpty } from 'lodash';
import { Stackframe } from '../../../../typings/APMDoc';
export interface StackframeCollapsed extends Stackframe {
libraryFrame?: boolean;
stackframes?: Stackframe[];
interface StackframesGroup {
isLibraryFrame: boolean;
stackframes: Stackframe[];
}
export function getCollapsedLibraryFrames(
function getNextGroupIndex(stackframes: Stackframe[]): number {
const isLibraryFrame = Boolean(stackframes[0].library_frame);
const groupEndIndex =
stackframes.findIndex(
stackframe =>
isLibraryFrame !== Boolean(stackframe.library_frame) ||
Boolean(stackframe.exclude_from_grouping)
) || 1;
return groupEndIndex === -1 ? stackframes.length : groupEndIndex;
}
export function getGroupedStackframes(
stackframes: Stackframe[]
): StackframeCollapsed[] {
return stackframes.reduce((acc: any, stackframe: StackframeCollapsed) => {
if (!stackframe.libraryFrame) {
return [...acc, stackframe];
}
): StackframesGroup[] {
if (stackframes.length === 0) {
return [];
}
// current stackframe is library frame
const prevItem: StackframeCollapsed = acc[acc.length - 1];
if (!get(prevItem, 'libraryFrame')) {
return [...acc, { libraryFrame: true, stackframes: [stackframe] }];
}
const nextGroupIndex = getNextGroupIndex(stackframes);
return [
...acc.slice(0, -1),
{
...prevItem,
stackframes: prevItem.stackframes
? [...prevItem.stackframes, stackframe]
: [stackframe]
}
];
}, []);
return [
{
isLibraryFrame: Boolean(stackframes[0].library_frame),
stackframes: stackframes.slice(0, nextGroupIndex)
},
...getGroupedStackframes(stackframes.slice(nextGroupIndex))
];
}
export function hasSourceLines(stackframe: Stackframe) {