feat: 로그 시스템 구현 및 관련 클래스 추가
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
LogCategory,
|
||||
type LogEntryDraft,
|
||||
LogFormat,
|
||||
LogScope,
|
||||
} from './types.js';
|
||||
|
||||
export class ActionLogger {
|
||||
private readonly generalId: number | undefined;
|
||||
private readonly nationId: number | undefined;
|
||||
private readonly logs: LogEntryDraft[] = [];
|
||||
|
||||
constructor(options: { generalId?: number; nationId?: number } = {}) {
|
||||
this.generalId = options.generalId;
|
||||
this.nationId = options.nationId;
|
||||
}
|
||||
|
||||
// 장수/국가/전역 로그를 한번에 모아두고 외부에서 저장한다.
|
||||
public flush(): LogEntryDraft[] {
|
||||
const items = this.logs.splice(0, this.logs.length);
|
||||
return items;
|
||||
}
|
||||
|
||||
public rollback(): LogEntryDraft[] {
|
||||
const backup = this.logs.splice(0, this.logs.length);
|
||||
return backup;
|
||||
}
|
||||
|
||||
public pushGeneralHistoryLog(
|
||||
text: string | string[],
|
||||
format: LogFormat = LogFormat.YEAR_MONTH
|
||||
): void {
|
||||
this.pushBatch(text, (message) => ({
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.HISTORY,
|
||||
text: message,
|
||||
...(this.generalId !== undefined
|
||||
? { generalId: this.generalId }
|
||||
: {}),
|
||||
format,
|
||||
}));
|
||||
}
|
||||
|
||||
public pushGeneralActionLog(
|
||||
text: string | string[],
|
||||
format: LogFormat = LogFormat.MONTH
|
||||
): void {
|
||||
this.pushBatch(text, (message) => ({
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.ACTION,
|
||||
text: message,
|
||||
...(this.generalId !== undefined
|
||||
? { generalId: this.generalId }
|
||||
: {}),
|
||||
format,
|
||||
}));
|
||||
}
|
||||
|
||||
public pushGeneralBattleResultLog(
|
||||
text: string | string[],
|
||||
format: LogFormat = LogFormat.RAWTEXT
|
||||
): void {
|
||||
this.pushBatch(text, (message) => ({
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.BATTLE_BRIEF,
|
||||
text: message,
|
||||
...(this.generalId !== undefined
|
||||
? { generalId: this.generalId }
|
||||
: {}),
|
||||
format,
|
||||
}));
|
||||
}
|
||||
|
||||
public pushGeneralBattleDetailLog(
|
||||
text: string | string[],
|
||||
format: LogFormat = LogFormat.PLAIN
|
||||
): void {
|
||||
this.pushBatch(text, (message) => ({
|
||||
scope: LogScope.GENERAL,
|
||||
category: LogCategory.BATTLE_DETAIL,
|
||||
text: message,
|
||||
...(this.generalId !== undefined
|
||||
? { generalId: this.generalId }
|
||||
: {}),
|
||||
format,
|
||||
}));
|
||||
}
|
||||
|
||||
public pushNationHistoryLog(
|
||||
text: string | string[],
|
||||
format: LogFormat = LogFormat.YEAR_MONTH,
|
||||
nationId: number | undefined = this.nationId
|
||||
): void {
|
||||
if (!nationId) {
|
||||
return;
|
||||
}
|
||||
this.pushBatch(text, (message) => ({
|
||||
scope: LogScope.NATION,
|
||||
category: LogCategory.HISTORY,
|
||||
text: message,
|
||||
nationId,
|
||||
format,
|
||||
}));
|
||||
}
|
||||
|
||||
public pushGlobalHistoryLog(
|
||||
text: string | string[],
|
||||
format: LogFormat = LogFormat.YEAR_MONTH
|
||||
): void {
|
||||
this.pushBatch(text, (message) => ({
|
||||
scope: LogScope.SYSTEM,
|
||||
category: LogCategory.HISTORY,
|
||||
text: message,
|
||||
format,
|
||||
}));
|
||||
}
|
||||
|
||||
public pushGlobalActionLog(
|
||||
text: string | string[],
|
||||
format: LogFormat = LogFormat.MONTH
|
||||
): void {
|
||||
this.pushBatch(text, (message) => ({
|
||||
scope: LogScope.SYSTEM,
|
||||
category: LogCategory.SUMMARY,
|
||||
text: message,
|
||||
format,
|
||||
}));
|
||||
}
|
||||
|
||||
private pushBatch(
|
||||
text: string | string[],
|
||||
builder: (message: string) => LogEntryDraft
|
||||
): void {
|
||||
if (Array.isArray(text)) {
|
||||
for (const item of text) {
|
||||
if (item) {
|
||||
this.logs.push(builder(item));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
this.logs.push(builder(text));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { formatLogText } from './formatter.js';
|
||||
import {
|
||||
type LogContext,
|
||||
type LogEntryDraft,
|
||||
type LogEntryRecord,
|
||||
LogFormat,
|
||||
LogScope,
|
||||
} from './types.js';
|
||||
|
||||
const shouldDropEntry = (entry: LogEntryDraft): boolean => {
|
||||
if (entry.scope === LogScope.GENERAL && !entry.generalId) {
|
||||
return true;
|
||||
}
|
||||
if (entry.scope === LogScope.NATION && !entry.nationId) {
|
||||
return true;
|
||||
}
|
||||
if (entry.scope === LogScope.USER && !entry.userId) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const finalizeLogEntry = (
|
||||
entry: LogEntryDraft,
|
||||
context: LogContext
|
||||
): LogEntryRecord | null => {
|
||||
if (shouldDropEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const format = entry.format ?? LogFormat.RAWTEXT;
|
||||
const text = formatLogText(entry.text, format, context.year, context.month);
|
||||
|
||||
const record: LogEntryRecord = {
|
||||
scope: entry.scope,
|
||||
category: entry.category,
|
||||
text,
|
||||
year: context.year,
|
||||
month: context.month,
|
||||
};
|
||||
|
||||
if (entry.generalId !== undefined) {
|
||||
record.generalId = entry.generalId;
|
||||
}
|
||||
if (entry.nationId !== undefined) {
|
||||
record.nationId = entry.nationId;
|
||||
}
|
||||
if (entry.userId !== undefined) {
|
||||
record.userId = entry.userId;
|
||||
}
|
||||
if (entry.subType !== undefined) {
|
||||
record.subType = entry.subType;
|
||||
}
|
||||
if (entry.meta !== undefined) {
|
||||
record.meta = entry.meta;
|
||||
}
|
||||
if (context.at !== undefined) {
|
||||
record.createdAt = context.at;
|
||||
}
|
||||
|
||||
return record;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { LogFormat } from './types.js';
|
||||
|
||||
// 로그 포맷은 기존 표시 규칙(<C>/<S>/<R> + 기호)을 그대로 유지한다.
|
||||
export const formatLogText = (
|
||||
text: string,
|
||||
format: LogFormat,
|
||||
year: number,
|
||||
month: number
|
||||
): string => {
|
||||
switch (format) {
|
||||
case LogFormat.RAWTEXT:
|
||||
return text;
|
||||
case LogFormat.PLAIN:
|
||||
return `<C>●</>${text}`;
|
||||
case LogFormat.YEAR_MONTH:
|
||||
return `<C>●</>${year}년 ${month}월:${text}`;
|
||||
case LogFormat.YEAR:
|
||||
return `<C>●</>${year}년:${text}`;
|
||||
case LogFormat.MONTH:
|
||||
return `<C>●</>${month}월:${text}`;
|
||||
case LogFormat.EVENT_PLAIN:
|
||||
return `<S>◆</>${text}`;
|
||||
case LogFormat.EVENT_YEAR_MONTH:
|
||||
return `<S>◆</>${year}년 ${month}월:${text}`;
|
||||
case LogFormat.NOTICE:
|
||||
return `<R>★</>${text}`;
|
||||
case LogFormat.NOTICE_YEAR_MONTH:
|
||||
return `<R>★</>${year}년 ${month}월:${text}`;
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './actionLogger.js';
|
||||
export * from './entries.js';
|
||||
export * from './formatter.js';
|
||||
export * from './types.js';
|
||||
export * from './userLogger.js';
|
||||
@@ -0,0 +1,72 @@
|
||||
export const LogScope = {
|
||||
SYSTEM: 'SYSTEM',
|
||||
NATION: 'NATION',
|
||||
GENERAL: 'GENERAL',
|
||||
USER: 'USER',
|
||||
} as const;
|
||||
|
||||
export type LogScope = (typeof LogScope)[keyof typeof LogScope];
|
||||
|
||||
export const LogCategory = {
|
||||
HISTORY: 'HISTORY',
|
||||
SUMMARY: 'SUMMARY',
|
||||
ACTION: 'ACTION',
|
||||
BATTLE_BRIEF: 'BATTLE_BRIEF',
|
||||
BATTLE_DETAIL: 'BATTLE_DETAIL',
|
||||
USER: 'USER',
|
||||
} as const;
|
||||
|
||||
export type LogCategory =
|
||||
(typeof LogCategory)[keyof typeof LogCategory];
|
||||
|
||||
export interface LogEntryDraft {
|
||||
scope: LogScope;
|
||||
category: LogCategory;
|
||||
text: string;
|
||||
generalId?: number;
|
||||
nationId?: number;
|
||||
userId?: number;
|
||||
subType?: string;
|
||||
meta?: Record<string, unknown>;
|
||||
format?: LogFormat;
|
||||
}
|
||||
|
||||
export interface LogEntryRecord {
|
||||
scope: LogScope;
|
||||
category: LogCategory;
|
||||
text: string;
|
||||
year: number;
|
||||
month: number;
|
||||
generalId?: number;
|
||||
nationId?: number;
|
||||
userId?: number;
|
||||
subType?: string;
|
||||
meta?: Record<string, unknown>;
|
||||
createdAt?: Date;
|
||||
}
|
||||
|
||||
export interface LogContext {
|
||||
year: number;
|
||||
month: number;
|
||||
at?: Date;
|
||||
}
|
||||
|
||||
export enum LogFormat {
|
||||
RAWTEXT = 0,
|
||||
/** <C>●</> */
|
||||
PLAIN = 1,
|
||||
/** <C>●</>{$year}년 {$month}월: */
|
||||
YEAR_MONTH = 2,
|
||||
/** <C>●</>{$year}년: */
|
||||
YEAR = 3,
|
||||
/** <C>●</>{$month}월: */
|
||||
MONTH = 4,
|
||||
/** <S>◆</> */
|
||||
EVENT_PLAIN = 5,
|
||||
/** <S>◆</>{$year}년 {$month}월: */
|
||||
EVENT_YEAR_MONTH = 6,
|
||||
/** <R>★</> */
|
||||
NOTICE = 7,
|
||||
/** <R>★</>{$year}년 {$month}월: */
|
||||
NOTICE_YEAR_MONTH = 8,
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { LogCategory, type LogEntryDraft, LogScope } from './types.js';
|
||||
|
||||
export class UserLogger {
|
||||
private readonly userId: number;
|
||||
private readonly logs: LogEntryDraft[] = [];
|
||||
|
||||
constructor(userId: number) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
// 유저 단위의 기록을 모아두었다가 외부에서 저장한다.
|
||||
public flush(): LogEntryDraft[] {
|
||||
const items = this.logs.splice(0, this.logs.length);
|
||||
return items;
|
||||
}
|
||||
|
||||
public rollback(): LogEntryDraft[] {
|
||||
const backup = this.logs.splice(0, this.logs.length);
|
||||
return backup;
|
||||
}
|
||||
|
||||
public push(text: string | string[], subType: string): void {
|
||||
if (Array.isArray(text)) {
|
||||
for (const item of text) {
|
||||
if (item) {
|
||||
this.logs.push({
|
||||
scope: LogScope.USER,
|
||||
category: LogCategory.USER,
|
||||
text: item,
|
||||
userId: this.userId,
|
||||
subType,
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
this.logs.push({
|
||||
scope: LogScope.USER,
|
||||
category: LogCategory.USER,
|
||||
text,
|
||||
userId: this.userId,
|
||||
subType,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user