feat: retain application log history
This commit is contained in:
1 parent
a2219b72f6
commit
ce61c87cd8
12 files changed
+396
-51
No files matched your search
@@ -1,6 +1,17 @@
|
||||
export const logLevels = ["error", "warn", "info", "debug"] as const;
|
||||
export type LogLevel = (typeof logLevels)[number];
|
||||
|
||||
export interface LoggerMessage {
|
||||
id: string;
|
||||
timestamp: Date;
|
||||
message: string;
|
||||
level: LogLevel;
|
||||
}
|
||||
|
||||
export const LOG_HISTORY_MAX_ENTRIES = 5_000;
|
||||
export const LOG_HISTORY_KEY = "list:logging:history";
|
||||
export const LOG_PUBLISH_CHANNEL = "pubSub:logging";
|
||||
|
||||
export const logLevelConfiguration = {
|
||||
error: {
|
||||
prefix: "🔴",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import superjson from "superjson";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { stringify } from "superjson";
|
||||
import Transport from "winston-transport";
|
||||
|
||||
import type { RedisClient } from "../../redis/client";
|
||||
import { createRedisClient } from "../../redis/client";
|
||||
import type { LoggerMessage } from "../constants";
|
||||
import { LOG_HISTORY_KEY, LOG_HISTORY_MAX_ENTRIES, LOG_PUBLISH_CHANNEL } from "../constants";
|
||||
|
||||
const messageSymbol = Symbol.for("message");
|
||||
const levelSymbol = Symbol.for("level");
|
||||
@@ -13,12 +16,11 @@ const levelSymbol = Symbol.for("level");
|
||||
//
|
||||
export class RedisTransport extends Transport {
|
||||
private redis: RedisClient | null = null;
|
||||
public static readonly publishChannel = "pubSub:logging";
|
||||
|
||||
/**
|
||||
* Log the info to the Redis channel
|
||||
* Retain the log in a bounded Redis list and publish it to live subscribers.
|
||||
*/
|
||||
log(info: { [messageSymbol]: string; [levelSymbol]: string }, callback: () => void) {
|
||||
log(info: { [messageSymbol]: string; [levelSymbol]: LoggerMessage["level"] }, callback: () => void) {
|
||||
setImmediate(() => {
|
||||
this.emit("logged", info);
|
||||
});
|
||||
@@ -26,19 +28,23 @@ export class RedisTransport extends Transport {
|
||||
// Is only initialized here because it did not work when initialized in the constructor or outside the class
|
||||
this.redis ??= createRedisClient();
|
||||
|
||||
const message: LoggerMessage = {
|
||||
id: randomUUID(),
|
||||
timestamp: new Date(),
|
||||
message: info[messageSymbol],
|
||||
level: info[levelSymbol],
|
||||
};
|
||||
const serializedMessage = stringify(message);
|
||||
|
||||
this.redis
|
||||
.publish(
|
||||
RedisTransport.publishChannel,
|
||||
superjson.stringify({
|
||||
message: info[messageSymbol],
|
||||
level: info[levelSymbol],
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
callback();
|
||||
})
|
||||
.multi()
|
||||
.lpush(LOG_HISTORY_KEY, serializedMessage)
|
||||
.ltrim(LOG_HISTORY_KEY, 0, LOG_HISTORY_MAX_ENTRIES - 1)
|
||||
.publish(LOG_PUBLISH_CHANNEL, serializedMessage)
|
||||
.exec()
|
||||
.catch(() => {
|
||||
// Ignore errors
|
||||
});
|
||||
})
|
||||
.finally(callback);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user