Merge branch 'feat-pixel-placement-log' into 'main'
Pixel placement log / pixels.log Closes #57 See merge request sc07/canvas!4
This commit is contained in:
commit
3337d82a63
|
@ -107,9 +107,13 @@ ENV PORT 3000
|
|||
ENV NODE_ENV production
|
||||
ENV SERVE_CLIENT /home/node/app/packages/client
|
||||
ENV SERVE_ADMIN /home/node/app/packages/admin
|
||||
ENV PIXEL_LOG_PATH /home/node/app/pixel.log
|
||||
|
||||
VOLUME /home/node/app/pixel.log
|
||||
|
||||
EXPOSE 3000
|
||||
# profiler port, only used if profiler is explicity running
|
||||
EXPOSE 9229
|
||||
|
||||
ENTRYPOINT [ "/bin/sh" ]
|
||||
CMD [ "./docker-start.sh" ]
|
|
@ -11,6 +11,7 @@ import {
|
|||
InstanceNotFound,
|
||||
} from "../models/Instance";
|
||||
import { AuditLog } from "../models/AuditLog";
|
||||
import { LogMan } from "../lib/LogMan";
|
||||
|
||||
const app = Router();
|
||||
const Logger = getLogger("HTTP/ADMIN");
|
||||
|
@ -50,6 +51,25 @@ app.get("/check", (req, res) => {
|
|||
res.send({ success: true });
|
||||
});
|
||||
|
||||
// TODO: Delete before merge
|
||||
app.get("/log", (req, res) => {
|
||||
const user = "grant@grants.cafe";
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
LogMan.log("pixel_place", user, { x: 0, y: 0, hex: "ABC123" });
|
||||
LogMan.log("pixel_undo", user, { x: 0, y: 0, hex: "FFFFFF" });
|
||||
LogMan.log("mod_fill", user, { from: [0, 0], to: [1, 1], hex: "000000" });
|
||||
LogMan.log("mod_override", user, { x: 0, y: 0, hex: "111111" });
|
||||
LogMan.log("mod_rollback", user, { x: 0, y: 0, hex: "222222" });
|
||||
LogMan.log("mod_rollback_undo", user, { x: 0, y: 0, hex: "333333" });
|
||||
LogMan.log("canvas_size", { width: 100, height: 100 });
|
||||
LogMan.log("canvas_freeze", {});
|
||||
LogMan.log("canvas_unfreeze", {});
|
||||
}
|
||||
|
||||
res.send("ok");
|
||||
});
|
||||
|
||||
app.get("/canvas/size", async (req, res) => {
|
||||
const config = Canvas.getCanvasConfig();
|
||||
|
||||
|
@ -86,6 +106,11 @@ app.post("/canvas/size", async (req, res) => {
|
|||
}
|
||||
|
||||
await Canvas.setSize(width, height);
|
||||
|
||||
// we log this here because Canvas#setSize is ran at launch
|
||||
// this is currently the only way the size is changed is via the API
|
||||
LogMan.log("canvas_size", { width, height });
|
||||
|
||||
const user = (await User.fromAuthSession(req.session.user!))!;
|
||||
const auditLog = AuditLog.Factory(user.sub)
|
||||
.doing("CANVAS_SIZE")
|
||||
|
@ -111,6 +136,9 @@ app.get("/canvas/freeze", async (req, res) => {
|
|||
app.post("/canvas/freeze", async (req, res) => {
|
||||
await Canvas.setFrozen(true);
|
||||
|
||||
// same reason as canvas size changes, we log this here because #setFrozen is ran at startup
|
||||
LogMan.log("canvas_freeze", {});
|
||||
|
||||
const user = (await User.fromAuthSession(req.session.user!))!;
|
||||
const auditLog = AuditLog.Factory(user.sub)
|
||||
.doing("CANVAS_FREEZE")
|
||||
|
@ -129,6 +157,9 @@ app.post("/canvas/freeze", async (req, res) => {
|
|||
app.delete("/canvas/freeze", async (req, res) => {
|
||||
await Canvas.setFrozen(false);
|
||||
|
||||
// same reason as canvas size changes, we log this here because #setFrozen is ran at startup
|
||||
LogMan.log("canvas_unfreeze", {});
|
||||
|
||||
const user = (await User.fromAuthSession(req.session.user!))!;
|
||||
const auditLog = AuditLog.Factory(user.sub)
|
||||
.doing("CANVAS_UNFREEZE")
|
||||
|
@ -272,6 +303,13 @@ app.put("/canvas/undo", async (req, res) => {
|
|||
? paletteColors.find((p) => p.hex === coveredPixel.color)?.id || -1
|
||||
: -1,
|
||||
});
|
||||
|
||||
// TODO: this spams the log, it would be nicer if it combined
|
||||
LogMan.log("mod_rollback", user_sub, {
|
||||
x: pixel.pixel.x,
|
||||
y: pixel.pixel.y,
|
||||
hex: coveredPixel?.color,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "rejected":
|
||||
|
|
|
@ -81,6 +81,10 @@ if (!process.env.INHIBIT_LOGIN) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!process.env.PIXEL_LOG_PATH) {
|
||||
Logger.warn("PIXEL_LOG_PATH is not defined, defaulting to packages/server");
|
||||
}
|
||||
|
||||
// run startup tasks, all of these need to be completed to serve
|
||||
Promise.all([
|
||||
Redis.getClient(),
|
||||
|
|
|
@ -5,6 +5,7 @@ import { SocketServer } from "./SocketServer";
|
|||
import { getLogger } from "./Logger";
|
||||
import { Pixel } from "@prisma/client";
|
||||
import { CanvasWorker } from "../workers/worker";
|
||||
import { LogMan } from "./LogMan";
|
||||
|
||||
const Logger = getLogger("CANVAS");
|
||||
|
||||
|
@ -182,7 +183,7 @@ class Canvas {
|
|||
x: pixel.x,
|
||||
y: pixel.y,
|
||||
createdAt: { lt: pixel.createdAt },
|
||||
deletedAt: null,
|
||||
deletedAt: null, // undone pixels will have this set
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 1,
|
||||
|
@ -198,6 +199,11 @@ class Canvas {
|
|||
});
|
||||
}
|
||||
|
||||
LogMan.log("pixel_undo", pixel.userId, {
|
||||
x: pixel.x,
|
||||
y: pixel.y,
|
||||
hex: coveringPixel?.color,
|
||||
});
|
||||
return coveringPixel;
|
||||
}
|
||||
|
||||
|
@ -392,6 +398,8 @@ class Canvas {
|
|||
hex,
|
||||
}))
|
||||
);
|
||||
|
||||
LogMan.log("mod_fill", user.sub, { from: start, to: end, hex });
|
||||
}
|
||||
|
||||
async setPixel(
|
||||
|
@ -430,6 +438,11 @@ class Canvas {
|
|||
await this.updateCanvasRedisAtPos(x, y);
|
||||
|
||||
Logger.info(`${user.sub} placed pixel at (${x}, ${y})`);
|
||||
LogMan.log(isModAction ? "mod_override" : "pixel_place", user.sub, {
|
||||
x,
|
||||
y,
|
||||
hex,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import { PixelLogger } from "./Logger";
|
||||
|
||||
interface UserEvents {
|
||||
pixel_place: { x: number; y: number; hex: string };
|
||||
pixel_undo: { x: number; y: number; hex?: string };
|
||||
mod_fill: {
|
||||
from: [x: number, y: number];
|
||||
to: [x: number, y: number];
|
||||
hex: string;
|
||||
};
|
||||
mod_override: { x: number; y: number; hex: string };
|
||||
mod_rollback: { x: number; y: number; hex?: string };
|
||||
mod_rollback_undo: { x: number; y: number; hex?: string };
|
||||
}
|
||||
|
||||
interface SystemEvents {
|
||||
canvas_size: { width: number; height: number };
|
||||
canvas_freeze: {};
|
||||
canvas_unfreeze: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle logs that should be written to a text file
|
||||
*
|
||||
* This could be used as an EventEmitter in the future, but as of right now
|
||||
* it just adds typing to logging of these events
|
||||
*
|
||||
* TODO: better name, this one is not it
|
||||
*
|
||||
* @see #57
|
||||
*/
|
||||
class LogMan_ {
|
||||
log<EventName extends keyof SystemEvents>(
|
||||
event: EventName,
|
||||
data: SystemEvents[EventName]
|
||||
): void;
|
||||
log<EventName extends keyof UserEvents>(
|
||||
event: EventName,
|
||||
user: string,
|
||||
data: UserEvents[EventName]
|
||||
): void;
|
||||
log<EventName extends keyof UserEvents | keyof SystemEvents>(
|
||||
event: EventName,
|
||||
...params: EventName extends keyof UserEvents
|
||||
? [user: string, data: UserEvents[EventName]]
|
||||
: EventName extends keyof SystemEvents
|
||||
? [data: SystemEvents[EventName]]
|
||||
: never
|
||||
): void {
|
||||
let parts: string[] = [];
|
||||
|
||||
if (params.length === 2) {
|
||||
// user event
|
||||
let user = params[0] as string;
|
||||
parts.push(user, event);
|
||||
|
||||
if (event === "mod_fill") {
|
||||
// this event format has a different line format
|
||||
let data: UserEvents["mod_fill"] = params[1] as any;
|
||||
|
||||
parts.push(data.from.join(","), data.to.join(","), data.hex);
|
||||
} else {
|
||||
let data: UserEvents[Exclude<keyof UserEvents, "mod_fill">] =
|
||||
params[1] as any;
|
||||
parts.push(...[data.x, data.y, data.hex || "unset"].map((a) => a + ""));
|
||||
}
|
||||
} else {
|
||||
// system event
|
||||
|
||||
parts.push("system", event);
|
||||
|
||||
switch (event) {
|
||||
case "canvas_size":
|
||||
let data: SystemEvents["canvas_size"] = params[0] as any;
|
||||
let { width, height } = data;
|
||||
parts.push(width + "", height + "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PixelLogger.info(parts.join("\t"));
|
||||
}
|
||||
}
|
||||
|
||||
export const LogMan = new LogMan_();
|
|
@ -1,6 +1,11 @@
|
|||
import winston, { format } from "winston";
|
||||
import path from "node:path";
|
||||
import { createEnum } from "./utils";
|
||||
|
||||
// if PIXEL_LOG_PATH is defined, use that, otherwise default to packages/server root
|
||||
const PIXEL_LOG_PATH =
|
||||
process.env.PIXEL_LOG_PATH || path.join(__dirname, "..", "..", "pixels.log");
|
||||
|
||||
const formatter = format.printf((options) => {
|
||||
let maxModuleWidth = 0;
|
||||
for (const module of Object.values(LoggerType)) {
|
||||
|
@ -26,6 +31,18 @@ const Winston = winston.createLogger({
|
|||
transports: [new winston.transports.Console()],
|
||||
});
|
||||
|
||||
// Used by LogMan for writing to pixels.log
|
||||
export const PixelLogger = winston.createLogger({
|
||||
format: format.printf((options) => {
|
||||
return [new Date().toISOString(), options.message].join("\t");
|
||||
}),
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: PIXEL_LOG_PATH,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const LoggerType = createEnum([
|
||||
"MAIN",
|
||||
"SETTINGS",
|
||||
|
|
|
@ -61,6 +61,8 @@ declare global {
|
|||
ELEMENT_HOST: string;
|
||||
MATRIX_GENERAL_ALIAS: string;
|
||||
|
||||
PIXEL_LOG_PATH?: string;
|
||||
|
||||
RECAPTCHA_SITE_KEY?: string;
|
||||
RECAPTCHA_SECRET_KEY?: string;
|
||||
RECAPTCHA_PIXEL_CHANCE?: string;
|
||||
|
|
Loading…
Reference in New Issue