diff --git a/packages/nodejs/.changesets/limit-appsignal-log-file-size-in-diagnose-report.md b/packages/nodejs/.changesets/limit-appsignal-log-file-size-in-diagnose-report.md new file mode 100644 index 00000000..415025fa --- /dev/null +++ b/packages/nodejs/.changesets/limit-appsignal-log-file-size-in-diagnose-report.md @@ -0,0 +1,5 @@ +--- +bump: "patch" +--- + +Limit the `appsignal.log` file size in diagnose report. It will only send the last 2MiB of the file. This prevents the diagnose report from sending too much data that it gets rejected by the server. diff --git a/packages/nodejs/src/__tests__/diagnose.test.ts b/packages/nodejs/src/__tests__/diagnose.test.ts index f8057df9..ceda26b0 100644 --- a/packages/nodejs/src/__tests__/diagnose.test.ts +++ b/packages/nodejs/src/__tests__/diagnose.test.ts @@ -1,3 +1,4 @@ +import fs from "fs" import { DiagnoseTool } from "../diagnose" import { VERSION, AGENT_VERSION } from "../version" @@ -31,10 +32,29 @@ describe("DiagnoseTool", () => { expect(tool.generate().paths.log_dir_path.path).toEqual("/tmp") }) - it("returns the appsignal.log path", () => { - expect(tool.generate().paths["appsignal.log"].path).toEqual( - "/tmp/appsignal.log" - ) + describe("appsignal.log path", () => { + it("returns the appsignal.log path", () => { + expect(tool.generate().paths["appsignal.log"].path).toEqual( + "/tmp/appsignal.log" + ) + }) + + it("returns all of the file if file is less than 2MiB in size", () => { + const fileSize = 1.9 * 1024 * 1024 // Write 1.9 MiB of content + const content = ".".repeat(fileSize) + fs.writeFileSync("/tmp/appsignal.log", content, { flag: "w" }) + + const contentArray = tool.generate().paths["appsignal.log"].content + expect(contentArray?.join("\n").length).toBeCloseTo(fileSize, 0) + }) + + it("returns maximum 2MiB of content", () => { + const content = ".".repeat(2.1 * 1024 * 1024) // Write 2.1 MiB of content + fs.writeFileSync("/tmp/appsignal.log", content, { flag: "w" }) + + const contentArray = tool.generate().paths["appsignal.log"].content + expect(contentArray?.join("\n").length).toEqual(2 * 1024 * 1024) + }) }) describe("when to log path is configured as a full path", () => { diff --git a/packages/nodejs/src/diagnose.ts b/packages/nodejs/src/diagnose.ts index 6cb79a8d..567e42c5 100644 --- a/packages/nodejs/src/diagnose.ts +++ b/packages/nodejs/src/diagnose.ts @@ -183,18 +183,52 @@ function getPathType(stats: fs.Stats) { } } +const BYTES_TO_READ_FOR_FILES = 2 * 1024 * 1024 // 2 Mebibytes + /** * Attempts to read a UTF-8 from `path`, and either returns the result * as a string, or an empty string on error */ function safeReadFromPath(path: string): string { try { - return fs.readFileSync(path, "utf8") + return readBytesFromPath(path, BYTES_TO_READ_FOR_FILES) } catch (_) { return "" } } +function readBytesFromPath(path: string, bytesToRead: number): string { + let fd + try { + const { readLength, startPosition } = readFileOptions(path, bytesToRead) + fd = fs.openSync(path, "r") + const buffer = Buffer.alloc(readLength) + fs.readSync(fd, buffer, 0, readLength, startPosition) + return buffer.toString("utf8") + } finally { + if (fd) { + fs.closeSync(fd) + } + } +} + +function readFileOptions(path: string, bytesToRead: number) { + const stats = fs.statSync(path) + const fileSize = stats.size + if (fileSize < bytesToRead) { + return { + readLength: fileSize, + startPosition: 0 + } + } else { + const startPosition = fileSize - bytesToRead + return { + readLength: bytesToRead, + startPosition + } + } +} + /** * the following lines are borrowed from https://github.com/sindresorhus/is-docker/ * thanks sindre! <3