Error with fs.exists wrapper.
Opened this issue · 4 comments
I am trying to use AppOptics in a nodejs project that uses the prisma library. I am also using esm imports, so I am instantiating AppOptics like so:
node -r appoptics-apm dist/src/main
The sequence of events that leads to the error:
function patchPathMethod (fs, method)
within this library wraps thefs.exists()
method.- Prisma begins its initialization and checks what OS it is running on. That results in this method being called ref:
export async function resolveDistro(): Promise<undefined | GetOSResult['distro']> {
// https://github.com/retrohacker/getos/blob/master/os.json
const osReleaseFile = '/etc/os-release'
const alpineReleaseFile = '/etc/alpine-release'
if (await exists(alpineReleaseFile)) {
return 'musl'
} else if (await exists(osReleaseFile)) {
return parseDistro(await readFile(osReleaseFile, 'utf-8'))
} else {
return
}
}
exists()
is simply a promisified version of fs.exists()
const exists = promisify(fs.exists)
- The second call to
exsits()
causes the boolean valuetrue
to be thrown instead of returned as it should be. The thrown boolean cascades into this error which crashes the application:
TypeError: Cannot create property 'clientVersion' on boolean 'true'
at RequestHandler.handleRequestError (/home/project/node_modules/@prisma/client/runtime/index.js:31959:26)
at RequestHandler.handleAndLogRequestError (/home/project/node_modules/@prisma/client/runtime/index.js:31913:12)
at /home/project/node_modules/@prisma/client/runtime/index.js:32458:25
at async PrismaService._executeRequest (/home/project/node_modules/@prisma/client/runtime/index.js:33022:22)
at async PrismaService._request (/home/project/node_modules/@prisma/client/runtime/index.js:32994:16)
After debugging it for a while, I believe that the problem stems from the fact that patchPathMethod
within node_modules/appoptics-apm/lib/probes/fs.js
is not meant to override suppressedCallback
within the fs library.
I believe the fs library has changed in different versions of node but mine looks like this (node version v16.17.0):
/**
* Tests whether or not the given path exists.
* @param {string | Buffer | URL} path
* @param {(exists?: boolean) => any} callback
* @returns {void}
*/
function exists(path, callback) {
maybeCallback(callback);
function suppressedCallback(err) {
callback(err ? false : true);
}
try {
fs.access(path, F_OK, suppressedCallback);
} catch {
return callback(false);
}
}
Here are the logs as requested by support:
err.log
An extra note.
I do not even need fs monitoring at all. I tried to disable it with the config file but that didn't seem to do anything:
appoptics-apm-config.json
{
"enabled": true,
"serviceKey": "REDACTED",
"probes": {
"fs": {
"enabled": false,
"ignoreErrors": {
"open": {
"ENOENT": true
},
"access": {
"ENOENT": true
}
}
}
}
}
Try fs
config:
registered: false
https://github.com/appoptics/appoptics-apm-node/blob/master/lib/probe-defaults.js#L28
That worked! Thank you!
By the way, here is a minimal reproduction of the error case:
import * as express from 'express';
import { promisify } from 'util';
import * as fs from 'fs';
const exists = promisify(fs.exists);
async function resolveDistro() {
const osReleaseFile = '/etc/os-release';
const alpineReleaseFile = '/etc/alpine-release';
if (await exists(alpineReleaseFile)) {
return 'musl';
} else if (await exists(osReleaseFile)) {
return 'not-musl';
} else {
return;
}
}
const initServer = async () => {
await resolveDistro();
const app = express();
app.get('/', (req, res) => {
res.send('Successful response.');
});
app.listen(3000, () => console.log('Example app is listening on port 3000.'));
};
initServer().catch((err) =>
console.log('This value was just thrown as an error: ' + err),
);
Results in:
ethan@Ethan-XPS:~/project$ node dist/src/main2
Example app is listening on port 3000.
^C
ethan@Ethan-XPS:~/project$ node -r appoptics-apm dist/src/main2
This value was just thrown as an error: true
^C