Skip to content

Commit

Permalink
feat: Add support for OPEN_NEXT_ERROR_LOG_LEVEL
Browse files Browse the repository at this point in the history
OPEN_NEXT_ERROR_LOG_LEVEL is the minimal error level from which internal errors are logged.
It can be set to
- "0" / "debug"
- "1" / "warn" (default)
- "2" / "error"
  • Loading branch information
vicb authored Nov 26, 2024
1 parent 2202f36 commit 2b2a48b
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 20 deletions.
12 changes: 12 additions & 0 deletions .changeset/chilly-cycles-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@opennextjs/aws": minor
---

feat: Add support for OPEN_NEXT_ERROR_LOG_LEVEL

OPEN_NEXT_ERROR_LOG_LEVEL is the minimal error level from which internal errors are logged.
It can be set to:

- "0" / "debug"
- "1" / "warn" (default)
- "2" / "error"
58 changes: 42 additions & 16 deletions packages/open-next/src/adapters/logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BaseOpenNextError } from "utils/error";
import { type BaseOpenNextError, isOpenNextError } from "utils/error";

export function debug(...args: any[]) {
if (globalThis.openNextDebug) {
Expand Down Expand Up @@ -42,28 +42,34 @@ const isDownplayedErrorLog = (errorLog: AwsSdkClientCommandErrorLog) =>

export function error(...args: any[]) {
// we try to catch errors from the aws-sdk client and downplay some of them
if (
args.some((arg: AwsSdkClientCommandErrorLog) => isDownplayedErrorLog(arg))
) {
if (args.some((arg) => isDownplayedErrorLog(arg))) {
debug(...args);
} else if (args.some((arg) => arg.__openNextInternal)) {
} else if (args.some((arg) => isOpenNextError(arg))) {
// In case of an internal error, we log it with the appropriate log level
const error = args.find(
(arg) => arg.__openNextInternal,
) as BaseOpenNextError;
if (error.logLevel === 0) {
debug(...args);
const error = args.find((arg) => isOpenNextError(arg))!;
if (error.logLevel < getOpenNextErrorLogLevel()) {
return;
}
if (error.logLevel === 0) {
// Display the name and the message instead of full Open Next errors.
// console.log is used so that logging does not depend on openNextDebug.
return console.log(
...args.map((arg) =>
isOpenNextError(arg) ? `${arg.name}: ${arg.message}` : arg,
),
);
}
if (error.logLevel === 1) {
warn(...args);
return;
// Display the name and the message instead of full Open Next errors.
return warn(
...args.map((arg) =>
isOpenNextError(arg) ? `${arg.name}: ${arg.message}` : arg,
),
);
}
console.error(...args);
return;
} else {
console.error(...args);
return console.error(...args);
}
console.error(...args);
}

export const awsLogger = {
Expand All @@ -73,3 +79,23 @@ export const awsLogger = {
warn,
error,
};

/**
* Retrieves the log level for internal errors from the
* OPEN_NEXT_ERROR_LOG_LEVEL environment variable.
*
* @returns The numerical log level 0 (debug), 1 (warn), or 2 (error)
*/
function getOpenNextErrorLogLevel(): number {
const strLevel = process.env.OPEN_NEXT_ERROR_LOG_LEVEL ?? "1";
switch (strLevel.toLowerCase()) {
case "debug":
case "0":
return 0;
case "error":
case "2":
return 2;
default:
return 1;
}
}
8 changes: 8 additions & 0 deletions packages/open-next/src/utils/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,11 @@ export class FatalError extends Error implements BaseOpenNextError {
this.name = "FatalError";
}
}

export function isOpenNextError(e: any): e is BaseOpenNextError & Error {
try {
return "__openNextInternal" in e;
} catch {
return false;
}
}
5 changes: 1 addition & 4 deletions packages/tests-unit/tests/adapters/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,7 @@ describe("S3Cache", () => {
beforeEach(() => {
vi.clearAllMocks();

cache = new S3Cache({
_appDir: false,
_requestHeaders: undefined as never,
});
cache = new S3Cache();

globalThis.disableIncrementalCache = false;
globalThis.isNextAfter15 = false;
Expand Down
86 changes: 86 additions & 0 deletions packages/tests-unit/tests/adapters/logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as logger from "@opennextjs/aws/adapters/logger.js";
import {
FatalError,
IgnorableError,
RecoverableError,
} from "@opennextjs/aws/utils/error.js";
import { vi } from "vitest";

describe("logger adapter", () => {
describe("Open Next errors", () => {
const debug = vi.spyOn(console, "log").mockImplementation(() => null);
const warn = vi.spyOn(console, "warn").mockImplementation(() => null);
const error = vi.spyOn(console, "error").mockImplementation(() => null);

beforeEach(() => {
debug.mockClear();
warn.mockClear();
error.mockClear();
});

const ignorableError = new IgnorableError("ignorable");
const recoverableError = new RecoverableError("recoverable");
const fatalError = new FatalError("fatal");

it("default to warn when OPEN_NEXT_ERROR_LOG_LEVEL is undefined", () => {
delete process.env.OPEN_NEXT_ERROR_LOG_LEVEL;
logger.error(ignorableError);
logger.error(recoverableError);
logger.error(fatalError);
expect(debug).not.toHaveBeenCalled();
expect(warn).toHaveBeenCalledWith("RecoverableError: recoverable");
expect(error).toHaveBeenCalledWith(fatalError);
});

it("OPEN_NEXT_ERROR_LOG_LEVEL is 'debug'/'0'", () => {
process.env.OPEN_NEXT_ERROR_LOG_LEVEL = "0";
logger.error(ignorableError);
logger.error(recoverableError);
logger.error(fatalError);
expect(debug).toHaveBeenCalledWith("IgnorableError: ignorable");
expect(warn).toHaveBeenCalledWith("RecoverableError: recoverable");
expect(error).toHaveBeenCalledWith(fatalError);
process.env.OPEN_NEXT_ERROR_LOG_LEVEL = "debug";
logger.error(ignorableError);
logger.error(recoverableError);
logger.error(fatalError);
expect(debug).toHaveBeenCalledWith("IgnorableError: ignorable");
expect(warn).toHaveBeenCalledWith("RecoverableError: recoverable");
expect(error).toHaveBeenCalledWith(fatalError);
});

it("OPEN_NEXT_ERROR_LOG_LEVEL is 'warn'/'1'", () => {
process.env.OPEN_NEXT_ERROR_LOG_LEVEL = "1";
logger.error(ignorableError);
logger.error(recoverableError);
logger.error(fatalError);
expect(debug).not.toHaveBeenCalled();
expect(warn).toHaveBeenCalledWith("RecoverableError: recoverable");
expect(error).toHaveBeenCalledWith(fatalError);
process.env.OPEN_NEXT_ERROR_LOG_LEVEL = "warn";
logger.error(ignorableError);
logger.error(recoverableError);
logger.error(fatalError);
expect(debug).not.toHaveBeenCalled();
expect(warn).toHaveBeenCalledWith("RecoverableError: recoverable");
expect(error).toHaveBeenCalledWith(fatalError);
});

it("OPEN_NEXT_ERROR_LOG_LEVEL is 'error'/'2'", () => {
process.env.OPEN_NEXT_ERROR_LOG_LEVEL = "2";
logger.error(ignorableError);
logger.error(recoverableError);
logger.error(fatalError);
expect(debug).not.toHaveBeenCalled();
expect(warn).not.toHaveBeenCalled();
expect(error).toHaveBeenCalledWith(fatalError);
process.env.OPEN_NEXT_ERROR_LOG_LEVEL = "error";
logger.error(ignorableError);
logger.error(recoverableError);
logger.error(fatalError);
expect(debug).not.toHaveBeenCalled();
expect(warn).not.toHaveBeenCalled();
expect(error).toHaveBeenCalledWith(fatalError);
});
});
});

0 comments on commit 2b2a48b

Please sign in to comment.