mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49:16 +01:00
Finish full rewrite of server and testing systems
This commit is contained in:
parent
0e4d6b401c
commit
0541776d3d
32 changed files with 1168 additions and 916 deletions
|
|
@ -16,7 +16,7 @@ export enum LogLevel {
|
|||
export class LogManager {
|
||||
constructor(private output: BunFile) {
|
||||
void this.write(
|
||||
`--- INIT LogManager at ${new Date().toISOString()} --`
|
||||
`--- INIT LogManager at ${new Date().toISOString()} ---`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -58,4 +58,114 @@ export class LogManager {
|
|||
async logError(level: LogLevel, entity: string, error: Error) {
|
||||
await this.log(level, entity, error.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a request to the output
|
||||
* @param req Request to log
|
||||
* @param ip IP of the request
|
||||
* @param logAllDetails Whether to log all details of the request
|
||||
*/
|
||||
async logRequest(req: Request, ip?: string, logAllDetails = false) {
|
||||
let string = ip ? `${ip}: ` : "";
|
||||
|
||||
string += `${req.method} ${req.url}`;
|
||||
|
||||
if (logAllDetails) {
|
||||
string += `\n`;
|
||||
string += ` [Headers]\n`;
|
||||
// Pretty print headers
|
||||
for (const [key, value] of req.headers.entries()) {
|
||||
string += ` ${key}: ${value}\n`;
|
||||
}
|
||||
|
||||
// Pretty print body
|
||||
string += ` [Body]\n`;
|
||||
const content_type = req.headers.get("Content-Type");
|
||||
|
||||
if (content_type && content_type.includes("application/json")) {
|
||||
const json = await req.json();
|
||||
const stringified = JSON.stringify(json, null, 4)
|
||||
.split("\n")
|
||||
.map(line => ` ${line}`)
|
||||
.join("\n");
|
||||
|
||||
string += `${stringified}\n`;
|
||||
} else if (
|
||||
content_type &&
|
||||
(content_type.includes("application/x-www-form-urlencoded") ||
|
||||
content_type.includes("multipart/form-data"))
|
||||
) {
|
||||
const formData = await req.formData();
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (value.toString().length < 300) {
|
||||
string += ` ${key}: ${value.toString()}\n`;
|
||||
} else {
|
||||
string += ` ${key}: <${value.toString().length} bytes>\n`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const text = await req.text();
|
||||
string += ` ${text}\n`;
|
||||
}
|
||||
}
|
||||
await this.log(LogLevel.INFO, "Request", string);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs to multiple LogManager instances at once
|
||||
*/
|
||||
export class MultiLogManager {
|
||||
constructor(private logManagers: LogManager[]) {}
|
||||
|
||||
/**
|
||||
* Logs a message to all logManagers
|
||||
* @param level Importance of the log
|
||||
* @param entity Emitter of the log
|
||||
* @param message Message to log
|
||||
* @param showTimestamp Whether to show the timestamp in the log
|
||||
*/
|
||||
async log(
|
||||
level: LogLevel,
|
||||
entity: string,
|
||||
message: string,
|
||||
showTimestamp = true
|
||||
) {
|
||||
for (const logManager of this.logManagers) {
|
||||
await logManager.log(level, entity, message, showTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error to all logManagers
|
||||
* @param level Importance of the log
|
||||
* @param entity Emitter of the log
|
||||
* @param error Error to log
|
||||
*/
|
||||
async logError(level: LogLevel, entity: string, error: Error) {
|
||||
for (const logManager of this.logManagers) {
|
||||
await logManager.logError(level, entity, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a request to all logManagers
|
||||
* @param req Request to log
|
||||
* @param ip IP of the request
|
||||
* @param logAllDetails Whether to log all details of the request
|
||||
*/
|
||||
async logRequest(req: Request, ip?: string, logAllDetails = false) {
|
||||
for (const logManager of this.logManagers) {
|
||||
await logManager.logRequest(req, ip, logAllDetails);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MultiLogManager from multiple LogManager instances
|
||||
* @param logManagers LogManager instances to use
|
||||
* @returns
|
||||
*/
|
||||
static fromLogManagers(...logManagers: LogManager[]) {
|
||||
return new MultiLogManager(logManagers);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// FILEPATH: /home/jessew/Dev/lysand/packages/log-manager/log-manager.test.ts
|
||||
import { LogManager, LogLevel } from "../index";
|
||||
import { LogManager, LogLevel, MultiLogManager } from "../index";
|
||||
import type fs from "fs/promises";
|
||||
import {
|
||||
describe,
|
||||
|
|
@ -91,4 +91,141 @@ describe("LogManager", () => {
|
|||
expect.stringContaining("[ERROR] TestEntity: Test error")
|
||||
);
|
||||
});
|
||||
|
||||
it("should log basic request details", async () => {
|
||||
const req = new Request("http://localhost/test", { method: "GET" });
|
||||
await logManager.logRequest(req, "127.0.0.1");
|
||||
|
||||
expect(mockAppend).toHaveBeenCalledWith(
|
||||
mockOutput.name,
|
||||
expect.stringContaining("127.0.0.1: GET http://localhost/test")
|
||||
);
|
||||
});
|
||||
|
||||
describe("Request logger", () => {
|
||||
it("should log all request details for JSON content type", async () => {
|
||||
const req = new Request("http://localhost/test", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ test: "value" }),
|
||||
});
|
||||
await logManager.logRequest(req, "127.0.0.1", true);
|
||||
|
||||
const expectedLog = `127.0.0.1: POST http://localhost/test
|
||||
[Headers]
|
||||
content-type: application/json
|
||||
[Body]
|
||||
{
|
||||
"test": "value"
|
||||
}
|
||||
`;
|
||||
|
||||
expect(mockAppend).toHaveBeenCalledWith(
|
||||
mockOutput.name,
|
||||
expect.stringContaining(expectedLog)
|
||||
);
|
||||
});
|
||||
|
||||
it("should log all request details for text content type", async () => {
|
||||
const req = new Request("http://localhost/test", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
body: "Test body",
|
||||
});
|
||||
await logManager.logRequest(req, "127.0.0.1", true);
|
||||
|
||||
const expectedLog = `127.0.0.1: POST http://localhost/test
|
||||
[Headers]
|
||||
content-type: text/plain
|
||||
[Body]
|
||||
Test body
|
||||
`;
|
||||
expect(mockAppend).toHaveBeenCalledWith(
|
||||
mockOutput.name,
|
||||
expect.stringContaining(expectedLog)
|
||||
);
|
||||
});
|
||||
|
||||
it("should log all request details for FormData content-type", async () => {
|
||||
const formData = new FormData();
|
||||
formData.append("test", "value");
|
||||
const req = new Request("http://localhost/test", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
await logManager.logRequest(req, "127.0.0.1", true);
|
||||
|
||||
const expectedLog = `127.0.0.1: POST http://localhost/test
|
||||
[Headers]
|
||||
content-type: multipart/form-data; boundary=${
|
||||
req.headers.get("Content-Type")?.split("boundary=")[1] ?? ""
|
||||
}
|
||||
[Body]
|
||||
test: value
|
||||
`;
|
||||
|
||||
expect(mockAppend).toHaveBeenCalledWith(
|
||||
mockOutput.name,
|
||||
expect.stringContaining(
|
||||
expectedLog.replace("----", expect.any(String))
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("MultiLogManager", () => {
|
||||
let multiLogManager: MultiLogManager;
|
||||
let mockLogManagers: LogManager[];
|
||||
let mockLog: jest.Mock;
|
||||
let mockLogError: jest.Mock;
|
||||
let mockLogRequest: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLog = jest.fn();
|
||||
mockLogError = jest.fn();
|
||||
mockLogRequest = jest.fn();
|
||||
mockLogManagers = [
|
||||
{
|
||||
log: mockLog,
|
||||
logError: mockLogError,
|
||||
logRequest: mockLogRequest,
|
||||
},
|
||||
{
|
||||
log: mockLog,
|
||||
logError: mockLogError,
|
||||
logRequest: mockLogRequest,
|
||||
},
|
||||
] as unknown as LogManager[];
|
||||
multiLogManager = MultiLogManager.fromLogManagers(...mockLogManagers);
|
||||
});
|
||||
|
||||
it("should log message to all logManagers", async () => {
|
||||
await multiLogManager.log(LogLevel.INFO, "TestEntity", "Test message");
|
||||
expect(mockLog).toHaveBeenCalledTimes(2);
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
LogLevel.INFO,
|
||||
"TestEntity",
|
||||
"Test message",
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("should log error to all logManagers", async () => {
|
||||
const error = new Error("Test error");
|
||||
await multiLogManager.logError(LogLevel.ERROR, "TestEntity", error);
|
||||
expect(mockLogError).toHaveBeenCalledTimes(2);
|
||||
expect(mockLogError).toHaveBeenCalledWith(
|
||||
LogLevel.ERROR,
|
||||
"TestEntity",
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
it("should log request to all logManagers", async () => {
|
||||
const req = new Request("http://localhost/test", { method: "GET" });
|
||||
await multiLogManager.logRequest(req, "127.0.0.1", true);
|
||||
expect(mockLogRequest).toHaveBeenCalledTimes(2);
|
||||
expect(mockLogRequest).toHaveBeenCalledWith(req, "127.0.0.1", true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue