From b4e28a23b7fd2dc69a6e34b04405569c0102f59b Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Tue, 7 Jan 2025 17:09:16 +0000 Subject: [PATCH 01/20] perf: switch to monomorphic object for LogLine --- log-viewer/modules/Database.ts | 16 +- .../modules/__tests__/ApexLogParser.test.ts | 94 ++++++----- .../components/calltree-view/CalltreeView.ts | 4 +- .../calltree-view/module/MiddleRowFocus.ts | 4 +- .../database-view/DatabaseSection.ts | 8 +- log-viewer/modules/parsers/ApexLogParser.ts | 147 ++++++++---------- log-viewer/modules/timeline/Timeline.ts | 11 +- 7 files changed, 129 insertions(+), 155 deletions(-) diff --git a/log-viewer/modules/Database.ts b/log-viewer/modules/Database.ts index 2e0c1118..ed9ea41c 100644 --- a/log-viewer/modules/Database.ts +++ b/log-viewer/modules/Database.ts @@ -1,9 +1,9 @@ /* * Copyright (c) 2020 Certinia Inc. All rights reserved. */ -import { ApexLog, DMLBeginLine, Method, SOQLExecuteBeginLine } from './parsers/ApexLogParser.js'; +import { ApexLog, DMLBeginLine, LogLine, SOQLExecuteBeginLine } from './parsers/ApexLogParser.js'; -export type Stack = Method[]; +export type Stack = LogLine[]; export class DatabaseAccess { private static _instance: DatabaseAccess | null = null; @@ -23,13 +23,13 @@ export class DatabaseAccess { public getStack( timestamp: number, stack: Stack = [], - line: Method = DatabaseAccess._treeRoot, + line: LogLine = DatabaseAccess._treeRoot, ): Stack { const children = line.children; const len = children.length; for (let i = 0; i < len; ++i) { const child = children[i]; - if (child instanceof Method) { + if (child?.exitTypes.length) { stack.push(child); if (child.timestamp === timestamp) { return stack; @@ -45,7 +45,7 @@ export class DatabaseAccess { return []; } - public getSOQLLines(line: Method = DatabaseAccess._treeRoot): SOQLExecuteBeginLine[] { + public getSOQLLines(line: LogLine = DatabaseAccess._treeRoot): SOQLExecuteBeginLine[] { const results: SOQLExecuteBeginLine[] = []; const children = line.children; @@ -56,7 +56,7 @@ export class DatabaseAccess { results.push(child); } - if (child instanceof Method) { + if (child?.exitTypes.length) { Array.prototype.push.apply(results, this.getSOQLLines(child)); } } @@ -64,7 +64,7 @@ export class DatabaseAccess { return results; } - public getDMLLines(line: Method = DatabaseAccess._treeRoot): DMLBeginLine[] { + public getDMLLines(line: LogLine = DatabaseAccess._treeRoot): DMLBeginLine[] { const results: DMLBeginLine[] = []; const children = line.children; @@ -75,7 +75,7 @@ export class DatabaseAccess { results.push(child); } - if (child instanceof Method) { + if (child?.exitTypes.length) { // results = results.concat(this.getDMLLines(child)); Array.prototype.push.apply(results, this.getDMLLines(child)); } diff --git a/log-viewer/modules/__tests__/ApexLogParser.test.ts b/log-viewer/modules/__tests__/ApexLogParser.test.ts index c1cb2cca..eb19f4fe 100644 --- a/log-viewer/modules/__tests__/ApexLogParser.test.ts +++ b/log-viewer/modules/__tests__/ApexLogParser.test.ts @@ -10,7 +10,6 @@ import { MethodEntryLine, SOQLExecuteBeginLine, SOQLExecuteExplainLine, - TimedNode, lineTypeMap, parse, parseObjectNamespace, @@ -79,14 +78,14 @@ describe('Pseudo EXIT events', () => { expect(log1.children.length).toEqual(4); expect(log1.duration).toEqual({ self: 0, total: 3 }); - const approval1 = log1.children[0] as Method; + const approval1 = log1.children[0]; expect(approval1).toMatchObject({ type: 'WF_APPROVAL_SUBMIT', timestamp: 1, duration: { self: 1, total: 1 }, }); - const processFound1 = log1.children[1] as Method; + const processFound1 = log1.children[1]; expect(processFound1).toMatchObject({ parent: log1, type: 'WF_PROCESS_FOUND', @@ -94,7 +93,7 @@ describe('Pseudo EXIT events', () => { duration: { self: 1, total: 1 }, }); - const approval2 = log1.children[2] as Method; + const approval2 = log1.children[2]; expect(approval2).toMatchObject({ parent: log1, type: 'WF_APPROVAL_SUBMIT', @@ -102,7 +101,7 @@ describe('Pseudo EXIT events', () => { duration: { self: 1, total: 1 }, }); - const processFound2 = log1.children[3] as Method; + const processFound2 = log1.children[3]; expect(processFound2).toMatchObject({ parent: log1, type: 'WF_PROCESS_FOUND', @@ -125,22 +124,22 @@ describe('Pseudo EXIT events', () => { expect(log1.children.length).toEqual(1); expect(log1.duration).toEqual({ self: 0, total: 6 }); - const children = (log1.children[0] as Method).children; + const children = log1.children[0]?.children ?? []; expect(children.length).toEqual(4); - const child1 = children[0] as Method; + const child1 = children[0]!; expect(child1.timestamp).toEqual(2); expect(child1.exitStamp).toEqual(3); - const child2 = children[1] as Method; + const child2 = children[1]!; expect(child2.timestamp).toEqual(3); expect(child2.exitStamp).toEqual(4); - const child3 = children[2] as Method; + const child3 = children[2]!; expect(child3.timestamp).toEqual(4); expect(child3.exitStamp).toEqual(5); - const child4 = children[3] as Method; + const child4 = children[3]!; expect(child4.timestamp).toEqual(5); expect(child4.exitStamp).toEqual(6); }); @@ -157,18 +156,18 @@ describe('Pseudo EXIT events', () => { expect(log1.children.length).toEqual(1); expect(log1.duration).toEqual({ self: 0, total: 4 }); - const children = (log1.children[0] as Method).children; + const children = log1.children[0]?.children ?? []; expect(children.length).toEqual(3); - const child1 = children[0] as Method; + const child1 = children[0]!; expect(child1.timestamp).toEqual(2); expect(child1.exitStamp).toEqual(3); - const child2 = children[1] as Method; + const child2 = children[1]!; expect(child2.timestamp).toEqual(3); expect(child2.exitStamp).toEqual(4); - const child3 = children[2] as Method; + const child3 = children[2]!; expect(child3.timestamp).toEqual(4); expect(child3.exitStamp).toEqual(5); }); @@ -204,7 +203,7 @@ describe('parseLog tests', () => { expect(logLines.length).toEqual(1); expect(logLines[0]).toBeInstanceOf(ExecutionStartedLine); - const firstChildren = (logLines[0] as Method).children; + const firstChildren = logLines[0]?.children ?? []; expect(firstChildren.length).toEqual(1); expect(firstChildren[0]).toBeInstanceOf(CodeUnitStartedLine); }); @@ -222,7 +221,7 @@ describe('parseLog tests', () => { expect(apexLog.children.length).toEqual(1); expect(apexLog.children[0]).toBeInstanceOf(ExecutionStartedLine); - const firstChildren = (apexLog.children[0] as Method).children; + const firstChildren = apexLog.children[0]?.children ?? []; expect(firstChildren.length).toEqual(1); expect(firstChildren[0]).toBeInstanceOf(CodeUnitStartedLine); }); @@ -237,7 +236,7 @@ describe('parseLog tests', () => { expect(apexLog.children.length).toBe(1); expect(apexLog.children[0]).toBeInstanceOf(ExecutionStartedLine); - const firstChildren = (apexLog.children[0] as Method).children; + const firstChildren = apexLog.children[0]?.children ?? []; expect(firstChildren[0]).toBeInstanceOf(CodeUnitStartedLine); }); @@ -406,7 +405,7 @@ describe('parseLog tests', () => { const apexLog = parse(log); - const methods = apexLog.children as Method[]; + const methods = apexLog.children; expect(methods.length).toBe(24); methods.forEach((line) => { expect(line.exitTypes.length).toBe(0); @@ -463,7 +462,7 @@ describe('getRootMethod tests', () => { const apexLog = parse(log); - const timedLogLines = apexLog.children as TimedNode[]; + const timedLogLines = apexLog.children; expect(timedLogLines.length).toBe(1); const startLine = timedLogLines[0]; expect(startLine?.type).toBe('EXECUTION_STARTED'); @@ -477,7 +476,7 @@ describe('getRootMethod tests', () => { }); expect(unitStart.children.length).toBe(1); - const interViewsBegin = unitStart.children[0] as TimedNode; + const interViewsBegin = unitStart.children[0]!; expect(interViewsBegin).toMatchObject({ parent: unitStart, type: 'FLOW_START_INTERVIEWS_BEGIN', @@ -507,7 +506,7 @@ describe('getRootMethod tests', () => { const apexLog = parse(log); - const timedLogLines = apexLog.children as TimedNode[]; + const timedLogLines = apexLog.children; expect(timedLogLines.length).toBe(1); const startLine = timedLogLines[0]; expect(startLine?.type).toBe('EXECUTION_STARTED'); @@ -518,7 +517,7 @@ describe('getRootMethod tests', () => { expect(unitStart.codeUnitType).toBe('Flow'); expect(unitStart.children.length).toBe(1); - const interViewsBegin = unitStart.children[0] as TimedNode; + const interViewsBegin = unitStart.children[0]!; expect(interViewsBegin.type).toBe('FLOW_START_INTERVIEWS_BEGIN'); expect(interViewsBegin.text).toBe('FLOW_START_INTERVIEWS : Example Flow'); expect(interViewsBegin.suffix).toBe(' (Flow)'); @@ -546,7 +545,7 @@ describe('getRootMethod tests', () => { const apexLog = parse(log); - const timedLogLines = apexLog.children as TimedNode[]; + const timedLogLines = apexLog.children; expect(timedLogLines.length).toBe(1); const startLine = timedLogLines[0]; expect(startLine?.type).toBe('EXECUTION_STARTED'); @@ -557,17 +556,17 @@ describe('getRootMethod tests', () => { expect(unitStart.codeUnitType).toBe('Workflow'); expect(unitStart.children.length).toBe(1); - const pbBegin = unitStart.children[0] as TimedNode; + const pbBegin = unitStart.children[0]!; expect(pbBegin.type).toBe('FLOW_START_INTERVIEWS_BEGIN'); expect(pbBegin.text).toBe('FLOW_START_INTERVIEWS : Example Process Builder'); expect(pbBegin.suffix).toBe(' (Process Builder)'); expect(pbBegin.children.length).toBe(1); - const pbDetail = pbBegin.children[0] as TimedNode; + const pbDetail = pbBegin.children[0]!; expect(pbDetail.type).toBe('FLOW_START_INTERVIEW_BEGIN'); expect(pbDetail.text).toBe('Example Process Builder'); - const interViewsBegin = pbDetail.children[0] as TimedNode; + const interViewsBegin = pbDetail.children[0]!; expect(interViewsBegin.type).toBe('FLOW_START_INTERVIEWS_BEGIN'); expect(interViewsBegin.text).toBe('FLOW_START_INTERVIEWS : Example Flow'); expect(interViewsBegin.suffix).toBe(' (Flow)'); @@ -642,9 +641,9 @@ describe('getRootMethod tests', () => { expect(apexLog.exitStamp).toBe(1100); expect(apexLog.executionEndTime).toBe(1100); - const rootChildren = apexLog.children as Method[]; + const rootChildren = apexLog.children; const executionStarted = rootChildren[0]; - const executionChildren = executionStarted?.children as Method[]; + const executionChildren = executionStarted?.children ?? []; expect(executionChildren.length).toBe(5); expect(executionChildren[0]).toMatchObject({ @@ -1138,27 +1137,26 @@ describe('Line Type Tests', () => { '', 'Rows:5', ]) as LogLine; - if (line instanceof Method) { - expect(line.exitTypes).not.toBe(null); - if (line.isExit) { - expect(line.exitTypes).toEqual([key]); - } - line.exitTypes.forEach((exitType) => { - const exitCls = lineTypeMap.get(exitType); - expect(exitCls).not.toBe(null); - if (exitCls) { - const exitLine = new exitCls!(parser, [ - '14:32:07.563 (17358806534)', - 'DUMMY', - '[10]', - 'Rows:3', - '', - 'Rows:5', - ]) as LogLine; - expect(exitLine.isExit).toBe(true); - } - }); + + expect(line.exitTypes).not.toBe(null); + if (line.isExit) { + expect(line.exitTypes).toEqual([key]); } + line.exitTypes.forEach((exitType) => { + const exitCls = lineTypeMap.get(exitType); + expect(exitCls).not.toBe(null); + if (exitCls) { + const exitLine = new exitCls!(parser, [ + '14:32:07.563 (17358806534)', + 'DUMMY', + '[10]', + 'Rows:3', + '', + 'Rows:5', + ]) as LogLine; + expect(exitLine.isExit).toBe(true); + } + }); } }); diff --git a/log-viewer/modules/components/calltree-view/CalltreeView.ts b/log-viewer/modules/components/calltree-view/CalltreeView.ts index 1f3bc34c..79011676 100644 --- a/log-viewer/modules/components/calltree-view/CalltreeView.ts +++ b/log-viewer/modules/components/calltree-view/CalltreeView.ts @@ -27,7 +27,7 @@ import { progressFormatter } from '../../datagrid/format/Progress.js'; import { RowKeyboardNavigation } from '../../datagrid/module/RowKeyboardNavigation.js'; import { RowNavigation } from '../../datagrid/module/RowNavigation.js'; import dataGridStyles from '../../datagrid/style/DataGrid.scss'; -import { ApexLog, LogLine, TimedNode, type LogEventType } from '../../parsers/ApexLogParser.js'; +import { ApexLog, LogLine, type LogEventType } from '../../parsers/ApexLogParser.js'; import { vscodeMessenger } from '../../services/VSCodeExtensionMessenger.js'; import { globalStyles } from '../../styles/global.styles.js'; import { isVisible } from '../../Util.js'; @@ -858,7 +858,7 @@ export class CalltreeView extends LitElement { if (!row) { break; } - const node = (row.getData() as CalltreeRow).originalData as TimedNode; + const node = (row.getData() as CalltreeRow).originalData as LogLine; // Return True if the element is present in the middle. const endTime = node.exitStamp ?? node.timestamp; diff --git a/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts b/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts index 51b677eb..a350494c 100644 --- a/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts +++ b/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts @@ -2,9 +2,9 @@ * Copyright (c) 2024 Certinia Inc. All rights reserved. */ import { Module, type RowComponent, type Tabulator } from 'tabulator-tables'; -import type { TimedNode } from '../../../parsers/ApexLogParser'; +import type { LogLine } from '../../../parsers/ApexLogParser'; -type TimedNodeProp = { originalData: TimedNode }; +type TimedNodeProp = { originalData: LogLine }; const middleRowFocusOption = 'middleRowFocus' as const; /** diff --git a/log-viewer/modules/components/database-view/DatabaseSection.ts b/log-viewer/modules/components/database-view/DatabaseSection.ts index ea8089d6..02c081d4 100644 --- a/log-viewer/modules/components/database-view/DatabaseSection.ts +++ b/log-viewer/modules/components/database-view/DatabaseSection.ts @@ -4,7 +4,7 @@ import { LitElement, css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { Method } from '../../parsers/ApexLogParser.js'; +import { LogLine } from '../../parsers/ApexLogParser.js'; import { globalStyles } from '../../styles/global.styles.js'; import '../BadgeBase.js'; @@ -13,7 +13,7 @@ export class DatabaseSection extends LitElement { @property({ type: String }) title = ''; @property({ type: Object, attribute: false }) - dbLines: Method[] = []; + dbLines: LogLine[] = []; static styles = [ globalStyles, @@ -35,8 +35,8 @@ export class DatabaseSection extends LitElement { render() { const totalCount = this.dbLines.length; let totalRows = 0; - this.dbLines.forEach((value) => { - totalRows += value.rowCount.self || 0; + this.dbLines.forEach((dbLine) => { + totalRows += dbLine.rowCount.self || 0; }); return html` diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index 14b1b604..265d81f0 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -9,6 +9,7 @@ type LineNumber = number | string | null; // an actual line-number or 'EXTERNAL' type IssueType = 'unexpected' | 'error' | 'skip'; export type LogSubCategory = + | '' | 'Method' | 'System Method' | 'Code Unit' @@ -157,17 +158,17 @@ export class ApexLogParser { private toLogTree(lineGenerator: Generator) { const rootMethod = new ApexLog(this), - stack: Method[] = []; + stack: LogLine[] = []; let line: LogLine | null; const lineIter = new LineIterator(lineGenerator); while ((line = lineIter.fetch())) { - if (line instanceof Method) { + if (line.exitTypes.length) { this.parseTree(line, lineIter, stack); } line.parent = rootMethod; - rootMethod.addChild(line); + rootMethod.children.push(line); } rootMethod.setTimes(); @@ -177,7 +178,7 @@ export class ApexLogParser { return rootMethod; } - private parseTree(currentLine: Method, lineIter: LineIterator, stack: Method[]) { + private parseTree(currentLine: LogLine, lineIter: LineIterator, stack: LogLine[]) { this.lastTimestamp = currentLine.timestamp; currentLine.namespace ||= 'default'; @@ -224,7 +225,7 @@ export class ApexLogParser { lineIter.fetch(); // it's a child - consume the line this.lastTimestamp = nextLine.timestamp; - if (nextLine instanceof Method) { + if (nextLine.exitTypes.length) { this.parseTree(nextLine, lineIter, stack); } @@ -263,7 +264,7 @@ export class ApexLogParser { } } - private isMatchingEnd(startMethod: Method, endLine: LogLine) { + private isMatchingEnd(startMethod: LogLine, endLine: LogLine) { return ( endLine.type && startMethod.exitTypes.includes(endLine.type) && @@ -274,10 +275,10 @@ export class ApexLogParser { } private endMethod( - startMethod: Method, + startMethod: LogLine, endLine: LogLine, lineIter: LineIterator, - stack: Method[], + stack: LogLine[], ) { startMethod.exitStamp = endLine.timestamp; @@ -359,40 +360,41 @@ export class ApexLogParser { } } - private insertPackageWrappers(node: Method) { + private insertPackageWrappers(node: LogLine) { const children = node.children; - let lastPkg: TimedNode | null = null; + let lastPkg: LogLine | null = null; const newChildren: LogLine[] = []; const len = children.length; for (let i = 0; i < len; i++) { const child = children[i]; - if (child) { - const isPkgType = child.type === 'ENTERING_MANAGED_PKG'; - if (lastPkg && child instanceof TimedNode) { - if (isPkgType && child.namespace === lastPkg.namespace) { - // combine adjacent (like) packages - lastPkg.exitStamp = child.exitStamp || child.timestamp; - continue; // skip any more child processing (it's gone) - } else if (!isPkgType) { - // we are done merging adjacent `ENTERING_MANAGED_PKG` of the same namesapce - lastPkg.recalculateDurations(); - lastPkg = null; - } + if (!child) { + continue; + } + const isPkgType = child.type === 'ENTERING_MANAGED_PKG'; + if (lastPkg && child.exitStamp) { + if (isPkgType && child.namespace === lastPkg.namespace) { + // combine adjacent (like) packages + lastPkg.exitStamp = child.exitStamp || child.timestamp; + continue; // skip any more child processing (it's gone) + } else if (!isPkgType) { + // we are done merging adjacent `ENTERING_MANAGED_PKG` of the same namesapce + lastPkg.recalculateDurations(); + lastPkg = null; } + } - if (child instanceof Method) { - this.insertPackageWrappers(child); - } + if (child.exitTypes) { + this.insertPackageWrappers(child); + } - // It is a ENTERING_MANAGED_PKG line that does not match the last one - // or we have not come across a ENTERING_MANAGED_PKG line yet. - if (isPkgType) { - lastPkg?.recalculateDurations(); - lastPkg = child as TimedNode; - } - newChildren.push(child); + // It is a ENTERING_MANAGED_PKG line that does not match the last one + // or we have not come across a ENTERING_MANAGED_PKG line yet. + if (isPkgType) { + lastPkg?.recalculateDurations(); + lastPkg = child; } + newChildren.push(child); } lastPkg?.recalculateDurations(); @@ -528,6 +530,11 @@ export abstract class LogLine { */ isExit = false; + /** + * Whether the log event was truncated when the log ended, e,g no matching end event + */ + isTruncated = false; + /** * Should the exitstamp be the timestamp of the next line? * These kind of lines can not be used as exit lines for anything othe than other pseudo exits. @@ -570,6 +577,21 @@ export abstract class LogLine { */ timestamp; + /** + * The timestamp when the node finished, in nanoseconds + */ + exitStamp: number | null = null; + + /** + * The log sub category this event belongs to + */ + subCategory: LogSubCategory = ''; + + /** + * The CPU type, e.g loading, method, custom + */ + cpuType: CPUType = ''; // the category key to collect our cpu usage + /** * The time spent. */ @@ -648,6 +670,12 @@ export abstract class LogLine { /** Called when the Log event after this one is created in the line parser*/ onAfter?(parser: ApexLogParser, next?: LogLine): void; + public recalculateDurations() { + if (this.exitStamp) { + this.duration.total = this.duration.self = this.exitStamp - this.timestamp; + } + } + private parseTimestamp(text: string): number { const start = text.indexOf('('); if (start !== -1) { @@ -680,60 +708,13 @@ class BasicExitLine extends LogLine { type CPUType = 'loading' | 'custom' | 'method' | 'free' | 'system' | 'pkg' | ''; -/** - * Log lines extend this class if they have a duration (and hence can be shown on the timeline). - * There are no real children (as there is no exit line), but children can get reparented here... - */ -export class TimedNode extends LogLine { - /** - * The timestamp when the node finished, in nanoseconds - */ - exitStamp: number | null = null; - - /** - * The log sub category this event belongs to - */ - subCategory: LogSubCategory; - - /** - * The CPU type, e.g loading, method, custom - */ - cpuType: CPUType; // the category key to collect our cpu usage - - constructor( - parser: ApexLogParser, - parts: string[] | null, - timelineKey: LogSubCategory, - cpuType: CPUType, - ) { - super(parser, parts); - this.subCategory = timelineKey; - this.cpuType = cpuType; - } - - addChild(line: LogLine) { - this.children.push(line); - } - - recalculateDurations() { - if (this.exitStamp) { - this.duration.total = this.duration.self = this.exitStamp - this.timestamp; - } - } -} - /** * Log lines extend this class if they have a start-line and an end-line (and hence can have children in-between). * - The start-line should extend "Method" and collect any children. * - The end-line should extend "Detail" and terminate the method (also providing the "exitStamp"). * The method will be rendered as "expandable" in the tree-view, if it has children. */ -export class Method extends TimedNode { - /** - * Whether the log event was truncated when the log ended, e,g no matching end event - */ - isTruncated = false; - +export class Method extends LogLine { constructor( parser: ApexLogParser, parts: string[] | null, @@ -741,7 +722,9 @@ export class Method extends TimedNode { timelineKey: LogSubCategory, cpuType: CPUType, ) { - super(parser, parts, timelineKey, cpuType); + super(parser, parts); + this.subCategory = timelineKey; + this.cpuType = cpuType; this.exitTypes = exitTypes as LogEventType[]; } } @@ -806,7 +789,7 @@ export class ApexLog extends Method { for (let i = reverseLen; i >= 0; i--) { const child = this.children[i]; // If there is no duration on a node then it is not going to be shown on the timeline anyway - if (child instanceof TimedNode && child.exitStamp) { + if (child?.exitStamp) { endTime ??= child.exitStamp; if (child.duration) { this.executionEndTime = child.exitStamp; diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index 130f3780..c5b3969e 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -8,7 +8,6 @@ import { ApexLog, LogLine, Method, - TimedNode, type LogIssue, type LogSubCategory, } from '../parsers/ApexLogParser.js'; @@ -660,7 +659,7 @@ function findTimelineTooltip( ): HTMLDivElement | null { const target = findByPosition(timelineRoot.children, 0, x, depth, shouldIgnoreWidth); - if (target && target instanceof TimedNode) { + if (target) { canvas.classList.remove('timeline-hover', 'timeline-dragging'); canvas.classList.add('timeline-event--hover'); @@ -816,13 +815,7 @@ function onClickCanvas(): void { const isClick = mouseDownPosition.x === lastMouseX && mouseDownPosition.y === lastMouseY; if (!dragging && isClick) { const depth = getDepth(lastMouseY); - let timeStamp = findByPosition( - timelineRoot.children as TimedNode[], - 0, - lastMouseX, - depth, - false, - )?.timestamp; + let timeStamp = findByPosition(timelineRoot.children, 0, lastMouseX, depth, false)?.timestamp; if (!timeStamp) { timeStamp = findLogIssue(lastMouseX)?.startTime; From 0b8839c97044c2ae025a6fff60d1d4a4ffd4d76c Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Tue, 7 Jan 2025 17:52:27 +0000 Subject: [PATCH 02/20] fix: package wrapper handlinmg when Enetering managed pkg event has no end time --- .../modules/__tests__/ApexLogParser.test.ts | 36 +++++++++---------- log-viewer/modules/parsers/ApexLogParser.ts | 4 +-- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/log-viewer/modules/__tests__/ApexLogParser.test.ts b/log-viewer/modules/__tests__/ApexLogParser.test.ts index eb19f4fe..34ec99fd 100644 --- a/log-viewer/modules/__tests__/ApexLogParser.test.ts +++ b/log-viewer/modules/__tests__/ApexLogParser.test.ts @@ -1128,7 +1128,7 @@ describe('Recalculate durations tests', () => { describe('Line Type Tests', () => { it('Lines referenced by exitTypes should be exits', () => { const parser = new ApexLogParser(); - for (const [key, lineType] of lineTypeMap) { + for (const lineType of Object.values(lineTypeMap)) { const line = new lineType(parser, [ '14:32:07.563 (17358806534)', 'DUMMY', @@ -1138,25 +1138,23 @@ describe('Line Type Tests', () => { 'Rows:5', ]) as LogLine; - expect(line.exitTypes).not.toBe(null); - if (line.isExit) { - expect(line.exitTypes).toEqual([key]); + if (line.exitTypes.length) { + line.exitTypes.forEach((exitType) => { + const exitCls = lineTypeMap.get(exitType); + expect(exitCls).not.toBe(null); + if (exitCls) { + const exitLine = new exitCls!(parser, [ + '14:32:07.563 (17358806534)', + 'DUMMY', + '[10]', + 'Rows:3', + '', + 'Rows:5', + ]) as LogLine; + expect(exitLine.isExit).toBe(true); + } + }); } - line.exitTypes.forEach((exitType) => { - const exitCls = lineTypeMap.get(exitType); - expect(exitCls).not.toBe(null); - if (exitCls) { - const exitLine = new exitCls!(parser, [ - '14:32:07.563 (17358806534)', - 'DUMMY', - '[10]', - 'Rows:3', - '', - 'Rows:5', - ]) as LogLine; - expect(exitLine.isExit).toBe(true); - } - }); } }); diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index 265d81f0..0eb4e372 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -372,12 +372,12 @@ export class ApexLogParser { continue; } const isPkgType = child.type === 'ENTERING_MANAGED_PKG'; - if (lastPkg && child.exitStamp) { + if (lastPkg) { if (isPkgType && child.namespace === lastPkg.namespace) { // combine adjacent (like) packages lastPkg.exitStamp = child.exitStamp || child.timestamp; continue; // skip any more child processing (it's gone) - } else if (!isPkgType) { + } else if (!isPkgType && child.exitStamp) { // we are done merging adjacent `ENTERING_MANAGED_PKG` of the same namesapce lastPkg.recalculateDurations(); lastPkg = null; From 79df1112318ba84f8e442c9e1caeae80c345ccef Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Tue, 7 Jan 2025 17:53:03 +0000 Subject: [PATCH 03/20] refactor: rename LogLine to LogEvent --- log-viewer/modules/Database.ts | 10 +- .../modules/__tests__/ApexLogParser.test.ts | 6 +- log-viewer/modules/components/CallStack.ts | 4 +- .../components/analysis-view/AnalysisView.ts | 10 +- .../column-calcs/CallStackSum.ts | 8 +- .../components/calltree-view/CalltreeView.ts | 16 +- .../calltree-view/module/MiddleRowFocus.ts | 4 +- .../database-view/DatabaseSection.ts | 4 +- log-viewer/modules/parsers/ApexLogParser.ts | 316 +++++++++--------- log-viewer/modules/timeline/Timeline.ts | 10 +- 10 files changed, 194 insertions(+), 194 deletions(-) diff --git a/log-viewer/modules/Database.ts b/log-viewer/modules/Database.ts index ed9ea41c..4e56ca9b 100644 --- a/log-viewer/modules/Database.ts +++ b/log-viewer/modules/Database.ts @@ -1,9 +1,9 @@ /* * Copyright (c) 2020 Certinia Inc. All rights reserved. */ -import { ApexLog, DMLBeginLine, LogLine, SOQLExecuteBeginLine } from './parsers/ApexLogParser.js'; +import { ApexLog, DMLBeginLine, LogEvent, SOQLExecuteBeginLine } from './parsers/ApexLogParser.js'; -export type Stack = LogLine[]; +export type Stack = LogEvent[]; export class DatabaseAccess { private static _instance: DatabaseAccess | null = null; @@ -23,7 +23,7 @@ export class DatabaseAccess { public getStack( timestamp: number, stack: Stack = [], - line: LogLine = DatabaseAccess._treeRoot, + line: LogEvent = DatabaseAccess._treeRoot, ): Stack { const children = line.children; const len = children.length; @@ -45,7 +45,7 @@ export class DatabaseAccess { return []; } - public getSOQLLines(line: LogLine = DatabaseAccess._treeRoot): SOQLExecuteBeginLine[] { + public getSOQLLines(line: LogEvent = DatabaseAccess._treeRoot): SOQLExecuteBeginLine[] { const results: SOQLExecuteBeginLine[] = []; const children = line.children; @@ -64,7 +64,7 @@ export class DatabaseAccess { return results; } - public getDMLLines(line: LogLine = DatabaseAccess._treeRoot): DMLBeginLine[] { + public getDMLLines(line: LogEvent = DatabaseAccess._treeRoot): DMLBeginLine[] { const results: DMLBeginLine[] = []; const children = line.children; diff --git a/log-viewer/modules/__tests__/ApexLogParser.test.ts b/log-viewer/modules/__tests__/ApexLogParser.test.ts index 34ec99fd..f4b2925a 100644 --- a/log-viewer/modules/__tests__/ApexLogParser.test.ts +++ b/log-viewer/modules/__tests__/ApexLogParser.test.ts @@ -5,7 +5,7 @@ import { ApexLogParser, CodeUnitStartedLine, ExecutionStartedLine, - LogLine, + LogEvent, Method, MethodEntryLine, SOQLExecuteBeginLine, @@ -1136,7 +1136,7 @@ describe('Line Type Tests', () => { 'Rows:3', '', 'Rows:5', - ]) as LogLine; + ]) as LogEvent; if (line.exitTypes.length) { line.exitTypes.forEach((exitType) => { @@ -1150,7 +1150,7 @@ describe('Line Type Tests', () => { 'Rows:3', '', 'Rows:5', - ]) as LogLine; + ]) as LogEvent; expect(exitLine.isExit).toBe(true); } }); diff --git a/log-viewer/modules/components/CallStack.ts b/log-viewer/modules/components/CallStack.ts index 72621f73..afab0e1a 100644 --- a/log-viewer/modules/components/CallStack.ts +++ b/log-viewer/modules/components/CallStack.ts @@ -5,7 +5,7 @@ import { LitElement, css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { DatabaseAccess } from '../Database.js'; -import { LogLine } from '../parsers/ApexLogParser.js'; +import { LogEvent } from '../parsers/ApexLogParser.js'; import { globalStyles } from '../styles/global.styles.js'; import { goToRow } from './calltree-view/CalltreeView.js'; @@ -77,7 +77,7 @@ export class CallStack extends LitElement { } } - private lineLink(line: LogLine) { + private lineLink(line: LogEvent) { return html` = new Map(); for (const child of root.children) { @@ -399,7 +399,7 @@ function groupMetrics(root: LogLine) { return Array.from(methodMap.values()); } -function addNodeToMap(map: Map, node: LogLine) { +function addNodeToMap(map: Map, node: LogEvent) { if (node.duration.total) { const key = node.namespace + node.text; let metric = map.get(key); diff --git a/log-viewer/modules/components/analysis-view/column-calcs/CallStackSum.ts b/log-viewer/modules/components/analysis-view/column-calcs/CallStackSum.ts index 436b3c0d..3b36c24c 100644 --- a/log-viewer/modules/components/analysis-view/column-calcs/CallStackSum.ts +++ b/log-viewer/modules/components/analysis-view/column-calcs/CallStackSum.ts @@ -1,12 +1,12 @@ -import type { LogLine } from '../../../parsers/ApexLogParser'; +import type { LogEvent } from '../../../parsers/ApexLogParser'; import { type Metric } from '../AnalysisView.js'; export function callStackSum(_values: number[], data: Metric[], _calcParams: unknown) { - const nodes: LogLine[] = []; + const nodes: LogEvent[] = []; for (const row of data) { Array.prototype.push.apply(nodes, row.nodes); } - const allNodes = new Set(nodes); + const allNodes = new Set(nodes); let total = 0; for (const node of nodes) { @@ -18,7 +18,7 @@ export function callStackSum(_values: number[], data: Metric[], _calcParams: unk return total; } -function _isChildOfOther(node: LogLine, filteredNodes: Set) { +function _isChildOfOther(node: LogEvent, filteredNodes: Set) { let parent = node.parent; while (parent) { if (filteredNodes.has(parent)) { diff --git a/log-viewer/modules/components/calltree-view/CalltreeView.ts b/log-viewer/modules/components/calltree-view/CalltreeView.ts index 79011676..cbc4a4fc 100644 --- a/log-viewer/modules/components/calltree-view/CalltreeView.ts +++ b/log-viewer/modules/components/calltree-view/CalltreeView.ts @@ -27,7 +27,7 @@ import { progressFormatter } from '../../datagrid/format/Progress.js'; import { RowKeyboardNavigation } from '../../datagrid/module/RowKeyboardNavigation.js'; import { RowNavigation } from '../../datagrid/module/RowNavigation.js'; import dataGridStyles from '../../datagrid/style/DataGrid.scss'; -import { ApexLog, LogLine, type LogEventType } from '../../parsers/ApexLogParser.js'; +import { ApexLog, LogEvent, type LogEventType } from '../../parsers/ApexLogParser.js'; import { vscodeMessenger } from '../../services/VSCodeExtensionMessenger.js'; import { globalStyles } from '../../styles/global.styles.js'; import { isVisible } from '../../Util.js'; @@ -219,7 +219,7 @@ export class CalltreeView extends LitElement { _findEvt = ((event: FindEvt) => this._find(event)) as EventListener; - _getAllTypes(data: LogLine[]): string[] { + _getAllTypes(data: LogEvent[]): string[] { const flattened = this._flatten(data); const types = new Set(); for (const line of flattened) { @@ -228,7 +228,7 @@ export class CalltreeView extends LitElement { return Array.from(types).sort(); } - _flat(arr: LogLine[], target: LogLine[]) { + _flat(arr: LogEvent[], target: LogEvent[]) { arr.forEach((el) => { target.push(el); if (el.children.length > 0) { @@ -237,8 +237,8 @@ export class CalltreeView extends LitElement { }); } - _flatten(arr: LogLine[]) { - const flattened: LogLine[] = []; + _flatten(arr: LogEvent[]) { + const flattened: LogEvent[] = []; this._flat(arr, flattened); return flattened; } @@ -811,7 +811,7 @@ export class CalltreeView extends LitElement { } } - private _toCallTree(nodes: LogLine[]): CalltreeRow[] | undefined { + private _toCallTree(nodes: LogEvent[]): CalltreeRow[] | undefined { const len = nodes.length; if (!len) { return undefined; @@ -858,7 +858,7 @@ export class CalltreeView extends LitElement { if (!row) { break; } - const node = (row.getData() as CalltreeRow).originalData as LogLine; + const node = (row.getData() as CalltreeRow).originalData as LogEvent; // Return True if the element is present in the middle. const endTime = node.exitStamp ?? node.timestamp; @@ -884,7 +884,7 @@ export class CalltreeView extends LitElement { interface CalltreeRow { id: string; - originalData: LogLine; + originalData: LogEvent; text: string; duration: number; namespace: string; diff --git a/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts b/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts index a350494c..fe58644a 100644 --- a/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts +++ b/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts @@ -2,9 +2,9 @@ * Copyright (c) 2024 Certinia Inc. All rights reserved. */ import { Module, type RowComponent, type Tabulator } from 'tabulator-tables'; -import type { LogLine } from '../../../parsers/ApexLogParser'; +import type { LogEvent } from '../../../parsers/ApexLogParser'; -type TimedNodeProp = { originalData: LogLine }; +type TimedNodeProp = { originalData: LogEvent }; const middleRowFocusOption = 'middleRowFocus' as const; /** diff --git a/log-viewer/modules/components/database-view/DatabaseSection.ts b/log-viewer/modules/components/database-view/DatabaseSection.ts index 02c081d4..846a9591 100644 --- a/log-viewer/modules/components/database-view/DatabaseSection.ts +++ b/log-viewer/modules/components/database-view/DatabaseSection.ts @@ -4,7 +4,7 @@ import { LitElement, css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { LogLine } from '../../parsers/ApexLogParser.js'; +import { LogEvent } from '../../parsers/ApexLogParser.js'; import { globalStyles } from '../../styles/global.styles.js'; import '../BadgeBase.js'; @@ -13,7 +13,7 @@ export class DatabaseSection extends LitElement { @property({ type: String }) title = ''; @property({ type: Object, attribute: false }) - dbLines: LogLine[] = []; + dbLines: LogEvent[] = []; static styles = [ globalStyles, diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index 0eb4e372..53396b41 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -64,7 +64,7 @@ export class ApexLogParser { return apexLog; } - private parseLine(line: string, lastEntry: LogLine | null): LogLine | null { + private parseLine(line: string, lastEntry: LogEvent | null): LogEvent | null { const parts = line.split('|'); const type = parts[1] ?? ''; @@ -113,7 +113,7 @@ export class ApexLogParser { return null; } - private *generateLogLines(log: string): Generator { + private *generateLogLines(log: string): Generator { const start = log.search(/^\d{2}:\d{2}:\d{2}.\d{1} \(\d+\)\|EXECUTION_STARTED$/m); if (start > -1) { log = log.slice(start); @@ -156,10 +156,10 @@ export class ApexLogParser { } } - private toLogTree(lineGenerator: Generator) { + private toLogTree(lineGenerator: Generator) { const rootMethod = new ApexLog(this), - stack: LogLine[] = []; - let line: LogLine | null; + stack: LogEvent[] = []; + let line: LogEvent | null; const lineIter = new LineIterator(lineGenerator); @@ -178,7 +178,7 @@ export class ApexLogParser { return rootMethod; } - private parseTree(currentLine: LogLine, lineIter: LineIterator, stack: LogLine[]) { + private parseTree(currentLine: LogEvent, lineIter: LineIterator, stack: LogEvent[]) { this.lastTimestamp = currentLine.timestamp; currentLine.namespace ||= 'default'; @@ -264,7 +264,7 @@ export class ApexLogParser { } } - private isMatchingEnd(startMethod: LogLine, endLine: LogLine) { + private isMatchingEnd(startMethod: LogEvent, endLine: LogEvent) { return ( endLine.type && startMethod.exitTypes.includes(endLine.type) && @@ -275,10 +275,10 @@ export class ApexLogParser { } private endMethod( - startMethod: LogLine, - endLine: LogLine, + startMethod: LogEvent, + endLine: LogEvent, lineIter: LineIterator, - stack: LogLine[], + stack: LogEvent[], ) { startMethod.exitStamp = endLine.timestamp; @@ -304,8 +304,8 @@ export class ApexLogParser { } } - private flattenByDepth(nodes: LogLine[]) { - const result = new Map(); + private flattenByDepth(nodes: LogEvent[]) { + const result = new Map(); result.set(0, nodes); let currentDepth = 1; @@ -332,7 +332,7 @@ export class ApexLogParser { return result; } - private aggregateTotals(nodes: LogLine[]) { + private aggregateTotals(nodes: LogEvent[]) { const len = nodes.length; if (!len) { return; @@ -360,11 +360,11 @@ export class ApexLogParser { } } - private insertPackageWrappers(node: LogLine) { + private insertPackageWrappers(node: LogEvent) { const children = node.children; - let lastPkg: LogLine | null = null; + let lastPkg: LogEvent | null = null; - const newChildren: LogLine[] = []; + const newChildren: LogEvent[] = []; const len = children.length; for (let i = 0; i < len; i++) { const child = children[i]; @@ -459,19 +459,19 @@ export class DebugLevel { } export class LineIterator { - next: LogLine | null; - lineGenerator: Generator; + next: LogEvent | null; + lineGenerator: Generator; - constructor(lineGenerator: Generator) { + constructor(lineGenerator: Generator) { this.lineGenerator = lineGenerator; this.next = this.lineGenerator.next().value; } - peek(): LogLine | null { + peek(): LogEvent | null { return this.next; } - fetch(): LogLine | null { + fetch(): LogEvent | null { const result = this.next; this.next = this.lineGenerator.next().value; return result; @@ -488,17 +488,17 @@ export interface LogIssue { /** * All log lines extend this base class. */ -export abstract class LogLine { +export abstract class LogEvent { logParser: ApexLogParser; // common metadata (available for all lines) - parent: LogLine | null = null; + parent: LogEvent | null = null; /** * All child nodes of the current node */ - children: LogLine[] = []; + children: LogEvent[] = []; /** * The type of this log line from the log file e.g METHOD_ENTRY @@ -665,10 +665,10 @@ export abstract class LogLine { } /** Called if a corresponding end event is found during tree parsing*/ - onEnd?(end: LogLine, stack: LogLine[]): void; + onEnd?(end: LogEvent, stack: LogEvent[]): void; /** Called when the Log event after this one is created in the line parser*/ - onAfter?(parser: ApexLogParser, next?: LogLine): void; + onAfter?(parser: ApexLogParser, next?: LogEvent): void; public recalculateDurations() { if (this.exitStamp) { @@ -701,8 +701,8 @@ export abstract class LogLine { } } -class BasicLogLine extends LogLine {} -class BasicExitLine extends LogLine { +class BasicLogLine extends LogEvent {} +class BasicExitLine extends LogEvent { isExit = true; } @@ -714,7 +714,7 @@ type CPUType = 'loading' | 'custom' | 'method' | 'free' | 'system' | 'pkg' | ''; * - The end-line should extend "Detail" and terminate the method (also providing the "exitStamp"). * The method will be rendered as "expandable" in the tree-view, if it has children. */ -export class Method extends LogLine { +export class Method extends LogEvent { constructor( parser: ApexLogParser, parts: string[] | null, @@ -845,7 +845,7 @@ export function parseRows(text: string | null | undefined): number { /* Log line entry Parsers */ -class BulkHeapAllocateLine extends LogLine { +class BulkHeapAllocateLine extends LogEvent { logCategory: 'Apex Code'; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); @@ -854,34 +854,34 @@ class BulkHeapAllocateLine extends LogLine { } } -class CalloutRequestLine extends LogLine { +class CalloutRequestLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[3]} : ${parts[2]}`; } } -class CalloutResponseLine extends LogLine { +class CalloutResponseLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[3]} : ${parts[2]}`; } } -class NamedCredentialRequestLine extends LogLine { +class NamedCredentialRequestLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; } } -class NamedCredentialResponseLine extends LogLine { +class NamedCredentialResponseLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}`; } } -class NamedCredentialResponseDetailLine extends LogLine { +class NamedCredentialResponseDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[3]} : ${parts[4]} ${parts[5]} : ${parts[6]} ${parts[7]}`; @@ -925,7 +925,7 @@ class ConstructorEntryLine extends Method { } } -class ConstructorExitLine extends LogLine { +class ConstructorExitLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -934,7 +934,7 @@ class ConstructorExitLine extends LogLine { } } -class EmailQueueLine extends LogLine { +class EmailQueueLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); @@ -960,7 +960,7 @@ export class MethodEntryLine extends Method { } } - onEnd(end: MethodExitLine, _stack: LogLine[]): void { + onEnd(end: MethodExitLine, _stack: LogEvent[]): void { if (end.namespace && !end.text.endsWith(')')) { this.namespace = end.namespace; } @@ -992,7 +992,7 @@ export class MethodEntryLine extends Method { return ''; } } -class MethodExitLine extends LogLine { +class MethodExitLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1019,7 +1019,7 @@ class SystemConstructorEntryLine extends Method { } } -class SystemConstructorExitLine extends LogLine { +class SystemConstructorExitLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1035,7 +1035,7 @@ class SystemMethodEntryLine extends Method { } } -class SystemMethodExitLine extends LogLine { +class SystemMethodExitLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1119,7 +1119,7 @@ export class CodeUnitStartedLine extends Method { this.namespace ||= 'default'; } } -export class CodeUnitFinishedLine extends LogLine { +export class CodeUnitFinishedLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1175,7 +1175,7 @@ class VFApexCallStartLine extends Method { } } -class VFApexCallEndLine extends LogLine { +class VFApexCallEndLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1199,7 +1199,7 @@ class VFFormulaStartLine extends Method { } } -class VFFormulaEndLine extends LogLine { +class VFFormulaEndLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1214,7 +1214,7 @@ class VFSeralizeViewStateStartLine extends Method { } } -class VFPageMessageLine extends LogLine { +class VFPageMessageLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); @@ -1239,7 +1239,7 @@ class DMLBeginLine extends Method { } } -class DMLEndLine extends LogLine { +class DMLEndLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1248,7 +1248,7 @@ class DMLEndLine extends LogLine { } } -class IdeasQueryExecuteLine extends LogLine { +class IdeasQueryExecuteLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1276,12 +1276,12 @@ class SOQLExecuteBeginLine extends Method { this.text = soqlString || ''; } - onEnd(end: SOQLExecuteEndLine, _stack: LogLine[]): void { + onEnd(end: SOQLExecuteEndLine, _stack: LogEvent[]): void { this.rowCount.total = this.rowCount.self = end.rowCount.total; } } -class SOQLExecuteEndLine extends LogLine { +class SOQLExecuteEndLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1291,7 +1291,7 @@ class SOQLExecuteEndLine extends LogLine { } } -class SOQLExecuteExplainLine extends LogLine { +class SOQLExecuteExplainLine extends LogEvent { cardinality: number | null = null; // The estimated number of records that the leading operation type would return fields: string[] | null = null; //The indexed field(s) used by the Query Optimizer. If the leading operation type is Index, the fields value is Index. Otherwise, the fields value is null. leadingOperationType: string | null = null; // The primary operation type that Salesforce will use to optimize the query. @@ -1344,12 +1344,12 @@ class SOSLExecuteBeginLine extends Method { this.text = `SOSL: ${parts[3]}`; } - onEnd(end: SOSLExecuteEndLine, _stack: LogLine[]): void { + onEnd(end: SOSLExecuteEndLine, _stack: LogEvent[]): void { this.rowCount.total = this.rowCount.self = end.rowCount.total; } } -class SOSLExecuteEndLine extends LogLine { +class SOSLExecuteEndLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1359,7 +1359,7 @@ class SOSLExecuteEndLine extends LogLine { } } -class HeapAllocateLine extends LogLine { +class HeapAllocateLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1367,21 +1367,21 @@ class HeapAllocateLine extends LogLine { } } -class HeapDeallocateLine extends LogLine { +class HeapDeallocateLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); } } -class StatementExecuteLine extends LogLine { +class StatementExecuteLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); } } -class VariableScopeBeginLine extends LogLine { +class VariableScopeBeginLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1389,14 +1389,14 @@ class VariableScopeBeginLine extends LogLine { } } -class VariableAssignmentLine extends LogLine { +class VariableAssignmentLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); this.text = parts.slice(3).join(' | '); } } -class UserInfoLine extends LogLine { +class UserInfoLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1404,7 +1404,7 @@ class UserInfoLine extends LogLine { } } -class UserDebugLine extends LogLine { +class UserDebugLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1421,7 +1421,7 @@ class CumulativeLimitUsageLine extends Method { } } -class CumulativeProfilingLine extends LogLine { +class CumulativeProfilingLine extends LogEvent { acceptsText = true; namespace = 'default'; constructor(parser: ApexLogParser, parts: string[]) { @@ -1437,7 +1437,7 @@ class CumulativeProfilingBeginLine extends Method { } } -class LimitUsageLine extends LogLine { +class LimitUsageLine extends LogEvent { namespace = 'default'; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); @@ -1446,7 +1446,7 @@ class LimitUsageLine extends LogLine { } } -class LimitUsageForNSLine extends LogLine { +class LimitUsageForNSLine extends LogEvent { acceptsText = true; namespace = 'default'; @@ -1455,7 +1455,7 @@ class LimitUsageForNSLine extends LogLine { this.text = parts[2] || ''; } - onAfter(parser: ApexLogParser, _next?: LogLine): void { + onAfter(parser: ApexLogParser, _next?: LogEvent): void { const matched = this.text.match(/Maximum CPU time: (\d+)/), cpuText = matched?.[1] || '0', cpuTime = parseInt(cpuText, 10) * 1000000; // convert from milli-seconds to nano-seconds @@ -1473,26 +1473,26 @@ class NBANodeBegin extends Method { } } -class NBANodeDetail extends LogLine { +class NBANodeDetail extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts.slice(2).join(' | '); } } -class NBANodeEnd extends LogLine { +class NBANodeEnd extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts.slice(2).join(' | '); } } -class NBANodeError extends LogLine { +class NBANodeError extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts.slice(2).join(' | '); } } -class NBAOfferInvalid extends LogLine { +class NBAOfferInvalid extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts.slice(2).join(' | '); @@ -1504,21 +1504,21 @@ class NBAStrategyBegin extends Method { this.text = parts.slice(2).join(' | '); } } -class NBAStrategyEnd extends LogLine { +class NBAStrategyEnd extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts.slice(2).join(' | '); } } -class NBAStrategyError extends LogLine { +class NBAStrategyError extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts.slice(2).join(' | '); } } -class PushTraceFlagsLine extends LogLine { +class PushTraceFlagsLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1526,7 +1526,7 @@ class PushTraceFlagsLine extends LogLine { } } -class PopTraceFlagsLine extends LogLine { +class PopTraceFlagsLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1542,7 +1542,7 @@ class QueryMoreBeginLine extends Method { } } -class QueryMoreEndLine extends LogLine { +class QueryMoreEndLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1551,7 +1551,7 @@ class QueryMoreEndLine extends LogLine { this.text = `line: ${this.lineNumber}`; } } -class QueryMoreIterationsLine extends LogLine { +class QueryMoreIterationsLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1559,7 +1559,7 @@ class QueryMoreIterationsLine extends LogLine { } } -class SavepointRollbackLine extends LogLine { +class SavepointRollbackLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1567,7 +1567,7 @@ class SavepointRollbackLine extends LogLine { } } -class SavePointSetLine extends LogLine { +class SavePointSetLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1575,14 +1575,14 @@ class SavePointSetLine extends LogLine { } } -class TotalEmailRecipientsQueuedLine extends LogLine { +class TotalEmailRecipientsQueuedLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class StackFrameVariableListLine extends LogLine { +class StackFrameVariableListLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1590,7 +1590,7 @@ class StackFrameVariableListLine extends LogLine { } } -class StaticVariableListLine extends LogLine { +class StaticVariableListLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1599,7 +1599,7 @@ class StaticVariableListLine extends LogLine { } // This looks like a method, but the exit line is often missing... -class SystemModeEnterLine extends LogLine { +class SystemModeEnterLine extends LogEvent { // namespace = "system"; constructor(parser: ApexLogParser, parts: string[]) { @@ -1608,7 +1608,7 @@ class SystemModeEnterLine extends LogLine { } } -class SystemModeExitLine extends LogLine { +class SystemModeExitLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; @@ -1631,7 +1631,7 @@ class EnteringManagedPackageLine extends Method { this.text = this.namespace = lastDot < 0 ? rawNs : rawNs.substring(lastDot + 1); } - onAfter(parser: ApexLogParser, end?: LogLine): void { + onAfter(parser: ApexLogParser, end?: LogEvent): void { if (end) { this.exitStamp = end.timestamp; } @@ -1645,7 +1645,7 @@ class EventSericePubBeginLine extends Method { } } -class EventSericePubEndLine extends LogLine { +class EventSericePubEndLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1654,7 +1654,7 @@ class EventSericePubEndLine extends LogLine { } } -class EventSericePubDetailLine extends LogLine { +class EventSericePubDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] + ' ' + parts[3] + ' ' + parts[4]; @@ -1668,7 +1668,7 @@ class EventSericeSubBeginLine extends Method { } } -class EventSericeSubEndLine extends LogLine { +class EventSericeSubEndLine extends LogEvent { isExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1677,7 +1677,7 @@ class EventSericeSubEndLine extends LogLine { } } -class EventSericeSubDetailLine extends LogLine { +class EventSericeSubDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} ${parts[3]} ${parts[4]} ${parts[6]} ${parts[6]}`; @@ -1692,13 +1692,13 @@ export class FlowStartInterviewsBeginLine extends Method { super(parser, parts, ['FLOW_START_INTERVIEWS_END'], 'Flow', 'custom'); } - onEnd(end: LogLine, stack: LogLine[]) { + onEnd(end: LogEvent, stack: LogEvent[]) { const flowType = this.getFlowType(stack); this.suffix = ` (${flowType})`; this.text += this.getFlowName(); } - getFlowType(stack: LogLine[]) { + getFlowType(stack: LogEvent[]) { let flowType; // ignore the last one on stack is it will be this FlowStartInterviewsBeginLine const len = stack.length - 2; @@ -1725,7 +1725,7 @@ export class FlowStartInterviewsBeginLine extends Method { } } -class FlowStartInterviewsErrorLine extends LogLine { +class FlowStartInterviewsErrorLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); @@ -1740,21 +1740,21 @@ class FlowStartInterviewBeginLine extends Method { } } -class FlowStartInterviewLimitUsageLine extends LogLine { +class FlowStartInterviewLimitUsageLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class FlowStartScheduledRecordsLine extends LogLine { +class FlowStartScheduledRecordsLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]}`; } } -class FlowCreateInterviewErrorLine extends LogLine { +class FlowCreateInterviewErrorLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; @@ -1770,7 +1770,7 @@ class FlowElementBeginLine extends Method { } } -class FlowElementDeferredLine extends LogLine { +class FlowElementDeferredLine extends LogEvent { declarative = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1779,7 +1779,7 @@ class FlowElementDeferredLine extends LogLine { } } -class FlowElementAssignmentLine extends LogLine { +class FlowElementAssignmentLine extends LogEvent { declarative = true; acceptsText = true; @@ -1789,56 +1789,56 @@ class FlowElementAssignmentLine extends LogLine { } } -class FlowWaitEventResumingDetailLine extends LogLine { +class FlowWaitEventResumingDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; } } -class FlowWaitEventWaitingDetailLine extends LogLine { +class FlowWaitEventWaitingDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; } } -class FlowWaitResumingDetailLine extends LogLine { +class FlowWaitResumingDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; } } -class FlowWaitWaitingDetailLine extends LogLine { +class FlowWaitWaitingDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; } } -class FlowInterviewFinishedLine extends LogLine { +class FlowInterviewFinishedLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[3] || ''; } } -class FlowInterviewResumedLine extends LogLine { +class FlowInterviewResumedLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]}`; } } -class FlowInterviewPausedLine extends LogLine { +class FlowInterviewPausedLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; } } -class FlowElementErrorLine extends LogLine { +class FlowElementErrorLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); @@ -1846,56 +1846,56 @@ class FlowElementErrorLine extends LogLine { } } -class FlowElementFaultLine extends LogLine { +class FlowElementFaultLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; } } -class FlowElementLimitUsageLine extends LogLine { +class FlowElementLimitUsageLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}`; } } -class FlowInterviewFinishedLimitUsageLine extends LogLine { +class FlowInterviewFinishedLimitUsageLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}`; } } -class FlowSubflowDetailLine extends LogLine { +class FlowSubflowDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; } } -class FlowActionCallDetailLine extends LogLine { +class FlowActionCallDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[3] + ' : ' + parts[4] + ' : ' + parts[5] + ' : ' + parts[6]; } } -class FlowAssignmentDetailLine extends LogLine { +class FlowAssignmentDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[3] + ' : ' + parts[4] + ' : ' + parts[5]; } } -class FlowLoopDetailLine extends LogLine { +class FlowLoopDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[3] + ' : ' + parts[4]; } } -class FlowRuleDetailLine extends LogLine { +class FlowRuleDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[3] + ' : ' + parts[4]; @@ -1911,7 +1911,7 @@ class FlowBulkElementBeginLine extends Method { } } -class FlowBulkElementDetailLine extends LogLine { +class FlowBulkElementDetailLine extends LogEvent { declarative = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1920,14 +1920,14 @@ class FlowBulkElementDetailLine extends LogLine { } } -class FlowBulkElementNotSupportedLine extends LogLine { +class FlowBulkElementNotSupportedLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; } } -class FlowBulkElementLimitUsageLine extends LogLine { +class FlowBulkElementLimitUsageLine extends LogEvent { declarative = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1936,61 +1936,61 @@ class FlowBulkElementLimitUsageLine extends LogLine { } } -class PNInvalidAppLine extends LogLine { +class PNInvalidAppLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}.${parts[3]}`; } } -class PNInvalidCertificateLine extends LogLine { +class PNInvalidCertificateLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}.${parts[3]}`; } } -class PNInvalidNotificationLine extends LogLine { +class PNInvalidNotificationLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}.${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]} : ${parts[7]} : ${parts[8]}`; } } -class PNNoDevicesLine extends LogLine { +class PNNoDevicesLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}.${parts[3]}`; } } -class PNSentLine extends LogLine { +class PNSentLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}.${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]} : ${parts[7]}`; } } -class SLAEndLine extends LogLine { +class SLAEndLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; } } -class SLAEvalMilestoneLine extends LogLine { +class SLAEvalMilestoneLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}`; } } -class SLAProcessCaseLine extends LogLine { +class SLAProcessCaseLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}`; } } -class TestingLimitsLine extends LogLine { +class TestingLimitsLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1998,14 +1998,14 @@ class TestingLimitsLine extends LogLine { } } -class ValidationRuleLine extends LogLine { +class ValidationRuleLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[3] || ''; } } -class ValidationErrorLine extends LogLine { +class ValidationErrorLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); @@ -2013,7 +2013,7 @@ class ValidationErrorLine extends LogLine { } } -class ValidationFormulaLine extends LogLine { +class ValidationFormulaLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -2024,14 +2024,14 @@ class ValidationFormulaLine extends LogLine { } } -class ValidationPassLine extends LogLine { +class ValidationPassLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[3] || ''; } } -class WFFlowActionErrorLine extends LogLine { +class WFFlowActionErrorLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); @@ -2039,7 +2039,7 @@ class WFFlowActionErrorLine extends LogLine { } } -class WFFlowActionErrorDetailLine extends LogLine { +class WFFlowActionErrorDetailLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); @@ -2065,14 +2065,14 @@ class WFRuleEvalBeginLine extends Method { } } -class WFRuleEvalValueLine extends LogLine { +class WFRuleEvalValueLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class WFRuleFilterLine extends LogLine { +class WFRuleFilterLine extends LogEvent { acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -2101,21 +2101,21 @@ class WFFormulaLine extends Method { } } -class WFActionLine extends LogLine { +class WFActionLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class WFActionsEndLine extends LogLine { +class WFActionsEndLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class WFActionTaskLine extends LogLine { +class WFActionTaskLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]} : ${parts[7]}`; @@ -2131,7 +2131,7 @@ class WFApprovalLine extends Method { } } -class WFApprovalRemoveLine extends LogLine { +class WFApprovalRemoveLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]}`; @@ -2147,14 +2147,14 @@ class WFApprovalSubmitLine extends Method { } } -class WFApprovalSubmitterLine extends LogLine { +class WFApprovalSubmitterLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; } } -class WFAssignLine extends LogLine { +class WFAssignLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]}`; @@ -2179,14 +2179,14 @@ class WFEmailSentLine extends Method { } } -class WFEnqueueActionsLine extends LogLine { +class WFEnqueueActionsLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class WFEscalationActionLine extends LogLine { +class WFEscalationActionLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]}`; @@ -2202,7 +2202,7 @@ class WFEvalEntryCriteriaLine extends Method { } } -class WFFlowActionDetailLine extends LogLine { +class WFFlowActionDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); const optional = parts[4] ? ` : ${parts[4]} :${parts[5]}` : ''; @@ -2219,7 +2219,7 @@ class WFNextApproverLine extends Method { } } -class WFOutboundMsgLine extends LogLine { +class WFOutboundMsgLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; @@ -2244,21 +2244,21 @@ class WFProcessNode extends Method { } } -class WFReassignRecordLine extends LogLine { +class WFReassignRecordLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]}`; } } -class WFResponseNotifyLine extends LogLine { +class WFResponseNotifyLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; } } -class WFRuleEntryOrderLine extends LogLine { +class WFRuleEntryOrderLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; @@ -2274,28 +2274,28 @@ class WFRuleInvocationLine extends Method { } } -class WFSoftRejectLine extends LogLine { +class WFSoftRejectLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class WFTimeTriggerLine extends LogLine { +class WFTimeTriggerLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; } } -class WFSpoolActionBeginLine extends LogLine { +class WFSpoolActionBeginLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class ExceptionThrownLine extends LogLine { +class ExceptionThrownLine extends LogEvent { discontinuity = true; acceptsText = true; totalThrownCount = 1; @@ -2306,7 +2306,7 @@ class ExceptionThrownLine extends LogLine { this.text = parts[3] || ''; } - onAfter(parser: ApexLogParser, _next?: LogLine): void { + onAfter(parser: ApexLogParser, _next?: LogEvent): void { if (this.text.indexOf('System.LimitException') >= 0) { const isMultiLine = this.text.indexOf('\n'); const len = isMultiLine < 0 ? 99 : isMultiLine; @@ -2318,7 +2318,7 @@ class ExceptionThrownLine extends LogLine { } } -class FatalErrorLine extends LogLine { +class FatalErrorLine extends LogEvent { acceptsText = true; hideable = false; discontinuity = true; @@ -2328,7 +2328,7 @@ class FatalErrorLine extends LogLine { this.text = parts[2] || ''; } - onAfter(parser: ApexLogParser, _next?: LogLine): void { + onAfter(parser: ApexLogParser, _next?: LogEvent): void { const newLineIndex = this.text.indexOf('\n'); const summary = newLineIndex > -1 ? this.text.slice(0, newLineIndex + 1) : this.text; const detailText = summary.length !== this.text.length ? this.text : ''; @@ -2336,27 +2336,27 @@ class FatalErrorLine extends LogLine { } } -class XDSDetailLine extends LogLine { +class XDSDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class XDSResponseLine extends LogLine { +class XDSResponseLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; } } -class XDSResponseDetailLine extends LogLine { +class XDSResponseDetailLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; } } -class XDSResponseErrorLine extends LogLine { +class XDSResponseErrorLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; @@ -2373,7 +2373,7 @@ class DuplicateDetectionBegin extends Method { } // e.g. "09:45:31.888 (38889067408)|DUPLICATE_DETECTION_RULE_INVOCATION|DuplicateRuleId:0Bm20000000CaSP|DuplicateRuleName:Duplicate Account|DmlType:UPDATE" -class DuplicateDetectionRule extends LogLine { +class DuplicateDetectionRule extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = `${parts[3]} - ${parts[4]}`; @@ -2384,7 +2384,7 @@ class DuplicateDetectionRule extends LogLine { * NOTE: These can be found in the org on the create new debug level page but are not found in the docs here * https://help.salesforce.com/s/articleView?id=sf.code_setting_debug_log_levels.htm */ -class BulkDMLEntry extends LogLine { +class BulkDMLEntry extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; @@ -2394,7 +2394,7 @@ class BulkDMLEntry extends LogLine { /** * DUPLICATE_DETECTION_MATCH_INVOCATION_DETAILS|EntityType:Account|ActionTaken:Allow_[Alert,Report]|DuplicateRecordIds: */ -class DuplicateDetectionDetails extends LogLine { +class DuplicateDetectionDetails extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts.slice(2).join(' | '); @@ -2404,7 +2404,7 @@ class DuplicateDetectionDetails extends LogLine { /** * DUPLICATE_DETECTION_MATCH_INVOCATION_SUMMARY|EntityType:Account|NumRecordsToBeSaved:200|NumRecordsToBeSavedWithDuplicates:0|NumDuplicateRecordsFound:0 */ -class DuplicateDetectionSummary extends LogLine { +class DuplicateDetectionSummary extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts.slice(2).join(' | '); @@ -2500,7 +2500,7 @@ function getLogEventClass(eventName: LogEventType): LogLineConstructor | null | return null; } -type LogLineConstructor = new ( +type LogLineConstructor = new ( parser: ApexLogParser, parts: string[], ) => T; diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index c5b3969e..d7b13b22 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -6,7 +6,7 @@ import formatDuration, { debounce } from '../Util.js'; import { goToRow } from '../components/calltree-view/CalltreeView.js'; import { ApexLog, - LogLine, + LogEvent, Method, type LogIssue, type LogSubCategory, @@ -171,8 +171,8 @@ let findArgs: { text: string; count: number; options: { matchCase: boolean } } = }; let totalMatches = 0; -function getMaxDepth(nodes: LogLine[]) { - const result = new Map(); +function getMaxDepth(nodes: LogEvent[]) { + const result = new Map(); result.set(0, nodes); let currentDepth = 1; @@ -596,12 +596,12 @@ function drawTimeLine() { } function findByPosition( - nodes: LogLine[], + nodes: LogEvent[], depth: number, x: number, targetDepth: number, shouldIgnoreWidth: boolean, -): LogLine | null { +): LogEvent | null { if (!nodes) { return null; } From c0550622d121b3c8b3f7db2d694cb9795fee4e84 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 1 May 2025 11:03:02 +0100 Subject: [PATCH 04/20] perf: speed up + simplify getMaxDepth --- log-viewer/modules/timeline/Timeline.ts | 37 ++++++++++--------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index 3035b406..24e83d49 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -172,33 +172,24 @@ let findArgs: { text: string; count: number; options: { matchCase: boolean } } = }; let totalMatches = 0; -function getMaxDepth(nodes: LogEvent[]) { - const result = new Map(); - result.set(0, nodes); - - let currentDepth = 1; - - let currentNodes = nodes; - let len = currentNodes.length; - while (len) { - result.set(currentDepth, []); - while (len--) { - const node = currentNodes[len]; - if (node?.children && node.duration) { - const children = result.get(currentDepth)!; - node.children.forEach((c) => { - if (c.children.length) { - children.push(c); - } - }); +function getMaxDepth(nodes: LogEvent[]): number { + let maxDepth = 0; + let currentLevel = nodes.filter((n) => n.exitTypes.length); + + while (currentLevel.length) { + maxDepth++; + const nextLevel: LogEvent[] = []; + for (const node of currentLevel) { + for (const child of node.children) { + if (child.exitTypes.length) { + nextLevel.push(child); + } } } - currentNodes = result.get(currentDepth++) || []; - len = currentNodes.length; + currentLevel = nextLevel; } - result.clear(); - return currentDepth; + return maxDepth; } function drawScale(ctx: CanvasRenderingContext2D) { From d3819b41178caf085587fa49aed2691a259e5dc2 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 1 May 2025 11:04:33 +0100 Subject: [PATCH 05/20] perf: speed up + simplify nodeToRectangles - Use a single while loop instead of 2 + removes len decrement - Remove the need for Map to represent levels --- log-viewer/modules/timeline/Timeline.ts | 54 +++++++++++-------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index 24e83d49..f2fcb1d0 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -256,40 +256,34 @@ function drawScale(ctx: CanvasRenderingContext2D) { ctx.stroke(); } -function nodesToRectangles(nodes: Method[]) { - const result = new Map(); - - let currentDepth = 0; - let currentNodes = nodes; - let len = currentNodes.length; - while (len) { - result.set(currentDepth, []); - while (len--) { - const node = currentNodes[len]; - if (node) { - const { subCategory: subCategory, duration } = node; - if (subCategory && duration) { - addToRectQueue(node, currentDepth); - } +function nodesToRectangles(rootNodes: LogEvent[]) { + // seed depth 0 + let depth = 0; + let currentLevel = rootNodes.filter((n) => n.exitTypes.length); + + while (currentLevel.length) { + const nextLevel: LogEvent[] = []; + + for (const node of currentLevel) { + if (node.subCategory && node.duration) { + addToRectQueue(node, depth); + } - // The spread operator caused Maximum call stack size exceeded when there are lots of child nodes. - const children = result.get(currentDepth)!; - node.children.forEach((child) => { - if (child instanceof Method) { - children.push(child); - } - }); + for (const child of node.children) { + if (child.exitTypes.length) { + nextLevel.push(child); + } } } - currentNodes = result.get(currentDepth++) || []; - len = currentNodes.length; - borderRenderQueue.set( - findMatchColor, - borderRenderQueue.get(findMatchColor)?.sort((a, b) => { - return a.x - b.x; - }) || [], - ); + depth++; + currentLevel = nextLevel; + } + + // sort all borders once + const borders = borderRenderQueue.get(findMatchColor); + if (borders) { + borders.sort((a, b) => a.x - b.x); } } const rectRenderQueue = new Map(); From 3672202899eda2d1ffd0507fab8ee3620faaeba4 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 1 May 2025 11:07:36 +0100 Subject: [PATCH 06/20] refactor: remove remaining external refs to Method class --- .../modules/__tests__/ApexLogParser.test.ts | 10 ++++-- .../modules/__tests__/soql/SOQLLinter.test.ts | 33 ++++++++++--------- log-viewer/modules/parsers/ApexLogParser.ts | 2 +- log-viewer/modules/timeline/Timeline.ts | 24 +++++--------- 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/log-viewer/modules/__tests__/ApexLogParser.test.ts b/log-viewer/modules/__tests__/ApexLogParser.test.ts index 324a085a..13be3d66 100644 --- a/log-viewer/modules/__tests__/ApexLogParser.test.ts +++ b/log-viewer/modules/__tests__/ApexLogParser.test.ts @@ -6,7 +6,6 @@ import { CodeUnitStartedLine, ExecutionStartedLine, LogEvent, - Method, MethodEntryLine, SOQLExecuteBeginLine, SOQLExecuteExplainLine, @@ -17,6 +16,12 @@ import { parseVfNamespace, } from '../parsers/ApexLogParser.js'; +class DummyLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + } +} + describe('parseObjectNamespace tests', () => { it('Should consider no separator to be unmanaged', () => { expect(parseObjectNamespace('Account')).toEqual('default'); @@ -1117,7 +1122,8 @@ describe('namespace tests', () => { describe('Recalculate durations tests', () => { it('Recalculates parent node', () => { const parser = new ApexLogParser(); - const node = new Method(parser, ['14:32:07.563 (1)', 'DUMMY'], [], 'Method', ''); + const node = new DummyLine(parser, ['14:32:07.563 (1)', 'DUMMY']); + node.subCategory = 'Method'; node.exitStamp = 3; node.recalculateDurations(); diff --git a/log-viewer/modules/__tests__/soql/SOQLLinter.test.ts b/log-viewer/modules/__tests__/soql/SOQLLinter.test.ts index 906c845d..6031bbe4 100644 --- a/log-viewer/modules/__tests__/soql/SOQLLinter.test.ts +++ b/log-viewer/modules/__tests__/soql/SOQLLinter.test.ts @@ -1,9 +1,18 @@ /* * Copyright (c) 2021 Certinia Inc. All rights reserved. */ -import { ApexLogParser, Method } from '../../parsers/ApexLogParser.js'; +import { ApexLogParser, LogEvent } from '../../parsers/ApexLogParser.js'; import { SOQLLinter } from '../../soql/SOQLLinter.js'; +class DummySOQLLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[] | null) { + super(parser, parts); + this.subCategory = 'Code Unit'; + this.cpuType = 'method'; + this.exitTypes = ['CODE_UNIT_FINISHED']; + } +} + describe('SOQL Linter rule tests', () => { it('No where clause should return rule', async () => { const soql = 'SELECT Id FROM ANOBJECT__c'; @@ -155,20 +164,14 @@ describe('SOQL in Trigger Rule tests', () => { it('soql in trigger should return rule', async () => { const parser = new ApexLogParser(); const soql = 'SELECT Id FROM AnObject__c WHERE value__c > 0'; - const mockTriggerLine = new Method( - parser, - [ - '04:16:39.166 (1166781977)', - 'CODE_UNIT_STARTED', - '[EXTERNAL]', - 'a0000000000aaaa', - 'Account on Account trigger event AfterInsert', - '__sfdc_trigger/Account', - ], - ['CODE_UNIT_FINISHED'], - 'Code Unit', - 'method', - ); + const mockTriggerLine = new DummySOQLLine(parser, [ + '04:16:39.166 (1166781977)', + 'CODE_UNIT_STARTED', + '[EXTERNAL]', + 'a0000000000aaaa', + 'Account on Account trigger event AfterInsert', + '__sfdc_trigger/Account', + ]); mockTriggerLine.text = 'Account on Account trigger event AfterInsert'; const results = await new SOQLLinter().lint(soql, [mockTriggerLine]); diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index e6c11274..a88b6e79 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -756,7 +756,7 @@ type CPUType = 'loading' | 'custom' | 'method' | 'free' | 'system' | 'pkg' | ''; * - The end-line should extend "Detail" and terminate the method (also providing the "exitStamp"). * The method will be rendered as "expandable" in the tree-view, if it has children. */ -export class Method extends LogEvent { +class Method extends LogEvent { constructor( parser: ApexLogParser, parts: string[] | null, diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index f2fcb1d0..5c4b88eb 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -4,13 +4,7 @@ //TODO:Refactor - usage should look more like `new TimeLine(timelineContainer, {tooltip:true}:Config)`; import formatDuration, { debounce } from '../Util.js'; import { goToRow } from '../components/calltree-view/CalltreeView.js'; -import { - ApexLog, - LogEvent, - Method, - type LogIssue, - type LogSubCategory, -} from '../parsers/ApexLogParser.js'; +import { ApexLog, LogEvent, type LogIssue, type LogSubCategory } from '../parsers/ApexLogParser.js'; export { ApexLog }; @@ -297,14 +291,14 @@ let currentFindMatchColor = '#9e6a03'; * @param node The node to be rendered * @param y The call depth of the node */ -function addToRectQueue(node: Method, y: number) { +function addToRectQueue(node: LogEvent, y: number) { const { subCategory: subCategory, timestamp: x, duration: { total: w }, } = node; - let borderColor = ''; + let borderColor = ''; if (hasFindMatch(node)) { borderColor = findMatchColor; } @@ -323,15 +317,15 @@ function addToRectQueue(node: Method, y: number) { borders.push(rect); } -function hasFindMatch(node: Method) { +function hasFindMatch(node: LogEvent) { if (!searchString || !node) { return false; } - const nodeType = node.type ?? ''; + const nodeType = node.type; const matchType = findArgs.options.matchCase - ? nodeType.includes(searchString) - : nodeType.toLowerCase().includes(searchString); + ? nodeType?.includes(searchString) + : nodeType?.toLowerCase().includes(searchString); if (matchType) { return matchType; } @@ -560,7 +554,7 @@ export function init(timelineContainer: HTMLDivElement, rootMethod: ApexLog) { onInitTimeline(); calculateSizes(); - nodesToRectangles(timelineRoot.children as Method[]); + nodesToRectangles(timelineRoot.children); if (ctx) { requestAnimationFrame(drawTimeLine); } @@ -968,7 +962,7 @@ function _findOnTimeline( if (newSearch || clearHighlights) { rectRenderQueue.clear(); borderRenderQueue.clear(); - nodesToRectangles(timelineRoot.children as Method[]); + nodesToRectangles(timelineRoot.children); const findResults = borderRenderQueue.get(findMatchColor) || []; totalMatches = findResults.length; From d565e657fadc0141bcc980e79fb0cedfac20e89c Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 1 May 2025 11:25:31 +0100 Subject: [PATCH 07/20] refactor: use set for namespace instead of set and array --- log-viewer/modules/parsers/ApexLogParser.ts | 53 +++++++++++---------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index a88b6e79..a63a9846 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -43,8 +43,7 @@ export class ApexLogParser { cpuUsed = 0; lastTimestamp = 0; discontinuity = false; - namespaces: string[] = []; - namespacesUniq = new Set(); + namespaces = new Set(); /** * Takes string input of a log and returns the ApexLog class, which represents a log tree @@ -59,7 +58,7 @@ export class ApexLogParser { apexLog.logIssues = this.logIssues; apexLog.parsingErrors = this.parsingErrors; apexLog.cpuTime = this.cpuUsed; - apexLog.namespaces = this.namespaces; + apexLog.namespaces = Array.from(this.namespaces); return apexLog; } @@ -68,14 +67,14 @@ export class ApexLogParser { const parts = line.split('|'); const type = parts[1] ?? ''; + const metaCtor = getLogEventClass(type as LogEventType); if (metaCtor) { const entry = new metaCtor(this, parts); entry.logLine = line; lastEntry?.onAfter?.(this, entry); - if (entry.namespace && !this.namespacesUniq.has(entry.namespace)) { - this.namespaces.push(entry.namespace); - this.namespacesUniq.add(entry.namespace); + if (entry.namespace) { + this.namespaces.add(entry.namespace); } return entry; } @@ -170,11 +169,10 @@ export class ApexLogParser { line.parent = rootMethod; rootMethod.children.push(line); } - rootMethod.setTimes(); + rootMethod.setTimes(); this.insertPackageWrappers(rootMethod); this.aggregateTotals([rootMethod]); - return rootMethod; } @@ -699,7 +697,7 @@ export abstract class LogEvent { if (parts) { const [timeData, type] = parts; this.text = this.type = type as LogEventType; - this.timestamp = this.parseTimestamp(timeData || ''); + this.timestamp = timeData ? this.parseTimestamp(timeData) : 0; } else { this.timestamp = 0; this.text = ''; @@ -948,14 +946,10 @@ class ConstructorEntryLine extends Method { _parseConstructorNamespace(className: string): string { let possibleNs = className.slice(0, className.indexOf('.')); - possibleNs = - this.logParser.namespaces.find((ns) => { - return ns === possibleNs; - }) || ''; - - if (possibleNs) { + if (this.logParser.namespaces.has(possibleNs)) { return possibleNs; } + const constructorParts = (className ?? '').split('.'); possibleNs = constructorParts[0] || ''; // inmner class with a namespace @@ -990,12 +984,12 @@ export class MethodEntryLine extends Method { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['METHOD_EXIT'], 'Method', 'method'); this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[4] || this.type || ''; + this.text = parts[4] || this.type || this.text; if (this.text.indexOf('System.Type.forName(') !== -1) { // assume we are not charged for class loading (or at least not lengthy remote-loading / compiling) this.cpuType = 'loading'; } else { - const possibleNs = this._parseMethodNamespace(parts[4] || ''); + const possibleNs = this._parseMethodNamespace(parts[4]); if (possibleNs) { this.namespace = possibleNs; } @@ -1008,23 +1002,27 @@ export class MethodEntryLine extends Method { } } - _parseMethodNamespace(methodName: string): string { + _parseMethodNamespace(methodName: string | undefined): string { + if (!methodName) { + return ''; + } + const methodBracketIndex = methodName.indexOf('('); if (methodBracketIndex === -1) { return ''; } - let possibleNs = methodName.slice(0, methodName.indexOf('.')); - possibleNs = - this.logParser.namespaces.find((ns) => { - return ns === possibleNs; - }) || ''; + const nsSeparator = methodName.indexOf('.'); + if (nsSeparator === -1) { + return ''; + } - if (possibleNs) { + const possibleNs = methodName.slice(0, nsSeparator); + if (this.logParser.namespaces.has(possibleNs)) { return possibleNs; } - const methodNameParts = methodName ? methodName.slice(0, methodBracketIndex)?.split('.') : ''; + const methodNameParts = methodName.slice(0, methodBracketIndex)?.split('.'); if (methodNameParts.length === 4) { return methodNameParts[0] ?? ''; } else if (methodNameParts.length === 2) { @@ -1040,9 +1038,12 @@ class MethodExitLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[4] ?? parts[3] ?? ''; + this.text = parts[4] ?? parts[3] ?? this.text; + /*A method will end with ')'. Without that this it represents the first reference to a class, outer or inner. One of the few reliable ways to determine valid namespaces. The first reference to a class (outer or inner) will always have an METHOD_EXIT containing the Outer class name with namespace if present. Other events will follow, CONSTRUCTOR_ENTRY etc. But this case will only ever have 2 parts ns.Outer even if the first reference was actually an inner class e.g new ns.Outer.Inner();*/ + // If does not end in ) then we have a reference to the class, either via outer or inner. if (!this.text.endsWith(')')) { + // if there is a . the we have a namespace e.g ns.Outer const index = this.text.indexOf('.'); if (index !== -1) { this.namespace = this.text.slice(0, index); From dfb5523a9bc172e92818f1429c0b45aeb73e7ed9 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 1 May 2025 11:26:22 +0100 Subject: [PATCH 08/20] refactor: reorder assignments --- log-viewer/modules/parsers/ApexLogParser.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index a63a9846..0b10f4ca 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -79,7 +79,7 @@ export class ApexLogParser { return entry; } - const hasType = type && typePattern.test(type); + const hasType = !!(type && typePattern.test(type)); if (!hasType && lastEntry?.acceptsText) { // wrapped text from the previous entry? lastEntry.text += '\n' + line; @@ -219,27 +219,26 @@ export class ApexLogParser { break; } - nextLine.namespace ||= currentLine.namespace || 'default'; lineIter.fetch(); // it's a child - consume the line this.lastTimestamp = nextLine.timestamp; + nextLine.namespace ||= currentLine.namespace || 'default'; + nextLine.parent = currentLine; + currentLine.children.push(nextLine); if (nextLine.exitTypes.length) { this.parseTree(nextLine, lineIter, stack); } - - nextLine.parent = currentLine; - currentLine.children.push(nextLine); } // End of line error handling. We have finished processing this log line and either got to the end // of the log without finding an exit line or the current line was truncated) if (!nextLine || currentLine.isTruncated) { // truncated method - terminate at the end of the log - currentLine.exitStamp = this.lastTimestamp; + currentLine.exitStamp = this.lastTimestamp ?? currentLine.timestamp; // we found an entry event on its own e.g a `METHOD_ENTRY` without a `METHOD_EXIT` and got to the end of the log this.addLogIssue( - this.lastTimestamp, + currentLine.exitStamp, 'Unexpected-End', 'An entry event was found without a corresponding exit event e.g a `METHOD_ENTRY` event without a `METHOD_EXIT`', 'unexpected', @@ -247,12 +246,12 @@ export class ApexLogParser { if (currentLine.isTruncated) { this.updateLogIssue( - this.lastTimestamp, + currentLine.exitStamp, 'Max-Size-reached', 'The maximum log size has been reached. Part of the log has been truncated.', 'skip', ); - this.maxSizeTimestamp = this.lastTimestamp; + this.maxSizeTimestamp = currentLine.exitStamp; } currentLine.isTruncated = true; } @@ -263,7 +262,7 @@ export class ApexLogParser { } private isMatchingEnd(startMethod: LogEvent, endLine: LogEvent) { - return ( + return !!( endLine.type && startMethod.exitTypes.includes(endLine.type) && (endLine.lineNumber === startMethod.lineNumber || From da2a6d6b4e61818990853790e039e25adb47bc39 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 1 May 2025 11:40:45 +0100 Subject: [PATCH 09/20] perf: switch to asyn processing of log content Pipes the fetch repsonsse through TextDecoderStream. We still read the full log content into memory but we do not block the main thread. It also open up a migration to reading the log line by line later instead of all into memory which will help with memory. --- log-viewer/modules/components/LogViewer.ts | 51 ++++++++++++---------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/log-viewer/modules/components/LogViewer.ts b/log-viewer/modules/components/LogViewer.ts index fea2d39c..15c3c91a 100644 --- a/log-viewer/modules/components/LogViewer.ts +++ b/log-viewer/modules/components/LogViewer.ts @@ -97,38 +97,41 @@ export class LogViewer extends LitElement { this.notifications = localNotifications; this.parserIssues = this.parserIssuesToMessages(apexLog); - this.logStatus = 'Ready'; } async _readLog(logUri: string): Promise { + let msg = ''; if (logUri) { - return fetch(logUri) - .then((response) => { - if (response.ok) { - return response.text(); - } else { - throw Error(response.statusText || `Error reading log file: ${response.status}`); + try { + const response = await fetch(logUri); + if (!response.ok || !response.body) { + throw new Error(response.statusText || `Error reading log file: ${response.status}`); + } + + const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); + const chunks: string[] = []; + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; } - }) - .catch((err: unknown) => { - const msg = err instanceof Error ? err.message : String(err); - - const logMessage = new Notification(); - logMessage.summary = 'Could not read log'; - logMessage.message = msg || ''; - logMessage.severity = 'Error'; - this.notifications.push(logMessage); - return Promise.resolve(''); - }); + chunks.push(value); + } + return chunks.join(''); + } catch (err: unknown) { + msg = (err instanceof Error ? err.message : String(err)) ?? ''; + } } else { - const logMessage = new Notification(); - logMessage.summary = 'Could not read log'; - logMessage.message = 'Invalid Log Path'; - logMessage.severity = 'Error'; - this.notifications.push(logMessage); - return Promise.resolve(''); + msg = 'Invalid Log Path'; } + + const logMessage = new Notification(); + logMessage.summary = 'Could not read log'; + logMessage.message = msg; + logMessage.severity = 'Error'; + this.notifications.push(logMessage); + return ''; } severity = new Map([ From 34786157a9b24b83bb005eaccc5304be574f2be0 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 1 May 2025 11:42:44 +0100 Subject: [PATCH 10/20] perf: reduce calls to get from map --- log-viewer/modules/parsers/ApexLogParser.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index 0b10f4ca..adc2353f 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -303,18 +303,18 @@ export class ApexLogParser { private flattenByDepth(nodes: LogEvent[]) { const result = new Map(); - result.set(0, nodes); - let currentDepth = 1; + let currentDepth = 0; let currentNodes = nodes; let len = currentNodes.length; while (len) { - result.set(currentDepth, []); + result.set(currentDepth, currentNodes); + + const children: LogEvent[] = []; while (len--) { const node = currentNodes[len]; if (node?.children) { - const children = result.get(currentDepth)!; node.children.forEach((c) => { if (c.children.length) { children.push(c); @@ -322,7 +322,8 @@ export class ApexLogParser { }); } } - currentNodes = result.get(currentDepth++) ?? []; + currentDepth++; + currentNodes = children; len = currentNodes.length; } From 0180a6087a1dea454ca279a325ac620228252676 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 1 May 2025 16:59:14 +0100 Subject: [PATCH 11/20] refactor: split ApexLogParser into multiple files - ApexLogParser.ts - Main parser logic - LogEvents.ts - All log event classes - LogLineMapping.ts - Mapping of log event types to classes - types.ts - Types and interfaces --- log-viewer/modules/Database.ts | 7 +- .../modules/__tests__/ApexLogParser.test.ts | 8 +- .../modules/__tests__/soql/SOQLLinter.test.ts | 3 +- log-viewer/modules/components/AppHeader.ts | 3 +- log-viewer/modules/components/CallStack.ts | 2 +- log-viewer/modules/components/LogViewer.ts | 3 +- .../modules/components/SOQLLinterIssues.ts | 2 +- .../components/analysis-view/AnalysisView.ts | 2 +- .../column-calcs/CallStackSum.ts | 4 +- .../components/calltree-view/CalltreeView.ts | 3 +- .../calltree-view/module/MiddleRowFocus.ts | 2 +- .../components/database-view/DMLView.ts | 2 +- .../database-view/DatabaseSection.ts | 2 +- .../components/database-view/DatabaseView.ts | 2 +- .../components/database-view/SOQLView.ts | 4 +- log-viewer/modules/parsers/ApexLogParser.ts | 2616 +---------------- log-viewer/modules/parsers/LogEvents.ts | 2037 +++++++++++++ log-viewer/modules/parsers/LogLineMapping.ts | 479 +++ log-viewer/modules/parsers/types.ts | 288 ++ log-viewer/modules/timeline/Timeline.ts | 5 +- log-viewer/modules/timeline/TimelineView.ts | 3 +- 21 files changed, 2841 insertions(+), 2636 deletions(-) create mode 100644 log-viewer/modules/parsers/LogEvents.ts create mode 100644 log-viewer/modules/parsers/LogLineMapping.ts create mode 100644 log-viewer/modules/parsers/types.ts diff --git a/log-viewer/modules/Database.ts b/log-viewer/modules/Database.ts index 4e56ca9b..ff52fa5d 100644 --- a/log-viewer/modules/Database.ts +++ b/log-viewer/modules/Database.ts @@ -1,7 +1,12 @@ /* * Copyright (c) 2020 Certinia Inc. All rights reserved. */ -import { ApexLog, DMLBeginLine, LogEvent, SOQLExecuteBeginLine } from './parsers/ApexLogParser.js'; +import { + type ApexLog, + DMLBeginLine, + type LogEvent, + SOQLExecuteBeginLine, +} from './parsers/LogEvents.js'; export type Stack = LogEvent[]; diff --git a/log-viewer/modules/__tests__/ApexLogParser.test.ts b/log-viewer/modules/__tests__/ApexLogParser.test.ts index 13be3d66..f22b4781 100644 --- a/log-viewer/modules/__tests__/ApexLogParser.test.ts +++ b/log-viewer/modules/__tests__/ApexLogParser.test.ts @@ -2,19 +2,19 @@ * Copyright (c) 2020 Certinia Inc. All rights reserved. */ import { - ApexLogParser, CodeUnitStartedLine, ExecutionStartedLine, LogEvent, MethodEntryLine, SOQLExecuteBeginLine, SOQLExecuteExplainLine, - lineTypeMap, - parse, parseObjectNamespace, parseRows, parseVfNamespace, -} from '../parsers/ApexLogParser.js'; +} from '../parsers/LogEvents.js'; +import { lineTypeMap } from '../parsers/LogLineMapping.js'; + +import { ApexLogParser, parse } from '../parsers/ApexLogParser.js'; class DummyLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { diff --git a/log-viewer/modules/__tests__/soql/SOQLLinter.test.ts b/log-viewer/modules/__tests__/soql/SOQLLinter.test.ts index 6031bbe4..a34da170 100644 --- a/log-viewer/modules/__tests__/soql/SOQLLinter.test.ts +++ b/log-viewer/modules/__tests__/soql/SOQLLinter.test.ts @@ -1,7 +1,8 @@ /* * Copyright (c) 2021 Certinia Inc. All rights reserved. */ -import { ApexLogParser, LogEvent } from '../../parsers/ApexLogParser.js'; +import { ApexLogParser } from '../../parsers/ApexLogParser.js'; +import { LogEvent } from '../../parsers/LogEvents.js'; import { SOQLLinter } from '../../soql/SOQLLinter.js'; class DummySOQLLine extends LogEvent { diff --git a/log-viewer/modules/components/AppHeader.ts b/log-viewer/modules/components/AppHeader.ts index ff52c226..3de0f8f0 100644 --- a/log-viewer/modules/components/AppHeader.ts +++ b/log-viewer/modules/components/AppHeader.ts @@ -10,9 +10,10 @@ import { import { LitElement, css, html, unsafeCSS } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; +import type { ApexLog } from '../parsers/LogEvents.js'; import codiconStyles from '../styles/codicon.css'; import { globalStyles } from '../styles/global.styles.js'; -import { ApexLog, type TimelineGroup } from '../timeline/Timeline.js'; +import type { TimelineGroup } from '../timeline/Timeline.js'; import '../timeline/TimelineView.js'; import './analysis-view/AnalysisView.js'; import './calltree-view/CalltreeView'; diff --git a/log-viewer/modules/components/CallStack.ts b/log-viewer/modules/components/CallStack.ts index 4d66b789..8aac118e 100644 --- a/log-viewer/modules/components/CallStack.ts +++ b/log-viewer/modules/components/CallStack.ts @@ -5,7 +5,7 @@ import { LitElement, css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { DatabaseAccess } from '../Database.js'; -import { LogEvent } from '../parsers/ApexLogParser.js'; +import type { LogEvent } from '../parsers/LogEvents.js'; import { globalStyles } from '../styles/global.styles.js'; import { goToRow } from './calltree-view/CalltreeView.js'; diff --git a/log-viewer/modules/components/LogViewer.ts b/log-viewer/modules/components/LogViewer.ts index 15c3c91a..96ef2be9 100644 --- a/log-viewer/modules/components/LogViewer.ts +++ b/log-viewer/modules/components/LogViewer.ts @@ -4,7 +4,8 @@ import { LitElement, css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { ApexLog, parse } from '../parsers/ApexLogParser.js'; +import { parse } from '../parsers/ApexLogParser.js'; +import { type ApexLog } from '../parsers/LogEvents.js'; import { vscodeMessenger } from '../services/VSCodeExtensionMessenger.js'; import { globalStyles } from '../styles/global.styles.js'; import type { TimelineGroup } from '../timeline/Timeline.js'; diff --git a/log-viewer/modules/components/SOQLLinterIssues.ts b/log-viewer/modules/components/SOQLLinterIssues.ts index 3864da70..f280b8bd 100644 --- a/log-viewer/modules/components/SOQLLinterIssues.ts +++ b/log-viewer/modules/components/SOQLLinterIssues.ts @@ -5,7 +5,7 @@ import { LitElement, css, html, type PropertyValues, type TemplateResult } from import { customElement, property, state } from 'lit/decorators.js'; import { DatabaseAccess } from '../Database.js'; -import { SOQLExecuteBeginLine, SOQLExecuteExplainLine } from '../parsers/ApexLogParser.js'; +import type { SOQLExecuteBeginLine, SOQLExecuteExplainLine } from '../parsers/LogEvents.js'; import { SEVERITY_TYPES, SOQLLinter, diff --git a/log-viewer/modules/components/analysis-view/AnalysisView.ts b/log-viewer/modules/components/analysis-view/AnalysisView.ts index 44814cc9..20cf15c8 100644 --- a/log-viewer/modules/components/analysis-view/AnalysisView.ts +++ b/log-viewer/modules/components/analysis-view/AnalysisView.ts @@ -10,7 +10,7 @@ import { } from '@vscode/webview-ui-toolkit'; import { LitElement, css, html, unsafeCSS, type PropertyValues } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { ApexLog, LogEvent } from '../../parsers/ApexLogParser.js'; +import type { ApexLog, LogEvent } from '../../parsers/LogEvents.js'; import { vscodeMessenger } from '../../services/VSCodeExtensionMessenger.js'; import { globalStyles } from '../../styles/global.styles.js'; diff --git a/log-viewer/modules/components/analysis-view/column-calcs/CallStackSum.ts b/log-viewer/modules/components/analysis-view/column-calcs/CallStackSum.ts index 3b36c24c..f62b5a52 100644 --- a/log-viewer/modules/components/analysis-view/column-calcs/CallStackSum.ts +++ b/log-viewer/modules/components/analysis-view/column-calcs/CallStackSum.ts @@ -1,5 +1,5 @@ -import type { LogEvent } from '../../../parsers/ApexLogParser'; -import { type Metric } from '../AnalysisView.js'; +import type { LogEvent } from '../../../parsers/LogEvents.js'; +import type { Metric } from '../AnalysisView.js'; export function callStackSum(_values: number[], data: Metric[], _calcParams: unknown) { const nodes: LogEvent[] = []; diff --git a/log-viewer/modules/components/calltree-view/CalltreeView.ts b/log-viewer/modules/components/calltree-view/CalltreeView.ts index ef72617c..736cdfe0 100644 --- a/log-viewer/modules/components/calltree-view/CalltreeView.ts +++ b/log-viewer/modules/components/calltree-view/CalltreeView.ts @@ -27,7 +27,8 @@ import { progressFormatter } from '../../datagrid/format/Progress.js'; import { RowKeyboardNavigation } from '../../datagrid/module/RowKeyboardNavigation.js'; import { RowNavigation } from '../../datagrid/module/RowNavigation.js'; import dataGridStyles from '../../datagrid/style/DataGrid.scss'; -import { ApexLog, LogEvent, type LogEventType } from '../../parsers/ApexLogParser.js'; +import type { ApexLog, LogEvent } from '../../parsers/LogEvents.js'; +import type { LogEventType } from '../../parsers/types.js'; import { vscodeMessenger } from '../../services/VSCodeExtensionMessenger.js'; import { globalStyles } from '../../styles/global.styles.js'; import { isVisible } from '../../Util.js'; diff --git a/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts b/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts index 450cb020..b9342f2a 100644 --- a/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts +++ b/log-viewer/modules/components/calltree-view/module/MiddleRowFocus.ts @@ -2,7 +2,7 @@ * Copyright (c) 2024 Certinia Inc. All rights reserved. */ import { Module, type RowComponent, type Tabulator } from 'tabulator-tables'; -import type { LogEvent } from '../../../parsers/ApexLogParser'; +import type { LogEvent } from '../../../parsers/LogEvents.js'; type TimedNodeProp = { originalData: LogEvent }; diff --git a/log-viewer/modules/components/database-view/DMLView.ts b/log-viewer/modules/components/database-view/DMLView.ts index e6b9f8e3..12e80640 100644 --- a/log-viewer/modules/components/database-view/DMLView.ts +++ b/log-viewer/modules/components/database-view/DMLView.ts @@ -25,7 +25,7 @@ import dataGridStyles from '../../datagrid/style/DataGrid.scss'; // others import { DatabaseAccess } from '../../Database.js'; -import { ApexLog, DMLBeginLine } from '../../parsers/ApexLogParser.js'; +import type { ApexLog, DMLBeginLine } from '../../parsers/LogEvents.js'; import { vscodeMessenger } from '../../services/VSCodeExtensionMessenger.js'; import codiconStyles from '../../styles/codicon.css'; import { globalStyles } from '../../styles/global.styles.js'; diff --git a/log-viewer/modules/components/database-view/DatabaseSection.ts b/log-viewer/modules/components/database-view/DatabaseSection.ts index 926688e1..a2dde7a5 100644 --- a/log-viewer/modules/components/database-view/DatabaseSection.ts +++ b/log-viewer/modules/components/database-view/DatabaseSection.ts @@ -4,7 +4,7 @@ import { LitElement, css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { LogEvent } from '../../parsers/ApexLogParser.js'; +import type { LogEvent } from '../../parsers/LogEvents.js'; import { globalStyles } from '../../styles/global.styles.js'; import '../BadgeBase.js'; diff --git a/log-viewer/modules/components/database-view/DatabaseView.ts b/log-viewer/modules/components/database-view/DatabaseView.ts index 83fe0620..434e647d 100644 --- a/log-viewer/modules/components/database-view/DatabaseView.ts +++ b/log-viewer/modules/components/database-view/DatabaseView.ts @@ -5,7 +5,7 @@ import { LitElement, css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { ApexLog } from '../../parsers/ApexLogParser.js'; +import type { ApexLog } from '../../parsers/LogEvents.js'; import { globalStyles } from '../../styles/global.styles.js'; import '../CallStack.js'; import './DMLView.js'; diff --git a/log-viewer/modules/components/database-view/SOQLView.ts b/log-viewer/modules/components/database-view/SOQLView.ts index db59b0bb..9c2a0d58 100644 --- a/log-viewer/modules/components/database-view/SOQLView.ts +++ b/log-viewer/modules/components/database-view/SOQLView.ts @@ -32,11 +32,11 @@ import dataGridStyles from '../../datagrid/style/DataGrid.scss'; // others import { DatabaseAccess } from '../../Database.js'; import { isVisible } from '../../Util.js'; -import { +import type { ApexLog, SOQLExecuteBeginLine, SOQLExecuteExplainLine, -} from '../../parsers/ApexLogParser.js'; +} from '../../parsers/LogEvents.js'; import { vscodeMessenger } from '../../services/VSCodeExtensionMessenger.js'; import codiconStyles from '../../styles/codicon.css'; import { globalStyles } from '../../styles/global.styles.js'; diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index adc2353f..12b3769b 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -2,22 +2,13 @@ * Copyright (c) 2020 Certinia Inc. All rights reserved. */ +import { ApexLog, type LogEvent } from './LogEvents.js'; +import { getLogEventClass } from './LogLineMapping.js'; +import type { IssueType, LogEventType, LogIssue } from './types.js'; + const typePattern = /^[A-Z_]*$/, settingsPattern = /^\d+\.\d+\sAPEX_CODE,\w+;APEX_PROFILING,.+$/m; -type LineNumber = number | string | null; // an actual line-number or 'EXTERNAL' -type IssueType = 'unexpected' | 'error' | 'skip'; - -export type LogSubCategory = - | '' - | 'Method' - | 'System Method' - | 'Code Unit' - | 'DML' - | 'SOQL' - | 'Flow' - | 'Workflow'; - /** * Takes string input of a log and returns the ApexLog class, which represents a log tree * @param {string} logData @@ -444,11 +435,6 @@ export class ApexLogParser { } } -interface SelfTotal { - self: number; - total: number; -} - export class DebugLevel { logCategory: string; logLevel: string; @@ -478,2597 +464,3 @@ export class LineIterator { return result; } } - -export interface LogIssue { - startTime?: number; - summary: string; - description: string; - type: IssueType; -} - -/** - * All log lines extend this base class. - */ -export abstract class LogEvent { - logParser: ApexLogParser; - - // common metadata (available for all lines) - - parent: LogEvent | null = null; - - /** - * All child nodes of the current node - */ - children: LogEvent[] = []; - - /** - * The type of this log line from the log file e.g METHOD_ENTRY - */ - type: LogEventType | null = null; - - /** - * The full raw text of this log line - */ - logLine = ''; // the raw text of this log line - - /** - * A parsed version of the log line text useful for display in UIs - */ - text; - - // optional metadata - /** - * Should this log entry pull in following text lines (as the log entry can contain newlines)? - */ - acceptsText = false; - - /** - * Is this log entry generated by a declarative process? - */ - declarative = false; - /** - * Is a method exit line? - */ - isExit = false; - - /** - * Whether the log event was truncated when the log ended, e,g no matching end event - */ - isTruncated = false; - - /** - * Should the exitstamp be the timestamp of the next line? - * These kind of lines can not be used as exit lines for anything othe than other pseudo exits. - */ - nextLineIsExit = false; - - /** - * The line number within the containing class - */ - lineNumber: LineNumber = null; - - /** - * The package namespace associated with this log line - * @default default - */ - namespace: string | 'default' = ''; - - /** - * The variable value - */ - value: string | null = null; - - /** - * Could match to a corresponding symbol in a file in the workspace? - */ - hasValidSymbols = false; - - /** - * Extra description context - */ - suffix: string | null = null; - - /** - * Does this line cause a discontinuity in the call stack? e.g an exception causing stack unwinding - */ - discontinuity = false; - - /** - * The timestamp of this log line, in nanoseconds - */ - timestamp; - - /** - * The timestamp when the node finished, in nanoseconds - */ - exitStamp: number | null = null; - - /** - * The log sub category this event belongs to - */ - subCategory: LogSubCategory = ''; - - /** - * The CPU type, e.g loading, method, custom - */ - cpuType: CPUType = ''; // the category key to collect our cpu usage - - /** - * The time spent. - */ - duration: SelfTotal = { - /** - * The net (wall) time spent in the node (when not inside children) - */ - self: 0, - /** - * The total (wall) time spent in the node - */ - total: 0, - }; - - /** - * Total + self row counts for DML - */ - dmlRowCount: SelfTotal = { - /** - * The net number of DML rows for this node, excluding child nodes - */ - self: 0, - /** - * The total number of DML rows for this node and child nodes - */ - total: 0, - }; - - /** - * Total + self row counts for SOQL - */ - soqlRowCount: SelfTotal = { - /** - * The net number of SOQL rows for this node, excluding child nodes - */ - self: 0, - /** - * The total number of SOQL rows for this node and child nodes - */ - total: 0, - }; - - /** - * Total + self row counts for SOSL - */ - soslRowCount: SelfTotal = { - /** - * The net number of SOSL rows for this node, excluding child nodes - */ - self: 0, - /** - * The total number of SOSL rows for this node and child nodes - */ - total: 0, - }; - - dmlCount: SelfTotal = { - /** - * The net number of DML operations (DML_BEGIN) in this node. - */ - self: 0, - /** - * The total number of DML operations (DML_BEGIN) in this node and child nodes - */ - total: 0, - }; - - soqlCount: SelfTotal = { - /** - * The net number of SOQL operations (SOQL_EXECUTE_BEGIN) in this node. - */ - self: 0, - /** - * The total number of SOQL operations (SOQL_EXECUTE_BEGIN) in this node and child nodes - */ - total: 0, - }; - - soslCount: SelfTotal = { - /** - * The net number of SOSL operations (SOSL_EXECUTE_BEGIN) in this node. - */ - self: 0, - /** - * The total number of SOSL operations (SOSL_EXECUTE_BEGIN) in this node and child nodes - */ - total: 0, - }; - - /** - * The total number of exceptions thrown (EXCEPTION_THROWN) in this node and child nodes - */ - totalThrownCount = 0; - - /** - * The line types which would legitimately end this method - */ - exitTypes: LogEventType[] = []; - - constructor(parser: ApexLogParser, parts: string[] | null) { - this.logParser = parser; - if (parts) { - const [timeData, type] = parts; - this.text = this.type = type as LogEventType; - this.timestamp = timeData ? this.parseTimestamp(timeData) : 0; - } else { - this.timestamp = 0; - this.text = ''; - } - } - - /** Called if a corresponding end event is found during tree parsing*/ - onEnd?(end: LogEvent, stack: LogEvent[]): void; - - /** Called when the Log event after this one is created in the line parser*/ - onAfter?(parser: ApexLogParser, next?: LogEvent): void; - - public recalculateDurations() { - if (this.exitStamp) { - this.duration.total = this.duration.self = this.exitStamp - this.timestamp; - } - } - - private parseTimestamp(text: string): number { - const start = text.indexOf('('); - if (start !== -1) { - return Number(text.slice(start + 1, -1)); - } - throw new Error(`Unable to parse timestamp: '${text}'`); - } - - protected parseLineNumber(text: string | null | undefined): string | number { - switch (true) { - case text === '[EXTERNAL]': - return 'EXTERNAL'; - case !!text: { - const lineNumberStr = text.slice(1, -1); - if (lineNumberStr) { - return Number(lineNumberStr); - } - throw new Error(`Unable to parse line number: '${text}'`); - } - default: - return 0; - } - } -} - -class BasicLogLine extends LogEvent {} -class BasicExitLine extends LogEvent { - isExit = true; -} - -type CPUType = 'loading' | 'custom' | 'method' | 'free' | 'system' | 'pkg' | ''; - -/** - * Log lines extend this class if they have a start-line and an end-line (and hence can have children in-between). - * - The start-line should extend "Method" and collect any children. - * - The end-line should extend "Detail" and terminate the method (also providing the "exitStamp"). - * The method will be rendered as "expandable" in the tree-view, if it has children. - */ -class Method extends LogEvent { - constructor( - parser: ApexLogParser, - parts: string[] | null, - exitTypes: string[], - timelineKey: LogSubCategory, - cpuType: CPUType, - ) { - super(parser, parts); - this.subCategory = timelineKey; - this.cpuType = cpuType; - this.exitTypes = exitTypes as LogEventType[]; - } -} - -/** - * This class represents the single root node for the node tree. - * It is a "pseudo" node and not present in the log. - * Since it has children it extends "Method". - */ -export class ApexLog extends Method { - type = null; - text = 'LOG_ROOT'; - timestamp = 0; - exitStamp = 0; - /** - * The size of the log, in bytes - */ - public size = 0; - - /** - * The total CPU time consumed, in ms - */ - public cpuTime: number = 0; - - /** - * The Apex Debug Logging Levels for the current log - */ - public debugLevels: DebugLevel[] = []; - - /** - * All the namespaces that appear in this log. - */ - public namespaces: string[] = []; - - /** - * Any issues within the log, such as cpu time exceeded or max log size reached. - */ - public logIssues: LogIssue[] = []; - - /** - * Any issues that occurred during the parsing of the log, such as an unrecognized log event type. - */ - public parsingErrors: string[] = []; - - /** - * The endtime with nodes of 0 duration excluded - */ - executionEndTime = 0; - - constructor(parser: ApexLogParser) { - super(parser, null, [], 'Code Unit', ''); - } - - setTimes() { - this.timestamp = - this.children.find((child) => { - return child.timestamp; - })?.timestamp || 0; - // We do not just want to use the very last exitStamp because it could be CUMULATIVE_USAGE which is not really part of the code execution time but does have a later time. - let endTime; - const reverseLen = this.children.length - 1; - for (let i = reverseLen; i >= 0; i--) { - const child = this.children[i]; - // If there is no duration on a node then it is not going to be shown on the timeline anyway - if (child?.exitStamp) { - endTime ??= child.exitStamp; - if (child.duration) { - this.executionEndTime = child.exitStamp; - break; - } - } - endTime ??= child?.timestamp; - } - this.exitStamp = endTime || 0; - this.recalculateDurations(); - } -} - -export function parseObjectNamespace(text: string | null | undefined): string { - if (!text) { - return ''; - } - - const sep = text.indexOf('__'); - if (sep === -1) { - return 'default'; - } - return text.slice(0, sep); -} - -export function parseVfNamespace(text: string): string { - const sep = text.indexOf('__'); - if (sep === -1) { - return 'default'; - } - const firstSlash = text.indexOf('/'); - if (firstSlash === -1) { - return 'default'; - } - const secondSlash = text.indexOf('/', firstSlash + 1); - if (secondSlash < 0) { - return 'default'; - } - return text.substring(secondSlash + 1, sep); -} - -export function parseRows(text: string | null | undefined): number { - if (!text) { - return 0; - } - - const rowCount = text.slice(text.indexOf('Rows:') + 5); - if (rowCount) { - return Number(rowCount); - } - throw new Error(`Unable to parse row count: '${text}'`); -} - -/* Log line entry Parsers */ - -class BulkHeapAllocateLine extends LogEvent { - logCategory: 'Apex Code'; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - this.logCategory = 'Apex Code'; - } -} - -class CalloutRequestLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[3]} : ${parts[2]}`; - } -} - -class CalloutResponseLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[3]} : ${parts[2]}`; - } -} -class NamedCredentialRequestLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; - } -} - -class NamedCredentialResponseLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}`; - } -} - -class NamedCredentialResponseDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[3]} : ${parts[4]} ${parts[5]} : ${parts[6]} ${parts[7]}`; - } -} - -class ConstructorEntryLine extends Method { - hasValidSymbols = true; - suffix = ' (constructor)'; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['CONSTRUCTOR_EXIT'], 'Method', 'method'); - this.lineNumber = this.parseLineNumber(parts[2]); - const [, , , , args, className] = parts; - - this.text = className + (args ? args.substring(args.lastIndexOf('(')) : ''); - const possibleNS = this._parseConstructorNamespace(className || ''); - if (possibleNS) { - this.namespace = possibleNS; - } - } - - _parseConstructorNamespace(className: string): string { - let possibleNs = className.slice(0, className.indexOf('.')); - if (this.logParser.namespaces.has(possibleNs)) { - return possibleNs; - } - - const constructorParts = (className ?? '').split('.'); - possibleNs = constructorParts[0] || ''; - // inmner class with a namespace - if (constructorParts.length === 3) { - return possibleNs; - } - - return ''; - } -} - -class ConstructorExitLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - } -} - -class EmailQueueLine extends LogEvent { - acceptsText = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - } -} - -export class MethodEntryLine extends Method { - hasValidSymbols = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['METHOD_EXIT'], 'Method', 'method'); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[4] || this.type || this.text; - if (this.text.indexOf('System.Type.forName(') !== -1) { - // assume we are not charged for class loading (or at least not lengthy remote-loading / compiling) - this.cpuType = 'loading'; - } else { - const possibleNs = this._parseMethodNamespace(parts[4]); - if (possibleNs) { - this.namespace = possibleNs; - } - } - } - - onEnd(end: MethodExitLine, _stack: LogEvent[]): void { - if (end.namespace && !end.text.endsWith(')')) { - this.namespace = end.namespace; - } - } - - _parseMethodNamespace(methodName: string | undefined): string { - if (!methodName) { - return ''; - } - - const methodBracketIndex = methodName.indexOf('('); - if (methodBracketIndex === -1) { - return ''; - } - - const nsSeparator = methodName.indexOf('.'); - if (nsSeparator === -1) { - return ''; - } - - const possibleNs = methodName.slice(0, nsSeparator); - if (this.logParser.namespaces.has(possibleNs)) { - return possibleNs; - } - - const methodNameParts = methodName.slice(0, methodBracketIndex)?.split('.'); - if (methodNameParts.length === 4) { - return methodNameParts[0] ?? ''; - } else if (methodNameParts.length === 2) { - return 'default'; - } - - return ''; - } -} -class MethodExitLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[4] ?? parts[3] ?? this.text; - - /*A method will end with ')'. Without that this it represents the first reference to a class, outer or inner. One of the few reliable ways to determine valid namespaces. The first reference to a class (outer or inner) will always have an METHOD_EXIT containing the Outer class name with namespace if present. Other events will follow, CONSTRUCTOR_ENTRY etc. But this case will only ever have 2 parts ns.Outer even if the first reference was actually an inner class e.g new ns.Outer.Inner();*/ - // If does not end in ) then we have a reference to the class, either via outer or inner. - if (!this.text.endsWith(')')) { - // if there is a . the we have a namespace e.g ns.Outer - const index = this.text.indexOf('.'); - if (index !== -1) { - this.namespace = this.text.slice(0, index); - } - } - } -} - -class SystemConstructorEntryLine extends Method { - suffix = '(system constructor)'; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['SYSTEM_CONSTRUCTOR_EXIT'], 'System Method', 'method'); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[3] || ''; - } -} - -class SystemConstructorExitLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - } -} -class SystemMethodEntryLine extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['SYSTEM_METHOD_EXIT'], 'System Method', 'method'); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[3] || ''; - } -} - -class SystemMethodExitLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - } -} - -export class CodeUnitStartedLine extends Method { - suffix = ' (entrypoint)'; - codeUnitType = ''; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['CODE_UNIT_FINISHED'], 'Code Unit', 'custom'); - - const typeString = parts[5] || parts[4] || parts[3] || ''; - let sepIndex = typeString.indexOf(':'); - if (sepIndex === -1) { - sepIndex = typeString.indexOf('/'); - } - this.codeUnitType = sepIndex !== -1 ? typeString.slice(0, sepIndex) : ''; - - const name = parts[4] || parts[3] || this.codeUnitType || ''; - switch (this.codeUnitType) { - case 'EventService': - this.cpuType = 'method'; - this.namespace = parseObjectNamespace(typeString.slice(sepIndex + 1)); - this.text = name; - break; - case 'Validation': - this.cpuType = 'custom'; - this.declarative = true; - - this.text = name; - break; - case 'Workflow': - this.cpuType = 'custom'; - this.declarative = true; - this.text = name; - break; - case 'Flow': - this.cpuType = 'custom'; - this.declarative = true; - this.text = name; - break; - case 'VF': - this.cpuType = 'method'; - this.namespace = parseVfNamespace(name); - this.text = name; - break; - case 'apex': { - this.cpuType = 'method'; - const namespaceIndex = name.indexOf('.'); - this.namespace = - namespaceIndex !== -1 - ? name.slice(name.indexOf('apex://') + 7, namespaceIndex) - : 'default'; - this.text = name; - break; - } - case '__sfdc_trigger': { - this.cpuType = 'method'; - this.text = name || parts[4] || ''; - const triggerParts = parts[5]?.split('/') || ''; - this.namespace = triggerParts.length === 3 ? triggerParts[1] || 'default' : 'default'; - break; - } - default: { - this.cpuType = 'method'; - this.text = name; - const openBracket = name.lastIndexOf('('); - const methodName = - openBracket !== -1 ? name.slice(0, openBracket + 1).split('.') : name.split('.'); - if (methodName.length === 3 || (methodName.length === 2 && !methodName[1]?.endsWith('('))) { - this.namespace = methodName[0] || 'default'; - } - break; - } - } - - this.namespace ||= 'default'; - } -} -export class CodeUnitFinishedLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class VFApexCallStartLine extends Method { - suffix = ' (VF APEX)'; - invalidClasses = [ - 'pagemessagescomponentcontroller', - 'pagemessagecomponentcontroller', - 'severitymessages', - ]; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['VF_APEX_CALL_END'], 'Method', 'method'); - this.lineNumber = this.parseLineNumber(parts[2]); - - const classText = parts[5] || parts[3] || ''; - let methodtext = parts[4] || ''; - if ( - !methodtext && - (!classText.includes(' ') || - this.invalidClasses.some((invalidCls: string) => - classText.toLowerCase().includes(invalidCls), - )) - ) { - // we have a system entry and they do not have exits - // e.g |VF_APEX_CALL_START|[EXTERNAL]|/apexpage/pagemessagescomponentcontroller.apex - // and they really mess with the logs so skip handling them. - this.exitTypes = []; - } else if (methodtext) { - this.hasValidSymbols = true; - // method call - const methodIndex = methodtext.indexOf('('); - const constructorIndex = methodtext.indexOf(''); - if (methodIndex > -1) { - // Method - methodtext = '.' + methodtext.substring(methodIndex).slice(1, -1) + '()'; - } else if (constructorIndex > -1) { - // Constructor - methodtext = methodtext.substring(constructorIndex + 6) + '()'; - } else { - // Property - methodtext = '.' + methodtext; - } - } else { - this.hasValidSymbols = true; - } - this.text = classText + methodtext; - } -} - -class VFApexCallEndLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class VFDeserializeViewstateBeginLine extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['VF_DESERIALIZE_VIEWSTATE_END'], 'System Method', 'method'); - } -} - -class VFFormulaStartLine extends Method { - suffix = ' (VF FORMULA)'; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['VF_EVALUATE_FORMULA_END'], 'System Method', 'custom'); - this.text = parts[3] || ''; - } -} - -class VFFormulaEndLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class VFSeralizeViewStateStartLine extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['VF_SERIALIZE_VIEWSTATE_END'], 'System Method', 'method'); - } -} - -class VFPageMessageLine extends LogEvent { - acceptsText = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class DMLBeginLine extends Method { - dmlCount = { - self: 1, - total: 1, - }; - - namespace = 'default'; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['DML_END'], 'DML', 'free'); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = 'DML ' + parts[3] + ' ' + parts[4]; - const rowCountString = parts[5]; - this.dmlRowCount.total = this.dmlRowCount.self = rowCountString ? parseRows(rowCountString) : 0; - } -} - -class DMLEndLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - } -} - -class IdeasQueryExecuteLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - } -} - -class SOQLExecuteBeginLine extends Method { - aggregations = 0; - soqlCount = { - self: 1, - total: 1, - }; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['SOQL_EXECUTE_END'], 'SOQL', 'free'); - this.lineNumber = this.parseLineNumber(parts[2]); - - const [, , , aggregations, soqlString] = parts; - - const aggregationText = aggregations || ''; - if (aggregationText) { - const aggregationIndex = aggregationText.indexOf('Aggregations:'); - this.aggregations = Number(aggregationText.slice(aggregationIndex + 13)); - } - this.text = soqlString || ''; - } - - onEnd(end: SOQLExecuteEndLine, _stack: LogEvent[]): void { - this.soqlRowCount.total = this.soqlRowCount.self = end.soqlRowCount.total; - } -} - -class SOQLExecuteEndLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.soqlRowCount.total = this.soqlRowCount.self = parseRows(parts[3] || ''); - } -} - -class SOQLExecuteExplainLine extends LogEvent { - cardinality: number | null = null; // The estimated number of records that the leading operation type would return - fields: string[] | null = null; //The indexed field(s) used by the Query Optimizer. If the leading operation type is Index, the fields value is Index. Otherwise, the fields value is null. - leadingOperationType: string | null = null; // The primary operation type that Salesforce will use to optimize the query. - relativeCost: number | null = null; // The cost of the query compared to the Force.com Query Optimizer’s selectivity threshold. Values above 1 mean that the query won’t be selective. - sObjectCardinality: number | null = null; // The approximate record count for the queried object. - sObjectType: string | null = null; //T he name of the queried SObject - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - - const queryPlanDetails = parts[3] || ''; - this.text = queryPlanDetails; - - const queryplanParts = queryPlanDetails.split('],'); - if (queryplanParts.length > 1) { - const planExplain = queryplanParts[0] || ''; - const [cardinalityText, sobjCardinalityText, costText] = (queryplanParts[1] || '').split(','); - - const onIndex = planExplain.indexOf(' on'); - this.leadingOperationType = planExplain.slice(0, onIndex); - - const colonIndex = planExplain.indexOf(' :'); - this.sObjectType = planExplain.slice(onIndex + 4, colonIndex); - - // remove whitespace if there is any. we could have [ field1__c, field2__c ] - // I am not 100% sure of format when we have multiple fields so this is safer - const fieldsAsString = planExplain.slice(planExplain.indexOf('[') + 1).replace(/\s+/g, ''); - this.fields = fieldsAsString === '' ? [] : fieldsAsString.split(','); - - this.cardinality = cardinalityText - ? Number(cardinalityText.slice(cardinalityText.indexOf('cardinality: ') + 13)) - : null; - this.sObjectCardinality = sobjCardinalityText - ? Number( - sobjCardinalityText.slice(sobjCardinalityText.indexOf('sobjectCardinality: ') + 20), - ) - : null; - this.relativeCost = costText - ? Number(costText.slice(costText.indexOf('relativeCost ') + 13)) - : null; - } - } -} - -class SOSLExecuteBeginLine extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['SOSL_EXECUTE_END'], 'SOQL', 'free'); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = `SOSL: ${parts[3]}`; - - this.soslCount = { - self: 1, - total: 1, - }; - } - - onEnd(end: SOSLExecuteEndLine, _stack: LogEvent[]): void { - this.soslRowCount.total = this.soslRowCount.self = end.soslRowCount.total; - } -} - -class SOSLExecuteEndLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.soslRowCount.total = this.soslRowCount.self = parseRows(parts[3] || ''); - } -} - -class HeapAllocateLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[3] || ''; - } -} - -class HeapDeallocateLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - } -} - -class StatementExecuteLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - } -} - -class VariableScopeBeginLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts.slice(3).join(' | '); - } -} - -class VariableAssignmentLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts.slice(3).join(' | '); - } -} -class UserInfoLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[3] + ' ' + parts[4]; - } -} - -class UserDebugLine extends LogEvent { - acceptsText = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts.slice(3).join(' | '); - } -} - -class CumulativeLimitUsageLine extends Method { - namespace = 'default'; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['CUMULATIVE_LIMIT_USAGE_END'], 'System Method', 'system'); - } -} - -class CumulativeProfilingLine extends LogEvent { - acceptsText = true; - namespace = 'default'; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] + ' ' + (parts[3] ?? ''); - } -} - -class CumulativeProfilingBeginLine extends Method { - namespace = 'default'; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['CUMULATIVE_PROFILING_END'], 'System Method', 'custom'); - } -} - -class LimitUsageLine extends LogEvent { - namespace = 'default'; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[3] + ' ' + parts[4] + ' out of ' + parts[5]; - } -} - -class LimitUsageForNSLine extends LogEvent { - acceptsText = true; - namespace = 'default'; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } - - onAfter(parser: ApexLogParser, _next?: LogEvent): void { - const matched = this.text.match(/Maximum CPU time: (\d+)/), - cpuText = matched?.[1] || '0', - cpuTime = parseInt(cpuText, 10) * 1000000; // convert from milli-seconds to nano-seconds - - if (!parser.cpuUsed || cpuTime > parser.cpuUsed) { - parser.cpuUsed = cpuTime; - } - } -} - -class NBANodeBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['NBA_NODE_END'], 'System Method', 'method'); - this.text = parts.slice(2).join(' | '); - } -} - -class NBANodeDetail extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts.slice(2).join(' | '); - } -} -class NBANodeEnd extends LogEvent { - isExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts.slice(2).join(' | '); - } -} -class NBANodeError extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts.slice(2).join(' | '); - } -} -class NBAOfferInvalid extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts.slice(2).join(' | '); - } -} -class NBAStrategyBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['NBA_STRATEGY_END'], 'System Method', 'method'); - this.text = parts.slice(2).join(' | '); - } -} -class NBAStrategyEnd extends LogEvent { - isExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts.slice(2).join(' | '); - } -} -class NBAStrategyError extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts.slice(2).join(' | '); - } -} - -class PushTraceFlagsLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[4] + ', line:' + this.lineNumber + ' - ' + parts[5]; - } -} - -class PopTraceFlagsLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[4] + ', line:' + this.lineNumber + ' - ' + parts[5]; - } -} - -class QueryMoreBeginLine extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['QUERY_MORE_END'], 'SOQL', 'custom'); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = `line: ${this.lineNumber}`; - } -} - -class QueryMoreEndLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = `line: ${this.lineNumber}`; - } -} -class QueryMoreIterationsLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = `line: ${this.lineNumber}, iterations:${parts[3]}`; - } -} - -class SavepointRollbackLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = `${parts[3]}, line: ${this.lineNumber}`; - } -} - -class SavePointSetLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = `${parts[3]}, line: ${this.lineNumber}`; - } -} - -class TotalEmailRecipientsQueuedLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class StackFrameVariableListLine extends LogEvent { - acceptsText = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - } -} - -class StaticVariableListLine extends LogEvent { - acceptsText = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - } -} - -// This looks like a method, but the exit line is often missing... -class SystemModeEnterLine extends LogEvent { - // namespace = "system"; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class SystemModeExitLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -export class ExecutionStartedLine extends Method { - namespace = 'default'; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['EXECUTION_FINISHED'], 'Method', 'method'); - } -} - -class EnteringManagedPackageLine extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, [], 'Method', 'pkg'); - const rawNs = parts[2] || '', - lastDot = rawNs.lastIndexOf('.'); - - this.text = this.namespace = lastDot < 0 ? rawNs : rawNs.substring(lastDot + 1); - } - - onAfter(parser: ApexLogParser, end?: LogEvent): void { - if (end) { - this.exitStamp = end.timestamp; - } - } -} - -class EventSericePubBeginLine extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['EVENT_SERVICE_PUB_END'], 'Flow', 'custom'); - this.text = parts[2] || ''; - } -} - -class EventSericePubEndLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class EventSericePubDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] + ' ' + parts[3] + ' ' + parts[4]; - } -} - -class EventSericeSubBeginLine extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['EVENT_SERVICE_SUB_END'], 'Flow', 'custom'); - this.text = `${parts[2]} ${parts[3]}`; - } -} - -class EventSericeSubEndLine extends LogEvent { - isExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} ${parts[3]}`; - } -} - -class EventSericeSubDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} ${parts[3]} ${parts[4]} ${parts[6]} ${parts[6]}`; - } -} - -export class FlowStartInterviewsBeginLine extends Method { - declarative = true; - text = 'FLOW_START_INTERVIEWS : '; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['FLOW_START_INTERVIEWS_END'], 'Flow', 'custom'); - } - - onEnd(end: LogEvent, stack: LogEvent[]) { - const flowType = this.getFlowType(stack); - this.suffix = ` (${flowType})`; - this.text += this.getFlowName(); - } - - getFlowType(stack: LogEvent[]) { - let flowType; - // ignore the last one on stack is it will be this FlowStartInterviewsBeginLine - const len = stack.length - 2; - for (let i = len; i >= 0; i--) { - const elem = stack[i]; - // type = "CODE_UNIT_STARTED" a flow or Processbuilder was started directly - // type = "FLOW_START_INTERVIEWS_BEGIN" a flow was started from a process builder - if (elem instanceof CodeUnitStartedLine) { - flowType = elem.codeUnitType === 'Flow' ? 'Flow' : 'Process Builder'; - break; - } else if (elem && elem.type === 'FLOW_START_INTERVIEWS_BEGIN') { - flowType = 'Flow'; - break; - } - } - return flowType || ''; - } - - getFlowName() { - if (this.children.length) { - return this.children[0]?.text || ''; - } - return ''; - } -} - -class FlowStartInterviewsErrorLine extends LogEvent { - acceptsText = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} - ${parts[4]}`; - } -} - -class FlowStartInterviewBeginLine extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['FLOW_START_INTERVIEW_END'], 'Flow', 'custom'); - this.text = parts[3] || ''; - } -} - -class FlowStartInterviewLimitUsageLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class FlowStartScheduledRecordsLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]}`; - } -} - -class FlowCreateInterviewErrorLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; - } -} - -class FlowElementBeginLine extends Method { - declarative = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['FLOW_ELEMENT_END'], 'Flow', 'custom'); - this.text = parts[3] + ' ' + parts[4]; - } -} - -class FlowElementDeferredLine extends LogEvent { - declarative = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] + ' ' + parts[3]; - } -} - -class FlowElementAssignmentLine extends LogEvent { - declarative = true; - acceptsText = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[3] + ' ' + parts[4]; - } -} - -class FlowWaitEventResumingDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; - } -} - -class FlowWaitEventWaitingDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; - } -} - -class FlowWaitResumingDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class FlowWaitWaitingDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; - } -} - -class FlowInterviewFinishedLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[3] || ''; - } -} - -class FlowInterviewResumedLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]}`; - } -} - -class FlowInterviewPausedLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class FlowElementErrorLine extends LogEvent { - acceptsText = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[1] || '' + parts[2] + ' ' + parts[3] + ' ' + parts[4]; - } -} - -class FlowElementFaultLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class FlowElementLimitUsageLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}`; - } -} - -class FlowInterviewFinishedLimitUsageLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}`; - } -} - -class FlowSubflowDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; - } -} - -class FlowActionCallDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[3] + ' : ' + parts[4] + ' : ' + parts[5] + ' : ' + parts[6]; - } -} - -class FlowAssignmentDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[3] + ' : ' + parts[4] + ' : ' + parts[5]; - } -} - -class FlowLoopDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[3] + ' : ' + parts[4]; - } -} - -class FlowRuleDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[3] + ' : ' + parts[4]; - } -} - -class FlowBulkElementBeginLine extends Method { - declarative = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['FLOW_BULK_ELEMENT_END'], 'Flow', 'custom'); - this.text = `${parts[2]} - ${parts[3]}`; - } -} - -class FlowBulkElementDetailLine extends LogEvent { - declarative = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] + ' : ' + parts[3] + ' : ' + parts[4]; - } -} - -class FlowBulkElementNotSupportedLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class FlowBulkElementLimitUsageLine extends LogEvent { - declarative = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class PNInvalidAppLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}.${parts[3]}`; - } -} - -class PNInvalidCertificateLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}.${parts[3]}`; - } -} -class PNInvalidNotificationLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}.${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]} : ${parts[7]} : ${parts[8]}`; - } -} -class PNNoDevicesLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}.${parts[3]}`; - } -} - -class PNSentLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}.${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]} : ${parts[7]}`; - } -} - -class SLAEndLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; - } -} - -class SLAEvalMilestoneLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}`; - } -} - -class SLAProcessCaseLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}`; - } -} - -class TestingLimitsLine extends LogEvent { - acceptsText = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - } -} - -class ValidationRuleLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[3] || ''; - } -} - -class ValidationErrorLine extends LogEvent { - acceptsText = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class ValidationFormulaLine extends LogEvent { - acceptsText = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - const extra = parts.length > 3 ? ' ' + parts[3] : ''; - - this.text = parts[2] + extra; - } -} - -class ValidationPassLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[3] || ''; - } -} - -class WFFlowActionErrorLine extends LogEvent { - acceptsText = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[1] + ' ' + parts[4]; - } -} - -class WFFlowActionErrorDetailLine extends LogEvent { - acceptsText = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[1] + ' ' + parts[2]; - } -} - -class WFFieldUpdateLine extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_FIELD_UPDATE'], 'Workflow', 'custom'); - this.text = ' ' + parts[2] + ' ' + parts[3] + ' ' + parts[4] + ' ' + parts[5] + ' ' + parts[6]; - } -} - -class WFRuleEvalBeginLine extends Method { - declarative = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_RULE_EVAL_END'], 'Workflow', 'custom'); - this.text = parts[2] || ''; - } -} - -class WFRuleEvalValueLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class WFRuleFilterLine extends LogEvent { - acceptsText = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class WFCriteriaBeginLine extends Method { - declarative = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_CRITERIA_END', 'WF_RULE_NOT_EVALUATED'], 'Workflow', 'custom'); - this.text = 'WF_CRITERIA : ' + parts[5] + ' : ' + parts[3]; - } -} - -class WFFormulaLine extends Method { - acceptsText = true; - isExit = true; - nextLineIsExit = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_FORMULA'], 'Workflow', 'custom'); - this.text = parts[2] + ' : ' + parts[3]; - } -} - -class WFActionLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class WFActionsEndLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class WFActionTaskLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]} : ${parts[7]}`; - } -} - -class WFApprovalLine extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_APPROVAL'], 'Workflow', 'custom'); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class WFApprovalRemoveLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]}`; - } -} - -class WFApprovalSubmitLine extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_APPROVAL_SUBMIT'], 'Workflow', 'custom'); - this.text = `${parts[2]}`; - } -} - -class WFApprovalSubmitterLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class WFAssignLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]}`; - } -} - -class WFEmailAlertLine extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_EMAIL_ALERT'], 'Workflow', 'custom'); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class WFEmailSentLine extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_EMAIL_SENT'], 'Workflow', 'custom'); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class WFEnqueueActionsLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class WFEscalationActionLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]}`; - } -} - -class WFEvalEntryCriteriaLine extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_EVAL_ENTRY_CRITERIA'], 'Workflow', 'custom'); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class WFFlowActionDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - const optional = parts[4] ? ` : ${parts[4]} :${parts[5]}` : ''; - this.text = `${parts[2]} : ${parts[3]}` + optional; - } -} - -class WFNextApproverLine extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_NEXT_APPROVER'], 'Workflow', 'custom'); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; - } -} - -class WFOutboundMsgLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; - } -} - -class WFProcessFoundLine extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_PROCESS_FOUND'], 'Workflow', 'custom'); - this.text = `${parts[2]} : ${parts[3]}`; - } -} - -class WFProcessNode extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_PROCESS_NODE'], 'Workflow', 'custom'); - this.text = parts[2] || ''; - } -} - -class WFReassignRecordLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]}`; - } -} - -class WFResponseNotifyLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; - } -} - -class WFRuleEntryOrderLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class WFRuleInvocationLine extends Method { - isExit = true; - nextLineIsExit = true; - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['WF_RULE_INVOCATION'], 'Workflow', 'custom'); - this.text = parts[2] || ''; - } -} - -class WFSoftRejectLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class WFTimeTriggerLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; - } -} - -class WFSpoolActionBeginLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class ExceptionThrownLine extends LogEvent { - discontinuity = true; - acceptsText = true; - totalThrownCount = 1; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[3] || ''; - } - - onAfter(parser: ApexLogParser, _next?: LogEvent): void { - if (this.text.indexOf('System.LimitException') >= 0) { - const isMultiLine = this.text.indexOf('\n'); - const len = isMultiLine < 0 ? 99 : isMultiLine; - const truncateText = this.text.length > len; - const summary = this.text.slice(0, len + 1) + (truncateText ? '…' : ''); - const message = truncateText ? this.text : ''; - parser.addLogIssue(this.timestamp, summary, message, 'error'); - } - } -} - -class FatalErrorLine extends LogEvent { - acceptsText = true; - hideable = false; - discontinuity = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } - - onAfter(parser: ApexLogParser, _next?: LogEvent): void { - const newLineIndex = this.text.indexOf('\n'); - const summary = newLineIndex > -1 ? this.text.slice(0, newLineIndex + 1) : this.text; - const detailText = summary.length !== this.text.length ? this.text : ''; - parser.addLogIssue(this.timestamp, 'FATAL ERROR! cause=' + summary, detailText, 'error'); - } -} - -class XDSDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class XDSResponseLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; - } -} -class XDSResponseDetailLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -class XDSResponseErrorLine extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -// e.g. "09:45:31.888 (38889007737)|DUPLICATE_DETECTION_BEGIN" -class DuplicateDetectionBegin extends Method { - declarative = true; - - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['DUPLICATE_DETECTION_END'], 'Workflow', 'custom'); - } -} - -// e.g. "09:45:31.888 (38889067408)|DUPLICATE_DETECTION_RULE_INVOCATION|DuplicateRuleId:0Bm20000000CaSP|DuplicateRuleName:Duplicate Account|DmlType:UPDATE" -class DuplicateDetectionRule extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = `${parts[3]} - ${parts[4]}`; - } -} - -/** - * NOTE: These can be found in the org on the create new debug level page but are not found in the docs here - * https://help.salesforce.com/s/articleView?id=sf.code_setting_debug_log_levels.htm - */ -class BulkDMLEntry extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts[2] || ''; - } -} - -/** - * DUPLICATE_DETECTION_MATCH_INVOCATION_DETAILS|EntityType:Account|ActionTaken:Allow_[Alert,Report]|DuplicateRecordIds: - */ -class DuplicateDetectionDetails extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts.slice(2).join(' | '); - } -} - -/** - * DUPLICATE_DETECTION_MATCH_INVOCATION_SUMMARY|EntityType:Account|NumRecordsToBeSaved:200|NumRecordsToBeSavedWithDuplicates:0|NumDuplicateRecordsFound:0 - */ -class DuplicateDetectionSummary extends LogEvent { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts); - this.text = parts.slice(2).join(' | '); - } -} - -class SessionCachePutBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['SESSION_CACHE_PUT_END'], 'Method', 'method'); - } -} -class SessionCacheGetBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['SESSION_CACHE_GET_END'], 'Method', 'method'); - } -} - -class SessionCacheRemoveBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['SESSION_CACHE_REMOVE_END'], 'Method', 'method'); - } -} - -class OrgCachePutBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['ORG_CACHE_PUT_END'], 'Method', 'method'); - } -} - -class OrgCacheGetBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['ORG_CACHE_GET_END'], 'Method', 'method'); - } -} - -class OrgCacheRemoveBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['ORG_CACHE_REMOVE_END'], 'Method', 'method'); - } -} - -class VFSerializeContinuationStateBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['VF_SERIALIZE_CONTINUATION_STATE_END'], 'Method', 'method'); - } -} - -class VFDeserializeContinuationStateBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['VF_SERIALIZE_CONTINUATION_STATE_END'], 'Method', 'method'); - } -} - -class MatchEngineBegin extends Method { - constructor(parser: ApexLogParser, parts: string[]) { - super(parser, parts, ['MATCH_ENGINE_END'], 'Method', 'method'); - } -} - -function getLogEventClass(eventName: LogEventType): LogLineConstructor | null | undefined { - if (!eventName) { - return null; - } - - // Fast path for the most commonly occuring types - switch (eventName) { - case 'METHOD_ENTRY': - return MethodEntryLine; - - case 'METHOD_EXIT': - return MethodExitLine; - - case 'CONSTRUCTOR_ENTRY': - return ConstructorEntryLine; - - case 'CONSTRUCTOR_EXIT': - return ConstructorExitLine; - - default: - break; - } - - // Handle all other types - const logType = lineTypeMap.get(eventName); - if (logType) { - return logType; - } else if (basicLogEvents.indexOf(eventName) !== -1) { - return BasicLogLine; - } else if (basicExitLogEvents.indexOf(eventName) !== -1) { - return BasicExitLine; - } - - return null; -} - -type LogLineConstructor = new ( - parser: ApexLogParser, - parts: string[], -) => T; -export const lineTypeMap: ReadonlyMap = new Map< - LogEventType, - LogLineConstructor ->([ - ['BULK_DML_RETRY', BulkDMLEntry], - ['BULK_HEAP_ALLOCATE', BulkHeapAllocateLine], - ['CALLOUT_REQUEST', CalloutRequestLine], - ['CALLOUT_RESPONSE', CalloutResponseLine], - ['NAMED_CREDENTIAL_REQUEST', NamedCredentialRequestLine], - ['NAMED_CREDENTIAL_RESPONSE', NamedCredentialResponseLine], - ['NAMED_CREDENTIAL_RESPONSE_DETAIL', NamedCredentialResponseDetailLine], - ['CONSTRUCTOR_ENTRY', ConstructorEntryLine], - ['CONSTRUCTOR_EXIT', ConstructorExitLine], - ['EMAIL_QUEUE', EmailQueueLine], - ['METHOD_ENTRY', MethodEntryLine], - ['METHOD_EXIT', MethodExitLine], - ['SYSTEM_CONSTRUCTOR_ENTRY', SystemConstructorEntryLine], - ['SYSTEM_CONSTRUCTOR_EXIT', SystemConstructorExitLine], - ['SYSTEM_METHOD_ENTRY', SystemMethodEntryLine], - ['SYSTEM_METHOD_EXIT', SystemMethodExitLine], - ['CODE_UNIT_STARTED', CodeUnitStartedLine], - ['CODE_UNIT_FINISHED', CodeUnitFinishedLine], - ['VF_APEX_CALL_START', VFApexCallStartLine], - ['VF_APEX_CALL_END', VFApexCallEndLine], - ['VF_DESERIALIZE_VIEWSTATE_BEGIN', VFDeserializeViewstateBeginLine], - ['VF_EVALUATE_FORMULA_BEGIN', VFFormulaStartLine], - ['VF_EVALUATE_FORMULA_END', VFFormulaEndLine], - ['VF_SERIALIZE_CONTINUATION_STATE_BEGIN', VFSerializeContinuationStateBegin], - ['VF_DESERIALIZE_CONTINUATION_STATE_BEGIN', VFDeserializeContinuationStateBegin], - ['VF_SERIALIZE_VIEWSTATE_BEGIN', VFSeralizeViewStateStartLine], - ['VF_PAGE_MESSAGE', VFPageMessageLine], - ['DML_BEGIN', DMLBeginLine], - ['DML_END', DMLEndLine], - ['IDEAS_QUERY_EXECUTE', IdeasQueryExecuteLine], - ['SOQL_EXECUTE_BEGIN', SOQLExecuteBeginLine], - ['SOQL_EXECUTE_END', SOQLExecuteEndLine], - ['SOQL_EXECUTE_EXPLAIN', SOQLExecuteExplainLine], - ['SOSL_EXECUTE_BEGIN', SOSLExecuteBeginLine], - ['SOSL_EXECUTE_END', SOSLExecuteEndLine], - ['HEAP_ALLOCATE', HeapAllocateLine], - ['HEAP_DEALLOCATE', HeapDeallocateLine], - ['STATEMENT_EXECUTE', StatementExecuteLine], - ['VARIABLE_SCOPE_BEGIN', VariableScopeBeginLine], - ['VARIABLE_ASSIGNMENT', VariableAssignmentLine], - ['USER_INFO', UserInfoLine], - ['USER_DEBUG', UserDebugLine], - ['CUMULATIVE_LIMIT_USAGE', CumulativeLimitUsageLine], - ['CUMULATIVE_PROFILING', CumulativeProfilingLine], - ['CUMULATIVE_PROFILING_BEGIN', CumulativeProfilingBeginLine], - ['LIMIT_USAGE', LimitUsageLine], - ['LIMIT_USAGE_FOR_NS', LimitUsageForNSLine], - ['NBA_NODE_BEGIN', NBANodeBegin], - ['NBA_NODE_DETAIL', NBANodeDetail], - ['NBA_NODE_END', NBANodeEnd], - ['NBA_NODE_ERROR', NBANodeError], - ['NBA_OFFER_INVALID', NBAOfferInvalid], - ['NBA_STRATEGY_BEGIN', NBAStrategyBegin], - ['NBA_STRATEGY_END', NBAStrategyEnd], - ['NBA_STRATEGY_ERROR', NBAStrategyError], - ['POP_TRACE_FLAGS', PopTraceFlagsLine], - ['PUSH_TRACE_FLAGS', PushTraceFlagsLine], - ['QUERY_MORE_BEGIN', QueryMoreBeginLine], - ['QUERY_MORE_END', QueryMoreEndLine], - ['QUERY_MORE_ITERATIONS', QueryMoreIterationsLine], - ['TOTAL_EMAIL_RECIPIENTS_QUEUED', TotalEmailRecipientsQueuedLine], - ['SAVEPOINT_ROLLBACK', SavepointRollbackLine], - ['SAVEPOINT_SET', SavePointSetLine], - ['STACK_FRAME_VARIABLE_LIST', StackFrameVariableListLine], - ['STATIC_VARIABLE_LIST', StaticVariableListLine], - ['SYSTEM_MODE_ENTER', SystemModeEnterLine], - ['SYSTEM_MODE_EXIT', SystemModeExitLine], - ['EXECUTION_STARTED', ExecutionStartedLine], - ['ENTERING_MANAGED_PKG', EnteringManagedPackageLine], - ['EVENT_SERVICE_PUB_BEGIN', EventSericePubBeginLine], - ['EVENT_SERVICE_PUB_END', EventSericePubEndLine], - ['EVENT_SERVICE_PUB_DETAIL', EventSericePubDetailLine], - ['EVENT_SERVICE_SUB_BEGIN', EventSericeSubBeginLine], - ['EVENT_SERVICE_SUB_DETAIL', EventSericeSubDetailLine], - ['EVENT_SERVICE_SUB_END', EventSericeSubEndLine], - ['FLOW_START_INTERVIEWS_BEGIN', FlowStartInterviewsBeginLine], - ['FLOW_START_INTERVIEWS_ERROR', FlowStartInterviewsErrorLine], - ['FLOW_START_INTERVIEW_BEGIN', FlowStartInterviewBeginLine], - ['FLOW_START_INTERVIEW_LIMIT_USAGE', FlowStartInterviewLimitUsageLine], - ['FLOW_START_SCHEDULED_RECORDS', FlowStartScheduledRecordsLine], - ['FLOW_CREATE_INTERVIEW_ERROR', FlowCreateInterviewErrorLine], - ['FLOW_ELEMENT_BEGIN', FlowElementBeginLine], - ['FLOW_ELEMENT_DEFERRED', FlowElementDeferredLine], - ['FLOW_ELEMENT_ERROR', FlowElementErrorLine], - ['FLOW_ELEMENT_FAULT', FlowElementFaultLine], - ['FLOW_ELEMENT_LIMIT_USAGE', FlowElementLimitUsageLine], - ['FLOW_INTERVIEW_FINISHED_LIMIT_USAGE', FlowInterviewFinishedLimitUsageLine], - ['FLOW_SUBFLOW_DETAIL', FlowSubflowDetailLine], - ['FLOW_VALUE_ASSIGNMENT', FlowElementAssignmentLine], - ['FLOW_WAIT_EVENT_RESUMING_DETAIL', FlowWaitEventResumingDetailLine], - ['FLOW_WAIT_EVENT_WAITING_DETAIL', FlowWaitEventWaitingDetailLine], - ['FLOW_WAIT_RESUMING_DETAIL', FlowWaitResumingDetailLine], - ['FLOW_WAIT_WAITING_DETAIL', FlowWaitWaitingDetailLine], - ['FLOW_INTERVIEW_FINISHED', FlowInterviewFinishedLine], - ['FLOW_INTERVIEW_PAUSED', FlowInterviewPausedLine], - ['FLOW_INTERVIEW_RESUMED', FlowInterviewResumedLine], - ['FLOW_ACTIONCALL_DETAIL', FlowActionCallDetailLine], - ['FLOW_ASSIGNMENT_DETAIL', FlowAssignmentDetailLine], - ['FLOW_LOOP_DETAIL', FlowLoopDetailLine], - ['FLOW_RULE_DETAIL', FlowRuleDetailLine], - ['FLOW_BULK_ELEMENT_BEGIN', FlowBulkElementBeginLine], - ['FLOW_BULK_ELEMENT_DETAIL', FlowBulkElementDetailLine], - ['FLOW_BULK_ELEMENT_LIMIT_USAGE', FlowBulkElementLimitUsageLine], - ['FLOW_BULK_ELEMENT_NOT_SUPPORTED', FlowBulkElementNotSupportedLine], - ['MATCH_ENGINE_BEGIN', MatchEngineBegin], - ['ORG_CACHE_PUT_BEGIN', OrgCachePutBegin], - ['ORG_CACHE_GET_BEGIN', OrgCacheGetBegin], - ['ORG_CACHE_REMOVE_BEGIN', OrgCacheRemoveBegin], - ['PUSH_NOTIFICATION_INVALID_APP', PNInvalidAppLine], - ['PUSH_NOTIFICATION_INVALID_CERTIFICATE', PNInvalidCertificateLine], - ['PUSH_NOTIFICATION_INVALID_NOTIFICATION', PNInvalidNotificationLine], - ['PUSH_NOTIFICATION_NO_DEVICES', PNNoDevicesLine], - ['PUSH_NOTIFICATION_SENT', PNSentLine], - ['SESSION_CACHE_PUT_BEGIN', SessionCachePutBegin], - ['SESSION_CACHE_GET_BEGIN', SessionCacheGetBegin], - ['SESSION_CACHE_REMOVE_BEGIN', SessionCacheRemoveBegin], - ['SLA_END', SLAEndLine], - ['SLA_EVAL_MILESTONE', SLAEvalMilestoneLine], - ['SLA_PROCESS_CASE', SLAProcessCaseLine], - ['TESTING_LIMITS', TestingLimitsLine], - ['VALIDATION_ERROR', ValidationErrorLine], - ['VALIDATION_FORMULA', ValidationFormulaLine], - ['VALIDATION_PASS', ValidationPassLine], - ['VALIDATION_RULE', ValidationRuleLine], - ['WF_FLOW_ACTION_ERROR', WFFlowActionErrorLine], - ['WF_FLOW_ACTION_ERROR_DETAIL', WFFlowActionErrorDetailLine], - ['WF_FIELD_UPDATE', WFFieldUpdateLine], - ['WF_RULE_EVAL_BEGIN', WFRuleEvalBeginLine], - ['WF_RULE_EVAL_VALUE', WFRuleEvalValueLine], - ['WF_RULE_FILTER', WFRuleFilterLine], - ['WF_CRITERIA_BEGIN', WFCriteriaBeginLine], - ['WF_FORMULA', WFFormulaLine], - ['WF_ACTION', WFActionLine], - ['WF_ACTIONS_END', WFActionsEndLine], - ['WF_ACTION_TASK', WFActionTaskLine], - ['WF_APPROVAL', WFApprovalLine], - ['WF_APPROVAL_REMOVE', WFApprovalRemoveLine], - ['WF_APPROVAL_SUBMIT', WFApprovalSubmitLine], - ['WF_APPROVAL_SUBMITTER', WFApprovalSubmitterLine], - ['WF_ASSIGN', WFAssignLine], - ['WF_EMAIL_ALERT', WFEmailAlertLine], - ['WF_EMAIL_SENT', WFEmailSentLine], - ['WF_ENQUEUE_ACTIONS', WFEnqueueActionsLine], - ['WF_ESCALATION_ACTION', WFEscalationActionLine], - ['WF_EVAL_ENTRY_CRITERIA', WFEvalEntryCriteriaLine], - ['WF_FLOW_ACTION_DETAIL', WFFlowActionDetailLine], - ['WF_NEXT_APPROVER', WFNextApproverLine], - ['WF_OUTBOUND_MSG', WFOutboundMsgLine], - ['WF_PROCESS_FOUND', WFProcessFoundLine], - ['WF_PROCESS_NODE', WFProcessNode], - ['WF_REASSIGN_RECORD', WFReassignRecordLine], - ['WF_RESPONSE_NOTIFY', WFResponseNotifyLine], - ['WF_RULE_ENTRY_ORDER', WFRuleEntryOrderLine], - ['WF_RULE_INVOCATION', WFRuleInvocationLine], - ['WF_SOFT_REJECT', WFSoftRejectLine], - ['WF_SPOOL_ACTION_BEGIN', WFSpoolActionBeginLine], - ['WF_TIME_TRIGGER', WFTimeTriggerLine], - ['EXCEPTION_THROWN', ExceptionThrownLine], - ['FATAL_ERROR', FatalErrorLine], - ['XDS_DETAIL', XDSDetailLine], - ['XDS_RESPONSE', XDSResponseLine], - ['XDS_RESPONSE_DETAIL', XDSResponseDetailLine], - ['XDS_RESPONSE_ERROR', XDSResponseErrorLine], - ['DUPLICATE_DETECTION_BEGIN', DuplicateDetectionBegin], - ['DUPLICATE_DETECTION_RULE_INVOCATION', DuplicateDetectionRule], - ['DUPLICATE_DETECTION_MATCH_INVOCATION_DETAILS', DuplicateDetectionDetails], - ['DUPLICATE_DETECTION_MATCH_INVOCATION_SUMMARY', DuplicateDetectionSummary], -]); - -const basicLogEvents: LogEventType[] = [ - 'BULK_COUNTABLE_STATEMENT_EXECUTE', - 'TEMPLATE_PROCESSING_ERROR', - 'EXTERNAL_SERVICE_REQUEST', - 'FLOW_CREATE_INTERVIEW_BEGIN', - 'FLOW_CREATE_INTERVIEW_END', - 'VARIABLE_SCOPE_END', - 'PUSH_NOTIFICATION_NOT_ENABLED', - 'SLA_NULL_START_DATE', - 'TEMPLATE_PROCESSING_ERROR', - 'VALIDATION_FAIL', - `WF_FLOW_ACTION_BEGIN`, - 'WF_FLOW_ACTION_END', - 'WF_ESCALATION_RULE', - 'WF_HARD_REJECT', - 'WF_NO_PROCESS_FOUND', - 'WF_TIME_TRIGGERS_BEGIN', - 'WF_KNOWLEDGE_ACTION', - 'WF_SEND_ACTION', - 'WAVE_APP_LIFECYCLE', - 'WF_QUICK_CREATE', - 'WF_APEX_ACTION', - 'INVOCABLE_ACTION_DETAIL', - 'INVOCABLE_ACTION_ERROR', - 'FLOW_COLLECTION_PROCESSOR_DETAIL', - 'FLOW_SCHEDULED_PATH_QUEUED', - 'ROUTE_WORK_ACTION', - 'ADD_SKILL_REQUIREMENT_ACTION', - 'ADD_SCREEN_POP_ACTION', - 'CALLOUT_REQUEST_PREPARE', - 'CALLOUT_REQUEST_FINALIZE', - 'FUNCTION_INVOCATION_REQUEST', - 'APP_CONTAINER_INITIATED', - 'FUNCTION_INVOCATION_RESPONSE', - 'XDS_REQUEST_DETAIL', - 'EXTERNAL_SERVICE_RESPONSE', - 'DATAWEAVE_USER_DEBUG', - 'USER_DEBUG_FINER', - 'USER_DEBUG_FINEST', - 'USER_DEBUG_FINE', - 'USER_DEBUG_DEBUG', - 'USER_DEBUG_INFO', - 'USER_DEBUG_WARN', - 'USER_DEBUG_ERROR', - 'VF_APEX_CALL', - 'HEAP_DUMP', - 'SCRIPT_EXECUTION', - 'SESSION_CACHE_MEMORY_USAGE', - 'ORG_CACHE_MEMORY_USAGE', - 'AE_PERSIST_VALIDATION', - 'REFERENCED_OBJECT_LIST', - 'DUPLICATE_RULE_FILTER', - 'DUPLICATE_RULE_FILTER_RESULT', - 'DUPLICATE_RULE_FILTER_VALUE', - 'TEMPLATED_ASSET', - 'TRANSFORMATION_SUMMARY', - 'RULES_EXECUTION_SUMMARY', - 'ASSET_DIFF_SUMMARY', - 'ASSET_DIFF_DETAIL', - 'RULES_EXECUTION_DETAIL', - 'JSON_DIFF_SUMMARY', - 'JSON_DIFF_DETAIL', - 'MATCH_ENGINE_INVOCATION', -]; - -const basicExitLogEvents: LogEventType[] = [ - 'FLOW_START_INTERVIEW_END', - 'VF_DESERIALIZE_VIEWSTATE_END', - 'VF_SERIALIZE_VIEWSTATE_END', - 'CUMULATIVE_LIMIT_USAGE_END', - 'CUMULATIVE_PROFILING_END', - 'EXECUTION_FINISHED', - 'FLOW_START_INTERVIEWS_END', - 'FLOW_ELEMENT_END', - 'FLOW_BULK_ELEMENT_END', - 'WF_RULE_EVAL_END', - 'WF_RULE_NOT_EVALUATED', - 'WF_CRITERIA_END', - 'DUPLICATE_DETECTION_END', - 'VF_SERIALIZE_CONTINUATION_STATE_END', - 'VF_DESERIALIZE_CONTINUATION_STATE_END', - 'MATCH_ENGINE_END', - 'ORG_CACHE_PUT_END', - 'ORG_CACHE_GET_END', - 'ORG_CACHE_REMOVE_END', - 'SESSION_CACHE_PUT_END', - 'SESSION_CACHE_GET_END', - 'SESSION_CACHE_REMOVE_END', -]; - -const _logEventNames = [ - 'BULK_DML_RETRY', - 'BULK_HEAP_ALLOCATE', - 'CALLOUT_REQUEST', - 'CALLOUT_RESPONSE', - 'NAMED_CREDENTIAL_REQUEST', - 'NAMED_CREDENTIAL_RESPONSE', - 'NAMED_CREDENTIAL_RESPONSE_DETAIL', - 'CONSTRUCTOR_ENTRY', - 'CONSTRUCTOR_EXIT', - 'EMAIL_QUEUE', - 'METHOD_ENTRY', - 'METHOD_EXIT', - 'SYSTEM_CONSTRUCTOR_ENTRY', - 'SYSTEM_CONSTRUCTOR_EXIT', - 'SYSTEM_METHOD_ENTRY', - 'SYSTEM_METHOD_EXIT', - 'CODE_UNIT_STARTED', - 'CODE_UNIT_FINISHED', - 'VF_APEX_CALL_START', - 'VF_APEX_CALL_END', - 'VF_DESERIALIZE_VIEWSTATE_BEGIN', - 'VF_EVALUATE_FORMULA_BEGIN', - 'VF_EVALUATE_FORMULA_END', - 'VF_SERIALIZE_CONTINUATION_STATE_BEGIN', - 'VF_DESERIALIZE_CONTINUATION_STATE_BEGIN', - 'VF_SERIALIZE_VIEWSTATE_BEGIN', - 'VF_PAGE_MESSAGE', - 'DML_BEGIN', - 'DML_END', - 'IDEAS_QUERY_EXECUTE', - 'SOQL_EXECUTE_BEGIN', - 'SOQL_EXECUTE_END', - 'SOQL_EXECUTE_EXPLAIN', - 'SOSL_EXECUTE_BEGIN', - 'SOSL_EXECUTE_END', - 'HEAP_ALLOCATE', - 'HEAP_DEALLOCATE', - 'STATEMENT_EXECUTE', - 'VARIABLE_SCOPE_BEGIN', - 'VARIABLE_ASSIGNMENT', - 'USER_INFO', - 'USER_DEBUG', - 'CUMULATIVE_LIMIT_USAGE', - 'CUMULATIVE_PROFILING', - 'CUMULATIVE_PROFILING_BEGIN', - 'LIMIT_USAGE', - 'LIMIT_USAGE_FOR_NS', - 'NBA_NODE_BEGIN', - 'NBA_NODE_DETAIL', - 'NBA_NODE_END', - 'NBA_NODE_ERROR', - 'NBA_OFFER_INVALID', - 'NBA_STRATEGY_BEGIN', - 'NBA_STRATEGY_END', - 'NBA_STRATEGY_ERROR', - 'POP_TRACE_FLAGS', - 'PUSH_TRACE_FLAGS', - 'QUERY_MORE_BEGIN', - 'QUERY_MORE_END', - 'QUERY_MORE_ITERATIONS', - 'TOTAL_EMAIL_RECIPIENTS_QUEUED', - 'SAVEPOINT_ROLLBACK', - 'SAVEPOINT_SET', - 'STACK_FRAME_VARIABLE_LIST', - 'STATIC_VARIABLE_LIST', - 'SYSTEM_MODE_ENTER', - 'SYSTEM_MODE_EXIT', - 'EXECUTION_STARTED', - 'ENTERING_MANAGED_PKG', - 'EVENT_SERVICE_PUB_BEGIN', - 'EVENT_SERVICE_PUB_END', - 'EVENT_SERVICE_PUB_DETAIL', - 'EVENT_SERVICE_SUB_BEGIN', - 'EVENT_SERVICE_SUB_DETAIL', - 'EVENT_SERVICE_SUB_END', - 'FLOW_START_INTERVIEWS_BEGIN', - 'FLOW_START_INTERVIEWS_ERROR', - 'FLOW_START_INTERVIEW_BEGIN', - 'FLOW_START_INTERVIEW_LIMIT_USAGE', - 'FLOW_START_SCHEDULED_RECORDS', - 'FLOW_CREATE_INTERVIEW_ERROR', - 'FLOW_ELEMENT_BEGIN', - 'FLOW_ELEMENT_DEFERRED', - 'FLOW_ELEMENT_ERROR', - 'FLOW_ELEMENT_FAULT', - 'FLOW_ELEMENT_LIMIT_USAGE', - 'FLOW_INTERVIEW_FINISHED_LIMIT_USAGE', - 'FLOW_SUBFLOW_DETAIL', - 'FLOW_VALUE_ASSIGNMENT', - 'FLOW_WAIT_EVENT_RESUMING_DETAIL', - 'FLOW_WAIT_EVENT_WAITING_DETAIL', - 'FLOW_WAIT_RESUMING_DETAIL', - 'FLOW_WAIT_WAITING_DETAIL', - 'FLOW_INTERVIEW_FINISHED', - 'FLOW_INTERVIEW_PAUSED', - 'FLOW_INTERVIEW_RESUMED', - 'FLOW_ACTIONCALL_DETAIL', - 'FLOW_ASSIGNMENT_DETAIL', - 'FLOW_LOOP_DETAIL', - 'FLOW_RULE_DETAIL', - 'FLOW_BULK_ELEMENT_BEGIN', - 'FLOW_BULK_ELEMENT_DETAIL', - 'FLOW_BULK_ELEMENT_LIMIT_USAGE', - 'FLOW_BULK_ELEMENT_NOT_SUPPORTED', - 'MATCH_ENGINE_BEGIN', - 'ORG_CACHE_PUT_BEGIN', - 'ORG_CACHE_GET_BEGIN', - 'ORG_CACHE_REMOVE_BEGIN', - 'PUSH_NOTIFICATION_INVALID_APP', - 'PUSH_NOTIFICATION_INVALID_CERTIFICATE', - 'PUSH_NOTIFICATION_INVALID_NOTIFICATION', - 'PUSH_NOTIFICATION_NO_DEVICES', - 'PUSH_NOTIFICATION_SENT', - 'SESSION_CACHE_PUT_BEGIN', - 'SESSION_CACHE_GET_BEGIN', - 'SESSION_CACHE_REMOVE_BEGIN', - 'SLA_END', - 'SLA_EVAL_MILESTONE', - 'SLA_PROCESS_CASE', - 'TESTING_LIMITS', - 'VALIDATION_ERROR', - 'VALIDATION_FORMULA', - 'VALIDATION_PASS', - 'VALIDATION_RULE', - 'WF_FLOW_ACTION_ERROR', - 'WF_FLOW_ACTION_ERROR_DETAIL', - 'WF_FIELD_UPDATE', - 'WF_RULE_EVAL_BEGIN', - 'WF_RULE_EVAL_VALUE', - 'WF_RULE_FILTER', - 'WF_CRITERIA_BEGIN', - 'WF_FORMULA', - 'WF_ACTION', - 'WF_ACTIONS_END', - 'WF_ACTION_TASK', - 'WF_APPROVAL', - 'WF_APPROVAL_REMOVE', - 'WF_APPROVAL_SUBMIT', - 'WF_APPROVAL_SUBMITTER', - 'WF_ASSIGN', - 'WF_EMAIL_ALERT', - 'WF_EMAIL_SENT', - 'WF_ENQUEUE_ACTIONS', - 'WF_ESCALATION_ACTION', - 'WF_EVAL_ENTRY_CRITERIA', - 'WF_FLOW_ACTION_DETAIL', - 'WF_NEXT_APPROVER', - 'WF_OUTBOUND_MSG', - 'WF_PROCESS_FOUND', - 'WF_PROCESS_NODE', - 'WF_REASSIGN_RECORD', - 'WF_RESPONSE_NOTIFY', - 'WF_RULE_ENTRY_ORDER', - 'WF_RULE_INVOCATION', - 'WF_SOFT_REJECT', - 'WF_SPOOL_ACTION_BEGIN', - 'WF_TIME_TRIGGER', - 'EXCEPTION_THROWN', - 'FATAL_ERROR', - 'XDS_DETAIL', - 'XDS_RESPONSE', - 'XDS_RESPONSE_DETAIL', - 'XDS_RESPONSE_ERROR', - 'DUPLICATE_DETECTION_BEGIN', - 'DUPLICATE_DETECTION_RULE_INVOCATION', - 'DUPLICATE_DETECTION_MATCH_INVOCATION_DETAILS', - 'DUPLICATE_DETECTION_MATCH_INVOCATION_SUMMARY', - 'BULK_COUNTABLE_STATEMENT_EXECUTE', - 'TEMPLATE_PROCESSING_ERROR', - 'EXTERNAL_SERVICE_REQUEST', - 'FLOW_START_INTERVIEW_END', - 'FLOW_CREATE_INTERVIEW_BEGIN', - 'FLOW_CREATE_INTERVIEW_END', - 'VARIABLE_SCOPE_END', - 'PUSH_NOTIFICATION_NOT_ENABLED', - 'SLA_NULL_START_DATE', - 'TEMPLATE_PROCESSING_ERROR', - 'VALIDATION_FAIL', - `WF_FLOW_ACTION_BEGIN`, - 'WF_FLOW_ACTION_END', - 'WF_ESCALATION_RULE', - 'WF_HARD_REJECT', - 'WF_NO_PROCESS_FOUND', - 'WF_TIME_TRIGGERS_BEGIN', - 'WF_KNOWLEDGE_ACTION', - 'WF_SEND_ACTION', - 'WAVE_APP_LIFECYCLE', - 'WF_QUICK_CREATE', - 'WF_APEX_ACTION', - 'INVOCABLE_ACTION_DETAIL', - 'INVOCABLE_ACTION_ERROR', - 'FLOW_COLLECTION_PROCESSOR_DETAIL', - 'FLOW_SCHEDULED_PATH_QUEUED', - 'ROUTE_WORK_ACTION', - 'ADD_SKILL_REQUIREMENT_ACTION', - 'ADD_SCREEN_POP_ACTION', - 'CALLOUT_REQUEST_PREPARE', - 'CALLOUT_REQUEST_FINALIZE', - 'FUNCTION_INVOCATION_REQUEST', - 'APP_CONTAINER_INITIATED', - 'FUNCTION_INVOCATION_RESPONSE', - 'XDS_REQUEST_DETAIL', - 'EXTERNAL_SERVICE_RESPONSE', - 'DATAWEAVE_USER_DEBUG', - 'USER_DEBUG_FINER', - 'USER_DEBUG_FINEST', - 'USER_DEBUG_FINE', - 'USER_DEBUG_DEBUG', - 'USER_DEBUG_INFO', - 'USER_DEBUG_WARN', - 'USER_DEBUG_ERROR', - 'VF_APEX_CALL', - 'HEAP_DUMP', - 'SCRIPT_EXECUTION', - 'SESSION_CACHE_MEMORY_USAGE', - 'ORG_CACHE_MEMORY_USAGE', - 'AE_PERSIST_VALIDATION', - 'REFERENCED_OBJECT_LIST', - 'DUPLICATE_RULE_FILTER', - 'DUPLICATE_RULE_FILTER_RESULT', - 'DUPLICATE_RULE_FILTER_VALUE', - 'TEMPLATED_ASSET', - 'TRANSFORMATION_SUMMARY', - 'RULES_EXECUTION_SUMMARY', - 'ASSET_DIFF_SUMMARY', - 'ASSET_DIFF_DETAIL', - 'RULES_EXECUTION_DETAIL', - 'JSON_DIFF_SUMMARY', - 'JSON_DIFF_DETAIL', - 'MATCH_ENGINE_INVOCATION', - 'VF_DESERIALIZE_VIEWSTATE_END', - 'VF_SERIALIZE_VIEWSTATE_END', - 'CUMULATIVE_LIMIT_USAGE_END', - 'CUMULATIVE_PROFILING_END', - 'EXECUTION_FINISHED', - 'FLOW_START_INTERVIEWS_END', - 'FLOW_ELEMENT_END', - 'FLOW_BULK_ELEMENT_END', - 'WF_RULE_EVAL_END', - 'WF_RULE_NOT_EVALUATED', - 'WF_CRITERIA_END', - 'DUPLICATE_DETECTION_END', - 'VF_SERIALIZE_CONTINUATION_STATE_END', - 'VF_DESERIALIZE_CONTINUATION_STATE_END', - 'MATCH_ENGINE_END', - 'ORG_CACHE_PUT_END', - 'ORG_CACHE_GET_END', - 'ORG_CACHE_REMOVE_END', - 'SESSION_CACHE_PUT_END', - 'SESSION_CACHE_GET_END', - 'SESSION_CACHE_REMOVE_END', -] as const; - -export type LogEventType = (typeof _logEventNames)[number]; - -export { DMLBeginLine, SOQLExecuteBeginLine, SOQLExecuteExplainLine }; diff --git a/log-viewer/modules/parsers/LogEvents.ts b/log-viewer/modules/parsers/LogEvents.ts new file mode 100644 index 00000000..fdeaa277 --- /dev/null +++ b/log-viewer/modules/parsers/LogEvents.ts @@ -0,0 +1,2037 @@ +/* + * Copyright (c) 2020 Certinia Inc. All rights reserved. + */ + +import type { ApexLogParser, DebugLevel } from './ApexLogParser'; +import type { + CPUType, + LineNumber, + LogEventType, + LogIssue, + LogSubCategory, + SelfTotal, +} from './types.js'; + +/** + * All log lines extend this base class. + */ +export abstract class LogEvent { + logParser: ApexLogParser; + + // common metadata (available for all lines) + + parent: LogEvent | null = null; + + /** + * All child nodes of the current node + */ + children: LogEvent[] = []; + + /** + * The type of this log line from the log file e.g METHOD_ENTRY + */ + type: LogEventType | null = null; + + /** + * The full raw text of this log line + */ + logLine = ''; // the raw text of this log line + + /** + * A parsed version of the log line text useful for display in UIs + */ + text; + + // optional metadata + /** + * Should this log entry pull in following text lines (as the log entry can contain newlines)? + */ + acceptsText = false; + + /** + * Is this log entry generated by a declarative process? + */ + declarative = false; + /** + * Is a method exit line? + */ + isExit = false; + + /** + * Whether the log event was truncated when the log ended, e,g no matching end event + */ + isTruncated = false; + + /** + * Should the exitstamp be the timestamp of the next line? + * These kind of lines can not be used as exit lines for anything othe than other pseudo exits. + */ + nextLineIsExit = false; + + /** + * The line number within the containing class + */ + lineNumber: LineNumber = null; + + /** + * The package namespace associated with this log line + * @default default + */ + namespace: string | 'default' = ''; + + /** + * The variable value + */ + value: string | null = null; + + /** + * Could match to a corresponding symbol in a file in the workspace? + */ + hasValidSymbols = false; + + /** + * Extra description context + */ + suffix: string | null = null; + + /** + * Does this line cause a discontinuity in the call stack? e.g an exception causing stack unwinding + */ + discontinuity = false; + + /** + * The timestamp of this log line, in nanoseconds + */ + timestamp; + + /** + * The timestamp when the node finished, in nanoseconds + */ + exitStamp: number | null = null; + + /** + * The log sub category this event belongs to + */ + subCategory: LogSubCategory = ''; + + /** + * The CPU type, e.g loading, method, custom + */ + cpuType: CPUType = ''; // the category key to collect our cpu usage + + /** + * The time spent. + */ + duration: SelfTotal = { + /** + * The net (wall) time spent in the node (when not inside children) + */ + self: 0, + /** + * The total (wall) time spent in the node + */ + total: 0, + }; + + /** + * Total + self row counts for DML + */ + dmlRowCount: SelfTotal = { + /** + * The net number of DML rows for this node, excluding child nodes + */ + self: 0, + /** + * The total number of DML rows for this node and child nodes + */ + total: 0, + }; + + /** + * Total + self row counts for SOQL + */ + soqlRowCount: SelfTotal = { + /** + * The net number of SOQL rows for this node, excluding child nodes + */ + self: 0, + /** + * The total number of SOQL rows for this node and child nodes + */ + total: 0, + }; + + /** + * Total + self row counts for SOSL + */ + soslRowCount: SelfTotal = { + /** + * The net number of SOSL rows for this node, excluding child nodes + */ + self: 0, + /** + * The total number of SOSL rows for this node and child nodes + */ + total: 0, + }; + + dmlCount: SelfTotal = { + /** + * The net number of DML operations (DML_BEGIN) in this node. + */ + self: 0, + /** + * The total number of DML operations (DML_BEGIN) in this node and child nodes + */ + total: 0, + }; + + soqlCount: SelfTotal = { + /** + * The net number of SOQL operations (SOQL_EXECUTE_BEGIN) in this node. + */ + self: 0, + /** + * The total number of SOQL operations (SOQL_EXECUTE_BEGIN) in this node and child nodes + */ + total: 0, + }; + + soslCount: SelfTotal = { + /** + * The net number of SOSL operations (SOSL_EXECUTE_BEGIN) in this node. + */ + self: 0, + /** + * The total number of SOSL operations (SOSL_EXECUTE_BEGIN) in this node and child nodes + */ + total: 0, + }; + + /** + * The total number of exceptions thrown (EXCEPTION_THROWN) in this node and child nodes + */ + totalThrownCount = 0; + + /** + * The line types which would legitimately end this method + */ + exitTypes: LogEventType[] = []; + + constructor(parser: ApexLogParser, parts: string[] | null) { + this.logParser = parser; + if (parts) { + const [timeData, type] = parts; + this.text = this.type = type as LogEventType; + this.timestamp = timeData ? this.parseTimestamp(timeData) : 0; + } else { + this.timestamp = 0; + this.text = ''; + } + } + + /** Called if a corresponding end event is found during tree parsing*/ + onEnd?(end: LogEvent, stack: LogEvent[]): void; + + /** Called when the Log event after this one is created in the line parser*/ + onAfter?(parser: ApexLogParser, next?: LogEvent): void; + + public recalculateDurations() { + if (this.exitStamp) { + this.duration.total = this.duration.self = this.exitStamp - this.timestamp; + } + } + + private parseTimestamp(text: string): number { + const start = text.indexOf('('); + if (start !== -1) { + return Number(text.slice(start + 1, -1)); + } + throw new Error(`Unable to parse timestamp: '${text}'`); + } + + protected parseLineNumber(text: string | null | undefined): LineNumber { + switch (true) { + case text === '[EXTERNAL]': + return 'EXTERNAL'; + case !!text: { + const lineNumberStr = text.slice(1, -1); + if (lineNumberStr) { + return Number(lineNumberStr); + } + throw new Error(`Unable to parse line number: '${text}'`); + } + default: + return 0; + } + } +} + +export class BasicLogLine extends LogEvent {} +export class BasicExitLine extends LogEvent { + isExit = true; +} + +/** + * Log lines extend this export class if they have a start-line and an end-line (and hence can have children in-between). + * - The start-line should extend "Method" and collect any children. + * - The end-line should extend "Detail" and terminate the method (also providing the "exitStamp"). + * The method will be rendered as "expandable" in the tree-view, if it has children. + */ +export class Method extends LogEvent { + constructor( + parser: ApexLogParser, + parts: string[] | null, + exitTypes: string[], + timelineKey: LogSubCategory, + cpuType: CPUType, + ) { + super(parser, parts); + this.subCategory = timelineKey; + this.cpuType = cpuType; + this.exitTypes = exitTypes as LogEventType[]; + } +} + +/** + * This export class represents the single root node for the node tree. + * It is a "pseudo" node and not present in the log. + * Since it has children it extends "Method". + */ +export class ApexLog extends Method { + type = null; + text = 'LOG_ROOT'; + timestamp = 0; + exitStamp = 0; + /** + * The size of the log, in bytes + */ + public size = 0; + + /** + * The total CPU time consumed, in ms + */ + public cpuTime: number = 0; + + /** + * The Apex Debug Logging Levels for the current log + */ + public debugLevels: DebugLevel[] = []; + + /** + * All the namespaces that appear in this log. + */ + public namespaces: string[] = []; + + /** + * Any issues within the log, such as cpu time exceeded or max log size reached. + */ + public logIssues: LogIssue[] = []; + + /** + * Any issues that occurred during the parsing of the log, such as an unrecognized log event type. + */ + public parsingErrors: string[] = []; + + /** + * The endtime with nodes of 0 duration excluded + */ + executionEndTime = 0; + + constructor(parser: ApexLogParser) { + super(parser, null, [], 'Code Unit', ''); + } + + setTimes() { + this.timestamp = + this.children.find((child) => { + return child.timestamp; + })?.timestamp || 0; + // We do not just want to use the very last exitStamp because it could be CUMULATIVE_USAGE which is not really part of the code execution time but does have a later time. + let endTime; + const reverseLen = this.children.length - 1; + for (let i = reverseLen; i >= 0; i--) { + const child = this.children[i]; + // If there is no duration on a node then it is not going to be shown on the timeline anyway + if (child?.exitStamp) { + endTime ??= child.exitStamp; + if (child.duration) { + this.executionEndTime = child.exitStamp; + break; + } + } + endTime ??= child?.timestamp; + } + this.exitStamp = endTime || 0; + this.recalculateDurations(); + } +} + +export function parseObjectNamespace(text: string | null | undefined): string { + if (!text) { + return ''; + } + + const sep = text.indexOf('__'); + if (sep === -1) { + return 'default'; + } + return text.slice(0, sep); +} + +export function parseVfNamespace(text: string): string { + const sep = text.indexOf('__'); + if (sep === -1) { + return 'default'; + } + const firstSlash = text.indexOf('/'); + if (firstSlash === -1) { + return 'default'; + } + const secondSlash = text.indexOf('/', firstSlash + 1); + if (secondSlash < 0) { + return 'default'; + } + return text.substring(secondSlash + 1, sep); +} + +export function parseRows(text: string | null | undefined): number { + if (!text) { + return 0; + } + + const rowCount = text.slice(text.indexOf('Rows:') + 5); + if (rowCount) { + return Number(rowCount); + } + throw new Error(`Unable to parse row count: '${text}'`); +} + +/* Log line entry Parsers */ + +export class BulkHeapAllocateLine extends LogEvent { + logCategory: 'Apex Code'; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + this.logCategory = 'Apex Code'; + } +} + +export class CalloutRequestLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[3]} : ${parts[2]}`; + } +} + +export class CalloutResponseLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[3]} : ${parts[2]}`; + } +} +export class NamedCredentialRequestLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; + } +} + +export class NamedCredentialResponseLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}`; + } +} + +export class NamedCredentialResponseDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[3]} : ${parts[4]} ${parts[5]} : ${parts[6]} ${parts[7]}`; + } +} + +export class ConstructorEntryLine extends Method { + hasValidSymbols = true; + suffix = ' (constructor)'; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['CONSTRUCTOR_EXIT'], 'Method', 'method'); + this.lineNumber = this.parseLineNumber(parts[2]); + const [, , , , args, className] = parts; + + this.text = className + (args ? args.substring(args.lastIndexOf('(')) : ''); + const possibleNS = this._parseConstructorNamespace(className || ''); + if (possibleNS) { + this.namespace = possibleNS; + } + } + + _parseConstructorNamespace(className: string): string { + let possibleNs = className.slice(0, className.indexOf('.')); + if (this.logParser.namespaces.has(possibleNs)) { + return possibleNs; + } + + const constructorParts = (className ?? '').split('.'); + possibleNs = constructorParts[0] || ''; + // inmner export class with a namespace + if (constructorParts.length === 3) { + return possibleNs; + } + + return ''; + } +} + +export class ConstructorExitLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + } +} + +export class EmailQueueLine extends LogEvent { + acceptsText = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + } +} + +export class MethodEntryLine extends Method { + hasValidSymbols = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['METHOD_EXIT'], 'Method', 'method'); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[4] || this.type || this.text; + if (this.text.indexOf('System.Type.forName(') !== -1) { + // assume we are not charged for export class loading (or at least not lengthy remote-loading / compiling) + this.cpuType = 'loading'; + } else { + const possibleNs = this._parseMethodNamespace(parts[4]); + if (possibleNs) { + this.namespace = possibleNs; + } + } + } + + onEnd(end: MethodExitLine, _stack: LogEvent[]): void { + if (end.namespace && !end.text.endsWith(')')) { + this.namespace = end.namespace; + } + } + + _parseMethodNamespace(methodName: string | undefined): string { + if (!methodName) { + return ''; + } + + const methodBracketIndex = methodName.indexOf('('); + if (methodBracketIndex === -1) { + return ''; + } + + const nsSeparator = methodName.indexOf('.'); + if (nsSeparator === -1) { + return ''; + } + + const possibleNs = methodName.slice(0, nsSeparator); + if (this.logParser.namespaces.has(possibleNs)) { + return possibleNs; + } + + const methodNameParts = methodName.slice(0, methodBracketIndex)?.split('.'); + if (methodNameParts.length === 4) { + return methodNameParts[0] ?? ''; + } else if (methodNameParts.length === 2) { + return 'default'; + } + + return ''; + } +} +export class MethodExitLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[4] ?? parts[3] ?? this.text; + + /*A method will end with ')'. Without that this it represents the first reference to a class, outer or inner. One of the few reliable ways to determine valid namespaces. The first reference to a export class (outer or inner) will always have an METHOD_EXIT containing the Outer export class name with namespace if present. Other events will follow, CONSTRUCTOR_ENTRY etc. But this case will only ever have 2 parts ns.Outer even if the first reference was actually an inner export class e.g new ns.Outer.Inner();*/ + // If does not end in ) then we have a reference to the class, either via outer or inner. + if (!this.text.endsWith(')')) { + // if there is a . the we have a namespace e.g ns.Outer + const index = this.text.indexOf('.'); + if (index !== -1) { + this.namespace = this.text.slice(0, index); + } + } + } +} + +export class SystemConstructorEntryLine extends Method { + suffix = '(system constructor)'; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['SYSTEM_CONSTRUCTOR_EXIT'], 'System Method', 'method'); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[3] || ''; + } +} + +export class SystemConstructorExitLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + } +} +export class SystemMethodEntryLine extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['SYSTEM_METHOD_EXIT'], 'System Method', 'method'); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[3] || ''; + } +} + +export class SystemMethodExitLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + } +} + +export class CodeUnitStartedLine extends Method { + suffix = ' (entrypoint)'; + codeUnitType = ''; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['CODE_UNIT_FINISHED'], 'Code Unit', 'custom'); + + const typeString = parts[5] || parts[4] || parts[3] || ''; + let sepIndex = typeString.indexOf(':'); + if (sepIndex === -1) { + sepIndex = typeString.indexOf('/'); + } + this.codeUnitType = sepIndex !== -1 ? typeString.slice(0, sepIndex) : ''; + + const name = parts[4] || parts[3] || this.codeUnitType || ''; + switch (this.codeUnitType) { + case 'EventService': + this.cpuType = 'method'; + this.namespace = parseObjectNamespace(typeString.slice(sepIndex + 1)); + this.text = name; + break; + case 'Validation': + this.cpuType = 'custom'; + this.declarative = true; + + this.text = name; + break; + case 'Workflow': + this.cpuType = 'custom'; + this.declarative = true; + this.text = name; + break; + case 'Flow': + this.cpuType = 'custom'; + this.declarative = true; + this.text = name; + break; + case 'VF': + this.cpuType = 'method'; + this.namespace = parseVfNamespace(name); + this.text = name; + break; + case 'apex': { + this.cpuType = 'method'; + const namespaceIndex = name.indexOf('.'); + this.namespace = + namespaceIndex !== -1 + ? name.slice(name.indexOf('apex://') + 7, namespaceIndex) + : 'default'; + this.text = name; + break; + } + case '__sfdc_trigger': { + this.cpuType = 'method'; + this.text = name || parts[4] || ''; + const triggerParts = parts[5]?.split('/') || ''; + this.namespace = triggerParts.length === 3 ? triggerParts[1] || 'default' : 'default'; + break; + } + default: { + this.cpuType = 'method'; + this.text = name; + const openBracket = name.lastIndexOf('('); + const methodName = + openBracket !== -1 ? name.slice(0, openBracket + 1).split('.') : name.split('.'); + if (methodName.length === 3 || (methodName.length === 2 && !methodName[1]?.endsWith('('))) { + this.namespace = methodName[0] || 'default'; + } + break; + } + } + + this.namespace ||= 'default'; + } +} +export class CodeUnitFinishedLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class VFApexCallStartLine extends Method { + suffix = ' (VF APEX)'; + invalidClasses = [ + 'pagemessagescomponentcontroller', + 'pagemessagecomponentcontroller', + 'severitymessages', + ]; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['VF_APEX_CALL_END'], 'Method', 'method'); + this.lineNumber = this.parseLineNumber(parts[2]); + + const classText = parts[5] || parts[3] || ''; + let methodtext = parts[4] || ''; + if ( + !methodtext && + (!classText.includes(' ') || + this.invalidClasses.some((invalidCls: string) => + classText.toLowerCase().includes(invalidCls), + )) + ) { + // we have a system entry and they do not have exits + // e.g |VF_APEX_CALL_START|[EXTERNAL]|/apexpage/pagemessagescomponentcontroller.apex + // and they really mess with the logs so skip handling them. + this.exitTypes = []; + } else if (methodtext) { + this.hasValidSymbols = true; + // method call + const methodIndex = methodtext.indexOf('('); + const constructorIndex = methodtext.indexOf(''); + if (methodIndex > -1) { + // Method + methodtext = '.' + methodtext.substring(methodIndex).slice(1, -1) + '()'; + } else if (constructorIndex > -1) { + // Constructor + methodtext = methodtext.substring(constructorIndex + 6) + '()'; + } else { + // Property + methodtext = '.' + methodtext; + } + } else { + this.hasValidSymbols = true; + } + this.text = classText + methodtext; + } +} + +export class VFApexCallEndLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class VFDeserializeViewstateBeginLine extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['VF_DESERIALIZE_VIEWSTATE_END'], 'System Method', 'method'); + } +} + +export class VFFormulaStartLine extends Method { + suffix = ' (VF FORMULA)'; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['VF_EVALUATE_FORMULA_END'], 'System Method', 'custom'); + this.text = parts[3] || ''; + } +} + +export class VFFormulaEndLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class VFSeralizeViewStateStartLine extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['VF_SERIALIZE_VIEWSTATE_END'], 'System Method', 'method'); + } +} + +export class VFPageMessageLine extends LogEvent { + acceptsText = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class DMLBeginLine extends Method { + dmlCount = { + self: 1, + total: 1, + }; + + namespace = 'default'; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['DML_END'], 'DML', 'free'); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = 'DML ' + parts[3] + ' ' + parts[4]; + const rowCountString = parts[5]; + this.dmlRowCount.total = this.dmlRowCount.self = rowCountString ? parseRows(rowCountString) : 0; + } +} + +export class DMLEndLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + } +} + +export class IdeasQueryExecuteLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + } +} + +export class SOQLExecuteBeginLine extends Method { + aggregations = 0; + soqlCount = { + self: 1, + total: 1, + }; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['SOQL_EXECUTE_END'], 'SOQL', 'free'); + this.lineNumber = this.parseLineNumber(parts[2]); + + const [, , , aggregations, soqlString] = parts; + + const aggregationText = aggregations || ''; + if (aggregationText) { + const aggregationIndex = aggregationText.indexOf('Aggregations:'); + this.aggregations = Number(aggregationText.slice(aggregationIndex + 13)); + } + this.text = soqlString || ''; + } + + onEnd(end: SOQLExecuteEndLine, _stack: LogEvent[]): void { + this.soqlRowCount.total = this.soqlRowCount.self = end.soqlRowCount.total; + } +} + +export class SOQLExecuteEndLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.soqlRowCount.total = this.soqlRowCount.self = parseRows(parts[3] || ''); + } +} + +export class SOQLExecuteExplainLine extends LogEvent { + cardinality: number | null = null; // The estimated number of records that the leading operation type would return + fields: string[] | null = null; //The indexed field(s) used by the Query Optimizer. If the leading operation type is Index, the fields value is Index. Otherwise, the fields value is null. + leadingOperationType: string | null = null; // The primary operation type that Salesforce will use to optimize the query. + relativeCost: number | null = null; // The cost of the query compared to the Force.com Query Optimizer’s selectivity threshold. Values above 1 mean that the query won’t be selective. + sObjectCardinality: number | null = null; // The approximate record count for the queried object. + sObjectType: string | null = null; //T he name of the queried SObject + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + + const queryPlanDetails = parts[3] || ''; + this.text = queryPlanDetails; + + const queryplanParts = queryPlanDetails.split('],'); + if (queryplanParts.length > 1) { + const planExplain = queryplanParts[0] || ''; + const [cardinalityText, sobjCardinalityText, costText] = (queryplanParts[1] || '').split(','); + + const onIndex = planExplain.indexOf(' on'); + this.leadingOperationType = planExplain.slice(0, onIndex); + + const colonIndex = planExplain.indexOf(' :'); + this.sObjectType = planExplain.slice(onIndex + 4, colonIndex); + + // remove whitespace if there is any. we could have [ field1__c, field2__c ] + // I am not 100% sure of format when we have multiple fields so this is safer + const fieldsAsString = planExplain.slice(planExplain.indexOf('[') + 1).replace(/\s+/g, ''); + this.fields = fieldsAsString === '' ? [] : fieldsAsString.split(','); + + this.cardinality = cardinalityText + ? Number(cardinalityText.slice(cardinalityText.indexOf('cardinality: ') + 13)) + : null; + this.sObjectCardinality = sobjCardinalityText + ? Number( + sobjCardinalityText.slice(sobjCardinalityText.indexOf('sobjectCardinality: ') + 20), + ) + : null; + this.relativeCost = costText + ? Number(costText.slice(costText.indexOf('relativeCost ') + 13)) + : null; + } + } +} + +export class SOSLExecuteBeginLine extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['SOSL_EXECUTE_END'], 'SOQL', 'free'); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = `SOSL: ${parts[3]}`; + + this.soslCount = { + self: 1, + total: 1, + }; + } + + onEnd(end: SOSLExecuteEndLine, _stack: LogEvent[]): void { + this.soslRowCount.total = this.soslRowCount.self = end.soslRowCount.total; + } +} + +export class SOSLExecuteEndLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.soslRowCount.total = this.soslRowCount.self = parseRows(parts[3] || ''); + } +} + +export class HeapAllocateLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[3] || ''; + } +} + +export class HeapDeallocateLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + } +} + +export class StatementExecuteLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + } +} + +export class VariableScopeBeginLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts.slice(3).join(' | '); + } +} + +export class VariableAssignmentLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts.slice(3).join(' | '); + } +} +export class UserInfoLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[3] + ' ' + parts[4]; + } +} + +export class UserDebugLine extends LogEvent { + acceptsText = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts.slice(3).join(' | '); + } +} + +export class CumulativeLimitUsageLine extends Method { + namespace = 'default'; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['CUMULATIVE_LIMIT_USAGE_END'], 'System Method', 'system'); + } +} + +export class CumulativeProfilingLine extends LogEvent { + acceptsText = true; + namespace = 'default'; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] + ' ' + (parts[3] ?? ''); + } +} + +export class CumulativeProfilingBeginLine extends Method { + namespace = 'default'; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['CUMULATIVE_PROFILING_END'], 'System Method', 'custom'); + } +} + +export class LimitUsageLine extends LogEvent { + namespace = 'default'; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[3] + ' ' + parts[4] + ' out of ' + parts[5]; + } +} + +export class LimitUsageForNSLine extends LogEvent { + acceptsText = true; + namespace = 'default'; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } + + onAfter(parser: ApexLogParser, _next?: LogEvent): void { + const matched = this.text.match(/Maximum CPU time: (\d+)/), + cpuText = matched?.[1] || '0', + cpuTime = parseInt(cpuText, 10) * 1000000; // convert from milli-seconds to nano-seconds + + if (!parser.cpuUsed || cpuTime > parser.cpuUsed) { + parser.cpuUsed = cpuTime; + } + } +} + +export class NBANodeBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['NBA_NODE_END'], 'System Method', 'method'); + this.text = parts.slice(2).join(' | '); + } +} + +export class NBANodeDetail extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts.slice(2).join(' | '); + } +} +export class NBANodeEnd extends LogEvent { + isExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts.slice(2).join(' | '); + } +} +export class NBANodeError extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts.slice(2).join(' | '); + } +} +export class NBAOfferInvalid extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts.slice(2).join(' | '); + } +} +export class NBAStrategyBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['NBA_STRATEGY_END'], 'System Method', 'method'); + this.text = parts.slice(2).join(' | '); + } +} +export class NBAStrategyEnd extends LogEvent { + isExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts.slice(2).join(' | '); + } +} +export class NBAStrategyError extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts.slice(2).join(' | '); + } +} + +export class PushTraceFlagsLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[4] + ', line:' + this.lineNumber + ' - ' + parts[5]; + } +} + +export class PopTraceFlagsLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[4] + ', line:' + this.lineNumber + ' - ' + parts[5]; + } +} + +export class QueryMoreBeginLine extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['QUERY_MORE_END'], 'SOQL', 'custom'); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = `line: ${this.lineNumber}`; + } +} + +export class QueryMoreEndLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = `line: ${this.lineNumber}`; + } +} +export class QueryMoreIterationsLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = `line: ${this.lineNumber}, iterations:${parts[3]}`; + } +} + +export class SavepointRollbackLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = `${parts[3]}, line: ${this.lineNumber}`; + } +} + +export class SavePointSetLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = `${parts[3]}, line: ${this.lineNumber}`; + } +} + +export class TotalEmailRecipientsQueuedLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class StackFrameVariableListLine extends LogEvent { + acceptsText = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + } +} + +export class StaticVariableListLine extends LogEvent { + acceptsText = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + } +} + +// This looks like a method, but the exit line is often missing... +export class SystemModeEnterLine extends LogEvent { + // namespace = "system"; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class SystemModeExitLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class ExecutionStartedLine extends Method { + namespace = 'default'; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['EXECUTION_FINISHED'], 'Method', 'method'); + } +} + +export class EnteringManagedPackageLine extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, [], 'Method', 'pkg'); + const rawNs = parts[2] || '', + lastDot = rawNs.lastIndexOf('.'); + + this.text = this.namespace = lastDot < 0 ? rawNs : rawNs.substring(lastDot + 1); + } + + onAfter(parser: ApexLogParser, end?: LogEvent): void { + if (end) { + this.exitStamp = end.timestamp; + } + } +} + +export class EventSericePubBeginLine extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['EVENT_SERVICE_PUB_END'], 'Flow', 'custom'); + this.text = parts[2] || ''; + } +} + +export class EventSericePubEndLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class EventSericePubDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] + ' ' + parts[3] + ' ' + parts[4]; + } +} + +export class EventSericeSubBeginLine extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['EVENT_SERVICE_SUB_END'], 'Flow', 'custom'); + this.text = `${parts[2]} ${parts[3]}`; + } +} + +export class EventSericeSubEndLine extends LogEvent { + isExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} ${parts[3]}`; + } +} + +export class EventSericeSubDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} ${parts[3]} ${parts[4]} ${parts[6]} ${parts[6]}`; + } +} + +export class FlowStartInterviewsBeginLine extends Method { + declarative = true; + text = 'FLOW_START_INTERVIEWS : '; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['FLOW_START_INTERVIEWS_END'], 'Flow', 'custom'); + } + + onEnd(end: LogEvent, stack: LogEvent[]) { + const flowType = this.getFlowType(stack); + this.suffix = ` (${flowType})`; + this.text += this.getFlowName(); + } + + getFlowType(stack: LogEvent[]) { + let flowType; + // ignore the last one on stack is it will be this FlowStartInterviewsBeginLine + const len = stack.length - 2; + for (let i = len; i >= 0; i--) { + const elem = stack[i]; + // type = "CODE_UNIT_STARTED" a flow or Processbuilder was started directly + // type = "FLOW_START_INTERVIEWS_BEGIN" a flow was started from a process builder + if (elem instanceof CodeUnitStartedLine) { + flowType = elem.codeUnitType === 'Flow' ? 'Flow' : 'Process Builder'; + break; + } else if (elem && elem.type === 'FLOW_START_INTERVIEWS_BEGIN') { + flowType = 'Flow'; + break; + } + } + return flowType || ''; + } + + getFlowName() { + if (this.children.length) { + return this.children[0]?.text || ''; + } + return ''; + } +} + +export class FlowStartInterviewsErrorLine extends LogEvent { + acceptsText = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} - ${parts[4]}`; + } +} + +export class FlowStartInterviewBeginLine extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['FLOW_START_INTERVIEW_END'], 'Flow', 'custom'); + this.text = parts[3] || ''; + } +} + +export class FlowStartInterviewLimitUsageLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class FlowStartScheduledRecordsLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]}`; + } +} + +export class FlowCreateInterviewErrorLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; + } +} + +export class FlowElementBeginLine extends Method { + declarative = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['FLOW_ELEMENT_END'], 'Flow', 'custom'); + this.text = parts[3] + ' ' + parts[4]; + } +} + +export class FlowElementDeferredLine extends LogEvent { + declarative = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] + ' ' + parts[3]; + } +} + +export class FlowElementAssignmentLine extends LogEvent { + declarative = true; + acceptsText = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[3] + ' ' + parts[4]; + } +} + +export class FlowWaitEventResumingDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; + } +} + +export class FlowWaitEventWaitingDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; + } +} + +export class FlowWaitResumingDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class FlowWaitWaitingDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; + } +} + +export class FlowInterviewFinishedLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[3] || ''; + } +} + +export class FlowInterviewResumedLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]}`; + } +} + +export class FlowInterviewPausedLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class FlowElementErrorLine extends LogEvent { + acceptsText = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[1] || '' + parts[2] + ' ' + parts[3] + ' ' + parts[4]; + } +} + +export class FlowElementFaultLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class FlowElementLimitUsageLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}`; + } +} + +export class FlowInterviewFinishedLimitUsageLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}`; + } +} + +export class FlowSubflowDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; + } +} + +export class FlowActionCallDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[3] + ' : ' + parts[4] + ' : ' + parts[5] + ' : ' + parts[6]; + } +} + +export class FlowAssignmentDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[3] + ' : ' + parts[4] + ' : ' + parts[5]; + } +} + +export class FlowLoopDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[3] + ' : ' + parts[4]; + } +} + +export class FlowRuleDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[3] + ' : ' + parts[4]; + } +} + +export class FlowBulkElementBeginLine extends Method { + declarative = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['FLOW_BULK_ELEMENT_END'], 'Flow', 'custom'); + this.text = `${parts[2]} - ${parts[3]}`; + } +} + +export class FlowBulkElementDetailLine extends LogEvent { + declarative = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] + ' : ' + parts[3] + ' : ' + parts[4]; + } +} + +export class FlowBulkElementNotSupportedLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class FlowBulkElementLimitUsageLine extends LogEvent { + declarative = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class PNInvalidAppLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}.${parts[3]}`; + } +} + +export class PNInvalidCertificateLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}.${parts[3]}`; + } +} +export class PNInvalidNotificationLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}.${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]} : ${parts[7]} : ${parts[8]}`; + } +} +export class PNNoDevicesLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}.${parts[3]}`; + } +} + +export class PNSentLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}.${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]} : ${parts[7]}`; + } +} + +export class SLAEndLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; + } +} + +export class SLAEvalMilestoneLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}`; + } +} + +export class SLAProcessCaseLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}`; + } +} + +export class TestingLimitsLine extends LogEvent { + acceptsText = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + } +} + +export class ValidationRuleLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[3] || ''; + } +} + +export class ValidationErrorLine extends LogEvent { + acceptsText = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class ValidationFormulaLine extends LogEvent { + acceptsText = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + const extra = parts.length > 3 ? ' ' + parts[3] : ''; + + this.text = parts[2] + extra; + } +} + +export class ValidationPassLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[3] || ''; + } +} + +export class WFFlowActionErrorLine extends LogEvent { + acceptsText = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[1] + ' ' + parts[4]; + } +} + +export class WFFlowActionErrorDetailLine extends LogEvent { + acceptsText = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[1] + ' ' + parts[2]; + } +} + +export class WFFieldUpdateLine extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_FIELD_UPDATE'], 'Workflow', 'custom'); + this.text = ' ' + parts[2] + ' ' + parts[3] + ' ' + parts[4] + ' ' + parts[5] + ' ' + parts[6]; + } +} + +export class WFRuleEvalBeginLine extends Method { + declarative = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_RULE_EVAL_END'], 'Workflow', 'custom'); + this.text = parts[2] || ''; + } +} + +export class WFRuleEvalValueLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class WFRuleFilterLine extends LogEvent { + acceptsText = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class WFCriteriaBeginLine extends Method { + declarative = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_CRITERIA_END', 'WF_RULE_NOT_EVALUATED'], 'Workflow', 'custom'); + this.text = 'WF_CRITERIA : ' + parts[5] + ' : ' + parts[3]; + } +} + +export class WFFormulaLine extends Method { + acceptsText = true; + isExit = true; + nextLineIsExit = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_FORMULA'], 'Workflow', 'custom'); + this.text = parts[2] + ' : ' + parts[3]; + } +} + +export class WFActionLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class WFActionsEndLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class WFActionTaskLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]} : ${parts[7]}`; + } +} + +export class WFApprovalLine extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_APPROVAL'], 'Workflow', 'custom'); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class WFApprovalRemoveLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]}`; + } +} + +export class WFApprovalSubmitLine extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_APPROVAL_SUBMIT'], 'Workflow', 'custom'); + this.text = `${parts[2]}`; + } +} + +export class WFApprovalSubmitterLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class WFAssignLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]}`; + } +} + +export class WFEmailAlertLine extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_EMAIL_ALERT'], 'Workflow', 'custom'); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class WFEmailSentLine extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_EMAIL_SENT'], 'Workflow', 'custom'); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class WFEnqueueActionsLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class WFEscalationActionLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]}`; + } +} + +export class WFEvalEntryCriteriaLine extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_EVAL_ENTRY_CRITERIA'], 'Workflow', 'custom'); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class WFFlowActionDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + const optional = parts[4] ? ` : ${parts[4]} :${parts[5]}` : ''; + this.text = `${parts[2]} : ${parts[3]}` + optional; + } +} + +export class WFNextApproverLine extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_NEXT_APPROVER'], 'Workflow', 'custom'); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]}`; + } +} + +export class WFOutboundMsgLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; + } +} + +export class WFProcessFoundLine extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_PROCESS_FOUND'], 'Workflow', 'custom'); + this.text = `${parts[2]} : ${parts[3]}`; + } +} + +export class WFProcessNode extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_PROCESS_NODE'], 'Workflow', 'custom'); + this.text = parts[2] || ''; + } +} + +export class WFReassignRecordLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]}`; + } +} + +export class WFResponseNotifyLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; + } +} + +export class WFRuleEntryOrderLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class WFRuleInvocationLine extends Method { + isExit = true; + nextLineIsExit = true; + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['WF_RULE_INVOCATION'], 'Workflow', 'custom'); + this.text = parts[2] || ''; + } +} + +export class WFSoftRejectLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class WFTimeTriggerLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]}`; + } +} + +export class WFSpoolActionBeginLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class ExceptionThrownLine extends LogEvent { + discontinuity = true; + acceptsText = true; + totalThrownCount = 1; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.lineNumber = this.parseLineNumber(parts[2]); + this.text = parts[3] || ''; + } + + onAfter(parser: ApexLogParser, _next?: LogEvent): void { + if (this.text.indexOf('System.LimitException') >= 0) { + const isMultiLine = this.text.indexOf('\n'); + const len = isMultiLine < 0 ? 99 : isMultiLine; + const truncateText = this.text.length > len; + const summary = this.text.slice(0, len + 1) + (truncateText ? '…' : ''); + const message = truncateText ? this.text : ''; + parser.addLogIssue(this.timestamp, summary, message, 'error'); + } + } +} + +export class FatalErrorLine extends LogEvent { + acceptsText = true; + hideable = false; + discontinuity = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } + + onAfter(parser: ApexLogParser, _next?: LogEvent): void { + const newLineIndex = this.text.indexOf('\n'); + const summary = newLineIndex > -1 ? this.text.slice(0, newLineIndex + 1) : this.text; + const detailText = summary.length !== this.text.length ? this.text : ''; + parser.addLogIssue(this.timestamp, 'FATAL ERROR! cause=' + summary, detailText, 'error'); + } +} + +export class XDSDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class XDSResponseLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[2]} : ${parts[3]} : ${parts[4]} : ${parts[5]} : ${parts[6]}`; + } +} +export class XDSResponseDetailLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +export class XDSResponseErrorLine extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +// e.g. "09:45:31.888 (38889007737)|DUPLICATE_DETECTION_BEGIN" +export class DuplicateDetectionBegin extends Method { + declarative = true; + + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['DUPLICATE_DETECTION_END'], 'Workflow', 'custom'); + } +} + +// e.g. "09:45:31.888 (38889067408)|DUPLICATE_DETECTION_RULE_INVOCATION|DuplicateRuleId:0Bm20000000CaSP|DuplicateRuleName:Duplicate Account|DmlType:UPDATE" +export class DuplicateDetectionRule extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = `${parts[3]} - ${parts[4]}`; + } +} + +/** + * NOTE: These can be found in the org on the create new debug level page but are not found in the docs here + * https://help.salesforce.com/s/articleView?id=sf.code_setting_debug_log_levels.htm + */ +export class BulkDMLEntry extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts[2] || ''; + } +} + +/** + * DUPLICATE_DETECTION_MATCH_INVOCATION_DETAILS|EntityType:Account|ActionTaken:Allow_[Alert,Report]|DuplicateRecordIds: + */ +export class DuplicateDetectionDetails extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts.slice(2).join(' | '); + } +} + +/** + * DUPLICATE_DETECTION_MATCH_INVOCATION_SUMMARY|EntityType:Account|NumRecordsToBeSaved:200|NumRecordsToBeSavedWithDuplicates:0|NumDuplicateRecordsFound:0 + */ +export class DuplicateDetectionSummary extends LogEvent { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts); + this.text = parts.slice(2).join(' | '); + } +} + +export class SessionCachePutBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['SESSION_CACHE_PUT_END'], 'Method', 'method'); + } +} +export class SessionCacheGetBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['SESSION_CACHE_GET_END'], 'Method', 'method'); + } +} + +export class SessionCacheRemoveBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['SESSION_CACHE_REMOVE_END'], 'Method', 'method'); + } +} + +export class OrgCachePutBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['ORG_CACHE_PUT_END'], 'Method', 'method'); + } +} + +export class OrgCacheGetBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['ORG_CACHE_GET_END'], 'Method', 'method'); + } +} + +export class OrgCacheRemoveBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['ORG_CACHE_REMOVE_END'], 'Method', 'method'); + } +} + +export class VFSerializeContinuationStateBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['VF_SERIALIZE_CONTINUATION_STATE_END'], 'Method', 'method'); + } +} + +export class VFDeserializeContinuationStateBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['VF_SERIALIZE_CONTINUATION_STATE_END'], 'Method', 'method'); + } +} + +export class MatchEngineBegin extends Method { + constructor(parser: ApexLogParser, parts: string[]) { + super(parser, parts, ['MATCH_ENGINE_END'], 'Method', 'method'); + } +} diff --git a/log-viewer/modules/parsers/LogLineMapping.ts b/log-viewer/modules/parsers/LogLineMapping.ts new file mode 100644 index 00000000..1a30d048 --- /dev/null +++ b/log-viewer/modules/parsers/LogLineMapping.ts @@ -0,0 +1,479 @@ +/* + * Copyright (c) 2020 Certinia Inc. All rights reserved. + */ + +import type { ApexLogParser } from './ApexLogParser.js'; +import { + BasicExitLine, + BasicLogLine, + BulkDMLEntry, + BulkHeapAllocateLine, + CalloutRequestLine, + CalloutResponseLine, + CodeUnitFinishedLine, + CodeUnitStartedLine, + ConstructorEntryLine, + ConstructorExitLine, + CumulativeLimitUsageLine, + CumulativeProfilingBeginLine, + CumulativeProfilingLine, + DMLBeginLine, + DMLEndLine, + DuplicateDetectionBegin, + DuplicateDetectionDetails, + DuplicateDetectionRule, + DuplicateDetectionSummary, + EmailQueueLine, + EnteringManagedPackageLine, + EventSericePubBeginLine, + EventSericePubDetailLine, + EventSericePubEndLine, + EventSericeSubBeginLine, + EventSericeSubDetailLine, + EventSericeSubEndLine, + ExceptionThrownLine, + ExecutionStartedLine, + FatalErrorLine, + FlowActionCallDetailLine, + FlowAssignmentDetailLine, + FlowBulkElementBeginLine, + FlowBulkElementDetailLine, + FlowBulkElementLimitUsageLine, + FlowBulkElementNotSupportedLine, + FlowCreateInterviewErrorLine, + FlowElementAssignmentLine, + FlowElementBeginLine, + FlowElementDeferredLine, + FlowElementErrorLine, + FlowElementFaultLine, + FlowElementLimitUsageLine, + FlowInterviewFinishedLimitUsageLine, + FlowInterviewFinishedLine, + FlowInterviewPausedLine, + FlowInterviewResumedLine, + FlowLoopDetailLine, + FlowRuleDetailLine, + FlowStartInterviewBeginLine, + FlowStartInterviewLimitUsageLine, + FlowStartInterviewsBeginLine, + FlowStartInterviewsErrorLine, + FlowStartScheduledRecordsLine, + FlowSubflowDetailLine, + FlowWaitEventResumingDetailLine, + FlowWaitEventWaitingDetailLine, + FlowWaitResumingDetailLine, + FlowWaitWaitingDetailLine, + HeapAllocateLine, + HeapDeallocateLine, + IdeasQueryExecuteLine, + LimitUsageForNSLine, + LimitUsageLine, + LogEvent, + MatchEngineBegin, + MethodEntryLine, + MethodExitLine, + NamedCredentialRequestLine, + NamedCredentialResponseDetailLine, + NamedCredentialResponseLine, + NBANodeBegin, + NBANodeDetail, + NBANodeEnd, + NBANodeError, + NBAOfferInvalid, + NBAStrategyBegin, + NBAStrategyEnd, + NBAStrategyError, + OrgCacheGetBegin, + OrgCachePutBegin, + OrgCacheRemoveBegin, + PNInvalidAppLine, + PNInvalidCertificateLine, + PNInvalidNotificationLine, + PNNoDevicesLine, + PNSentLine, + PopTraceFlagsLine, + PushTraceFlagsLine, + QueryMoreBeginLine, + QueryMoreEndLine, + QueryMoreIterationsLine, + SavepointRollbackLine, + SavePointSetLine, + SessionCacheGetBegin, + SessionCachePutBegin, + SessionCacheRemoveBegin, + SLAEndLine, + SLAEvalMilestoneLine, + SLAProcessCaseLine, + SOQLExecuteBeginLine, + SOQLExecuteEndLine, + SOQLExecuteExplainLine, + SOSLExecuteBeginLine, + SOSLExecuteEndLine, + StackFrameVariableListLine, + StatementExecuteLine, + StaticVariableListLine, + SystemConstructorEntryLine, + SystemConstructorExitLine, + SystemMethodEntryLine, + SystemMethodExitLine, + SystemModeEnterLine, + SystemModeExitLine, + TestingLimitsLine, + TotalEmailRecipientsQueuedLine, + UserDebugLine, + UserInfoLine, + ValidationErrorLine, + ValidationFormulaLine, + ValidationPassLine, + ValidationRuleLine, + VariableAssignmentLine, + VariableScopeBeginLine, + VFApexCallEndLine, + VFApexCallStartLine, + VFDeserializeContinuationStateBegin, + VFDeserializeViewstateBeginLine, + VFFormulaEndLine, + VFFormulaStartLine, + VFPageMessageLine, + VFSeralizeViewStateStartLine, + VFSerializeContinuationStateBegin, + WFActionLine, + WFActionsEndLine, + WFActionTaskLine, + WFApprovalLine, + WFApprovalRemoveLine, + WFApprovalSubmitLine, + WFApprovalSubmitterLine, + WFAssignLine, + WFCriteriaBeginLine, + WFEmailAlertLine, + WFEmailSentLine, + WFEnqueueActionsLine, + WFEscalationActionLine, + WFEvalEntryCriteriaLine, + WFFieldUpdateLine, + WFFlowActionDetailLine, + WFFlowActionErrorDetailLine, + WFFlowActionErrorLine, + WFFormulaLine, + WFNextApproverLine, + WFOutboundMsgLine, + WFProcessFoundLine, + WFProcessNode, + WFReassignRecordLine, + WFResponseNotifyLine, + WFRuleEntryOrderLine, + WFRuleEvalBeginLine, + WFRuleEvalValueLine, + WFRuleFilterLine, + WFRuleInvocationLine, + WFSoftRejectLine, + WFSpoolActionBeginLine, + WFTimeTriggerLine, + XDSDetailLine, + XDSResponseDetailLine, + XDSResponseErrorLine, + XDSResponseLine, +} from './LogEvents.js'; +import type { LogEventType, LogLineConstructor } from './types.js'; + +export function getLogEventClass( + eventName: LogEventType, +): LogLineConstructor | null | undefined { + if (!eventName) { + return null; + } + + // Fast path for the most commonly occuring types + switch (eventName) { + case 'METHOD_ENTRY': + return MethodEntryLine; + + case 'METHOD_EXIT': + return MethodExitLine; + + case 'CONSTRUCTOR_ENTRY': + return ConstructorEntryLine; + + case 'CONSTRUCTOR_EXIT': + return ConstructorExitLine; + + default: + break; + } + + // Handle all other types + const logType = lineTypeMap.get(eventName); + if (logType) { + return logType; + } else if (basicLogEvents.indexOf(eventName) !== -1) { + return BasicLogLine; + } else if (basicExitLogEvents.indexOf(eventName) !== -1) { + return BasicExitLine; + } + + return null; +} + +export const lineTypeMap: ReadonlyMap< + LogEventType, + LogLineConstructor +> = new Map>([ + ['BULK_DML_RETRY', BulkDMLEntry], + ['BULK_HEAP_ALLOCATE', BulkHeapAllocateLine], + ['CALLOUT_REQUEST', CalloutRequestLine], + ['CALLOUT_RESPONSE', CalloutResponseLine], + ['NAMED_CREDENTIAL_REQUEST', NamedCredentialRequestLine], + ['NAMED_CREDENTIAL_RESPONSE', NamedCredentialResponseLine], + ['NAMED_CREDENTIAL_RESPONSE_DETAIL', NamedCredentialResponseDetailLine], + ['CONSTRUCTOR_ENTRY', ConstructorEntryLine], + ['CONSTRUCTOR_EXIT', ConstructorExitLine], + ['EMAIL_QUEUE', EmailQueueLine], + ['METHOD_ENTRY', MethodEntryLine], + ['METHOD_EXIT', MethodExitLine], + ['SYSTEM_CONSTRUCTOR_ENTRY', SystemConstructorEntryLine], + ['SYSTEM_CONSTRUCTOR_EXIT', SystemConstructorExitLine], + ['SYSTEM_METHOD_ENTRY', SystemMethodEntryLine], + ['SYSTEM_METHOD_EXIT', SystemMethodExitLine], + ['CODE_UNIT_STARTED', CodeUnitStartedLine], + ['CODE_UNIT_FINISHED', CodeUnitFinishedLine], + ['VF_APEX_CALL_START', VFApexCallStartLine], + ['VF_APEX_CALL_END', VFApexCallEndLine], + ['VF_DESERIALIZE_VIEWSTATE_BEGIN', VFDeserializeViewstateBeginLine], + ['VF_EVALUATE_FORMULA_BEGIN', VFFormulaStartLine], + ['VF_EVALUATE_FORMULA_END', VFFormulaEndLine], + ['VF_SERIALIZE_CONTINUATION_STATE_BEGIN', VFSerializeContinuationStateBegin], + ['VF_DESERIALIZE_CONTINUATION_STATE_BEGIN', VFDeserializeContinuationStateBegin], + ['VF_SERIALIZE_VIEWSTATE_BEGIN', VFSeralizeViewStateStartLine], + ['VF_PAGE_MESSAGE', VFPageMessageLine], + ['DML_BEGIN', DMLBeginLine], + ['DML_END', DMLEndLine], + ['IDEAS_QUERY_EXECUTE', IdeasQueryExecuteLine], + ['SOQL_EXECUTE_BEGIN', SOQLExecuteBeginLine], + ['SOQL_EXECUTE_END', SOQLExecuteEndLine], + ['SOQL_EXECUTE_EXPLAIN', SOQLExecuteExplainLine], + ['SOSL_EXECUTE_BEGIN', SOSLExecuteBeginLine], + ['SOSL_EXECUTE_END', SOSLExecuteEndLine], + ['HEAP_ALLOCATE', HeapAllocateLine], + ['HEAP_DEALLOCATE', HeapDeallocateLine], + ['STATEMENT_EXECUTE', StatementExecuteLine], + ['VARIABLE_SCOPE_BEGIN', VariableScopeBeginLine], + ['VARIABLE_ASSIGNMENT', VariableAssignmentLine], + ['USER_INFO', UserInfoLine], + ['USER_DEBUG', UserDebugLine], + ['CUMULATIVE_LIMIT_USAGE', CumulativeLimitUsageLine], + ['CUMULATIVE_PROFILING', CumulativeProfilingLine], + ['CUMULATIVE_PROFILING_BEGIN', CumulativeProfilingBeginLine], + ['LIMIT_USAGE', LimitUsageLine], + ['LIMIT_USAGE_FOR_NS', LimitUsageForNSLine], + ['NBA_NODE_BEGIN', NBANodeBegin], + ['NBA_NODE_DETAIL', NBANodeDetail], + ['NBA_NODE_END', NBANodeEnd], + ['NBA_NODE_ERROR', NBANodeError], + ['NBA_OFFER_INVALID', NBAOfferInvalid], + ['NBA_STRATEGY_BEGIN', NBAStrategyBegin], + ['NBA_STRATEGY_END', NBAStrategyEnd], + ['NBA_STRATEGY_ERROR', NBAStrategyError], + ['POP_TRACE_FLAGS', PopTraceFlagsLine], + ['PUSH_TRACE_FLAGS', PushTraceFlagsLine], + ['QUERY_MORE_BEGIN', QueryMoreBeginLine], + ['QUERY_MORE_END', QueryMoreEndLine], + ['QUERY_MORE_ITERATIONS', QueryMoreIterationsLine], + ['TOTAL_EMAIL_RECIPIENTS_QUEUED', TotalEmailRecipientsQueuedLine], + ['SAVEPOINT_ROLLBACK', SavepointRollbackLine], + ['SAVEPOINT_SET', SavePointSetLine], + ['STACK_FRAME_VARIABLE_LIST', StackFrameVariableListLine], + ['STATIC_VARIABLE_LIST', StaticVariableListLine], + ['SYSTEM_MODE_ENTER', SystemModeEnterLine], + ['SYSTEM_MODE_EXIT', SystemModeExitLine], + ['EXECUTION_STARTED', ExecutionStartedLine], + ['ENTERING_MANAGED_PKG', EnteringManagedPackageLine], + ['EVENT_SERVICE_PUB_BEGIN', EventSericePubBeginLine], + ['EVENT_SERVICE_PUB_END', EventSericePubEndLine], + ['EVENT_SERVICE_PUB_DETAIL', EventSericePubDetailLine], + ['EVENT_SERVICE_SUB_BEGIN', EventSericeSubBeginLine], + ['EVENT_SERVICE_SUB_DETAIL', EventSericeSubDetailLine], + ['EVENT_SERVICE_SUB_END', EventSericeSubEndLine], + ['FLOW_START_INTERVIEWS_BEGIN', FlowStartInterviewsBeginLine], + ['FLOW_START_INTERVIEWS_ERROR', FlowStartInterviewsErrorLine], + ['FLOW_START_INTERVIEW_BEGIN', FlowStartInterviewBeginLine], + ['FLOW_START_INTERVIEW_LIMIT_USAGE', FlowStartInterviewLimitUsageLine], + ['FLOW_START_SCHEDULED_RECORDS', FlowStartScheduledRecordsLine], + ['FLOW_CREATE_INTERVIEW_ERROR', FlowCreateInterviewErrorLine], + ['FLOW_ELEMENT_BEGIN', FlowElementBeginLine], + ['FLOW_ELEMENT_DEFERRED', FlowElementDeferredLine], + ['FLOW_ELEMENT_ERROR', FlowElementErrorLine], + ['FLOW_ELEMENT_FAULT', FlowElementFaultLine], + ['FLOW_ELEMENT_LIMIT_USAGE', FlowElementLimitUsageLine], + ['FLOW_INTERVIEW_FINISHED_LIMIT_USAGE', FlowInterviewFinishedLimitUsageLine], + ['FLOW_SUBFLOW_DETAIL', FlowSubflowDetailLine], + ['FLOW_VALUE_ASSIGNMENT', FlowElementAssignmentLine], + ['FLOW_WAIT_EVENT_RESUMING_DETAIL', FlowWaitEventResumingDetailLine], + ['FLOW_WAIT_EVENT_WAITING_DETAIL', FlowWaitEventWaitingDetailLine], + ['FLOW_WAIT_RESUMING_DETAIL', FlowWaitResumingDetailLine], + ['FLOW_WAIT_WAITING_DETAIL', FlowWaitWaitingDetailLine], + ['FLOW_INTERVIEW_FINISHED', FlowInterviewFinishedLine], + ['FLOW_INTERVIEW_PAUSED', FlowInterviewPausedLine], + ['FLOW_INTERVIEW_RESUMED', FlowInterviewResumedLine], + ['FLOW_ACTIONCALL_DETAIL', FlowActionCallDetailLine], + ['FLOW_ASSIGNMENT_DETAIL', FlowAssignmentDetailLine], + ['FLOW_LOOP_DETAIL', FlowLoopDetailLine], + ['FLOW_RULE_DETAIL', FlowRuleDetailLine], + ['FLOW_BULK_ELEMENT_BEGIN', FlowBulkElementBeginLine], + ['FLOW_BULK_ELEMENT_DETAIL', FlowBulkElementDetailLine], + ['FLOW_BULK_ELEMENT_LIMIT_USAGE', FlowBulkElementLimitUsageLine], + ['FLOW_BULK_ELEMENT_NOT_SUPPORTED', FlowBulkElementNotSupportedLine], + ['MATCH_ENGINE_BEGIN', MatchEngineBegin], + ['ORG_CACHE_PUT_BEGIN', OrgCachePutBegin], + ['ORG_CACHE_GET_BEGIN', OrgCacheGetBegin], + ['ORG_CACHE_REMOVE_BEGIN', OrgCacheRemoveBegin], + ['PUSH_NOTIFICATION_INVALID_APP', PNInvalidAppLine], + ['PUSH_NOTIFICATION_INVALID_CERTIFICATE', PNInvalidCertificateLine], + ['PUSH_NOTIFICATION_INVALID_NOTIFICATION', PNInvalidNotificationLine], + ['PUSH_NOTIFICATION_NO_DEVICES', PNNoDevicesLine], + ['PUSH_NOTIFICATION_SENT', PNSentLine], + ['SESSION_CACHE_PUT_BEGIN', SessionCachePutBegin], + ['SESSION_CACHE_GET_BEGIN', SessionCacheGetBegin], + ['SESSION_CACHE_REMOVE_BEGIN', SessionCacheRemoveBegin], + ['SLA_END', SLAEndLine], + ['SLA_EVAL_MILESTONE', SLAEvalMilestoneLine], + ['SLA_PROCESS_CASE', SLAProcessCaseLine], + ['TESTING_LIMITS', TestingLimitsLine], + ['VALIDATION_ERROR', ValidationErrorLine], + ['VALIDATION_FORMULA', ValidationFormulaLine], + ['VALIDATION_PASS', ValidationPassLine], + ['VALIDATION_RULE', ValidationRuleLine], + ['WF_FLOW_ACTION_ERROR', WFFlowActionErrorLine], + ['WF_FLOW_ACTION_ERROR_DETAIL', WFFlowActionErrorDetailLine], + ['WF_FIELD_UPDATE', WFFieldUpdateLine], + ['WF_RULE_EVAL_BEGIN', WFRuleEvalBeginLine], + ['WF_RULE_EVAL_VALUE', WFRuleEvalValueLine], + ['WF_RULE_FILTER', WFRuleFilterLine], + ['WF_CRITERIA_BEGIN', WFCriteriaBeginLine], + ['WF_FORMULA', WFFormulaLine], + ['WF_ACTION', WFActionLine], + ['WF_ACTIONS_END', WFActionsEndLine], + ['WF_ACTION_TASK', WFActionTaskLine], + ['WF_APPROVAL', WFApprovalLine], + ['WF_APPROVAL_REMOVE', WFApprovalRemoveLine], + ['WF_APPROVAL_SUBMIT', WFApprovalSubmitLine], + ['WF_APPROVAL_SUBMITTER', WFApprovalSubmitterLine], + ['WF_ASSIGN', WFAssignLine], + ['WF_EMAIL_ALERT', WFEmailAlertLine], + ['WF_EMAIL_SENT', WFEmailSentLine], + ['WF_ENQUEUE_ACTIONS', WFEnqueueActionsLine], + ['WF_ESCALATION_ACTION', WFEscalationActionLine], + ['WF_EVAL_ENTRY_CRITERIA', WFEvalEntryCriteriaLine], + ['WF_FLOW_ACTION_DETAIL', WFFlowActionDetailLine], + ['WF_NEXT_APPROVER', WFNextApproverLine], + ['WF_OUTBOUND_MSG', WFOutboundMsgLine], + ['WF_PROCESS_FOUND', WFProcessFoundLine], + ['WF_PROCESS_NODE', WFProcessNode], + ['WF_REASSIGN_RECORD', WFReassignRecordLine], + ['WF_RESPONSE_NOTIFY', WFResponseNotifyLine], + ['WF_RULE_ENTRY_ORDER', WFRuleEntryOrderLine], + ['WF_RULE_INVOCATION', WFRuleInvocationLine], + ['WF_SOFT_REJECT', WFSoftRejectLine], + ['WF_SPOOL_ACTION_BEGIN', WFSpoolActionBeginLine], + ['WF_TIME_TRIGGER', WFTimeTriggerLine], + ['EXCEPTION_THROWN', ExceptionThrownLine], + ['FATAL_ERROR', FatalErrorLine], + ['XDS_DETAIL', XDSDetailLine], + ['XDS_RESPONSE', XDSResponseLine], + ['XDS_RESPONSE_DETAIL', XDSResponseDetailLine], + ['XDS_RESPONSE_ERROR', XDSResponseErrorLine], + ['DUPLICATE_DETECTION_BEGIN', DuplicateDetectionBegin], + ['DUPLICATE_DETECTION_RULE_INVOCATION', DuplicateDetectionRule], + ['DUPLICATE_DETECTION_MATCH_INVOCATION_DETAILS', DuplicateDetectionDetails], + ['DUPLICATE_DETECTION_MATCH_INVOCATION_SUMMARY', DuplicateDetectionSummary], +]); + +const basicLogEvents: LogEventType[] = [ + 'BULK_COUNTABLE_STATEMENT_EXECUTE', + 'TEMPLATE_PROCESSING_ERROR', + 'EXTERNAL_SERVICE_REQUEST', + 'FLOW_CREATE_INTERVIEW_BEGIN', + 'FLOW_CREATE_INTERVIEW_END', + 'VARIABLE_SCOPE_END', + 'PUSH_NOTIFICATION_NOT_ENABLED', + 'SLA_NULL_START_DATE', + 'TEMPLATE_PROCESSING_ERROR', + 'VALIDATION_FAIL', + `WF_FLOW_ACTION_BEGIN`, + 'WF_FLOW_ACTION_END', + 'WF_ESCALATION_RULE', + 'WF_HARD_REJECT', + 'WF_NO_PROCESS_FOUND', + 'WF_TIME_TRIGGERS_BEGIN', + 'WF_KNOWLEDGE_ACTION', + 'WF_SEND_ACTION', + 'WAVE_APP_LIFECYCLE', + 'WF_QUICK_CREATE', + 'WF_APEX_ACTION', + 'INVOCABLE_ACTION_DETAIL', + 'INVOCABLE_ACTION_ERROR', + 'FLOW_COLLECTION_PROCESSOR_DETAIL', + 'FLOW_SCHEDULED_PATH_QUEUED', + 'ROUTE_WORK_ACTION', + 'ADD_SKILL_REQUIREMENT_ACTION', + 'ADD_SCREEN_POP_ACTION', + 'CALLOUT_REQUEST_PREPARE', + 'CALLOUT_REQUEST_FINALIZE', + 'FUNCTION_INVOCATION_REQUEST', + 'APP_CONTAINER_INITIATED', + 'FUNCTION_INVOCATION_RESPONSE', + 'XDS_REQUEST_DETAIL', + 'EXTERNAL_SERVICE_RESPONSE', + 'DATAWEAVE_USER_DEBUG', + 'USER_DEBUG_FINER', + 'USER_DEBUG_FINEST', + 'USER_DEBUG_FINE', + 'USER_DEBUG_DEBUG', + 'USER_DEBUG_INFO', + 'USER_DEBUG_WARN', + 'USER_DEBUG_ERROR', + 'VF_APEX_CALL', + 'HEAP_DUMP', + 'SCRIPT_EXECUTION', + 'SESSION_CACHE_MEMORY_USAGE', + 'ORG_CACHE_MEMORY_USAGE', + 'AE_PERSIST_VALIDATION', + 'REFERENCED_OBJECT_LIST', + 'DUPLICATE_RULE_FILTER', + 'DUPLICATE_RULE_FILTER_RESULT', + 'DUPLICATE_RULE_FILTER_VALUE', + 'TEMPLATED_ASSET', + 'TRANSFORMATION_SUMMARY', + 'RULES_EXECUTION_SUMMARY', + 'ASSET_DIFF_SUMMARY', + 'ASSET_DIFF_DETAIL', + 'RULES_EXECUTION_DETAIL', + 'JSON_DIFF_SUMMARY', + 'JSON_DIFF_DETAIL', + 'MATCH_ENGINE_INVOCATION', +]; + +const basicExitLogEvents: LogEventType[] = [ + 'FLOW_START_INTERVIEW_END', + 'VF_DESERIALIZE_VIEWSTATE_END', + 'VF_SERIALIZE_VIEWSTATE_END', + 'CUMULATIVE_LIMIT_USAGE_END', + 'CUMULATIVE_PROFILING_END', + 'EXECUTION_FINISHED', + 'FLOW_START_INTERVIEWS_END', + 'FLOW_ELEMENT_END', + 'FLOW_BULK_ELEMENT_END', + 'WF_RULE_EVAL_END', + 'WF_RULE_NOT_EVALUATED', + 'WF_CRITERIA_END', + 'DUPLICATE_DETECTION_END', + 'VF_SERIALIZE_CONTINUATION_STATE_END', + 'VF_DESERIALIZE_CONTINUATION_STATE_END', + 'MATCH_ENGINE_END', + 'ORG_CACHE_PUT_END', + 'ORG_CACHE_GET_END', + 'ORG_CACHE_REMOVE_END', + 'SESSION_CACHE_PUT_END', + 'SESSION_CACHE_GET_END', + 'SESSION_CACHE_REMOVE_END', +]; diff --git a/log-viewer/modules/parsers/types.ts b/log-viewer/modules/parsers/types.ts new file mode 100644 index 00000000..283440e6 --- /dev/null +++ b/log-viewer/modules/parsers/types.ts @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2020 Certinia Inc. All rights reserved. + */ + +export type CPUType = 'loading' | 'custom' | 'method' | 'free' | 'system' | 'pkg' | ''; + +export type IssueType = 'unexpected' | 'error' | 'skip'; + +export type LineNumber = number | 'EXTERNAL' | null; // an actual line-number or 'EXTERNAL' + +export type LogSubCategory = + | '' + | 'Method' + | 'System Method' + | 'Code Unit' + | 'DML' + | 'SOQL' + | 'Flow' + | 'Workflow'; + +export interface LogIssue { + startTime?: number; + summary: string; + description: string; + type: IssueType; +} +export type LogLineConstructor = new (parser: P, parts: string[]) => T; + +export type LogEventType = (typeof _logEventNames)[number]; + +export interface SelfTotal { + self: number; + total: number; +} + +const _logEventNames = [ + 'BULK_DML_RETRY', + 'BULK_HEAP_ALLOCATE', + 'CALLOUT_REQUEST', + 'CALLOUT_RESPONSE', + 'NAMED_CREDENTIAL_REQUEST', + 'NAMED_CREDENTIAL_RESPONSE', + 'NAMED_CREDENTIAL_RESPONSE_DETAIL', + 'CONSTRUCTOR_ENTRY', + 'CONSTRUCTOR_EXIT', + 'EMAIL_QUEUE', + 'METHOD_ENTRY', + 'METHOD_EXIT', + 'SYSTEM_CONSTRUCTOR_ENTRY', + 'SYSTEM_CONSTRUCTOR_EXIT', + 'SYSTEM_METHOD_ENTRY', + 'SYSTEM_METHOD_EXIT', + 'CODE_UNIT_STARTED', + 'CODE_UNIT_FINISHED', + 'VF_APEX_CALL_START', + 'VF_APEX_CALL_END', + 'VF_DESERIALIZE_VIEWSTATE_BEGIN', + 'VF_EVALUATE_FORMULA_BEGIN', + 'VF_EVALUATE_FORMULA_END', + 'VF_SERIALIZE_CONTINUATION_STATE_BEGIN', + 'VF_DESERIALIZE_CONTINUATION_STATE_BEGIN', + 'VF_SERIALIZE_VIEWSTATE_BEGIN', + 'VF_PAGE_MESSAGE', + 'DML_BEGIN', + 'DML_END', + 'IDEAS_QUERY_EXECUTE', + 'SOQL_EXECUTE_BEGIN', + 'SOQL_EXECUTE_END', + 'SOQL_EXECUTE_EXPLAIN', + 'SOSL_EXECUTE_BEGIN', + 'SOSL_EXECUTE_END', + 'HEAP_ALLOCATE', + 'HEAP_DEALLOCATE', + 'STATEMENT_EXECUTE', + 'VARIABLE_SCOPE_BEGIN', + 'VARIABLE_ASSIGNMENT', + 'USER_INFO', + 'USER_DEBUG', + 'CUMULATIVE_LIMIT_USAGE', + 'CUMULATIVE_PROFILING', + 'CUMULATIVE_PROFILING_BEGIN', + 'LIMIT_USAGE', + 'LIMIT_USAGE_FOR_NS', + 'NBA_NODE_BEGIN', + 'NBA_NODE_DETAIL', + 'NBA_NODE_END', + 'NBA_NODE_ERROR', + 'NBA_OFFER_INVALID', + 'NBA_STRATEGY_BEGIN', + 'NBA_STRATEGY_END', + 'NBA_STRATEGY_ERROR', + 'POP_TRACE_FLAGS', + 'PUSH_TRACE_FLAGS', + 'QUERY_MORE_BEGIN', + 'QUERY_MORE_END', + 'QUERY_MORE_ITERATIONS', + 'TOTAL_EMAIL_RECIPIENTS_QUEUED', + 'SAVEPOINT_ROLLBACK', + 'SAVEPOINT_SET', + 'STACK_FRAME_VARIABLE_LIST', + 'STATIC_VARIABLE_LIST', + 'SYSTEM_MODE_ENTER', + 'SYSTEM_MODE_EXIT', + 'EXECUTION_STARTED', + 'ENTERING_MANAGED_PKG', + 'EVENT_SERVICE_PUB_BEGIN', + 'EVENT_SERVICE_PUB_END', + 'EVENT_SERVICE_PUB_DETAIL', + 'EVENT_SERVICE_SUB_BEGIN', + 'EVENT_SERVICE_SUB_DETAIL', + 'EVENT_SERVICE_SUB_END', + 'FLOW_START_INTERVIEWS_BEGIN', + 'FLOW_START_INTERVIEWS_ERROR', + 'FLOW_START_INTERVIEW_BEGIN', + 'FLOW_START_INTERVIEW_LIMIT_USAGE', + 'FLOW_START_SCHEDULED_RECORDS', + 'FLOW_CREATE_INTERVIEW_ERROR', + 'FLOW_ELEMENT_BEGIN', + 'FLOW_ELEMENT_DEFERRED', + 'FLOW_ELEMENT_ERROR', + 'FLOW_ELEMENT_FAULT', + 'FLOW_ELEMENT_LIMIT_USAGE', + 'FLOW_INTERVIEW_FINISHED_LIMIT_USAGE', + 'FLOW_SUBFLOW_DETAIL', + 'FLOW_VALUE_ASSIGNMENT', + 'FLOW_WAIT_EVENT_RESUMING_DETAIL', + 'FLOW_WAIT_EVENT_WAITING_DETAIL', + 'FLOW_WAIT_RESUMING_DETAIL', + 'FLOW_WAIT_WAITING_DETAIL', + 'FLOW_INTERVIEW_FINISHED', + 'FLOW_INTERVIEW_PAUSED', + 'FLOW_INTERVIEW_RESUMED', + 'FLOW_ACTIONCALL_DETAIL', + 'FLOW_ASSIGNMENT_DETAIL', + 'FLOW_LOOP_DETAIL', + 'FLOW_RULE_DETAIL', + 'FLOW_BULK_ELEMENT_BEGIN', + 'FLOW_BULK_ELEMENT_DETAIL', + 'FLOW_BULK_ELEMENT_LIMIT_USAGE', + 'FLOW_BULK_ELEMENT_NOT_SUPPORTED', + 'MATCH_ENGINE_BEGIN', + 'ORG_CACHE_PUT_BEGIN', + 'ORG_CACHE_GET_BEGIN', + 'ORG_CACHE_REMOVE_BEGIN', + 'PUSH_NOTIFICATION_INVALID_APP', + 'PUSH_NOTIFICATION_INVALID_CERTIFICATE', + 'PUSH_NOTIFICATION_INVALID_NOTIFICATION', + 'PUSH_NOTIFICATION_NO_DEVICES', + 'PUSH_NOTIFICATION_SENT', + 'SESSION_CACHE_PUT_BEGIN', + 'SESSION_CACHE_GET_BEGIN', + 'SESSION_CACHE_REMOVE_BEGIN', + 'SLA_END', + 'SLA_EVAL_MILESTONE', + 'SLA_PROCESS_CASE', + 'TESTING_LIMITS', + 'VALIDATION_ERROR', + 'VALIDATION_FORMULA', + 'VALIDATION_PASS', + 'VALIDATION_RULE', + 'WF_FLOW_ACTION_ERROR', + 'WF_FLOW_ACTION_ERROR_DETAIL', + 'WF_FIELD_UPDATE', + 'WF_RULE_EVAL_BEGIN', + 'WF_RULE_EVAL_VALUE', + 'WF_RULE_FILTER', + 'WF_CRITERIA_BEGIN', + 'WF_FORMULA', + 'WF_ACTION', + 'WF_ACTIONS_END', + 'WF_ACTION_TASK', + 'WF_APPROVAL', + 'WF_APPROVAL_REMOVE', + 'WF_APPROVAL_SUBMIT', + 'WF_APPROVAL_SUBMITTER', + 'WF_ASSIGN', + 'WF_EMAIL_ALERT', + 'WF_EMAIL_SENT', + 'WF_ENQUEUE_ACTIONS', + 'WF_ESCALATION_ACTION', + 'WF_EVAL_ENTRY_CRITERIA', + 'WF_FLOW_ACTION_DETAIL', + 'WF_NEXT_APPROVER', + 'WF_OUTBOUND_MSG', + 'WF_PROCESS_FOUND', + 'WF_PROCESS_NODE', + 'WF_REASSIGN_RECORD', + 'WF_RESPONSE_NOTIFY', + 'WF_RULE_ENTRY_ORDER', + 'WF_RULE_INVOCATION', + 'WF_SOFT_REJECT', + 'WF_SPOOL_ACTION_BEGIN', + 'WF_TIME_TRIGGER', + 'EXCEPTION_THROWN', + 'FATAL_ERROR', + 'XDS_DETAIL', + 'XDS_RESPONSE', + 'XDS_RESPONSE_DETAIL', + 'XDS_RESPONSE_ERROR', + 'DUPLICATE_DETECTION_BEGIN', + 'DUPLICATE_DETECTION_RULE_INVOCATION', + 'DUPLICATE_DETECTION_MATCH_INVOCATION_DETAILS', + 'DUPLICATE_DETECTION_MATCH_INVOCATION_SUMMARY', + 'BULK_COUNTABLE_STATEMENT_EXECUTE', + 'TEMPLATE_PROCESSING_ERROR', + 'EXTERNAL_SERVICE_REQUEST', + 'FLOW_START_INTERVIEW_END', + 'FLOW_CREATE_INTERVIEW_BEGIN', + 'FLOW_CREATE_INTERVIEW_END', + 'VARIABLE_SCOPE_END', + 'PUSH_NOTIFICATION_NOT_ENABLED', + 'SLA_NULL_START_DATE', + 'TEMPLATE_PROCESSING_ERROR', + 'VALIDATION_FAIL', + `WF_FLOW_ACTION_BEGIN`, + 'WF_FLOW_ACTION_END', + 'WF_ESCALATION_RULE', + 'WF_HARD_REJECT', + 'WF_NO_PROCESS_FOUND', + 'WF_TIME_TRIGGERS_BEGIN', + 'WF_KNOWLEDGE_ACTION', + 'WF_SEND_ACTION', + 'WAVE_APP_LIFECYCLE', + 'WF_QUICK_CREATE', + 'WF_APEX_ACTION', + 'INVOCABLE_ACTION_DETAIL', + 'INVOCABLE_ACTION_ERROR', + 'FLOW_COLLECTION_PROCESSOR_DETAIL', + 'FLOW_SCHEDULED_PATH_QUEUED', + 'ROUTE_WORK_ACTION', + 'ADD_SKILL_REQUIREMENT_ACTION', + 'ADD_SCREEN_POP_ACTION', + 'CALLOUT_REQUEST_PREPARE', + 'CALLOUT_REQUEST_FINALIZE', + 'FUNCTION_INVOCATION_REQUEST', + 'APP_CONTAINER_INITIATED', + 'FUNCTION_INVOCATION_RESPONSE', + 'XDS_REQUEST_DETAIL', + 'EXTERNAL_SERVICE_RESPONSE', + 'DATAWEAVE_USER_DEBUG', + 'USER_DEBUG_FINER', + 'USER_DEBUG_FINEST', + 'USER_DEBUG_FINE', + 'USER_DEBUG_DEBUG', + 'USER_DEBUG_INFO', + 'USER_DEBUG_WARN', + 'USER_DEBUG_ERROR', + 'VF_APEX_CALL', + 'HEAP_DUMP', + 'SCRIPT_EXECUTION', + 'SESSION_CACHE_MEMORY_USAGE', + 'ORG_CACHE_MEMORY_USAGE', + 'AE_PERSIST_VALIDATION', + 'REFERENCED_OBJECT_LIST', + 'DUPLICATE_RULE_FILTER', + 'DUPLICATE_RULE_FILTER_RESULT', + 'DUPLICATE_RULE_FILTER_VALUE', + 'TEMPLATED_ASSET', + 'TRANSFORMATION_SUMMARY', + 'RULES_EXECUTION_SUMMARY', + 'ASSET_DIFF_SUMMARY', + 'ASSET_DIFF_DETAIL', + 'RULES_EXECUTION_DETAIL', + 'JSON_DIFF_SUMMARY', + 'JSON_DIFF_DETAIL', + 'MATCH_ENGINE_INVOCATION', + 'VF_DESERIALIZE_VIEWSTATE_END', + 'VF_SERIALIZE_VIEWSTATE_END', + 'CUMULATIVE_LIMIT_USAGE_END', + 'CUMULATIVE_PROFILING_END', + 'EXECUTION_FINISHED', + 'FLOW_START_INTERVIEWS_END', + 'FLOW_ELEMENT_END', + 'FLOW_BULK_ELEMENT_END', + 'WF_RULE_EVAL_END', + 'WF_RULE_NOT_EVALUATED', + 'WF_CRITERIA_END', + 'DUPLICATE_DETECTION_END', + 'VF_SERIALIZE_CONTINUATION_STATE_END', + 'VF_DESERIALIZE_CONTINUATION_STATE_END', + 'MATCH_ENGINE_END', + 'ORG_CACHE_PUT_END', + 'ORG_CACHE_GET_END', + 'ORG_CACHE_REMOVE_END', + 'SESSION_CACHE_PUT_END', + 'SESSION_CACHE_GET_END', + 'SESSION_CACHE_REMOVE_END', +] as const; diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index 5c4b88eb..57af1211 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -4,9 +4,8 @@ //TODO:Refactor - usage should look more like `new TimeLine(timelineContainer, {tooltip:true}:Config)`; import formatDuration, { debounce } from '../Util.js'; import { goToRow } from '../components/calltree-view/CalltreeView.js'; -import { ApexLog, LogEvent, type LogIssue, type LogSubCategory } from '../parsers/ApexLogParser.js'; - -export { ApexLog }; +import type { ApexLog, LogEvent } from '../parsers/LogEvents.js'; +import type { LogIssue, LogSubCategory } from '../parsers/types.js'; export interface TimelineGroup { label: string; diff --git a/log-viewer/modules/timeline/TimelineView.ts b/log-viewer/modules/timeline/TimelineView.ts index f9e18ba9..d5bda4a5 100644 --- a/log-viewer/modules/timeline/TimelineView.ts +++ b/log-viewer/modules/timeline/TimelineView.ts @@ -5,8 +5,9 @@ import { LitElement, css, html, type PropertyValues } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { skeletonStyles } from '../components/skeleton/skeleton.styles.js'; +import type { ApexLog } from '../parsers/LogEvents.js'; import { globalStyles } from '../styles/global.styles.js'; -import { ApexLog, init as timelineInit, type TimelineGroup } from './Timeline.js'; +import { init as timelineInit, type TimelineGroup } from './Timeline.js'; import './TimelineKey.js'; @customElement('timeline-view') From 1088c6e4c4638d4ee211766d7fc08c3411b59a29 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Fri, 2 May 2025 11:32:29 +0100 Subject: [PATCH 12/20] fix: duplicate declaration after bad merge --- log-viewer/modules/parsers/ApexLogParser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index 53300716..67180236 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -35,7 +35,6 @@ export class ApexLogParser { lastTimestamp = 0; discontinuity = false; namespaces = new Set(); - namespaces = new Set(); /** * Takes string input of a log and returns the ApexLog class, which represents a log tree From 5fde7a18af8276912b7baa7bff8147246fa765b8 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Fri, 2 May 2025 11:41:03 +0100 Subject: [PATCH 13/20] fix: remove duplicate code after merge --- log-viewer/modules/parsers/ApexLogParser.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index 67180236..6eb2c262 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -315,8 +315,6 @@ export class ApexLogParser { } currentDepth++; currentNodes = children; - currentDepth++; - currentNodes = children; len = currentNodes.length; } @@ -378,7 +376,7 @@ export class ApexLogParser { } } - if (child.exitTypes) { + if (child.exitTypes.length) { this.insertPackageWrappers(child); } From 4cd7dcb57112985c3fba7c87ae85c999a577deb2 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:48:22 +0100 Subject: [PATCH 14/20] perf: replace Method class with isDetail property --- log-viewer/modules/Database.ts | 6 +- .../modules/__tests__/ApexLogParser.test.ts | 2 +- log-viewer/modules/parsers/ApexLogParser.ts | 8 +- log-viewer/modules/parsers/LogEvents.ts | 154 +++++++++--------- log-viewer/modules/timeline/Timeline.ts | 4 +- 5 files changed, 88 insertions(+), 86 deletions(-) diff --git a/log-viewer/modules/Database.ts b/log-viewer/modules/Database.ts index ff52fa5d..b9ec9351 100644 --- a/log-viewer/modules/Database.ts +++ b/log-viewer/modules/Database.ts @@ -34,7 +34,7 @@ export class DatabaseAccess { const len = children.length; for (let i = 0; i < len; ++i) { const child = children[i]; - if (child?.exitTypes.length) { + if (child && !child.isDetail) { stack.push(child); if (child.timestamp === timestamp) { return stack; @@ -61,7 +61,7 @@ export class DatabaseAccess { results.push(child); } - if (child?.exitTypes.length) { + if (child && !child.isDetail) { Array.prototype.push.apply(results, this.getSOQLLines(child)); } } @@ -80,7 +80,7 @@ export class DatabaseAccess { results.push(child); } - if (child?.exitTypes.length) { + if (child && !child.isDetail) { // results = results.concat(this.getDMLLines(child)); Array.prototype.push.apply(results, this.getDMLLines(child)); } diff --git a/log-viewer/modules/__tests__/ApexLogParser.test.ts b/log-viewer/modules/__tests__/ApexLogParser.test.ts index e1fbb288..e3d01efe 100644 --- a/log-viewer/modules/__tests__/ApexLogParser.test.ts +++ b/log-viewer/modules/__tests__/ApexLogParser.test.ts @@ -1264,7 +1264,7 @@ describe('Line Type Tests', () => { 'Rows:5', ]) as LogEvent; - if (line.exitTypes.length) { + if (!line.isDetail) { line.exitTypes.forEach((exitType) => { const exitCls = lineTypeMap.get(exitType); expect(exitCls).not.toBe(null); diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index e25ee5b0..af74b2bc 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -190,7 +190,7 @@ export class ApexLogParser { const lineIter = new LineIterator(lineGenerator); while ((line = lineIter.fetch())) { - if (line.exitTypes.length) { + if (!line.isDetail) { this.parseTree(line, lineIter, stack); } line.parent = rootMethod; @@ -252,7 +252,7 @@ export class ApexLogParser { nextLine.parent = currentLine; currentLine.children.push(nextLine); - if (nextLine.exitTypes.length) { + if (!nextLine.isDetail) { this.parseTree(nextLine, lineIter, stack); } } @@ -400,7 +400,7 @@ export class ApexLogParser { continue; } const isPkgType = child.type === 'ENTERING_MANAGED_PKG'; - if (lastPkg) { + if (lastPkg && !child.isDetail) { if (isPkgType && child.namespace === lastPkg.namespace) { // combine adjacent (like) packages lastPkg.exitStamp = child.exitStamp || child.timestamp; @@ -412,7 +412,7 @@ export class ApexLogParser { } } - if (child.exitTypes.length) { + if (!child.isDetail) { this.insertPackageWrappers(child); } diff --git a/log-viewer/modules/parsers/LogEvents.ts b/log-viewer/modules/parsers/LogEvents.ts index ff0cefee..d7507d2c 100644 --- a/log-viewer/modules/parsers/LogEvents.ts +++ b/log-viewer/modules/parsers/LogEvents.ts @@ -42,7 +42,7 @@ export abstract class LogEvent { /** * A parsed version of the log line text useful for display in UIs */ - text; + text = ''; // optional metadata /** @@ -59,6 +59,12 @@ export abstract class LogEvent { */ isExit = false; + /** + * Indicates whether the current log event could have an entry and exit event and hence children. + * It is possible this is true but there is not defined exit events of children. + */ + isDetail = true; + /** * Whether the log event was truncated when the log ended, e,g no matching end event */ @@ -104,7 +110,7 @@ export abstract class LogEvent { /** * The timestamp of this log line, in nanoseconds */ - timestamp; + timestamp = 0; /** * The timestamp when the node finished, in nanoseconds @@ -220,15 +226,32 @@ export abstract class LogEvent { */ exitTypes: LogEventType[] = []; - constructor(parser: ApexLogParser, parts: string[] | null) { + constructor( + parser: ApexLogParser, + parts: string[] | null, + exitTypes?: string[], + timelineKey?: LogSubCategory, + cpuType?: CPUType, + ) { this.logParser = parser; + // Now set actual values from parts if (parts) { const [timeData, type] = parts; this.text = this.type = type as LogEventType; this.timestamp = timeData ? this.parseTimestamp(timeData) : 0; - } else { - this.timestamp = 0; - this.text = ''; + } + + if (exitTypes) { + this.exitTypes = exitTypes as LogEventType[]; + this.isDetail = false; + } + + if (timelineKey) { + this.subCategory = timelineKey; + } + + if (cpuType) { + this.cpuType = cpuType; } } @@ -274,33 +297,12 @@ export class BasicExitLine extends LogEvent { isExit = true; } -/** - * Log lines extend this export class if they have a start-line and an end-line (and hence can have children in-between). - * - The start-line should extend "Method" and collect any children. - * - The end-line should extend "Detail" and terminate the method (also providing the "exitStamp"). - * The method will be rendered as "expandable" in the tree-view, if it has children. - */ -export class Method extends LogEvent { - constructor( - parser: ApexLogParser, - parts: string[] | null, - exitTypes: string[], - timelineKey: LogSubCategory, - cpuType: CPUType, - ) { - super(parser, parts); - this.subCategory = timelineKey; - this.cpuType = cpuType; - this.exitTypes = exitTypes as LogEventType[]; - } -} - /** * This export class represents the single root node for the node tree. * It is a "pseudo" node and not present in the log. * Since it has children it extends "Method". */ -export class ApexLog extends Method { +export class ApexLog extends LogEvent { type = null; text = 'LOG_ROOT'; timestamp = 0; @@ -466,7 +468,7 @@ export class NamedCredentialResponseDetailLine extends LogEvent { } } -export class ConstructorEntryLine extends Method { +export class ConstructorEntryLine extends LogEvent { hasValidSymbols = true; suffix = ' (constructor)'; @@ -516,7 +518,7 @@ export class EmailQueueLine extends LogEvent { } } -export class MethodEntryLine extends Method { +export class MethodEntryLine extends LogEvent { hasValidSymbols = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -590,7 +592,7 @@ export class MethodExitLine extends LogEvent { } } -export class SystemConstructorEntryLine extends Method { +export class SystemConstructorEntryLine extends LogEvent { suffix = '(system constructor)'; constructor(parser: ApexLogParser, parts: string[]) { @@ -608,7 +610,7 @@ export class SystemConstructorExitLine extends LogEvent { this.lineNumber = this.parseLineNumber(parts[2]); } } -export class SystemMethodEntryLine extends Method { +export class SystemMethodEntryLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SYSTEM_METHOD_EXIT'], 'System Method', 'method'); this.lineNumber = this.parseLineNumber(parts[2]); @@ -625,7 +627,7 @@ export class SystemMethodExitLine extends LogEvent { } } -export class CodeUnitStartedLine extends Method { +export class CodeUnitStartedLine extends LogEvent { suffix = ' (entrypoint)'; codeUnitType = ''; @@ -709,7 +711,7 @@ export class CodeUnitFinishedLine extends LogEvent { } } -export class VFApexCallStartLine extends Method { +export class VFApexCallStartLine extends LogEvent { suffix = ' (VF APEX)'; invalidClasses = [ 'pagemessagescomponentcontroller', @@ -765,13 +767,13 @@ export class VFApexCallEndLine extends LogEvent { } } -export class VFDeserializeViewstateBeginLine extends Method { +export class VFDeserializeViewstateBeginLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['VF_DESERIALIZE_VIEWSTATE_END'], 'System Method', 'method'); } } -export class VFFormulaStartLine extends Method { +export class VFFormulaStartLine extends LogEvent { suffix = ' (VF FORMULA)'; constructor(parser: ApexLogParser, parts: string[]) { @@ -789,7 +791,7 @@ export class VFFormulaEndLine extends LogEvent { } } -export class VFSeralizeViewStateStartLine extends Method { +export class VFSeralizeViewStateStartLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['VF_SERIALIZE_VIEWSTATE_END'], 'System Method', 'method'); } @@ -803,7 +805,7 @@ export class VFPageMessageLine extends LogEvent { } } -export class DMLBeginLine extends Method { +export class DMLBeginLine extends LogEvent { dmlCount = { self: 1, total: 1, @@ -836,7 +838,7 @@ export class IdeasQueryExecuteLine extends LogEvent { } } -export class SOQLExecuteBeginLine extends Method { +export class SOQLExecuteBeginLine extends LogEvent { aggregations = 0; soqlCount = { self: 1, @@ -918,7 +920,7 @@ export class SOQLExecuteExplainLine extends LogEvent { } } -export class SOSLExecuteBeginLine extends Method { +export class SOSLExecuteBeginLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SOSL_EXECUTE_END'], 'SOQL', 'free'); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1000,7 +1002,7 @@ export class UserDebugLine extends LogEvent { } } -export class CumulativeLimitUsageLine extends Method { +export class CumulativeLimitUsageLine extends LogEvent { namespace = 'default'; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['CUMULATIVE_LIMIT_USAGE_END'], 'System Method', 'system'); @@ -1016,7 +1018,7 @@ export class CumulativeProfilingLine extends LogEvent { } } -export class CumulativeProfilingBeginLine extends Method { +export class CumulativeProfilingBeginLine extends LogEvent { namespace = 'default'; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['CUMULATIVE_PROFILING_END'], 'System Method', 'custom'); @@ -1107,7 +1109,7 @@ export class LimitUsageForNSLine extends LogEvent { } } -export class NBANodeBegin extends Method { +export class NBANodeBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['NBA_NODE_END'], 'System Method', 'method'); this.text = parts.slice(2).join(' | '); @@ -1139,7 +1141,7 @@ export class NBAOfferInvalid extends LogEvent { this.text = parts.slice(2).join(' | '); } } -export class NBAStrategyBegin extends Method { +export class NBAStrategyBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['NBA_STRATEGY_END'], 'System Method', 'method'); this.text = parts.slice(2).join(' | '); @@ -1175,7 +1177,7 @@ export class PopTraceFlagsLine extends LogEvent { } } -export class QueryMoreBeginLine extends Method { +export class QueryMoreBeginLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['QUERY_MORE_END'], 'SOQL', 'custom'); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1256,14 +1258,14 @@ export class SystemModeExitLine extends LogEvent { } } -export class ExecutionStartedLine extends Method { +export class ExecutionStartedLine extends LogEvent { namespace = 'default'; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['EXECUTION_FINISHED'], 'Method', 'method'); } } -export class EnteringManagedPackageLine extends Method { +export class EnteringManagedPackageLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, [], 'Method', 'pkg'); const rawNs = parts[2] || '', @@ -1279,7 +1281,7 @@ export class EnteringManagedPackageLine extends Method { } } -export class EventSericePubBeginLine extends Method { +export class EventSericePubBeginLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['EVENT_SERVICE_PUB_END'], 'Flow', 'custom'); this.text = parts[2] || ''; @@ -1302,7 +1304,7 @@ export class EventSericePubDetailLine extends LogEvent { } } -export class EventSericeSubBeginLine extends Method { +export class EventSericeSubBeginLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['EVENT_SERVICE_SUB_END'], 'Flow', 'custom'); this.text = `${parts[2]} ${parts[3]}`; @@ -1325,7 +1327,7 @@ export class EventSericeSubDetailLine extends LogEvent { } } -export class FlowStartInterviewsBeginLine extends Method { +export class FlowStartInterviewsBeginLine extends LogEvent { declarative = true; text = 'FLOW_START_INTERVIEWS : '; @@ -1374,7 +1376,7 @@ export class FlowStartInterviewsErrorLine extends LogEvent { } } -export class FlowStartInterviewBeginLine extends Method { +export class FlowStartInterviewBeginLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['FLOW_START_INTERVIEW_END'], 'Flow', 'custom'); this.text = parts[3] || ''; @@ -1402,7 +1404,7 @@ export class FlowCreateInterviewErrorLine extends LogEvent { } } -export class FlowElementBeginLine extends Method { +export class FlowElementBeginLine extends LogEvent { declarative = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1543,7 +1545,7 @@ export class FlowRuleDetailLine extends LogEvent { } } -export class FlowBulkElementBeginLine extends Method { +export class FlowBulkElementBeginLine extends LogEvent { declarative = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1688,7 +1690,7 @@ export class WFFlowActionErrorDetailLine extends LogEvent { } } -export class WFFieldUpdateLine extends Method { +export class WFFieldUpdateLine extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1697,7 +1699,7 @@ export class WFFieldUpdateLine extends Method { } } -export class WFRuleEvalBeginLine extends Method { +export class WFRuleEvalBeginLine extends LogEvent { declarative = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1722,7 +1724,7 @@ export class WFRuleFilterLine extends LogEvent { } } -export class WFCriteriaBeginLine extends Method { +export class WFCriteriaBeginLine extends LogEvent { declarative = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1731,7 +1733,7 @@ export class WFCriteriaBeginLine extends Method { } } -export class WFFormulaLine extends Method { +export class WFFormulaLine extends LogEvent { acceptsText = true; isExit = true; nextLineIsExit = true; @@ -1763,7 +1765,7 @@ export class WFActionTaskLine extends LogEvent { } } -export class WFApprovalLine extends Method { +export class WFApprovalLine extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1779,7 +1781,7 @@ export class WFApprovalRemoveLine extends LogEvent { } } -export class WFApprovalSubmitLine extends Method { +export class WFApprovalSubmitLine extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1802,7 +1804,7 @@ export class WFAssignLine extends LogEvent { } } -export class WFEmailAlertLine extends Method { +export class WFEmailAlertLine extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1811,7 +1813,7 @@ export class WFEmailAlertLine extends Method { } } -export class WFEmailSentLine extends Method { +export class WFEmailSentLine extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1834,7 +1836,7 @@ export class WFEscalationActionLine extends LogEvent { } } -export class WFEvalEntryCriteriaLine extends Method { +export class WFEvalEntryCriteriaLine extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1851,7 +1853,7 @@ export class WFFlowActionDetailLine extends LogEvent { } } -export class WFNextApproverLine extends Method { +export class WFNextApproverLine extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1867,7 +1869,7 @@ export class WFOutboundMsgLine extends LogEvent { } } -export class WFProcessFoundLine extends Method { +export class WFProcessFoundLine extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1876,7 +1878,7 @@ export class WFProcessFoundLine extends Method { } } -export class WFProcessNode extends Method { +export class WFProcessNode extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1906,7 +1908,7 @@ export class WFRuleEntryOrderLine extends LogEvent { } } -export class WFRuleInvocationLine extends Method { +export class WFRuleInvocationLine extends LogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -2005,7 +2007,7 @@ export class XDSResponseErrorLine extends LogEvent { } // e.g. "09:45:31.888 (38889007737)|DUPLICATE_DETECTION_BEGIN" -export class DuplicateDetectionBegin extends Method { +export class DuplicateDetectionBegin extends LogEvent { declarative = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -2052,54 +2054,54 @@ export class DuplicateDetectionSummary extends LogEvent { } } -export class SessionCachePutBegin extends Method { +export class SessionCachePutBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SESSION_CACHE_PUT_END'], 'Method', 'method'); } } -export class SessionCacheGetBegin extends Method { +export class SessionCacheGetBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SESSION_CACHE_GET_END'], 'Method', 'method'); } } -export class SessionCacheRemoveBegin extends Method { +export class SessionCacheRemoveBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SESSION_CACHE_REMOVE_END'], 'Method', 'method'); } } -export class OrgCachePutBegin extends Method { +export class OrgCachePutBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['ORG_CACHE_PUT_END'], 'Method', 'method'); } } -export class OrgCacheGetBegin extends Method { +export class OrgCacheGetBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['ORG_CACHE_GET_END'], 'Method', 'method'); } } -export class OrgCacheRemoveBegin extends Method { +export class OrgCacheRemoveBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['ORG_CACHE_REMOVE_END'], 'Method', 'method'); } } -export class VFSerializeContinuationStateBegin extends Method { +export class VFSerializeContinuationStateBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['VF_SERIALIZE_CONTINUATION_STATE_END'], 'Method', 'method'); } } -export class VFDeserializeContinuationStateBegin extends Method { +export class VFDeserializeContinuationStateBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['VF_SERIALIZE_CONTINUATION_STATE_END'], 'Method', 'method'); } } -export class MatchEngineBegin extends Method { +export class MatchEngineBegin extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['MATCH_ENGINE_END'], 'Method', 'method'); } diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index bf40ab26..2d99b517 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -263,7 +263,7 @@ function nodesToRectangles(rootNodes: LogEvent[]) { } for (const child of node.children) { - if (child.exitTypes.length) { + if (!child.isDetail) { nextLevel.push(child); } } @@ -648,7 +648,7 @@ function findTimelineTooltip( ): HTMLDivElement | null { const target = findByPosition(timelineRoot.children, 0, x, depth, shouldIgnoreWidth); - if (target) { + if (target && !target.isDetail) { canvas.classList.remove('timeline-hover', 'timeline-dragging'); canvas.classList.add('timeline-event--hover'); From ccaa3a13800d69053425bd4517390924210dca2d Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Tue, 26 Aug 2025 17:00:03 +0100 Subject: [PATCH 15/20] refactor: use property initialization instead of constructor --- log-viewer/modules/parsers/LogEvents.ts | 33 ++++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/log-viewer/modules/parsers/LogEvents.ts b/log-viewer/modules/parsers/LogEvents.ts index d7507d2c..4548f138 100644 --- a/log-viewer/modules/parsers/LogEvents.ts +++ b/log-viewer/modules/parsers/LogEvents.ts @@ -307,6 +307,10 @@ export class ApexLog extends LogEvent { text = 'LOG_ROOT'; timestamp = 0; exitStamp = 0; + exitTypes = []; + subCategory: LogSubCategory = ''; + cpuType: CPUType = ''; + /** * The size of the log, in bytes */ @@ -355,7 +359,7 @@ export class ApexLog extends LogEvent { executionEndTime = 0; constructor(parser: ApexLogParser) { - super(parser, null, [], 'Code Unit', ''); + super(parser, null); } setTimes() { @@ -426,11 +430,10 @@ export function parseRows(text: string | null | undefined): number { /* Log line entry Parsers */ export class BulkHeapAllocateLine extends LogEvent { - logCategory: 'Apex Code'; + logCategory = 'Apex Code'; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; - this.logCategory = 'Apex Code'; } } @@ -492,7 +495,7 @@ export class ConstructorEntryLine extends LogEvent { const constructorParts = (className ?? '').split('.'); possibleNs = constructorParts[0] || ''; - // inmner export class with a namespace + // inner class with a namespace if (constructorParts.length === 3) { return possibleNs; } @@ -523,13 +526,14 @@ export class MethodEntryLine extends LogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['METHOD_EXIT'], 'Method', 'method'); - this.lineNumber = this.parseLineNumber(parts[2]); - this.text = parts[4] || this.type || this.text; + const [, , lineNumber, , methodName] = parts; + this.lineNumber = this.parseLineNumber(lineNumber); + this.text = methodName || this.type || this.text; if (this.text.indexOf('System.Type.forName(') !== -1) { // assume we are not charged for export class loading (or at least not lengthy remote-loading / compiling) this.cpuType = 'loading'; } else { - const possibleNs = this._parseMethodNamespace(parts[4]); + const possibleNs = this._parseMethodNamespace(methodName); if (possibleNs) { this.namespace = possibleNs; } @@ -810,7 +814,6 @@ export class DMLBeginLine extends LogEvent { self: 1, total: 1, }; - namespace = 'default'; constructor(parser: ApexLogParser, parts: string[]) { @@ -921,15 +924,15 @@ export class SOQLExecuteExplainLine extends LogEvent { } export class SOSLExecuteBeginLine extends LogEvent { + soslCount = { + self: 1, + total: 1, + }; + constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SOSL_EXECUTE_END'], 'SOQL', 'free'); this.lineNumber = this.parseLineNumber(parts[2]); this.text = `SOSL: ${parts[3]}`; - - this.soslCount = { - self: 1, - total: 1, - }; } onEnd(end: SOSLExecuteEndLine, _stack: LogEvent[]): void { @@ -1051,11 +1054,11 @@ export class LimitUsageForNSLine extends LogEvent { ['Number of Mobile Apex push calls', 'mobileApexPushCalls'], ]); + namespace = 'default'; + constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.acceptsText = true; - this.namespace = 'default'; - this.text = parts[2] || ''; } From c055e3c055837eefd9fdb32e4fd60aea484294d1 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:06:01 +0100 Subject: [PATCH 16/20] refactor: remove unused properties --- log-viewer/modules/parsers/LogEvents.ts | 32 ------------------------- 1 file changed, 32 deletions(-) diff --git a/log-viewer/modules/parsers/LogEvents.ts b/log-viewer/modules/parsers/LogEvents.ts index 4548f138..7d9eee9b 100644 --- a/log-viewer/modules/parsers/LogEvents.ts +++ b/log-viewer/modules/parsers/LogEvents.ts @@ -50,10 +50,6 @@ export abstract class LogEvent { */ acceptsText = false; - /** - * Is this log entry generated by a declarative process? - */ - declarative = false; /** * Is a method exit line? */ @@ -87,11 +83,6 @@ export abstract class LogEvent { */ namespace: string | 'default' = ''; - /** - * The variable value - */ - value: string | null = null; - /** * Could match to a corresponding symbol in a file in the workspace? */ @@ -654,18 +645,14 @@ export class CodeUnitStartedLine extends LogEvent { break; case 'Validation': this.cpuType = 'custom'; - this.declarative = true; - this.text = name; break; case 'Workflow': this.cpuType = 'custom'; - this.declarative = true; this.text = name; break; case 'Flow': this.cpuType = 'custom'; - this.declarative = true; this.text = name; break; case 'VF': @@ -1331,7 +1318,6 @@ export class EventSericeSubDetailLine extends LogEvent { } export class FlowStartInterviewsBeginLine extends LogEvent { - declarative = true; text = 'FLOW_START_INTERVIEWS : '; constructor(parser: ApexLogParser, parts: string[]) { @@ -1408,8 +1394,6 @@ export class FlowCreateInterviewErrorLine extends LogEvent { } export class FlowElementBeginLine extends LogEvent { - declarative = true; - constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['FLOW_ELEMENT_END'], 'Flow', 'custom'); this.text = parts[3] + ' ' + parts[4]; @@ -1417,8 +1401,6 @@ export class FlowElementBeginLine extends LogEvent { } export class FlowElementDeferredLine extends LogEvent { - declarative = true; - constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] + ' ' + parts[3]; @@ -1426,7 +1408,6 @@ export class FlowElementDeferredLine extends LogEvent { } export class FlowElementAssignmentLine extends LogEvent { - declarative = true; acceptsText = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1549,8 +1530,6 @@ export class FlowRuleDetailLine extends LogEvent { } export class FlowBulkElementBeginLine extends LogEvent { - declarative = true; - constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['FLOW_BULK_ELEMENT_END'], 'Flow', 'custom'); this.text = `${parts[2]} - ${parts[3]}`; @@ -1558,8 +1537,6 @@ export class FlowBulkElementBeginLine extends LogEvent { } export class FlowBulkElementDetailLine extends LogEvent { - declarative = true; - constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] + ' : ' + parts[3] + ' : ' + parts[4]; @@ -1574,8 +1551,6 @@ export class FlowBulkElementNotSupportedLine extends LogEvent { } export class FlowBulkElementLimitUsageLine extends LogEvent { - declarative = true; - constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.text = parts[2] || ''; @@ -1703,8 +1678,6 @@ export class WFFieldUpdateLine extends LogEvent { } export class WFRuleEvalBeginLine extends LogEvent { - declarative = true; - constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['WF_RULE_EVAL_END'], 'Workflow', 'custom'); this.text = parts[2] || ''; @@ -1728,8 +1701,6 @@ export class WFRuleFilterLine extends LogEvent { } export class WFCriteriaBeginLine extends LogEvent { - declarative = true; - constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['WF_CRITERIA_END', 'WF_RULE_NOT_EVALUATED'], 'Workflow', 'custom'); this.text = 'WF_CRITERIA : ' + parts[5] + ' : ' + parts[3]; @@ -1966,7 +1937,6 @@ export class ExceptionThrownLine extends LogEvent { export class FatalErrorLine extends LogEvent { acceptsText = true; - hideable = false; discontinuity = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -2011,8 +1981,6 @@ export class XDSResponseErrorLine extends LogEvent { // e.g. "09:45:31.888 (38889007737)|DUPLICATE_DETECTION_BEGIN" export class DuplicateDetectionBegin extends LogEvent { - declarative = true; - constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['DUPLICATE_DETECTION_END'], 'Workflow', 'custom'); } From 266c2c3461394c7317be5d7ed0c0c3173999f9dd Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:18:01 +0100 Subject: [PATCH 17/20] refactor: narrow child type for SOQLExecuteBeginLine --- log-viewer/modules/__tests__/ApexLogParser.test.ts | 4 ++-- log-viewer/modules/components/SOQLLinterIssues.ts | 4 ++-- log-viewer/modules/components/database-view/SOQLView.ts | 8 ++------ log-viewer/modules/parsers/LogEvents.ts | 1 + 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/log-viewer/modules/__tests__/ApexLogParser.test.ts b/log-viewer/modules/__tests__/ApexLogParser.test.ts index e3d01efe..4690e919 100644 --- a/log-viewer/modules/__tests__/ApexLogParser.test.ts +++ b/log-viewer/modules/__tests__/ApexLogParser.test.ts @@ -424,7 +424,7 @@ describe('parseLog tests', () => { '09:19:13.82 (51595120059)|EXECUTION_FINISHED\n'; const apexLog = parse(log); - const execEvent = apexLog.children[0] as MethodEntryLine; + const execEvent = apexLog.children[0]; expect(execEvent).toBeInstanceOf(ExecutionStartedLine); expect(execEvent.children.length).toEqual(1); @@ -437,7 +437,7 @@ describe('parseLog tests', () => { soqlCount: { self: 1, total: 1 }, }); - const soqlExplain = soqlLine.children[0] as SOQLExecuteExplainLine; + const soqlExplain = soqlLine.children[0]; expect(soqlExplain).toMatchObject({ parent: soqlLine, type: 'SOQL_EXECUTE_EXPLAIN', diff --git a/log-viewer/modules/components/SOQLLinterIssues.ts b/log-viewer/modules/components/SOQLLinterIssues.ts index f280b8bd..c8708687 100644 --- a/log-viewer/modules/components/SOQLLinterIssues.ts +++ b/log-viewer/modules/components/SOQLLinterIssues.ts @@ -5,7 +5,7 @@ import { LitElement, css, html, type PropertyValues, type TemplateResult } from import { customElement, property, state } from 'lit/decorators.js'; import { DatabaseAccess } from '../Database.js'; -import type { SOQLExecuteBeginLine, SOQLExecuteExplainLine } from '../parsers/LogEvents.js'; +import type { SOQLExecuteBeginLine } from '../parsers/LogEvents.js'; import { SEVERITY_TYPES, SOQLLinter, @@ -93,7 +93,7 @@ export class SOQLLinterIssues extends LitElement { getIssuesFromSOQLLine(soqlLine: SOQLExecuteBeginLine | null): SOQLLinterRule[] { const soqlIssues = []; if (soqlLine) { - const explain = soqlLine.children[0] as SOQLExecuteExplainLine; + const explain = soqlLine.children[0]; if (explain?.relativeCost && explain.relativeCost > 1) { soqlIssues.push(new ExplainLineSelectivityRule(explain.relativeCost)); } diff --git a/log-viewer/modules/components/database-view/SOQLView.ts b/log-viewer/modules/components/database-view/SOQLView.ts index 8182d696..6e32801c 100644 --- a/log-viewer/modules/components/database-view/SOQLView.ts +++ b/log-viewer/modules/components/database-view/SOQLView.ts @@ -32,11 +32,7 @@ import dataGridStyles from '../../datagrid/style/DataGrid.scss'; // others import { DatabaseAccess } from '../../Database.js'; import { isVisible } from '../../Util.js'; -import type { - ApexLog, - SOQLExecuteBeginLine, - SOQLExecuteExplainLine, -} from '../../parsers/LogEvents.js'; +import type { ApexLog, SOQLExecuteBeginLine } from '../../parsers/LogEvents.js'; import { vscodeMessenger } from '../../services/VSCodeExtensionMessenger.js'; import codiconStyles from '../../styles/codicon.css'; import { globalStyles } from '../../styles/global.styles.js'; @@ -298,7 +294,7 @@ export class SOQLView extends LitElement { const soqlData: GridSOQLData[] = []; if (soqlLines) { for (const soql of soqlLines) { - const explainLine = soql.children[0] as SOQLExecuteExplainLine; + const explainLine = soql.children[0]; soqlData.push({ isSelective: explainLine?.relativeCost ? explainLine.relativeCost <= 1 : null, relativeCost: explainLine?.relativeCost, diff --git a/log-viewer/modules/parsers/LogEvents.ts b/log-viewer/modules/parsers/LogEvents.ts index 7d9eee9b..3cde4664 100644 --- a/log-viewer/modules/parsers/LogEvents.ts +++ b/log-viewer/modules/parsers/LogEvents.ts @@ -830,6 +830,7 @@ export class IdeasQueryExecuteLine extends LogEvent { export class SOQLExecuteBeginLine extends LogEvent { aggregations = 0; + children: SOQLExecuteExplainLine[] = []; soqlCount = { self: 1, total: 1, From 597d6d5488161d5f6af2716290574406672359e2 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:26:06 +0100 Subject: [PATCH 18/20] refactor: fix type --- log-viewer/modules/parsers/LogEvents.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/log-viewer/modules/parsers/LogEvents.ts b/log-viewer/modules/parsers/LogEvents.ts index 3cde4664..7b1962f2 100644 --- a/log-viewer/modules/parsers/LogEvents.ts +++ b/log-viewer/modules/parsers/LogEvents.ts @@ -220,7 +220,7 @@ export abstract class LogEvent { constructor( parser: ApexLogParser, parts: string[] | null, - exitTypes?: string[], + exitTypes?: LogEventType[], timelineKey?: LogSubCategory, cpuType?: CPUType, ) { @@ -233,7 +233,7 @@ export abstract class LogEvent { } if (exitTypes) { - this.exitTypes = exitTypes as LogEventType[]; + this.exitTypes = exitTypes; this.isDetail = false; } From 038b85aeed573358fca3f7ce1bf42c6572f3d171 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:19:22 +0100 Subject: [PATCH 19/20] refactor: rename isDetail to isParent --- log-viewer/modules/Database.ts | 6 +- .../modules/__tests__/ApexLogParser.test.ts | 2 +- log-viewer/modules/parsers/ApexLogParser.ts | 8 +- log-viewer/modules/parsers/LogEvents.ts | 144 +++++++++--------- log-viewer/modules/timeline/Timeline.ts | 2 +- 5 files changed, 79 insertions(+), 83 deletions(-) diff --git a/log-viewer/modules/Database.ts b/log-viewer/modules/Database.ts index b9ec9351..1c8dc029 100644 --- a/log-viewer/modules/Database.ts +++ b/log-viewer/modules/Database.ts @@ -34,7 +34,7 @@ export class DatabaseAccess { const len = children.length; for (let i = 0; i < len; ++i) { const child = children[i]; - if (child && !child.isDetail) { + if (child?.isParent) { stack.push(child); if (child.timestamp === timestamp) { return stack; @@ -61,7 +61,7 @@ export class DatabaseAccess { results.push(child); } - if (child && !child.isDetail) { + if (child?.isParent) { Array.prototype.push.apply(results, this.getSOQLLines(child)); } } @@ -80,7 +80,7 @@ export class DatabaseAccess { results.push(child); } - if (child && !child.isDetail) { + if (child?.isParent) { // results = results.concat(this.getDMLLines(child)); Array.prototype.push.apply(results, this.getDMLLines(child)); } diff --git a/log-viewer/modules/__tests__/ApexLogParser.test.ts b/log-viewer/modules/__tests__/ApexLogParser.test.ts index 4690e919..bf1c54fc 100644 --- a/log-viewer/modules/__tests__/ApexLogParser.test.ts +++ b/log-viewer/modules/__tests__/ApexLogParser.test.ts @@ -1264,7 +1264,7 @@ describe('Line Type Tests', () => { 'Rows:5', ]) as LogEvent; - if (!line.isDetail) { + if (line.isParent) { line.exitTypes.forEach((exitType) => { const exitCls = lineTypeMap.get(exitType); expect(exitCls).not.toBe(null); diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index af74b2bc..d341ee7d 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -190,7 +190,7 @@ export class ApexLogParser { const lineIter = new LineIterator(lineGenerator); while ((line = lineIter.fetch())) { - if (!line.isDetail) { + if (line.isParent) { this.parseTree(line, lineIter, stack); } line.parent = rootMethod; @@ -252,7 +252,7 @@ export class ApexLogParser { nextLine.parent = currentLine; currentLine.children.push(nextLine); - if (!nextLine.isDetail) { + if (nextLine.isParent) { this.parseTree(nextLine, lineIter, stack); } } @@ -400,7 +400,7 @@ export class ApexLogParser { continue; } const isPkgType = child.type === 'ENTERING_MANAGED_PKG'; - if (lastPkg && !child.isDetail) { + if (lastPkg && child.isParent) { if (isPkgType && child.namespace === lastPkg.namespace) { // combine adjacent (like) packages lastPkg.exitStamp = child.exitStamp || child.timestamp; @@ -412,7 +412,7 @@ export class ApexLogParser { } } - if (!child.isDetail) { + if (child.isParent) { this.insertPackageWrappers(child); } diff --git a/log-viewer/modules/parsers/LogEvents.ts b/log-viewer/modules/parsers/LogEvents.ts index 7b1962f2..b2f94bae 100644 --- a/log-viewer/modules/parsers/LogEvents.ts +++ b/log-viewer/modules/parsers/LogEvents.ts @@ -56,10 +56,10 @@ export abstract class LogEvent { isExit = false; /** - * Indicates whether the current log event could have an entry and exit event and hence children. - * It is possible this is true but there is not defined exit events of children. + * Indicates whether the current log event could have children. + * It is possible this is true but there are no defined exit events or children. */ - isDetail = true; + isParent = false; /** * Whether the log event was truncated when the log ended, e,g no matching end event @@ -217,13 +217,7 @@ export abstract class LogEvent { */ exitTypes: LogEventType[] = []; - constructor( - parser: ApexLogParser, - parts: string[] | null, - exitTypes?: LogEventType[], - timelineKey?: LogSubCategory, - cpuType?: CPUType, - ) { + constructor(parser: ApexLogParser, parts: string[] | null) { this.logParser = parser; // Now set actual values from parts if (parts) { @@ -231,19 +225,6 @@ export abstract class LogEvent { this.text = this.type = type as LogEventType; this.timestamp = timeData ? this.parseTimestamp(timeData) : 0; } - - if (exitTypes) { - this.exitTypes = exitTypes; - this.isDetail = false; - } - - if (timelineKey) { - this.subCategory = timelineKey; - } - - if (cpuType) { - this.cpuType = cpuType; - } } /** Called if a corresponding end event is found during tree parsing*/ @@ -283,6 +264,22 @@ export abstract class LogEvent { } } +export class DurationLogEvent extends LogEvent { + isParent = true; + constructor( + parser: ApexLogParser, + parts: string[] | null, + exitTypes: LogEventType[], + subCategory: LogSubCategory, + cpuType: CPUType, + ) { + super(parser, parts); + this.exitTypes = exitTypes; + this.subCategory = subCategory; + this.cpuType = cpuType; + } +} + export class BasicLogLine extends LogEvent {} export class BasicExitLine extends LogEvent { isExit = true; @@ -462,7 +459,7 @@ export class NamedCredentialResponseDetailLine extends LogEvent { } } -export class ConstructorEntryLine extends LogEvent { +export class ConstructorEntryLine extends DurationLogEvent { hasValidSymbols = true; suffix = ' (constructor)'; @@ -512,7 +509,7 @@ export class EmailQueueLine extends LogEvent { } } -export class MethodEntryLine extends LogEvent { +export class MethodEntryLine extends DurationLogEvent { hasValidSymbols = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -587,7 +584,7 @@ export class MethodExitLine extends LogEvent { } } -export class SystemConstructorEntryLine extends LogEvent { +export class SystemConstructorEntryLine extends DurationLogEvent { suffix = '(system constructor)'; constructor(parser: ApexLogParser, parts: string[]) { @@ -605,7 +602,7 @@ export class SystemConstructorExitLine extends LogEvent { this.lineNumber = this.parseLineNumber(parts[2]); } } -export class SystemMethodEntryLine extends LogEvent { +export class SystemMethodEntryLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SYSTEM_METHOD_EXIT'], 'System Method', 'method'); this.lineNumber = this.parseLineNumber(parts[2]); @@ -622,7 +619,7 @@ export class SystemMethodExitLine extends LogEvent { } } -export class CodeUnitStartedLine extends LogEvent { +export class CodeUnitStartedLine extends DurationLogEvent { suffix = ' (entrypoint)'; codeUnitType = ''; @@ -702,7 +699,8 @@ export class CodeUnitFinishedLine extends LogEvent { } } -export class VFApexCallStartLine extends LogEvent { +export class VFApexCallStartLine extends DurationLogEvent { + hasValidSymbols = true; suffix = ' (VF APEX)'; invalidClasses = [ 'pagemessagescomponentcontroller', @@ -727,8 +725,8 @@ export class VFApexCallStartLine extends LogEvent { // e.g |VF_APEX_CALL_START|[EXTERNAL]|/apexpage/pagemessagescomponentcontroller.apex // and they really mess with the logs so skip handling them. this.exitTypes = []; + this.hasValidSymbols = false; } else if (methodtext) { - this.hasValidSymbols = true; // method call const methodIndex = methodtext.indexOf('('); const constructorIndex = methodtext.indexOf(''); @@ -742,8 +740,6 @@ export class VFApexCallStartLine extends LogEvent { // Property methodtext = '.' + methodtext; } - } else { - this.hasValidSymbols = true; } this.text = classText + methodtext; } @@ -758,13 +754,13 @@ export class VFApexCallEndLine extends LogEvent { } } -export class VFDeserializeViewstateBeginLine extends LogEvent { +export class VFDeserializeViewstateBeginLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['VF_DESERIALIZE_VIEWSTATE_END'], 'System Method', 'method'); } } -export class VFFormulaStartLine extends LogEvent { +export class VFFormulaStartLine extends DurationLogEvent { suffix = ' (VF FORMULA)'; constructor(parser: ApexLogParser, parts: string[]) { @@ -782,7 +778,7 @@ export class VFFormulaEndLine extends LogEvent { } } -export class VFSeralizeViewStateStartLine extends LogEvent { +export class VFSeralizeViewStateStartLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['VF_SERIALIZE_VIEWSTATE_END'], 'System Method', 'method'); } @@ -796,7 +792,7 @@ export class VFPageMessageLine extends LogEvent { } } -export class DMLBeginLine extends LogEvent { +export class DMLBeginLine extends DurationLogEvent { dmlCount = { self: 1, total: 1, @@ -828,7 +824,7 @@ export class IdeasQueryExecuteLine extends LogEvent { } } -export class SOQLExecuteBeginLine extends LogEvent { +export class SOQLExecuteBeginLine extends DurationLogEvent { aggregations = 0; children: SOQLExecuteExplainLine[] = []; soqlCount = { @@ -911,7 +907,7 @@ export class SOQLExecuteExplainLine extends LogEvent { } } -export class SOSLExecuteBeginLine extends LogEvent { +export class SOSLExecuteBeginLine extends DurationLogEvent { soslCount = { self: 1, total: 1, @@ -993,7 +989,7 @@ export class UserDebugLine extends LogEvent { } } -export class CumulativeLimitUsageLine extends LogEvent { +export class CumulativeLimitUsageLine extends DurationLogEvent { namespace = 'default'; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['CUMULATIVE_LIMIT_USAGE_END'], 'System Method', 'system'); @@ -1009,7 +1005,7 @@ export class CumulativeProfilingLine extends LogEvent { } } -export class CumulativeProfilingBeginLine extends LogEvent { +export class CumulativeProfilingBeginLine extends DurationLogEvent { namespace = 'default'; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['CUMULATIVE_PROFILING_END'], 'System Method', 'custom'); @@ -1100,7 +1096,7 @@ export class LimitUsageForNSLine extends LogEvent { } } -export class NBANodeBegin extends LogEvent { +export class NBANodeBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['NBA_NODE_END'], 'System Method', 'method'); this.text = parts.slice(2).join(' | '); @@ -1132,7 +1128,7 @@ export class NBAOfferInvalid extends LogEvent { this.text = parts.slice(2).join(' | '); } } -export class NBAStrategyBegin extends LogEvent { +export class NBAStrategyBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['NBA_STRATEGY_END'], 'System Method', 'method'); this.text = parts.slice(2).join(' | '); @@ -1168,7 +1164,7 @@ export class PopTraceFlagsLine extends LogEvent { } } -export class QueryMoreBeginLine extends LogEvent { +export class QueryMoreBeginLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['QUERY_MORE_END'], 'SOQL', 'custom'); this.lineNumber = this.parseLineNumber(parts[2]); @@ -1249,14 +1245,14 @@ export class SystemModeExitLine extends LogEvent { } } -export class ExecutionStartedLine extends LogEvent { +export class ExecutionStartedLine extends DurationLogEvent { namespace = 'default'; constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['EXECUTION_FINISHED'], 'Method', 'method'); } } -export class EnteringManagedPackageLine extends LogEvent { +export class EnteringManagedPackageLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, [], 'Method', 'pkg'); const rawNs = parts[2] || '', @@ -1272,7 +1268,7 @@ export class EnteringManagedPackageLine extends LogEvent { } } -export class EventSericePubBeginLine extends LogEvent { +export class EventSericePubBeginLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['EVENT_SERVICE_PUB_END'], 'Flow', 'custom'); this.text = parts[2] || ''; @@ -1295,7 +1291,7 @@ export class EventSericePubDetailLine extends LogEvent { } } -export class EventSericeSubBeginLine extends LogEvent { +export class EventSericeSubBeginLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['EVENT_SERVICE_SUB_END'], 'Flow', 'custom'); this.text = `${parts[2]} ${parts[3]}`; @@ -1318,7 +1314,7 @@ export class EventSericeSubDetailLine extends LogEvent { } } -export class FlowStartInterviewsBeginLine extends LogEvent { +export class FlowStartInterviewsBeginLine extends DurationLogEvent { text = 'FLOW_START_INTERVIEWS : '; constructor(parser: ApexLogParser, parts: string[]) { @@ -1366,7 +1362,7 @@ export class FlowStartInterviewsErrorLine extends LogEvent { } } -export class FlowStartInterviewBeginLine extends LogEvent { +export class FlowStartInterviewBeginLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['FLOW_START_INTERVIEW_END'], 'Flow', 'custom'); this.text = parts[3] || ''; @@ -1394,7 +1390,7 @@ export class FlowCreateInterviewErrorLine extends LogEvent { } } -export class FlowElementBeginLine extends LogEvent { +export class FlowElementBeginLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['FLOW_ELEMENT_END'], 'Flow', 'custom'); this.text = parts[3] + ' ' + parts[4]; @@ -1530,7 +1526,7 @@ export class FlowRuleDetailLine extends LogEvent { } } -export class FlowBulkElementBeginLine extends LogEvent { +export class FlowBulkElementBeginLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['FLOW_BULK_ELEMENT_END'], 'Flow', 'custom'); this.text = `${parts[2]} - ${parts[3]}`; @@ -1669,7 +1665,7 @@ export class WFFlowActionErrorDetailLine extends LogEvent { } } -export class WFFieldUpdateLine extends LogEvent { +export class WFFieldUpdateLine extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1678,7 +1674,7 @@ export class WFFieldUpdateLine extends LogEvent { } } -export class WFRuleEvalBeginLine extends LogEvent { +export class WFRuleEvalBeginLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['WF_RULE_EVAL_END'], 'Workflow', 'custom'); this.text = parts[2] || ''; @@ -1701,14 +1697,14 @@ export class WFRuleFilterLine extends LogEvent { } } -export class WFCriteriaBeginLine extends LogEvent { +export class WFCriteriaBeginLine extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['WF_CRITERIA_END', 'WF_RULE_NOT_EVALUATED'], 'Workflow', 'custom'); this.text = 'WF_CRITERIA : ' + parts[5] + ' : ' + parts[3]; } } -export class WFFormulaLine extends LogEvent { +export class WFFormulaLine extends DurationLogEvent { acceptsText = true; isExit = true; nextLineIsExit = true; @@ -1740,7 +1736,7 @@ export class WFActionTaskLine extends LogEvent { } } -export class WFApprovalLine extends LogEvent { +export class WFApprovalLine extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1756,7 +1752,7 @@ export class WFApprovalRemoveLine extends LogEvent { } } -export class WFApprovalSubmitLine extends LogEvent { +export class WFApprovalSubmitLine extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1779,7 +1775,7 @@ export class WFAssignLine extends LogEvent { } } -export class WFEmailAlertLine extends LogEvent { +export class WFEmailAlertLine extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1788,7 +1784,7 @@ export class WFEmailAlertLine extends LogEvent { } } -export class WFEmailSentLine extends LogEvent { +export class WFEmailSentLine extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1811,7 +1807,7 @@ export class WFEscalationActionLine extends LogEvent { } } -export class WFEvalEntryCriteriaLine extends LogEvent { +export class WFEvalEntryCriteriaLine extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1828,7 +1824,7 @@ export class WFFlowActionDetailLine extends LogEvent { } } -export class WFNextApproverLine extends LogEvent { +export class WFNextApproverLine extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1844,7 +1840,7 @@ export class WFOutboundMsgLine extends LogEvent { } } -export class WFProcessFoundLine extends LogEvent { +export class WFProcessFoundLine extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1853,7 +1849,7 @@ export class WFProcessFoundLine extends LogEvent { } } -export class WFProcessNode extends LogEvent { +export class WFProcessNode extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1883,7 +1879,7 @@ export class WFRuleEntryOrderLine extends LogEvent { } } -export class WFRuleInvocationLine extends LogEvent { +export class WFRuleInvocationLine extends DurationLogEvent { isExit = true; nextLineIsExit = true; constructor(parser: ApexLogParser, parts: string[]) { @@ -1981,7 +1977,7 @@ export class XDSResponseErrorLine extends LogEvent { } // e.g. "09:45:31.888 (38889007737)|DUPLICATE_DETECTION_BEGIN" -export class DuplicateDetectionBegin extends LogEvent { +export class DuplicateDetectionBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['DUPLICATE_DETECTION_END'], 'Workflow', 'custom'); } @@ -2026,54 +2022,54 @@ export class DuplicateDetectionSummary extends LogEvent { } } -export class SessionCachePutBegin extends LogEvent { +export class SessionCachePutBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SESSION_CACHE_PUT_END'], 'Method', 'method'); } } -export class SessionCacheGetBegin extends LogEvent { +export class SessionCacheGetBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SESSION_CACHE_GET_END'], 'Method', 'method'); } } -export class SessionCacheRemoveBegin extends LogEvent { +export class SessionCacheRemoveBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['SESSION_CACHE_REMOVE_END'], 'Method', 'method'); } } -export class OrgCachePutBegin extends LogEvent { +export class OrgCachePutBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['ORG_CACHE_PUT_END'], 'Method', 'method'); } } -export class OrgCacheGetBegin extends LogEvent { +export class OrgCacheGetBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['ORG_CACHE_GET_END'], 'Method', 'method'); } } -export class OrgCacheRemoveBegin extends LogEvent { +export class OrgCacheRemoveBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['ORG_CACHE_REMOVE_END'], 'Method', 'method'); } } -export class VFSerializeContinuationStateBegin extends LogEvent { +export class VFSerializeContinuationStateBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['VF_SERIALIZE_CONTINUATION_STATE_END'], 'Method', 'method'); } } -export class VFDeserializeContinuationStateBegin extends LogEvent { +export class VFDeserializeContinuationStateBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['VF_SERIALIZE_CONTINUATION_STATE_END'], 'Method', 'method'); } } -export class MatchEngineBegin extends LogEvent { +export class MatchEngineBegin extends DurationLogEvent { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts, ['MATCH_ENGINE_END'], 'Method', 'method'); } diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index 2d99b517..2cbb363f 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -648,7 +648,7 @@ function findTimelineTooltip( ): HTMLDivElement | null { const target = findByPosition(timelineRoot.children, 0, x, depth, shouldIgnoreWidth); - if (target && !target.isDetail) { + if (target?.isParent) { canvas.classList.remove('timeline-hover', 'timeline-dragging'); canvas.classList.add('timeline-event--hover'); From 288457afb36889dcaef629d602b394f16f5caeb0 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:22:54 +0100 Subject: [PATCH 20/20] refactor: rename isDetail to isParent --- log-viewer/modules/timeline/Timeline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index 2cbb363f..9ff6f268 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -263,7 +263,7 @@ function nodesToRectangles(rootNodes: LogEvent[]) { } for (const child of node.children) { - if (!child.isDetail) { + if (child.isParent) { nextLevel.push(child); } }