Add a new /singleplayer/log route (!160)

Add a new /singleplayer/log route for logging data to the server console from the client

Supports:
- All server log levels
- `Custom` log level with text/background color
- Specifying the source of the log line (ex. Plugin name)

Example output:
![Example](https://i.imgur.com/c0XBYLm.png)

Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/160
Co-authored-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com>
Co-committed-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com>
This commit is contained in:
DrakiaXYZ 2023-10-26 09:44:17 +00:00 committed by chomp
parent 1f90ae0bb0
commit 170a185928
6 changed files with 135 additions and 0 deletions

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -2,6 +2,7 @@ import { DependencyContainer, Lifecycle } from "tsyringe";
import { BotCallbacks } from "@spt-aki/callbacks/BotCallbacks"; import { BotCallbacks } from "@spt-aki/callbacks/BotCallbacks";
import { BundleCallbacks } from "@spt-aki/callbacks/BundleCallbacks"; import { BundleCallbacks } from "@spt-aki/callbacks/BundleCallbacks";
import { ClientLogCallbacks } from "@spt-aki/callbacks/ClientLogCallbacks";
import { CustomizationCallbacks } from "@spt-aki/callbacks/CustomizationCallbacks"; import { CustomizationCallbacks } from "@spt-aki/callbacks/CustomizationCallbacks";
import { DataCallbacks } from "@spt-aki/callbacks/DataCallbacks"; import { DataCallbacks } from "@spt-aki/callbacks/DataCallbacks";
import { DialogueCallbacks } from "@spt-aki/callbacks/DialogueCallbacks"; 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 { WishlistCallbacks } from "@spt-aki/callbacks/WishlistCallbacks";
import { ApplicationContext } from "@spt-aki/context/ApplicationContext"; import { ApplicationContext } from "@spt-aki/context/ApplicationContext";
import { BotController } from "@spt-aki/controllers/BotController"; import { BotController } from "@spt-aki/controllers/BotController";
import { ClientLogController } from "@spt-aki/controllers/ClientLogController";
import { CustomizationController } from "@spt-aki/controllers/CustomizationController"; import { CustomizationController } from "@spt-aki/controllers/CustomizationController";
import { DialogueController } from "@spt-aki/controllers/DialogueController"; import { DialogueController } from "@spt-aki/controllers/DialogueController";
import { GameController } from "@spt-aki/controllers/GameController"; 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 { NotifySerializer } from "@spt-aki/routers/serializers/NotifySerializer";
import { BotStaticRouter } from "@spt-aki/routers/static/BotStaticRouter"; import { BotStaticRouter } from "@spt-aki/routers/static/BotStaticRouter";
import { BundleStaticRouter } from "@spt-aki/routers/static/BundleStaticRouter"; 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 { CustomizationStaticRouter } from "@spt-aki/routers/static/CustomizationStaticRouter";
import { DataStaticRouter } from "@spt-aki/routers/static/DataStaticRouter"; import { DataStaticRouter } from "@spt-aki/routers/static/DataStaticRouter";
import { DialogStaticRouter } from "@spt-aki/routers/static/DialogStaticRouter"; import { DialogStaticRouter } from "@spt-aki/routers/static/DialogStaticRouter";
@ -305,6 +308,7 @@ export class Container
depContainer.registerType("OnUpdate", "SaveCallbacks"); depContainer.registerType("OnUpdate", "SaveCallbacks");
depContainer.registerType("StaticRoutes", "BotStaticRouter"); depContainer.registerType("StaticRoutes", "BotStaticRouter");
depContainer.registerType("StaticRoutes", "ClientLogStaticRouter");
depContainer.registerType("StaticRoutes", "CustomizationStaticRouter"); depContainer.registerType("StaticRoutes", "CustomizationStaticRouter");
depContainer.registerType("StaticRoutes", "DataStaticRouter"); depContainer.registerType("StaticRoutes", "DataStaticRouter");
depContainer.registerType("StaticRoutes", "DialogStaticRouter"); depContainer.registerType("StaticRoutes", "DialogStaticRouter");
@ -429,6 +433,7 @@ export class Container
// Static routes // Static routes
depContainer.register<BotStaticRouter>("BotStaticRouter", { useClass: BotStaticRouter }); depContainer.register<BotStaticRouter>("BotStaticRouter", { useClass: BotStaticRouter });
depContainer.register<BundleStaticRouter>("BundleStaticRouter", { useClass: BundleStaticRouter }); depContainer.register<BundleStaticRouter>("BundleStaticRouter", { useClass: BundleStaticRouter });
depContainer.register<ClientLogStaticRouter>("ClientLogStaticRouter", { useClass: ClientLogStaticRouter });
depContainer.register<CustomizationStaticRouter>("CustomizationStaticRouter", { useClass: CustomizationStaticRouter }); depContainer.register<CustomizationStaticRouter>("CustomizationStaticRouter", { useClass: CustomizationStaticRouter });
depContainer.register<DataStaticRouter>("DataStaticRouter", { useClass: DataStaticRouter }); depContainer.register<DataStaticRouter>("DataStaticRouter", { useClass: DataStaticRouter });
depContainer.register<DialogStaticRouter>("DialogStaticRouter", { useClass: DialogStaticRouter }); depContainer.register<DialogStaticRouter>("DialogStaticRouter", { useClass: DialogStaticRouter });
@ -538,6 +543,7 @@ export class Container
// Callbacks // Callbacks
depContainer.register<BotCallbacks>("BotCallbacks", { useClass: BotCallbacks }); depContainer.register<BotCallbacks>("BotCallbacks", { useClass: BotCallbacks });
depContainer.register<BundleCallbacks>("BundleCallbacks", { useClass: BundleCallbacks }); depContainer.register<BundleCallbacks>("BundleCallbacks", { useClass: BundleCallbacks });
depContainer.register<ClientLogCallbacks>("ClientLogCallbacks", { useClass: ClientLogCallbacks });
depContainer.register<CustomizationCallbacks>("CustomizationCallbacks", { useClass: CustomizationCallbacks }); depContainer.register<CustomizationCallbacks>("CustomizationCallbacks", { useClass: CustomizationCallbacks });
depContainer.register<DataCallbacks>("DataCallbacks", { useClass: DataCallbacks }); depContainer.register<DataCallbacks>("DataCallbacks", { useClass: DataCallbacks });
depContainer.register<DialogueCallbacks>("DialogueCallbacks", { useClass: DialogueCallbacks }); depContainer.register<DialogueCallbacks>("DialogueCallbacks", { useClass: DialogueCallbacks });
@ -632,6 +638,7 @@ export class Container
{ {
// Controllers // Controllers
depContainer.register<BotController>("BotController", { useClass: BotController }); depContainer.register<BotController>("BotController", { useClass: BotController });
depContainer.register<ClientLogController>("ClientLogController", { useClass: ClientLogController });
depContainer.register<CustomizationController>("CustomizationController", { useClass: CustomizationController }); depContainer.register<CustomizationController>("CustomizationController", { useClass: CustomizationController });
depContainer.register<DialogueController>("DialogueController", { useClass: DialogueController }); depContainer.register<DialogueController>("DialogueController", { useClass: DialogueController });
depContainer.register<GameController>("GameController", { useClass: GameController }); depContainer.register<GameController>("GameController", { useClass: GameController });

View File

@ -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
}

View File

@ -0,0 +1,9 @@
export enum LogLevel
{
ERROR = 0,
WARN = 1,
SUCCESS = 2,
INFO = 3,
CUSTOM = 4,
DEBUG = 5
}

View File

@ -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);
}
)
]
);
}
}