Skip to content

Execution logger #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Thumbs.db
# npm package lock files (if you don't want them tracked by Git)
package-lock.json
yarn.lock
pnpm-lock.yaml

# Test coverage directory
coverage/
Expand Down
1 change: 0 additions & 1 deletion eko-browser-extension-template
Submodule eko-browser-extension-template deleted from 53e406
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"chromium-bidi": "^0.12.0",
"dotenv": "^16.0.0",
"html2canvas": "^1.4.1",
"loglevel": "^1.9.2",
"openai": "^4.77.0",
"playwright": "^1.49.1",
"uuid": "^11.0.3",
Expand Down
5 changes: 3 additions & 2 deletions src/common/chrome/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
* ```
* In this example, `tabs_get` is a mock implementation that logs the `tabId` before calling the original `chrome.tabs.get` method, and the same as `chrome.windows.create` method.
*/
import { logger } from '../log';
export function createChromeApiProxy(mockClass: any): any {
console.log("debug mockClass:");
console.log(mockClass);
logger.debug("debug mockClass:");
logger.debug(mockClass);

return new Proxy(chrome, {
get(target: any, prop: string | symbol) {
Expand Down
43 changes: 43 additions & 0 deletions src/common/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import log from 'loglevel';

export class EkoLogger {
private logger: log.Logger;
private logStorage: string[] = [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

潜在的性能瓶颈?当大量日志涌入时,这里的性能会不会过慢而拖慢整个工作流的后腿?问一下 AI。

Copy link
Contributor Author

@1579923417 1579923417 Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在高并发或大量日志的情况下,这段代码确实会存在性能瓶颈。

1. 性能瓶颈分析

(1)日志存储(logStorage

logStorage 是一个数组,用于存储所有日志内容。随着日志数量的增加,logStorage 的大小会不断增长,这可能导致以下问题:

  • 内存占用:大量日志会导致内存占用快速上升,尤其是在长时间运行的应用中。
  • 数组操作性能:每次调用 logStorage.push() 时,数组会动态扩展,这在 JavaScript 中可能涉及内存重新分配。当数组非常大时,频繁的内存分配和扩展会显著降低性能。
  • 垃圾回收压力:大量的日志数据可能导致频繁的垃圾回收,进一步影响应用性能。

(2)日志内容截断和序列化

toReadableString() 方法中,对日志内容进行了截断和序列化:

  • JSON 序列化JSON.stringify() 是一个相对较慢的操作,尤其是对于复杂对象。在高并发场景下,大量调用 JSON.stringify() 可能会拖慢性能。
  • 并且这里toReadableString的方法不能很好地处理嵌套对象,无法达到预期的效果(没有进行截断),目前修改了几版代码还是无法解决这个问题。

2. 优化建议

限制日志存储大小

  • 固定大小的存储:可以将 logStorage 设计为一个固定大小的存储结构,例如使用环形缓冲区(FIFO 队列)。当存储满时,自动丢弃最早的日志。
  • 日志采样:在高并发场景下,可以对日志进行采样,只记录部分日志,而不是所有日志。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我让你问 AI 不是因为我懒得问,是因为我想让你参考 AI 的回复来决定是否修改这里的实现……


constructor() {
this.logger = log.getLogger('EkoLogger');
this.logger.setLevel(log.levels.TRACE);

const originalFactory = this.logger.methodFactory;

this.logger.methodFactory = (methodName, logLevel, loggerName) => {
const rawMethod = originalFactory(methodName, logLevel, loggerName);
return (...args: any[]) => {
const truncatedArgs = args.map(arg => this.toReadableString(arg));
this.logStorage.push(`[${methodName.toUpperCase()}] ${truncatedArgs.join(' ')}`);
rawMethod(...truncatedArgs);
};
};

}
// truncate content if it exceeds 2048 characters
private toReadableString(content: any): string {
const contentString = JSON.stringify(content);
const maxLength = 2048;
if (contentString.length > maxLength) {
return contentString.substring(0, maxLength - 3) + '...';
} else {
return contentString;
}
}

public getLoggerInstance(): log.Logger {
return this.logger;
}

public getAllLogs(): string[] {
return this.logStorage;
}
}

export const logger = new EkoLogger().getLoggerInstance();
3 changes: 2 additions & 1 deletion src/common/tools/cancel_workflow.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CancelWorkflowInput } from '../../types/tools.types';
import { Tool, InputSchema, ExecutionContext } from '../../types/action.types';
import { logger } from '../../common/log';

export class CancelWorkflow implements Tool<CancelWorkflowInput, void> {
name: string;
Expand All @@ -26,7 +27,7 @@ export class CancelWorkflow implements Tool<CancelWorkflowInput, void> {
throw new Error('Invalid parameters. Expected an object with a "reason" property.');
}
const reason = params.reason;
console.log("The workflow has been cancelled because: " + reason);
logger.debug("The workflow has been cancelled because: " + reason);
await context.workflow?.cancel();
return;
}
Expand Down
37 changes: 19 additions & 18 deletions src/common/tools/human.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
HumanOperateResult,
} from '../../types/tools.types';
import { Tool, InputSchema, ExecutionContext } from '../../types/action.types';
import { logger } from '../../common/log';

export class HumanInputText implements Tool<HumanInputTextInput, HumanInputTextResult> {
name: string;
Expand All @@ -35,20 +36,20 @@ export class HumanInputText implements Tool<HumanInputTextInput, HumanInputTextR
throw new Error('Invalid parameters. Expected an object with a "question" property.');
}
const question = params.question;
console.log("question: " + question);
logger.debug("question: " + question);
let onHumanInputText = context.callback?.hooks.onHumanInputText;
if (onHumanInputText) {
let answer;
try {
answer = await onHumanInputText(question);
} catch (e) {
console.error(e);
logger.error(e);
return {status: "Error: Cannot get user's answer.", answer: ""};
}
console.log("answer: " + answer);
logger.debug("answer: " + answer);
return {status: "OK", answer: answer};
} else {
console.error("`onHumanInputText` not implemented");
logger.error("`onHumanInputText` not implemented");
return {status: "Error: Cannot get user's answer.", answer: ""};
}
}
Expand Down Expand Up @@ -84,21 +85,21 @@ export class HumanInputSingleChoice implements Tool<HumanInputSingleChoiceInput,
}
const question = params.question;
const choices = params.choices;
console.log("question: " + question);
console.log("choices: " + choices);
logger.debug("question: " + question);
logger.debug("choices: " + choices);
let onHumanInputSingleChoice = context.callback?.hooks.onHumanInputSingleChoice;
if (onHumanInputSingleChoice) {
let answer;
try {
answer = await onHumanInputSingleChoice(question, choices);
} catch (e) {
console.error(e);
logger.error(e);
return {status: "Error: Cannot get user's answer.", answer: ""};
}
console.log("answer: " + answer);
logger.debug("answer: " + answer);
return {status: "OK", answer: answer};
} else {
console.error("`onHumanInputSingleChoice` not implemented");
logger.error("`onHumanInputSingleChoice` not implemented");
return {status: "Error: Cannot get user's answer.", answer: ""};
}
}
Expand Down Expand Up @@ -134,21 +135,21 @@ export class HumanInputMultipleChoice implements Tool<HumanInputMultipleChoiceIn
}
const question = params.question;
const choices = params.choices;
console.log("question: " + question);
console.log("choices: " + choices);
logger.debug("question: " + question);
logger.debug("choices: " + choices);
let onHumanInputMultipleChoice = context.callback?.hooks.onHumanInputMultipleChoice;
if (onHumanInputMultipleChoice) {
let answer;
try {
answer = await onHumanInputMultipleChoice(question, choices)
} catch (e) {
console.error(e);
logger.error(e);
return {status: "`onHumanInputMultipleChoice` not implemented", answer: []};
}
console.log("answer: " + answer);
logger.debug("answer: " + answer);
return {status: "OK", answer: answer};
} else {
console.error("Cannot get user's answer.");
logger.error("Cannot get user's answer.");
return {status: "Error: Cannot get user's answer.", answer: []};
}
}
Expand Down Expand Up @@ -179,20 +180,20 @@ export class HumanOperate implements Tool<HumanOperateInput, HumanOperateResult>
throw new Error('Invalid parameters. Expected an object with a "reason" property.');
}
const reason = params.reason;
console.log("reason: " + reason);
logger.debug("reason: " + reason);
let onHumanOperate = context.callback?.hooks.onHumanOperate;
if (onHumanOperate) {
let userOperation;
try {
userOperation = await onHumanOperate(reason);
} catch (e) {
console.error(e);
logger.error(e);
return {status: "`onHumanOperate` not implemented", userOperation: ""};
}
console.log("userOperation: " + userOperation);
logger.debug("userOperation: " + userOperation);
return {status: "OK", userOperation: userOperation};
} else {
console.error("Cannot get user's operation.");
logger.error("Cannot get user's operation.");
return {status: "Error: Cannot get user's operation.", userOperation: ""};
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/common/tools/summary_workflow.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SummaryWorkflowInput } from '../../types/tools.types';
import { Tool, InputSchema, ExecutionContext } from '../../types/action.types';
import { logger } from '../log';

export class SummaryWorkflow implements Tool<SummaryWorkflowInput, any> {
name: string;
Expand Down Expand Up @@ -29,8 +30,8 @@ export class SummaryWorkflow implements Tool<SummaryWorkflowInput, any> {
if (typeof params !== 'object' || params === null || !params.summary) {
throw new Error('Invalid parameters. Expected an object with a "summary" property.');
}
console.log("isSuccessful: " + params.isSuccessful);
console.log("summary: " + params.summary);
logger.debug("isSuccessful: " + params.isSuccessful);
logger.debug("summary: " + params.summary);
context.variables.set("workflow_is_successful", params.isSuccessful);
context.variables.set("workflow_summary", params.summary);
await context.callback?.hooks.onSummaryWorkflow?.(params.summary);
Expand Down
9 changes: 5 additions & 4 deletions src/core/eko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
WorkflowResult
} from '../types';
import { ToolRegistry } from './tool-registry';
import { logger } from '../common/log';

/**
* Eko core
Expand All @@ -26,15 +27,15 @@ export class Eko {
private workflowGeneratorMap = new Map<Workflow, WorkflowGenerator>();

constructor(llmConfig: LLMConfig, ekoConfig?: EkoConfig) {
console.info("using Eko@" + process.env.COMMIT_HASH);
logger.info("using Eko@" + process.env.COMMIT_HASH);
this.llmProvider = LLMProviderFactory.buildLLMProvider(llmConfig);
this.ekoConfig = this.buildEkoConfig(ekoConfig);
this.registerTools();
}

private buildEkoConfig(ekoConfig: Partial<EkoConfig> | undefined): EkoConfig {
if (!ekoConfig) {
console.warn("`ekoConfig` is missing when construct `Eko` instance");
logger.warn("`ekoConfig` is missing when construct `Eko` instance");
}
return {
...DefaultEkoConfig,
Expand Down Expand Up @@ -66,7 +67,7 @@ export class Eko {
}
});
} else {
console.warn("`ekoConfig.callback` is missing when construct `Eko` instance.")
logger.warn("`ekoConfig.callback` is missing when construct `Eko` instance.")
}

tools.forEach(tool => this.toolRegistry.registerTool(tool));
Expand Down Expand Up @@ -112,7 +113,7 @@ export class Eko {
}

const result = await workflow.execute(this.ekoConfig.callback);
console.log(result);
logger.debug(result);
return result;
}

Expand Down
14 changes: 8 additions & 6 deletions src/extension/content/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { logger } from '../../common/log';

declare const eko: any;

if (!(window as any).eko) {
Expand Down Expand Up @@ -29,7 +31,7 @@ chrome.runtime.onMessage.addListener(function (request: any, sender: any, sendRe
try {
result = await eko.subListeners[request.event](request.params);
} catch (e) {
console.log(e);
logger.error(e);
}
}
sendResponse(result);
Expand Down Expand Up @@ -94,7 +96,7 @@ chrome.runtime.onMessage.addListener(function (request: any, sender: any, sendRe
}
}
} catch (e) {
console.log('onMessage error', e);
logger.error('onMessage error', e);
sendResponse(false);
}
})();
Expand Down Expand Up @@ -178,7 +180,7 @@ function type(request: any): boolean {
input.dispatchEvent(event);
});
}
console.log('type', input, request, result);
logger.debug('type', input, request, result);
return true;
}

Expand All @@ -196,7 +198,7 @@ function mouse_move(request: any): boolean {
clientY: y,
});
let result = document.body.dispatchEvent(event);
console.log('mouse_move', document.body, request, result);
logger.debug('mouse_move', document.body, request, result);
return true;
}

Expand Down Expand Up @@ -234,7 +236,7 @@ function simulateMouseEvent(request: any, eventTypes: Array<string>, button: 0 |
button, // 0 left; 2 right
});
let result = element.dispatchEvent(event);
console.log('simulateMouse', element, { ...request, eventTypes, button }, result);
logger.debug('simulateMouse', element, { ...request, eventTypes, button }, result);
}
return true;
}
Expand Down Expand Up @@ -272,7 +274,7 @@ function scroll_to(request: any): boolean {
behavior: 'smooth',
});
}
console.log('scroll_to', request);
logger.debug('scroll_to', request);
return true;
}

Expand Down
3 changes: 2 additions & 1 deletion src/extension/core.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as tools from './tools';
import { Tool } from '../types';
import { logger } from '../common/log';

export async function pub(chromeProxy: any, tabId: number, event: string, params: any): Promise<any> {
return await chromeProxy.tabs.sendMessage(tabId as number, {
Expand Down Expand Up @@ -28,7 +29,7 @@ export function loadTools(): Map<string, Tool<any, any>> {
let instance = new tool();
toolsMap.set(instance.name || key, instance);
} catch (e) {
console.error(`Failed to instantiate ${key}:`, e);
logger.error(`Failed to instantiate ${key}:`, e);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/extension/script/build_dom_tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ function build_dom_tree(doHighlightElements) {
nodeData.children.push(...iframeChildren);
}
} catch (e) {
console.warn('Unable to access iframe:', node);
logger.warn('Unable to access iframe:', node);
}
} else {
const children = Array.from(node.childNodes).map((child) =>
Expand Down
Loading