refactor(logging): migrate from CCLog to structured Logger

This commit is contained in:
2025-11-21 14:23:10 +08:00
parent 3287661318
commit cf7ddefc2e
3 changed files with 611 additions and 633 deletions

View File

@@ -1,4 +1,3 @@
import { CCLog, DAY, LogLevel } from "@/lib/ccLog";
import { ToastConfig, UserGroupConfig, loadConfig } from "./config"; import { ToastConfig, UserGroupConfig, loadConfig } from "./config";
import { createAccessControlCli } from "./cli"; import { createAccessControlCli } from "./cli";
import { launchAccessControlTUI } from "./tui"; import { launchAccessControlTUI } from "./tui";
@@ -7,14 +6,31 @@ import { ReadWriteLock } from "@/lib/mutex/ReadWriteLock";
import { ChatManager } from "@/lib/ChatManager"; import { ChatManager } from "@/lib/ChatManager";
import { gTimerManager } from "@/lib/TimerManager"; import { gTimerManager } from "@/lib/TimerManager";
import { KeyEvent, pullEventAs } from "@/lib/event"; import { KeyEvent, pullEventAs } from "@/lib/event";
import {
ConditionalStream,
ConsoleStream,
DAY,
FileStream,
Logger,
LogLevel,
processor,
textRenderer,
} from "@/lib/ccStructLog";
const args = [...$vararg]; const args = [...$vararg];
// Init Log // Init Log
const logger = new CCLog("accesscontrol.log", { let isOnConsoleStream = true;
printTerminal: true, const logger = new Logger({
logInterval: DAY, processors: [
outputMinLevel: LogLevel.Info, processor.filterByLevel(LogLevel.Info),
processor.addTimestamp(),
],
renderer: textRenderer,
streams: [
new ConditionalStream(new ConsoleStream(), () => isOnConsoleStream),
new FileStream("accesscontrol.log", DAY),
],
}); });
// Load Config // Load Config
@@ -26,7 +42,7 @@ logger.debug(textutils.serialise(config, { allow_repetitions: true }));
// Peripheral // Peripheral
const playerDetector = peripheral.find( const playerDetector = peripheral.find(
"playerDetector", "playerDetector",
)[0] as PlayerDetectorPeripheral; )[0] as PlayerDetectorPeripheral;
const chatBox = peripheral.find("chatBox")[0] as ChatBoxPeripheral; const chatBox = peripheral.find("chatBox")[0] as ChatBoxPeripheral;
const chatManager: ChatManager = new ChatManager([chatBox]); const chatManager: ChatManager = new ChatManager([chatBox]);
@@ -37,410 +53,419 @@ let gWatchPlayersInfo: { name: string; hasNoticeTimes: number }[] = [];
let gIsRunning = true; let gIsRunning = true;
interface ParseParams { interface ParseParams {
playerName?: string; playerName?: string;
groupName?: string; groupName?: string;
info?: PlayerInfo; info?: PlayerInfo;
} }
function reloadConfig() { function reloadConfig() {
let releaser = configLock.tryAcquireWrite(); let releaser = configLock.tryAcquireWrite();
while (releaser === undefined) { while (releaser === undefined) {
sleep(1); sleep(1);
releaser = configLock.tryAcquireWrite(); releaser = configLock.tryAcquireWrite();
} }
config = loadConfig(configFilepath)!; config = loadConfig(configFilepath)!;
gInRangePlayers = []; gInRangePlayers = [];
gWatchPlayersInfo = []; gWatchPlayersInfo = [];
releaser.release(); releaser.release();
logger.info("Reload config successfully!"); logger.info("Reload config successfully!");
} }
function safeParseTextComponent( function safeParseTextComponent(
component: MinecraftTextComponent, component: MinecraftTextComponent,
params?: ParseParams, params?: ParseParams,
): MinecraftTextComponent { ): MinecraftTextComponent {
const newComponent = deepCopy(component); const newComponent = deepCopy(component);
if (newComponent.text == undefined) { if (newComponent.text == undefined) {
newComponent.text = "Wrong text, please contanct with admin"; newComponent.text = "Wrong text, please contanct with admin";
} else if (newComponent.text.includes("%")) { } else if (newComponent.text.includes("%")) {
newComponent.text = newComponent.text.replace( newComponent.text = newComponent.text.replace(
"%playerName%", "%playerName%",
params?.playerName ?? "UnknowPlayer", params?.playerName ?? "UnknowPlayer",
); );
newComponent.text = newComponent.text.replace( newComponent.text = newComponent.text.replace(
"%groupName%", "%groupName%",
params?.groupName ?? "UnknowGroup", params?.groupName ?? "UnknowGroup",
); );
newComponent.text = newComponent.text.replace( newComponent.text = newComponent.text.replace(
"%playerPosX%", "%playerPosX%",
params?.info?.x.toString() ?? "UnknowPosX", params?.info?.x.toString() ?? "UnknowPosX",
); );
newComponent.text = newComponent.text.replace( newComponent.text = newComponent.text.replace(
"%playerPosY%", "%playerPosY%",
params?.info?.y.toString() ?? "UnknowPosY", params?.info?.y.toString() ?? "UnknowPosY",
); );
newComponent.text = newComponent.text.replace( newComponent.text = newComponent.text.replace(
"%playerPosZ%", "%playerPosZ%",
params?.info?.z.toString() ?? "UnknowPosZ", params?.info?.z.toString() ?? "UnknowPosZ",
); );
} }
return newComponent; return newComponent;
} }
function sendMessage( function sendMessage(
toastConfig: ToastConfig, toastConfig: ToastConfig,
targetPlayer: string, targetPlayer: string,
params: ParseParams, params: ParseParams,
) { ) {
let releaser = configLock.tryAcquireRead(); let releaser = configLock.tryAcquireRead();
while (releaser === undefined) { while (releaser === undefined) {
sleep(0.1); sleep(0.1);
releaser = configLock.tryAcquireRead(); releaser = configLock.tryAcquireRead();
} }
chatManager.sendMessage({ chatManager.sendMessage({
message: safeParseTextComponent( message: safeParseTextComponent(
toastConfig.msg ?? config.welcomeToastConfig.msg, toastConfig.msg ?? config.welcomeToastConfig.msg,
params, params,
), ),
prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix, prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix,
brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets, brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets,
bracketColor: bracketColor:
toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor, toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor,
targetPlayer: targetPlayer, targetPlayer: targetPlayer,
utf8Support: true, utf8Support: true,
}); });
releaser.release(); releaser.release();
} }
function sendToast( function sendToast(
toastConfig: ToastConfig, toastConfig: ToastConfig,
targetPlayer: string, targetPlayer: string,
params: ParseParams, params: ParseParams,
) { ) {
let releaser = configLock.tryAcquireRead(); let releaser = configLock.tryAcquireRead();
while (releaser === undefined) { while (releaser === undefined) {
sleep(0.1); sleep(0.1);
releaser = configLock.tryAcquireRead(); releaser = configLock.tryAcquireRead();
} }
chatManager.sendToast({ chatManager.sendToast({
message: safeParseTextComponent( message: safeParseTextComponent(
toastConfig.msg ?? config.welcomeToastConfig.msg, toastConfig.msg ?? config.welcomeToastConfig.msg,
params, params,
), ),
title: safeParseTextComponent( title: safeParseTextComponent(
toastConfig.title ?? config.welcomeToastConfig.title, toastConfig.title ?? config.welcomeToastConfig.title,
params, params,
), ),
prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix, prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix,
brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets, brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets,
bracketColor: bracketColor:
toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor, toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor,
targetPlayer: targetPlayer, targetPlayer: targetPlayer,
utf8Support: true, utf8Support: true,
}); });
releaser.release(); releaser.release();
} }
function sendNotice(player: string, playerInfo?: PlayerInfo) { function sendNotice(player: string, playerInfo?: PlayerInfo) {
let releaser = configLock.tryAcquireRead(); let releaser = configLock.tryAcquireRead();
while (releaser === undefined) { while (releaser === undefined) {
sleep(0.1); sleep(0.1);
releaser = configLock.tryAcquireRead(); releaser = configLock.tryAcquireRead();
} }
const onlinePlayers = playerDetector.getOnlinePlayers(); const onlinePlayers = playerDetector.getOnlinePlayers();
const noticeTargetPlayers = config.adminGroupConfig.groupUsers.concat( const noticeTargetPlayers = config.adminGroupConfig.groupUsers.concat(
config.usersGroups config.usersGroups
.filter((value) => value.isNotice) .filter((value) => value.isNotice)
.flatMap((value) => value.groupUsers ?? []), .flatMap((value) => value.groupUsers ?? []),
); );
logger.debug(`noticeTargetPlayers: ${noticeTargetPlayers.join(", ")}`); logger.debug(`noticeTargetPlayers: ${noticeTargetPlayers.join(", ")}`);
for (const targetPlayer of noticeTargetPlayers) { for (const targetPlayer of noticeTargetPlayers) {
if (!onlinePlayers.includes(targetPlayer)) continue; if (!onlinePlayers.includes(targetPlayer)) continue;
sendToast(config.noticeToastConfig, targetPlayer, { sendToast(config.noticeToastConfig, targetPlayer, {
playerName: player, playerName: player,
info: playerInfo, info: playerInfo,
}); });
sleep(1); sleep(1);
} }
releaser.release(); releaser.release();
} }
function sendWarn(player: string) { function sendWarn(player: string) {
const warnMsg = `Not Allowed Player ${player} Break in Home `; const warnMsg = `Not Allowed Player ${player} Break in Home `;
logger.warn(warnMsg); logger.warn(warnMsg);
let releaser = configLock.tryAcquireRead();
while (releaser === undefined) {
sleep(0.1);
releaser = configLock.tryAcquireRead();
}
sendToast(config.warnToastConfig, player, { playerName: player });
chatManager.sendMessage({
message: safeParseTextComponent(config.warnToastConfig.msg, {
playerName: player,
}),
targetPlayer: player,
prefix: "AccessControl",
brackets: "[]",
utf8Support: true,
});
releaser.release();
}
function watchLoop() {
while (gIsRunning) {
const releaser = configLock.tryAcquireRead();
if (releaser === undefined) {
os.sleep(1);
continue;
}
const watchPlayerNames = gWatchPlayersInfo.flatMap((value) => value.name);
logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`);
for (const player of gWatchPlayersInfo) {
const playerInfo = playerDetector.getPlayerPos(player.name);
if (gInRangePlayers.includes(player.name)) {
// Notice
if (player.hasNoticeTimes < config.noticeTimes) {
sendNotice(player.name, playerInfo);
player.hasNoticeTimes += 1;
}
// Warn
if (config.isWarn) sendWarn(player.name);
// Record
logger.warn(
`Stranger ${player.name} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
} else {
// Get rid of player from list
gWatchPlayersInfo = gWatchPlayersInfo.filter(
(value) => value.name != player.name,
);
logger.info(
`Stranger ${player.name} has left the range at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
}
os.sleep(1);
}
releaser.release();
os.sleep(config.watchInterval);
}
}
function mainLoop() {
while (gIsRunning) {
const releaser = configLock.tryAcquireRead();
if (releaser === undefined) {
os.sleep(0.1);
continue;
}
const players = playerDetector.getPlayersInRange(config.detectRange);
const playersList = "[ " + players.join(",") + " ]";
logger.debug(`Detected ${players.length} players: ${playersList}`);
for (const player of players) {
if (gInRangePlayers.includes(player)) continue;
// Get player Info
const playerInfo = playerDetector.getPlayerPos(player);
if (config.adminGroupConfig.groupUsers.includes(player)) {
logger.info(
`Admin ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
if (config.adminGroupConfig.isWelcome)
sendMessage(config.welcomeToastConfig, player, {
playerName: player,
groupName: "Admin",
info: playerInfo,
});
continue;
}
// New player appear
let groupConfig: UserGroupConfig = {
groupName: "Unfamiliar",
groupUsers: [],
isAllowed: false,
isNotice: false,
isWelcome: false,
};
// Get user group config
for (const userGroupConfig of config.usersGroups) {
if (userGroupConfig.groupUsers == undefined) continue;
if (!userGroupConfig.groupUsers.includes(player)) continue;
groupConfig = userGroupConfig;
logger.info(
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
if (userGroupConfig.isWelcome)
sendMessage(config.welcomeToastConfig, player, {
playerName: player,
groupName: groupConfig.groupName,
info: playerInfo,
});
break;
}
if (groupConfig.isAllowed) continue;
logger.warn(
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
if (config.isWelcome)
sendMessage(config.welcomeToastConfig, player, {
playerName: player,
groupName: groupConfig.groupName,
info: playerInfo,
});
if (config.isWarn) sendWarn(player);
gWatchPlayersInfo = [
...gWatchPlayersInfo,
{ name: player, hasNoticeTimes: 0 },
];
}
gInRangePlayers = players;
releaser.release();
os.sleep(config.detectInterval);
}
}
function keyboardLoop() {
while (gIsRunning) {
const event = pullEventAs(KeyEvent, "key");
if (event === undefined) continue;
if (event.key === keys.c) {
logger.info("Launching Access Control TUI...");
try {
logger.setInTerminal(false);
launchAccessControlTUI();
logger.info("TUI closed, resuming normal operation");
} catch (error) {
logger.error(`TUI error: ${textutils.serialise(error as object)}`);
} finally {
logger.setInTerminal(true);
reloadConfig();
}
} else if (event.key === keys.r) {
reloadConfig();
}
// else if (event.key === keys.q) {
// gIsRunning = false;
// }
}
}
function cliLoop() {
let printTargetPlayer: string | undefined;
const cli = createAccessControlCli({
configFilepath: configFilepath,
reloadConfig: () => reloadConfig(),
logger: logger,
print: (msg) =>
chatManager.sendMessage({
message: msg,
targetPlayer: printTargetPlayer,
prefix: "Access Control System",
brackets: "[]",
utf8Support: true,
}),
});
while (gIsRunning) {
const result = chatManager.getReceivedMessage();
if (result.isErr()) {
sleep(0.5);
continue;
}
logger.debug(`Received message: ${result.value.message}`);
const ev = result.value;
let releaser = configLock.tryAcquireRead(); let releaser = configLock.tryAcquireRead();
while (releaser === undefined) { while (releaser === undefined) {
sleep(0.1); sleep(0.1);
releaser = configLock.tryAcquireRead(); releaser = configLock.tryAcquireRead();
} }
const isAdmin = config.adminGroupConfig.groupUsers.includes(ev.username); sendToast(config.warnToastConfig, player, { playerName: player });
chatManager.sendMessage({
message: safeParseTextComponent(config.warnToastConfig.msg, {
playerName: player,
}),
targetPlayer: player,
prefix: "AccessControl",
brackets: "[]",
utf8Support: true,
});
releaser.release(); releaser.release();
if (!isAdmin) continue; }
if (!ev.message.startsWith("@AC")) continue;
printTargetPlayer = ev.username; function watchLoop() {
logger.info( while (gIsRunning) {
`Received command "${ev.message}" from admin ${printTargetPlayer}`, const releaser = configLock.tryAcquireRead();
); if (releaser === undefined) {
os.sleep(1);
continue;
}
const commandArgs = ev.message const watchPlayerNames = gWatchPlayersInfo.flatMap(
.substring(3) (value) => value.name,
.split(" ") );
.filter((s) => s.length > 0); logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`);
logger.debug(`Command arguments: ${commandArgs.join(", ")}`); for (const player of gWatchPlayersInfo) {
const playerInfo = playerDetector.getPlayerPos(player.name);
if (gInRangePlayers.includes(player.name)) {
// Notice
if (player.hasNoticeTimes < config.noticeTimes) {
sendNotice(player.name, playerInfo);
player.hasNoticeTimes += 1;
}
cli(commandArgs); // Warn
printTargetPlayer = undefined; if (config.isWarn) sendWarn(player.name);
}
// Record
logger.warn(
`Stranger ${player.name} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
} else {
// Get rid of player from list
gWatchPlayersInfo = gWatchPlayersInfo.filter(
(value) => value.name != player.name,
);
logger.info(
`Stranger ${player.name} has left the range at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
}
os.sleep(1);
}
releaser.release();
os.sleep(config.watchInterval);
}
}
function mainLoop() {
while (gIsRunning) {
const releaser = configLock.tryAcquireRead();
if (releaser === undefined) {
os.sleep(0.1);
continue;
}
const players = playerDetector.getPlayersInRange(config.detectRange);
const playersList = "[ " + players.join(",") + " ]";
logger.debug(`Detected ${players.length} players: ${playersList}`);
for (const player of players) {
if (gInRangePlayers.includes(player)) continue;
// Get player Info
const playerInfo = playerDetector.getPlayerPos(player);
if (config.adminGroupConfig.groupUsers.includes(player)) {
logger.info(
`Admin ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
if (config.adminGroupConfig.isWelcome)
sendMessage(config.welcomeToastConfig, player, {
playerName: player,
groupName: "Admin",
info: playerInfo,
});
continue;
}
// New player appear
let groupConfig: UserGroupConfig = {
groupName: "Unfamiliar",
groupUsers: [],
isAllowed: false,
isNotice: false,
isWelcome: false,
};
// Get user group config
for (const userGroupConfig of config.usersGroups) {
if (userGroupConfig.groupUsers == undefined) continue;
if (!userGroupConfig.groupUsers.includes(player)) continue;
groupConfig = userGroupConfig;
logger.info(
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
if (userGroupConfig.isWelcome)
sendMessage(config.welcomeToastConfig, player, {
playerName: player,
groupName: groupConfig.groupName,
info: playerInfo,
});
break;
}
if (groupConfig.isAllowed) continue;
logger.warn(
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
if (config.isWelcome)
sendMessage(config.welcomeToastConfig, player, {
playerName: player,
groupName: groupConfig.groupName,
info: playerInfo,
});
if (config.isWarn) sendWarn(player);
gWatchPlayersInfo = [
...gWatchPlayersInfo,
{ name: player, hasNoticeTimes: 0 },
];
}
gInRangePlayers = players;
releaser.release();
os.sleep(config.detectInterval);
}
}
function keyboardLoop() {
while (gIsRunning) {
const event = pullEventAs(KeyEvent, "key");
if (event === undefined) continue;
if (event.key === keys.c) {
logger.info("Launching Access Control TUI...");
try {
isOnConsoleStream = false;
launchAccessControlTUI();
logger.info("TUI closed, resuming normal operation");
} catch (error) {
logger.error(
`TUI error: ${textutils.serialise(error as object)}`,
);
} finally {
isOnConsoleStream = true;
reloadConfig();
}
} else if (event.key === keys.r) {
reloadConfig();
}
// else if (event.key === keys.q) {
// gIsRunning = false;
// }
}
}
function cliLoop() {
let printTargetPlayer: string | undefined;
const cli = createAccessControlCli({
configFilepath: configFilepath,
reloadConfig: () => reloadConfig(),
logger: logger,
print: (msg) =>
chatManager.sendMessage({
message: msg,
targetPlayer: printTargetPlayer,
prefix: "Access Control System",
brackets: "[]",
utf8Support: true,
}),
});
while (gIsRunning) {
const result = chatManager.getReceivedMessage();
if (result.isErr()) {
sleep(0.5);
continue;
}
logger.debug(`Received message: ${result.value.message}`);
const ev = result.value;
let releaser = configLock.tryAcquireRead();
while (releaser === undefined) {
sleep(0.1);
releaser = configLock.tryAcquireRead();
}
const isAdmin = config.adminGroupConfig.groupUsers.includes(
ev.username,
);
releaser.release();
if (!isAdmin) continue;
if (!ev.message.startsWith("@AC")) continue;
printTargetPlayer = ev.username;
logger.info(
`Received command "${ev.message}" from admin ${printTargetPlayer}`,
);
const commandArgs = ev.message
.substring(3)
.split(" ")
.filter((s) => s.length > 0);
logger.debug(`Command arguments: ${commandArgs.join(", ")}`);
cli(commandArgs);
printTargetPlayer = undefined;
}
} }
function main(args: string[]) { function main(args: string[]) {
logger.info("Starting access control system, get args: " + args.join(", ")); logger.info("Starting access control system, get args: " + args.join(", "));
if (args.length == 1) { if (args.length == 1) {
if (args[0] == "start") { if (args[0] == "start") {
const tutorial: string[] = []; const tutorial: string[] = [];
tutorial.push("Access Control System started."); tutorial.push("Access Control System started.");
tutorial.push("\tPress 'c' to open configuration TUI."); tutorial.push("\tPress 'c' to open configuration TUI.");
tutorial.push("\tPress 'r' to reload configuration."); tutorial.push("\tPress 'r' to reload configuration.");
print(tutorial.join("\n")); print(tutorial.join("\n"));
parallel.waitForAll( parallel.waitForAll(
() => mainLoop(), () => mainLoop(),
() => gTimerManager.run(), () => gTimerManager.run(),
() => cliLoop(), () => cliLoop(),
() => watchLoop(), () => watchLoop(),
() => keyboardLoop(), () => keyboardLoop(),
() => chatManager.run(), () => chatManager.run(),
); );
return; return;
} else if (args[0] == "config") { } else if (args[0] == "config") {
logger.info("Launching Access Control TUI..."); logger.info("Launching Access Control TUI...");
logger.setInTerminal(false); isOnConsoleStream = false;
try {
launchAccessControlTUI(); try {
} catch (error) { launchAccessControlTUI();
logger.error(`TUI error: ${textutils.serialise(error as object)}`); } catch (error) {
} logger.error(
return; `TUI error: ${textutils.serialise(error as object)}`,
);
}
return;
}
} }
}
print(`Usage: accesscontrol start | config`); print(`Usage: accesscontrol start | config`);
print(" start - Start the access control system with monitoring"); print(" start - Start the access control system with monitoring");
print(" config - Open configuration TUI"); print(" config - Open configuration TUI");
} }
try { try {
main(args); main(args);
} catch (error: unknown) { } catch (error: unknown) {
logger.error(textutils.serialise(error as object)); logger.error(textutils.serialise(error as object));
} finally { } finally {
logger.close(); logger.close();
} }

View File

@@ -1,215 +1,242 @@
import { import {
CraftManager, CraftManager,
CraftRecipe, CraftRecipe,
CreatePackageTag, CreatePackageTag,
} from "@/lib/CraftManager"; } from "@/lib/CraftManager";
import { CCLog, LogLevel } from "@/lib/ccLog";
import { Queue } from "@/lib/datatype/Queue"; import { Queue } from "@/lib/datatype/Queue";
import {
ConsoleStream,
Logger,
LogLevel,
processor,
textRenderer,
} from "@/lib/ccStructLog";
const logger = new CCLog("autocraft.log", { outputMinLevel: LogLevel.Info }); const logger = new Logger({
processors: [
processor.filterByLevel(LogLevel.Info),
processor.addFullTimestamp(),
],
renderer: textRenderer,
streams: [new ConsoleStream()],
});
const peripheralsNames = { const peripheralsNames = {
// packsInventory: "minecraft:chest_14", // packsInventory: "minecraft:chest_14",
// itemsInventory: "minecraft:chest_15", // itemsInventory: "minecraft:chest_15",
// packageExtractor: "create:packager_3", // packageExtractor: "create:packager_3",
blockReader: "bottom", blockReader: "bottom",
wiredModem: "right", wiredModem: "right",
redstone: "left", redstone: "left",
packsInventory: "minecraft:chest_1121", packsInventory: "minecraft:chest_1121",
itemsInventory: "minecraft:chest_1120", itemsInventory: "minecraft:chest_1120",
packageExtractor: "create:packager_0", packageExtractor: "create:packager_0",
}; };
const packsInventory = peripheral.wrap( const packsInventory = peripheral.wrap(
peripheralsNames.packsInventory, peripheralsNames.packsInventory,
) as InventoryPeripheral; ) as InventoryPeripheral;
const itemsInventory = peripheral.wrap( const itemsInventory = peripheral.wrap(
peripheralsNames.itemsInventory, peripheralsNames.itemsInventory,
) as InventoryPeripheral; ) as InventoryPeripheral;
const packageExtractor = peripheral.wrap( const packageExtractor = peripheral.wrap(
peripheralsNames.packageExtractor, peripheralsNames.packageExtractor,
) as InventoryPeripheral; ) as InventoryPeripheral;
const blockReader = peripheral.wrap( const blockReader = peripheral.wrap(
peripheralsNames.blockReader, peripheralsNames.blockReader,
) as BlockReaderPeripheral; ) as BlockReaderPeripheral;
const wiredModem = peripheral.wrap( const wiredModem = peripheral.wrap(
peripheralsNames.wiredModem, peripheralsNames.wiredModem,
) as WiredModemPeripheral; ) as WiredModemPeripheral;
const turtleLocalName = wiredModem.getNameLocal(); const turtleLocalName = wiredModem.getNameLocal();
enum State { enum State {
IDLE, IDLE,
READ_RECIPE, READ_RECIPE,
CRAFT_OUTPUT, CRAFT_OUTPUT,
} }
function main() { function main() {
const craftManager = new CraftManager(turtleLocalName, itemsInventory); const craftManager = new CraftManager(turtleLocalName, itemsInventory);
const recipesQueue = new Queue<CraftRecipe>(); const recipesQueue = new Queue<CraftRecipe>();
const recipesWaitingMap = new Map<number, CraftRecipe[] | CraftRecipe>(); const recipesWaitingMap = new Map<number, CraftRecipe[] | CraftRecipe>();
let currentState = State.IDLE; let currentState = State.IDLE;
let nextState = State.IDLE; let nextState = State.IDLE;
let hasPackage = redstone.getInput(peripheralsNames.redstone); let hasPackage = redstone.getInput(peripheralsNames.redstone);
while (hasPackage) { while (hasPackage) {
hasPackage = redstone.getInput(peripheralsNames.redstone);
logger.warn("redstone activated when init, please clear inventory");
sleep(1);
}
logger.info("AutoCraft init finished...");
while (true) {
// Switch state
switch (currentState) {
case State.IDLE: {
nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
break;
}
case State.READ_RECIPE: {
nextState = hasPackage ? State.READ_RECIPE : State.CRAFT_OUTPUT;
break;
}
case State.CRAFT_OUTPUT: {
nextState =
recipesQueue.size() > 0
? State.CRAFT_OUTPUT
: hasPackage
? State.READ_RECIPE
: State.IDLE;
break;
}
default: {
logger.error(`Unknown state`);
nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
break;
}
}
// State logic
switch (currentState) {
case State.IDLE: {
if (!hasPackage) os.pullEvent("redstone");
hasPackage = redstone.getInput(peripheralsNames.redstone); hasPackage = redstone.getInput(peripheralsNames.redstone);
break; logger.warn("redstone activated when init, please clear inventory");
}
case State.READ_RECIPE: {
logger.info(`Package detected`);
const packagesInfoRecord = packsInventory.list();
for (const key in packagesInfoRecord) {
const slotNum = parseInt(key);
packsInventory.pushItems(turtleLocalName, slotNum);
// Get package NBT
logger.debug(
`Turtle:\n${textutils.serialise(blockReader.getBlockData()!, { allow_repetitions: true })}`,
);
const packageDetailInfo = blockReader.getBlockData()?.Items[1];
if (packageDetailInfo === undefined) {
logger.error(`Package detail info not found`);
continue;
}
// Get OrderId and isFinal
const packageOrderId = (packageDetailInfo.tag as CreatePackageTag)
.Fragment.OrderId;
const packageIsFinal =
(packageDetailInfo.tag as CreatePackageTag).Fragment.IsFinal > 0
? true
: false;
// Get recipe
const packageRecipes =
CraftManager.getPackageRecipe(packageDetailInfo);
if (packageRecipes.isSome()) {
if (packageIsFinal) recipesQueue.enqueue(packageRecipes.value);
else recipesWaitingMap.set(packageOrderId, packageRecipes.value);
} else {
if (packageIsFinal && recipesWaitingMap.has(packageOrderId)) {
recipesQueue.enqueue(recipesWaitingMap.get(packageOrderId)!);
recipesWaitingMap.delete(packageOrderId);
} else {
logger.debug(`No recipe, just pass`);
}
}
packageExtractor.pullItems(turtleLocalName, 1);
}
if (
currentState === State.READ_RECIPE &&
nextState === State.CRAFT_OUTPUT
) {
craftManager.initItemsMap();
}
break;
}
case State.CRAFT_OUTPUT: {
// Check recipe
const recipe = recipesQueue.dequeue();
if (recipe === undefined) break;
let restCraftCnt = recipe.Count;
let maxSignleCraftCnt = restCraftCnt;
let craftItemDetail: ItemDetail | undefined = undefined;
do {
// Clear workbench
craftManager.clearTurtle();
logger.info(`Pull items according to a recipe`);
const craftCnt = craftManager
.pullItemsWithRecipe(recipe, maxSignleCraftCnt)
.unwrapOrElse((error) => {
logger.error(error.message);
return 0;
});
if (craftCnt == 0) break;
if (craftCnt < maxSignleCraftCnt) maxSignleCraftCnt = craftCnt;
const craftRet = craftManager.craft(maxSignleCraftCnt);
craftItemDetail ??= craftRet;
logger.info(`Craft ${craftCnt} times`);
restCraftCnt -= craftCnt;
} while (restCraftCnt > 0);
// Finally output
if (restCraftCnt > 0) {
logger.warn(
`Only craft ${recipe.Count - restCraftCnt}x ${craftItemDetail?.name ?? "UnknownItem"}`,
);
} else {
logger.info(
`Finish craft ${recipe.Count}x ${craftItemDetail?.name ?? "UnknownItem"}`,
);
}
// Clear workbench and inventory
const turtleItemSlots = Object.values(
blockReader.getBlockData()!.Items,
).map((val) => val.Slot + 1);
craftManager.clearTurtle(turtleItemSlots);
break;
}
default: {
sleep(1); sleep(1);
break;
}
} }
// Check packages logger.info("AutoCraft init finished...");
hasPackage = redstone.getInput(peripheralsNames.redstone); while (true) {
// State update // Switch state
currentState = nextState; switch (currentState) {
} case State.IDLE: {
nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
break;
}
case State.READ_RECIPE: {
nextState = hasPackage ? State.READ_RECIPE : State.CRAFT_OUTPUT;
break;
}
case State.CRAFT_OUTPUT: {
nextState =
recipesQueue.size() > 0
? State.CRAFT_OUTPUT
: hasPackage
? State.READ_RECIPE
: State.IDLE;
break;
}
default: {
logger.error(`Unknown state`);
nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
break;
}
}
// State logic
switch (currentState) {
case State.IDLE: {
if (!hasPackage) os.pullEvent("redstone");
hasPackage = redstone.getInput(peripheralsNames.redstone);
break;
}
case State.READ_RECIPE: {
logger.info(`Package detected`);
const packagesInfoRecord = packsInventory.list();
for (const key in packagesInfoRecord) {
const slotNum = parseInt(key);
packsInventory.pushItems(turtleLocalName, slotNum);
// Get package NBT
logger.debug(
`Turtle:\n${textutils.serialise(blockReader.getBlockData()!, { allow_repetitions: true })}`,
);
const packageDetailInfo =
blockReader.getBlockData()?.Items[1];
if (packageDetailInfo === undefined) {
logger.error(`Package detail info not found`);
continue;
}
// Get OrderId and isFinal
const packageOrderId = (
packageDetailInfo.tag as CreatePackageTag
).Fragment.OrderId;
const packageIsFinal =
(packageDetailInfo.tag as CreatePackageTag).Fragment
.IsFinal > 0
? true
: false;
// Get recipe
const packageRecipes =
CraftManager.getPackageRecipe(packageDetailInfo);
if (packageRecipes.isSome()) {
if (packageIsFinal)
recipesQueue.enqueue(packageRecipes.value);
else
recipesWaitingMap.set(
packageOrderId,
packageRecipes.value,
);
} else {
if (
packageIsFinal &&
recipesWaitingMap.has(packageOrderId)
) {
recipesQueue.enqueue(
recipesWaitingMap.get(packageOrderId)!,
);
recipesWaitingMap.delete(packageOrderId);
} else {
logger.debug(`No recipe, just pass`);
}
}
packageExtractor.pullItems(turtleLocalName, 1);
}
if (
currentState === State.READ_RECIPE &&
nextState === State.CRAFT_OUTPUT
) {
craftManager.initItemsMap();
}
break;
}
case State.CRAFT_OUTPUT: {
// Check recipe
const recipe = recipesQueue.dequeue();
if (recipe === undefined) break;
let restCraftCnt = recipe.Count;
let maxSignleCraftCnt = restCraftCnt;
let craftItemDetail: ItemDetail | undefined = undefined;
do {
// Clear workbench
craftManager.clearTurtle();
logger.info(`Pull items according to a recipe`);
const craftCnt = craftManager
.pullItemsWithRecipe(recipe, maxSignleCraftCnt)
.unwrapOrElse((error) => {
logger.error(error.message);
return 0;
});
if (craftCnt == 0) break;
if (craftCnt < maxSignleCraftCnt)
maxSignleCraftCnt = craftCnt;
const craftRet = craftManager.craft(maxSignleCraftCnt);
craftItemDetail ??= craftRet;
logger.info(`Craft ${craftCnt} times`);
restCraftCnt -= craftCnt;
} while (restCraftCnt > 0);
// Finally output
if (restCraftCnt > 0) {
logger.warn(
`Only craft ${recipe.Count - restCraftCnt}x ${craftItemDetail?.name ?? "UnknownItem"}`,
);
} else {
logger.info(
`Finish craft ${recipe.Count}x ${craftItemDetail?.name ?? "UnknownItem"}`,
);
}
// Clear workbench and inventory
const turtleItemSlots = Object.values(
blockReader.getBlockData()!.Items,
).map((val) => val.Slot + 1);
craftManager.clearTurtle(turtleItemSlots);
break;
}
default: {
sleep(1);
break;
}
}
// Check packages
hasPackage = redstone.getInput(peripheralsNames.redstone);
// State update
currentState = nextState;
}
} }
try { try {
main(); main();
} catch (error: unknown) { } catch (error: unknown) {
logger.error(textutils.serialise(error as object)); logger.error(textutils.serialise(error as object));
} finally { } finally {
logger.close(); logger.close();
} }

View File

@@ -7,56 +7,16 @@
import { import {
Logger, Logger,
createDevLogger, processor,
createProdLogger,
// Processors
addTimestamp,
addFormattedTimestamp,
addFullTimestamp,
addSource,
addComputerId,
addStaticFields,
transformField,
// Renderers
textRenderer,
jsonRenderer,
// Streams
ConsoleStream, ConsoleStream,
FileStream, FileStream,
BufferStream, BufferStream,
DAY, ConditionalStream,
HOUR, HOUR,
jsonRenderer,
LogLevel, LogLevel,
textRenderer,
} from "../lib/ccStructLog"; } from "../lib/ccStructLog";
import { ConditionalStream } from "@/lib/ccStructLog/streams";
// =============================================================================
// Basic Usage Examples
// =============================================================================
print("=== Basic Usage Examples ===");
// 1. Quick start with pre-configured loggers
const devLog = createDevLogger();
devLog.info("Application started", { version: "1.0.0", port: 8080 });
devLog.debug("Debug information", { userId: 123, action: "login" });
devLog.error("Something went wrong", {
error: "Connection failed",
retries: 3,
});
// 2. Production logging to file
const prodLog = createProdLogger("app.log", {
source: "MyApplication",
rotationInterval: DAY,
includeConsole: true,
});
prodLog.info("User action", { userId: 456, action: "purchase", amount: 29.99 });
prodLog.warn("Low disk space", { available: 1024, threshold: 2048 });
// ============================================================================= // =============================================================================
// Custom Logger Configurations // Custom Logger Configurations
@@ -67,10 +27,10 @@ print("\n=== Custom Logger Configurations ===");
// 4. Custom logger with specific processors and renderer // 4. Custom logger with specific processors and renderer
const customLogger = new Logger({ const customLogger = new Logger({
processors: [ processors: [
addFullTimestamp(), processor.addTimestamp(),
addComputerId(), processor.addComputerId(),
addSource("CustomApp"), processor.addSource("CustomApp"),
addStaticFields({ processor.addStaticFields({
environment: "development", environment: "development",
version: "2.1.0", version: "2.1.0",
}), }),
@@ -109,10 +69,10 @@ const sanitizePasswords = (event: Map<string, unknown>) => {
const secureLogger = new Logger({ const secureLogger = new Logger({
processors: [ processors: [
addTimestamp(), processor.addTimestamp(),
addRequestId, addRequestId,
sanitizePasswords, sanitizePasswords,
transformField("message", (msg) => `[SECURE] ${msg}`), processor.transformField("message", (msg) => `[SECURE] ${msg}`),
], ],
renderer: jsonRenderer, renderer: jsonRenderer,
streams: [new ConsoleStream()], streams: [new ConsoleStream()],
@@ -133,7 +93,7 @@ print("\n=== Stream Examples ===");
// 11. Buffer stream for batch processing // 11. Buffer stream for batch processing
const bufferStream = new BufferStream(100); // Keep last 100 messages const bufferStream = new BufferStream(100); // Keep last 100 messages
const bufferLogger = new Logger({ const bufferLogger = new Logger({
processors: [addFormattedTimestamp()], processors: [processor.addTimestamp()],
renderer: textRenderer, renderer: textRenderer,
streams: [ streams: [
new ConditionalStream(new ConsoleStream(), (msg, event) => { new ConditionalStream(new ConsoleStream(), (msg, event) => {
@@ -162,7 +122,7 @@ for (const msg of bufferedMessages) {
// 12. Multi-stream with different formats // 12. Multi-stream with different formats
const multiFormatLogger = new Logger({ const multiFormatLogger = new Logger({
processors: [addFullTimestamp(), addComputerId()], processors: [processor.addTimestamp(), processor.addComputerId()],
renderer: (event) => "default", // This won't be used renderer: (event) => "default", // This won't be used
streams: [ streams: [
// Console with human-readable format // Console with human-readable format
@@ -196,7 +156,7 @@ print("\n=== Error Handling Examples ===");
// 13. Robust error handling // 13. Robust error handling
const robustLogger = new Logger({ const robustLogger = new Logger({
processors: [ processors: [
addTimestamp(), processor.addTimestamp(),
// Processor that might fail // Processor that might fail
(event) => { (event) => {
try { try {
@@ -231,7 +191,7 @@ print("\n=== Cleanup Examples ===");
// 14. Proper cleanup // 14. Proper cleanup
const fileLogger = new Logger({ const fileLogger = new Logger({
processors: [addTimestamp()], processors: [processor.addTimestamp()],
renderer: jsonRenderer, renderer: jsonRenderer,
streams: [new FileStream("temp.log")], streams: [new FileStream("temp.log")],
}); });
@@ -249,37 +209,3 @@ print("- all.log (complete log)");
print("- debug.log (detailed debug info)"); print("- debug.log (detailed debug info)");
print("- structured.log (JSON format)"); print("- structured.log (JSON format)");
print("- temp.log (temporary file, now closed)"); print("- temp.log (temporary file, now closed)");
// =============================================================================
// Performance Comparison (commented out to avoid noise)
// =============================================================================
/*
print("\n=== Performance Comparison ===");
const iterations = 1000;
// Test simple console logging
const startTime1 = os.clock();
const simpleLogger = createMinimalLogger();
for (let i = 0; i < iterations; i++) {
simpleLogger.info(`Simple message ${i}`);
}
const endTime1 = os.clock();
print(`Simple Console Logger: ${endTime1 - startTime1} seconds`);
// Test complex processor chain
const startTime2 = os.clock();
const complexLogger = createDetailedLogger("perf_test.log", {
source: "PerfTest"
});
for (let i = 0; i < iterations; i++) {
complexLogger.info(`Complex message ${i}`, {
iteration: i,
data: { nested: { value: i * 2 } }
});
}
complexLogger.close();
const endTime2 = os.clock();
print(`Complex Processor Chain: ${endTime2 - startTime2} seconds`);
*/