Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: enhanced Logger to accept a log file location #2298

Merged
merged 9 commits into from
Jun 3, 2024
4 changes: 2 additions & 2 deletions src/Executable.js
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ export default class Executable {
throw this._mapStatusError(request, response);
default:
throw new Error(
"(BUG) non-exhuastive switch statement for `ExecutionState`",
"(BUG) non-exhaustive switch statement for `ExecutionState`",
);
}
}
Expand All @@ -742,7 +742,7 @@ export default class Executable {
/**
* The current purpose of this method is to easily support signature providers since
* signature providers need to serialize _any_ request into bytes. `Query` and `Transaction`
* already implement `toBytes()` so it only made sense to make it avaiable here too.
* already implement `toBytes()` so it only made sense to make it available here too.
*
* @abstract
* @returns {Uint8Array}
Expand Down
11 changes: 10 additions & 1 deletion src/grpc/GrpcServiceError.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,19 @@ export default class GrpcServiceError extends Error {
if (obj.code != null && obj.details != null) {
const status = GrpcStatus._fromValue(obj.code);
const err = new GrpcServiceError(status);
err.message = obj.details;
err.stack +=
"\nCaused by: " + (obj.stack ? obj.stack.toString() : "");
jeromy-cannon marked this conversation as resolved.
Show resolved Hide resolved
err.message += `: ${obj.details}`;
return err;
} else {
return /** @type {Error} */ (obj);
}
}

/**
* @returns {string}
*/
toString() {
return `${this.name}: ${this.message}`;
}
}
38 changes: 19 additions & 19 deletions src/grpc/GrpcStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,43 +89,43 @@ export default class GrpcStatus {
toString() {
switch (this) {
case GrpcStatus.Ok:
return "OK";
return `OK (${this._code})`;
jeromy-cannon marked this conversation as resolved.
Show resolved Hide resolved
case GrpcStatus.Cancelled:
return "CANCELLED";
return `CANCELLED (${this._code})`;
case GrpcStatus.Unknown:
return "UNKNOWN";
return `UNKNOWN (${this._code})`;
case GrpcStatus.InvalidArgument:
return "INVALID_ARGUMENT";
return `INVALID_ARGUMENT (${this._code})`;
case GrpcStatus.DeadlineExceeded:
return "DEADLINE_EXCEEDED";
return `DEADLINE_EXCEEDED (${this._code})`;
case GrpcStatus.NotFound:
return "NOT_FOUND";
return `NOT_FOUND (${this._code})`;
case GrpcStatus.AlreadyExists:
return "ALREADY_EXISTS";
return `ALREADY_EXISTS (${this._code})`;
case GrpcStatus.PermissionDenied:
return "PERMISSION_DENIED";
return `PERMISSION_DENIED (${this._code})`;
case GrpcStatus.Unauthenticated:
return "UNAUTHENTICATED";
return `UNAUTHENTICATED (${this._code})`;
case GrpcStatus.ResourceExhausted:
return "RESOURCE_EXHAUSTED";
return `RESOURCE_EXHAUSTED (${this._code})`;
case GrpcStatus.FailedPrecondition:
return "FAILED_PRECONDITION";
return `FAILED_PRECONDITION (${this._code})`;
case GrpcStatus.Aborted:
return "ABORTED";
return `ABORTED (${this._code})`;
case GrpcStatus.OutOfRange:
return "OUT_OF_RANGE";
return `OUT_OF_RANGE (${this._code})`;
case GrpcStatus.Unimplemented:
return "UNIMPLEMENTED";
return `UNIMPLEMENTED (${this._code})`;
case GrpcStatus.Internal:
return "INTERNAL";
return `INTERNAL (${this._code})`;
case GrpcStatus.Unavailable:
return "UNAVAILABLE";
return `UNAVAILABLE (${this._code})`;
case GrpcStatus.DataLoss:
return "DATA_LOSS";
return `DATA_LOSS (${this._code})`;
case GrpcStatus.Timeout:
return "TIMEOUT";
return `TIMEOUT (${this._code})`;
case GrpcStatus.GrpcWeb:
return "GRPC_WEB";
return `GRPC_WEB (${this._code})`;
default:
return `UNKNOWN (${this._code})`;
}
Expand Down
63 changes: 52 additions & 11 deletions src/logger/Logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,63 @@ import LogLevel from "./LogLevel.js";
export default class Logger {
/**
* @param {LogLevel} level
* @param {string} logFile the file to log to, if empty, logs to console
* @param {boolean} sync perform writes synchronously (similar to console.log)
* @param {boolean} fsync perform a fsyncSync every time a write is completed
* @param {boolean} mkdir ensure directory for dest file exists when true (default false)
* @param {number} minLength the minimum length of the internal buffer that is required to be full before flushing
*/
constructor(level) {
constructor(
level,
logFile = "",
sync = true,
fsync = true,
mkdir = true,
minLength = 0,
) {
const fileTransport = logFile
? pino.destination({
dest: logFile,
sync,
fsync,
mkdir,
minLength,
})
: null;

const loggerOptions = fileTransport
? {
level: level.toString(),
timestamp: pino.stdTimeFunctions.isoTime,
formatters: {
bindings: () => {
return {};
},
// @ts-ignore
level: (label) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
return { level: label.toUpperCase() };
},
},
}
: {
level: level.toString(),
transport: {
target: "pino-pretty",
options: {
translateTime: "SYS:dd-mm-yyyy HH:MM:ss",
ignore: "pid,hostname",
},
},
};

/**
* @private
* @type {import("pino").Logger}
*/
this._logger = pino({
level: level.toString(),
transport: {
target: "pino-pretty",
options: {
translateTime: "SYS:dd-mm-yyyy HH:MM:ss",
ignore: "pid,hostname",
},
},
});
this._logger = fileTransport
? pino(loggerOptions, fileTransport)
: pino(loggerOptions);

/**
* @private
Expand Down
2 changes: 1 addition & 1 deletion test/unit/AccountInfoMocking.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ describe("AccountInfoMocking", function () {
await query.execute(client, 1);

expect(query._queryPayment.toTinybars().toInt()).to.be.equal(10);
});
}, 15000);

it("setQueryPayemnt() + setMaxQueryPayment() avoids querying actual cost", async function () {
this.timeout(10000);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/FileAppendMocking.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe("FileAppendMocking", function () {
} catch (error) {
if (
error.message !==
"max attempts of 1 was reached for request with last error being: GrpcServiceError: node is UNAVAILABLE"
"max attempts of 1 was reached for request with last error being: GrpcServiceError: gRPC service failed with status: UNAVAILABLE (14): node is UNAVAILABLE"
) {
throw error;
}
Expand Down
21 changes: 21 additions & 0 deletions test/unit/LoggerTest.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Logger, LogLevel, Transaction } from "../../src/exports.js";
import { Client } from "../../src/index.js";
import { tmpdir } from "node:os";
import fs from "fs";

describe("Logger", function () {
this.timeout(50000);
Expand Down Expand Up @@ -68,4 +70,23 @@ describe("Logger", function () {
expect(levels).to.include("error");
expect(levels).to.include("fatal");
});

jeromy-cannon marked this conversation as resolved.
Show resolved Hide resolved
it("check that it can write to a log file", async function () {
const logFile = tmpdir() + "/test.log";
const logger = new Logger(LogLevel.Info, logFile);
logger.info("This is a test log message");
logger.warn("This is a test warning message");
logger.error("This is a test error message");
logger.fatal("This is a test fatal message");
// read the log file and check if the messages are there and check that the levels are correct
const logContent = fs.readFileSync(logFile, "utf8");
expect(logContent).to.contain("This is a test log message");
expect(logContent).to.contain(LogLevel.Info.toString().toUpperCase());
expect(logContent).to.contain("This is a test warning message");
expect(logContent).to.contain(LogLevel.Warn.toString().toUpperCase());
expect(logContent).to.contain("This is a test error message");
expect(logContent).to.contain(LogLevel.Error.toString().toUpperCase());
expect(logContent).to.contain("This is a test fatal message");
expect(logContent).to.contain(LogLevel.Fatal.toString().toUpperCase());
});
});