Commit 3337d82a authored by Grant's avatar Grant
Browse files

Merge branch 'feat-pixel-placement-log' into 'main'

Pixel placement log / pixels.log

Closes #57

See merge request sc07/canvas!4
parents 73e0b760 54574e35
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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" ]
 No newline at end of file
+38 −0
Original line number Diff line number Diff line
@@ -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":
+4 −0
Original line number Diff line number Diff line
@@ -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(),
+14 −1
Original line number Diff line number Diff line
@@ -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,
    });
  }

  /**
+85 −0
Original line number Diff line number Diff line
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_();
Loading