You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何全局修改module.exports,实现所有模块导出自动应用日志代理

全局日志代理自动应用方案咨询

我想做一个日志工具,在所有方法调用前记录信息(比如性能追踪)。目前已经用JS装饰器实现了类及类方法的日志功能:

logger.ts

// 给类所有方法添加日志的类装饰器
export async function logger(something): Promise<void> {
  // 类装饰器逻辑
}

// 方法装饰器
export async function logMethod(something): Promise<void> {
  console.log("方法调用前日志");
  something.apply(this, args);
}

类文件中的使用方式:
someclass.ts

@logger
export default class SomeClass extends BaseClass {
  async somemethod(message: string): Promise<boolean> {
    return true;
  }
}

但非类方法没法用装饰器,所以我写了一个logProxy代理对象实现相同功能:

const logProxy = (obj) => {
  return new Proxy(obj, {
    get(target, prop, receiver) {
      console.log(`访问属性/方法: ${String(prop)}`);
      // 日志/性能追踪逻辑
      return Reflect.get(target, prop, receiver);
    }
  });
};

非类文件中的使用方式:
somefile.ts

export async function dosomething(): Promise<void> {
  const endPoint = `${url}`;
  await get(endPoint);
}

module.exports = logProxy(module.exports);

现在要给大量文件手动加这段代码,太麻烦了。有没有全局方案能让所有模块导出自动应用这个代理?或者重写module.exports让它自动执行module.exports = logProxy(module.exports)?


方案1:Node.js 模块加载钩子(--experimental-loader)

Node.js 16+支持自定义模块加载器,可在模块加载完成后自动包装导出内容。

  1. 创建加载器文件log-loader.js:
import { readFile } from 'fs/promises';
import { logProxy } from './logger.js'; // 导入你的logProxy

export async function load(url, context, defaultLoad) {
  const result = await defaultLoad(url, context, defaultLoad);
  
  // 仅处理JS/TS模块,跳过node_modules
  if ((url.endsWith('.js') || url.endsWith('.ts')) && !url.includes('node_modules')) {
    const originalExports = await result.module;
    // 包装默认导出和所有函数类型的具名导出
    result.module = {
      ...originalExports,
      default: logProxy(originalExports.default || originalExports),
      ...Object.fromEntries(
        Object.entries(originalExports).map(([key, value]) => 
          [key, typeof value === 'function' ? logProxy(value) : value]
        )
      )
    };
  }
  
  return result;
}
  1. 启动项目时指定加载器:
node --experimental-loader ./log-loader.js your-entry-file.js

注意:TS项目需先编译为JS,或结合ts-node的加载器一起使用。

方案2:Webpack/Babel 插件(前端/TS构建项目)

如果用Webpack或Babel构建,可写插件自动在每个模块末尾添加代理包装代码。

Babel插件示例:

const t = require('@babel/types');

module.exports = function() {
  return {
    visitor: {
      Program(path) {
        // 在模块最后插入代理包装代码
        path.pushContainer('body', [
          t.expressionStatement(
            t.assignmentExpression(
              '=',
              t.memberExpression(t.identifier('module'), t.identifier('exports')),
              t.callExpression(
                t.identifier('logProxy'),
                [t.memberExpression(t.identifier('module'), t.identifier('exports'))]
              )
            )
          )
        ]);
      }
    }
  };
};

将该插件加入Babel配置,即可自动给所有模块添加代理。

方案3:重写Module._compile(Node.js)

在Node.js中通过猴子补丁重写Module._compile,在模块编译完成后自动包装导出:

const Module = require('module');
const originalCompile = Module._compile;
const { logProxy } = require('./logger');

Module._compile = function(content, filename) {
  // 执行原编译逻辑
  originalCompile.call(this, content, filename);
  
  // 跳过node_modules文件
  if (!filename.includes('node_modules')) {
    this.exports = logProxy(this.exports);
  }
};

把这段代码放在项目入口文件最顶部,即可全局生效。注意这种方式可能和其他模块加载逻辑冲突,需测试验证。

方案4:TypeScript自定义转换器(纯TS项目)

针对纯TS项目,可写TS转换器在编译阶段修改AST,自动给所有导出添加代理:

import * as ts from 'typescript';

export function logTransformer(program: ts.Program): ts.TransformerFactory<ts.SourceFile> {
  return (context) => {
    return (sourceFile) => {
      function visit(node: ts.Node): ts.Node {
        // 包装默认导出
        if (ts.isExportAssignment(node)) {
          return ts.factory.createExportAssignment(
            undefined,
            undefined,
            ts.factory.createCallExpression(
              ts.factory.createIdentifier('logProxy'),
              undefined,
              [node.expression]
            )
          );
        }
        // 处理具名导出(可按需扩展)
        if (ts.isExportDeclaration(node) && node.exportClause) {
          const updatedElements = node.exportClause.elements.map(element => {
            if (ts.isIdentifier(element.name)) {
              return ts.factory.createExportSpecifier(
                undefined,
                element.name,
                ts.factory.createCallExpression(
                  ts.factory.createIdentifier('logProxy'),
                  undefined,
                  [element.name]
                )
              );
            }
            return element;
          });
          return ts.factory.updateExportDeclaration(
            node,
            node.modifiers,
            ts.factory.createNamedExports(updatedElements),
            node.moduleSpecifier
          );
        }
        return ts.visitEachChild(node, visit, context);
      }
      return ts.visitNode(sourceFile, visit);
    };
  };
}

在tsconfig.json中配置该转换器,编译时自动处理所有TS文件。


内容的提问来源于stack exchange,提问作者PDHide

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 04:30:58