Skip to content

Commit

Permalink
feat: Instrument calls to libraries - ESM support
Browse files Browse the repository at this point in the history
  • Loading branch information
zermelo-wisen committed Apr 15, 2024
1 parent b074452 commit 6132680
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 4 deletions.
17 changes: 17 additions & 0 deletions src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ import transform, { findHook } from "./transform";
import { readPkgUp } from "./util/readPkgUp";
import { forceRequire } from "./register";

export const resolve: NodeLoaderHooksAPI2["resolve"] = async function resolve(
url,
context,
nextResolve,
) {
const result = await nextResolve(url, context, nextResolve);

// For libraries, we preempt import with CommonJS require here, instead
// of load function, because for third party libraries we can catch
// their import name here (i.e. url: "json5"). Then it gets resolved
// to a path (i.e. result.path: ".../node_modules/json5/lib/index.js")
// and passed to the load function.
if (config.getPackage(url, true) != undefined) forceRequire(url);

return result;
};

export const load: NodeLoaderHooksAPI2["load"] = async function load(url, context, defaultLoad) {
const urlObj = new URL(url);

Expand Down
200 changes: 200 additions & 0 deletions test/__snapshots__/libraryCalls.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,205 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`mapping standard library calls - ESM 1`] = `
{
"./tmp/appmap/process/<timestamp 0>.appmap.json": {
"classMap": [
{
"children": [
{
"children": [
{
"location": "console:1",
"name": "log",
"static": true,
"type": "function",
},
{
"location": "console:2",
"name": "count",
"static": true,
"type": "function",
},
],
"name": "console",
"type": "class",
},
{
"children": [
{
"location": "json5:3",
"name": "parse",
"static": true,
"type": "function",
},
],
"name": "json5",
"type": "class",
},
],
"name": "appmap-node",
"type": "package",
},
],
"events": [
{
"defined_class": "console",
"event": "call",
"id": 1,
"lineno": 1,
"method_id": "log",
"parameters": [
{
"class": "String",
"value": "'Hello World'",
},
],
"path": "console",
"receiver": {
"class": "Object",
"object_id": 1,
"value": "[console]",
},
"static": false,
"thread_id": 0,
},
{
"elapsed": 31.337,
"event": "return",
"id": 2,
"parent_id": 1,
"thread_id": 0,
},
{
"defined_class": "console",
"event": "call",
"id": 3,
"lineno": 2,
"method_id": "count",
"parameters": [
{
"class": "String",
"value": "'abc'",
},
],
"path": "console",
"receiver": {
"class": "Object",
"object_id": 1,
"value": "[console]",
},
"static": false,
"thread_id": 0,
},
{
"elapsed": 31.337,
"event": "return",
"id": 4,
"parent_id": 3,
"thread_id": 0,
},
{
"defined_class": "json5",
"event": "call",
"id": 5,
"lineno": 3,
"method_id": "parse",
"parameters": [
{
"class": "String",
"value": "'{ a: 123 }'",
},
],
"path": "json5",
"receiver": {
"class": "Object",
"object_id": 2,
"value": "[json5]",
},
"static": false,
"thread_id": 0,
},
{
"elapsed": 31.337,
"event": "return",
"id": 6,
"parent_id": 5,
"return_value": {
"class": "Object",
"object_id": 3,
"properties": [
{
"class": "Number",
"name": "a",
},
],
"value": "{ a: 123 }",
},
"thread_id": 0,
},
{
"defined_class": "console",
"event": "call",
"id": 7,
"lineno": 1,
"method_id": "log",
"parameters": [
{
"class": "String",
"value": "'obj'",
},
{
"class": "Object",
"object_id": 3,
"properties": [
{
"class": "Number",
"name": "a",
},
],
"value": "{ a: 123 }",
},
],
"path": "console",
"receiver": {
"class": "Object",
"object_id": 1,
"value": "[console]",
},
"static": false,
"thread_id": 0,
},
{
"elapsed": 31.337,
"event": "return",
"id": 8,
"parent_id": 7,
"thread_id": 0,
},
],
"metadata": {
"app": "appmap-node",
"client": {
"name": "appmap-node",
"url": "https://github.com/getappmap/appmap-node",
"version": "test node-appmap version",
},
"language": {
"engine": "Node.js",
"name": "javascript",
"version": "test node version",
},
"name": "test process recording",
"recorder": {
"name": "process",
"type": "process",
},
},
"version": "1.12",
},
}
`;

exports[`mapping standard library calls 1`] = `
{
"./tmp/appmap/process/<timestamp 0>.appmap.json": {
Expand Down
22 changes: 18 additions & 4 deletions test/libraryCalls.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import AppMap from "../src/AppMap";
import { integrationTest, readAppmaps, runAppmapNode } from "./helpers";

integrationTest("mapping standard library calls", () => {
expect(runAppmapNode("yarn", "exec", "ts-node", "index.ts").status).toBe(0);

const appmaps = readAppmaps();
// properties of "console" object can be different accross node versions
fixAppMaps(appmaps);

expect(appmaps).toMatchSnapshot();
});

integrationTest("mapping standard library calls - ESM", () => {
expect(runAppmapNode("index.mjs").status).toBe(0);

const appmaps = readAppmaps();
fixAppMaps(appmaps);

expect(appmaps).toMatchSnapshot();
});

// properties of "console" object can be different accross node versions
function fixAppMaps(appmaps: Record<string, AppMap.AppMap>) {
for (const key in appmaps) {
appmaps[key].events?.forEach((e) => {
if (e.event == "call" && "receiver" in e) delete e.receiver?.properties;
});
}

expect(appmaps).toMatchSnapshot();
});
}
14 changes: 14 additions & 0 deletions test/libraryCalls/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import console from "node:console";
import json5 from "json5";

console.log("Hello World");

// This calls console.log internally, and this internal
// console.log call won't be recorded in shallow mode.
// https://github.com/nodejs/node/blob/57d2e4881c9a7f9ac52d49d19d781dc455b2789d/lib/internal/console/constructor.js#L475
console.count("abc");

console.warn("This is excluded in settings");

const obj = json5.parse("{ a: 123 }");
console.log("obj", obj);

0 comments on commit 6132680

Please sign in to comment.