diff --git a/project/src/callbacks/ClientLogCallbacks.ts b/project/src/callbacks/ClientLogCallbacks.ts new file mode 100644 index 00000000..79fa117c --- /dev/null +++ b/project/src/callbacks/ClientLogCallbacks.ts @@ -0,0 +1,26 @@ +import { ClientLogController } from "@spt-aki/controllers/ClientLogController"; +import { INullResponseData } from "@spt-aki/models/eft/httpResponse/INullResponseData"; +import { IClientLogRequest } from "@spt-aki/models/spt/logging/IClientLogRequest"; +import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; +import { inject, injectable } from "tsyringe"; + +/** Handle client logging related events */ +@injectable() +export class ClientLogCallbacks +{ + constructor( + @inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil, + @inject("ClientLogController") protected clientLogController: ClientLogController + ) + { } + + /** + * Handle /singleplayer/log + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public clientLog(url: string, info: IClientLogRequest, sessionID: string): INullResponseData + { + this.clientLogController.clientLog(info); + return this.httpResponse.nullResponse(); + } +} \ No newline at end of file diff --git a/project/src/controllers/ClientLogController.ts b/project/src/controllers/ClientLogController.ts new file mode 100644 index 00000000..f8c0b6dc --- /dev/null +++ b/project/src/controllers/ClientLogController.ts @@ -0,0 +1,57 @@ +import { IClientLogRequest } from "@spt-aki/models/spt/logging/IClientLogRequest"; +import { LogBackgroundColor } from "@spt-aki/models/spt/logging/LogBackgroundColor"; +import { LogLevel } from "@spt-aki/models/spt/logging/LogLevel"; +import { LogTextColor } from "@spt-aki/models/spt/logging/LogTextColor"; +import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; +import { inject, injectable } from "tsyringe"; + +@injectable() +export class ClientLogController +{ + constructor( + @inject("WinstonLogger") protected logger: ILogger + ) + { } + + /** + * Handle /singleplayer/log + */ + public clientLog(logRequest: IClientLogRequest): void + { + const message = `[${logRequest.Source}] ${logRequest.Message}`; + const color = logRequest.Color ?? LogTextColor.WHITE; + const backgroundColor = logRequest.BackgroundColor ?? LogBackgroundColor.DEFAULT; + + // Allow supporting either string or enum levels + // Required due to the C# modules serializing enums as their name + let level = logRequest.Level; + if (typeof level === "string") + { + level = LogLevel[level.toUpperCase() as keyof typeof LogLevel]; + } + + switch (level) + { + case LogLevel.ERROR: + this.logger.error(message); + break; + case LogLevel.WARN: + this.logger.warning(message); + break; + case LogLevel.SUCCESS: + this.logger.success(message); + break; + case LogLevel.INFO: + this.logger.info(message); + break; + case LogLevel.CUSTOM: + this.logger.log(message, color, backgroundColor); + break; + case LogLevel.DEBUG: + this.logger.debug(message); + break; + default: + this.logger.info(message); + } + } +} \ No newline at end of file diff --git a/project/src/di/Container.ts b/project/src/di/Container.ts index 1c4645ce..a0fc804a 100644 --- a/project/src/di/Container.ts +++ b/project/src/di/Container.ts @@ -2,6 +2,7 @@ import { DependencyContainer, Lifecycle } from "tsyringe"; import { BotCallbacks } from "@spt-aki/callbacks/BotCallbacks"; import { BundleCallbacks } from "@spt-aki/callbacks/BundleCallbacks"; +import { ClientLogCallbacks } from "@spt-aki/callbacks/ClientLogCallbacks"; import { CustomizationCallbacks } from "@spt-aki/callbacks/CustomizationCallbacks"; import { DataCallbacks } from "@spt-aki/callbacks/DataCallbacks"; import { DialogueCallbacks } from "@spt-aki/callbacks/DialogueCallbacks"; @@ -33,6 +34,7 @@ import { WeatherCallbacks } from "@spt-aki/callbacks/WeatherCallbacks"; import { WishlistCallbacks } from "@spt-aki/callbacks/WishlistCallbacks"; import { ApplicationContext } from "@spt-aki/context/ApplicationContext"; import { BotController } from "@spt-aki/controllers/BotController"; +import { ClientLogController } from "@spt-aki/controllers/ClientLogController"; import { CustomizationController } from "@spt-aki/controllers/CustomizationController"; import { DialogueController } from "@spt-aki/controllers/DialogueController"; import { GameController } from "@spt-aki/controllers/GameController"; @@ -157,6 +159,7 @@ import { ImageSerializer } from "@spt-aki/routers/serializers/ImageSerializer"; import { NotifySerializer } from "@spt-aki/routers/serializers/NotifySerializer"; import { BotStaticRouter } from "@spt-aki/routers/static/BotStaticRouter"; import { BundleStaticRouter } from "@spt-aki/routers/static/BundleStaticRouter"; +import { ClientLogStaticRouter } from "@spt-aki/routers/static/ClientLogStaticRouter"; import { CustomizationStaticRouter } from "@spt-aki/routers/static/CustomizationStaticRouter"; import { DataStaticRouter } from "@spt-aki/routers/static/DataStaticRouter"; import { DialogStaticRouter } from "@spt-aki/routers/static/DialogStaticRouter"; @@ -305,6 +308,7 @@ export class Container depContainer.registerType("OnUpdate", "SaveCallbacks"); depContainer.registerType("StaticRoutes", "BotStaticRouter"); + depContainer.registerType("StaticRoutes", "ClientLogStaticRouter"); depContainer.registerType("StaticRoutes", "CustomizationStaticRouter"); depContainer.registerType("StaticRoutes", "DataStaticRouter"); depContainer.registerType("StaticRoutes", "DialogStaticRouter"); @@ -429,6 +433,7 @@ export class Container // Static routes depContainer.register("BotStaticRouter", { useClass: BotStaticRouter }); depContainer.register("BundleStaticRouter", { useClass: BundleStaticRouter }); + depContainer.register("ClientLogStaticRouter", { useClass: ClientLogStaticRouter }); depContainer.register("CustomizationStaticRouter", { useClass: CustomizationStaticRouter }); depContainer.register("DataStaticRouter", { useClass: DataStaticRouter }); depContainer.register("DialogStaticRouter", { useClass: DialogStaticRouter }); @@ -538,6 +543,7 @@ export class Container // Callbacks depContainer.register("BotCallbacks", { useClass: BotCallbacks }); depContainer.register("BundleCallbacks", { useClass: BundleCallbacks }); + depContainer.register("ClientLogCallbacks", { useClass: ClientLogCallbacks }); depContainer.register("CustomizationCallbacks", { useClass: CustomizationCallbacks }); depContainer.register("DataCallbacks", { useClass: DataCallbacks }); depContainer.register("DialogueCallbacks", { useClass: DialogueCallbacks }); @@ -632,6 +638,7 @@ export class Container { // Controllers depContainer.register("BotController", { useClass: BotController }); + depContainer.register("ClientLogController", { useClass: ClientLogController }); depContainer.register("CustomizationController", { useClass: CustomizationController }); depContainer.register("DialogueController", { useClass: DialogueController }); depContainer.register("GameController", { useClass: GameController }); diff --git a/project/src/models/spt/logging/IClientLogRequest.ts b/project/src/models/spt/logging/IClientLogRequest.ts new file mode 100644 index 00000000..fab6e8d7 --- /dev/null +++ b/project/src/models/spt/logging/IClientLogRequest.ts @@ -0,0 +1,10 @@ +import { LogLevel } from "@spt-aki/models/spt/logging/LogLevel"; + +export interface IClientLogRequest +{ + Source: string + Level: LogLevel | string + Message: string + Color?: string + BackgroundColor?: string +} \ No newline at end of file diff --git a/project/src/models/spt/logging/LogLevel.ts b/project/src/models/spt/logging/LogLevel.ts new file mode 100644 index 00000000..a7517d9b --- /dev/null +++ b/project/src/models/spt/logging/LogLevel.ts @@ -0,0 +1,9 @@ +export enum LogLevel + { + ERROR = 0, + WARN = 1, + SUCCESS = 2, + INFO = 3, + CUSTOM = 4, + DEBUG = 5 +} \ No newline at end of file diff --git a/project/src/routers/static/ClientLogStaticRouter.ts b/project/src/routers/static/ClientLogStaticRouter.ts new file mode 100644 index 00000000..3df61bd5 --- /dev/null +++ b/project/src/routers/static/ClientLogStaticRouter.ts @@ -0,0 +1,26 @@ +import { inject, injectable } from "tsyringe"; + +import { ClientLogCallbacks } from "@spt-aki/callbacks/ClientLogCallbacks"; +import { RouteAction, StaticRouter } from "@spt-aki/di/Router"; + +@injectable() +export class ClientLogStaticRouter extends StaticRouter +{ + constructor( + @inject("ClientLogCallbacks") protected clientLogCallbacks: ClientLogCallbacks + ) + { + super( + [ + new RouteAction( + "/singleplayer/log", + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (url: string, info: any, sessionID: string, output: string): any => + { + return this.clientLogCallbacks.clientLog(url, info, sessionID); + } + ) + ] + ); + } +} \ No newline at end of file