fix: adding comprehensive logs for live server (#7947)
* fix: adding comprehensive logs * fix: document controller naming convention * fix: axios interception logger
This commit is contained in:
parent
9ce6179421
commit
8cd29c5009
10 changed files with 112 additions and 72 deletions
|
|
@ -22,11 +22,11 @@ export class CollaborationController {
|
||||||
|
|
||||||
// Set up error handling for the connection
|
// Set up error handling for the connection
|
||||||
ws.on("error", (error: Error) => {
|
ws.on("error", (error: Error) => {
|
||||||
logger.error("WebSocket connection error:", error);
|
logger.error("COLLABORATION_CONTROLLER: WebSocket connection error:", error);
|
||||||
ws.close(1011, "Internal server error");
|
ws.close(1011, "Internal server error");
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("WebSocket connection error:", error);
|
logger.error("COLLABORATION_CONTROLLER: WebSocket connection error:", error);
|
||||||
ws.close(1011, "Internal server error");
|
ws.close(1011, "Internal server error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import type { Request, Response } from "express";
|
|
||||||
// plane imports
|
|
||||||
import { Controller, Post } from "@plane/decorators";
|
|
||||||
import { logger } from "@plane/logger";
|
|
||||||
// types
|
|
||||||
import type { TConvertDocumentRequestBody } from "@/types";
|
|
||||||
// utils
|
|
||||||
import { convertHTMLDocumentToAllFormats } from "@/utils";
|
|
||||||
|
|
||||||
@Controller("/convert-document")
|
|
||||||
export class ConvertDocumentController {
|
|
||||||
@Post("/")
|
|
||||||
handleConvertDocument(req: Request, res: Response) {
|
|
||||||
const { description_html, variant } = req.body as TConvertDocumentRequestBody;
|
|
||||||
try {
|
|
||||||
if (typeof description_html !== "string" || variant === undefined) {
|
|
||||||
res.status(400).json({
|
|
||||||
message: "Missing required fields",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { description, description_binary } = convertHTMLDocumentToAllFormats({
|
|
||||||
document_html: description_html,
|
|
||||||
variant,
|
|
||||||
});
|
|
||||||
res.status(200).json({
|
|
||||||
description,
|
|
||||||
description_binary,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error in /convert-document endpoint:", error);
|
|
||||||
res.status(500).json({
|
|
||||||
message: `Internal server error.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
63
apps/live/src/controllers/document.controller.ts
Normal file
63
apps/live/src/controllers/document.controller.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import type { Request, Response } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
// helpers
|
||||||
|
import { Controller, Post } from "@plane/decorators";
|
||||||
|
import { convertHTMLDocumentToAllFormats } from "@plane/editor";
|
||||||
|
// logger
|
||||||
|
import { logger } from "@plane/logger";
|
||||||
|
import { type TConvertDocumentRequestBody } from "@/types";
|
||||||
|
|
||||||
|
// Define the schema with more robust validation
|
||||||
|
const convertDocumentSchema = z.object({
|
||||||
|
description_html: z
|
||||||
|
.string()
|
||||||
|
.min(1, "HTML content cannot be empty")
|
||||||
|
.refine((html) => html.trim().length > 0, "HTML content cannot be just whitespace")
|
||||||
|
.refine((html) => html.includes("<") && html.includes(">"), "Content must be valid HTML"),
|
||||||
|
variant: z.enum(["rich", "document"]),
|
||||||
|
});
|
||||||
|
|
||||||
|
@Controller("/convert-document")
|
||||||
|
export class DocumentController {
|
||||||
|
@Post("/")
|
||||||
|
async convertDocument(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
// Validate request body
|
||||||
|
const validatedData = convertDocumentSchema.parse(req.body as TConvertDocumentRequestBody);
|
||||||
|
const { description_html, variant } = validatedData;
|
||||||
|
|
||||||
|
// Process document conversion
|
||||||
|
const { description, description_binary } = convertHTMLDocumentToAllFormats({
|
||||||
|
document_html: description_html,
|
||||||
|
variant,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return successful response
|
||||||
|
res.status(200).json({
|
||||||
|
description,
|
||||||
|
description_binary,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
const validationErrors = error.errors.map((err) => ({
|
||||||
|
path: err.path.join("."),
|
||||||
|
message: err.message,
|
||||||
|
}));
|
||||||
|
logger.error("DOCUMENT_CONTROLLER: Validation error", {
|
||||||
|
validationErrors,
|
||||||
|
});
|
||||||
|
return res.status(400).json({
|
||||||
|
message: `Validation error`,
|
||||||
|
context: {
|
||||||
|
validationErrors,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.error("DOCUMENT_CONTROLLER: Internal server error", error);
|
||||||
|
return res.status(500).json({
|
||||||
|
message: `Internal server error.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { CollaborationController } from "./collaboration.controller";
|
import { CollaborationController } from "./collaboration.controller";
|
||||||
import { ConvertDocumentController } from "./convert-document.controller";
|
import { DocumentController } from "./document.controller";
|
||||||
import { HealthController } from "./health.controller";
|
import { HealthController } from "./health.controller";
|
||||||
|
|
||||||
export const CONTROLLERS = [CollaborationController, ConvertDocumentController, HealthController];
|
export const CONTROLLERS = [CollaborationController, DocumentController, HealthController];
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import * as dotenv from "@dotenvx/dotenvx";
|
import * as dotenv from "@dotenvx/dotenvx";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { logger } from "@plane/logger";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
|
@ -27,7 +28,7 @@ const envSchema = z.object({
|
||||||
const validateEnv = () => {
|
const validateEnv = () => {
|
||||||
const result = envSchema.safeParse(process.env);
|
const result = envSchema.safeParse(process.env);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
console.error("❌ Invalid environment variables:", JSON.stringify(result.error.format(), null, 4));
|
logger.error("❌ Invalid environment variables:", JSON.stringify(result.error.format(), null, 4));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
return result.data;
|
return result.data;
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ const fetchDocument = async ({ context, documentName: pageId }: FetchPayloadWith
|
||||||
// return binary data
|
// return binary data
|
||||||
return binaryData;
|
return binaryData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error in fetching document", error);
|
logger.error("DATABASE_EXTENSION: Error in fetching document", error);
|
||||||
throw normalizeToError(error, `Failed to fetch document: ${pageId}`);
|
throw normalizeToError(error, `Failed to fetch document: ${pageId}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -57,7 +57,7 @@ const storeDocument = async ({ context, state: pageBinaryData, documentName: pag
|
||||||
};
|
};
|
||||||
await service.updateDescriptionBinary(pageId, payload);
|
await service.updateDescriptionBinary(pageId, payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error in updating document:", error);
|
logger.error("DATABASE_EXTENSION: Error in updating document:", error);
|
||||||
throw normalizeToError(error, `Failed to update document: ${pageId}`);
|
throw normalizeToError(error, `Failed to update document: ${pageId}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export const onAuthenticate = async ({
|
||||||
cookie = parsedToken.cookie;
|
cookie = parsedToken.cookie;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If token parsing fails, fallback to request headers
|
// If token parsing fails, fallback to request headers
|
||||||
logger.error("Token parsing failed, using request headers:", error);
|
logger.error("AUTH: Token parsing failed, using request headers:", error);
|
||||||
} finally {
|
} finally {
|
||||||
// If cookie is still not found, fallback to request headers
|
// If cookie is still not found, fallback to request headers
|
||||||
if (!cookie) {
|
if (!cookie) {
|
||||||
|
|
@ -76,7 +76,8 @@ export const handleAuthentication = async ({ cookie, userId }: { cookie: string;
|
||||||
name: user.display_name,
|
name: user.display_name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch (error) {
|
||||||
|
logger.error("AUTH: Token parsing failed, using request headers:", error);
|
||||||
throw Error("Authentication unsuccessful!");
|
throw Error("Authentication unsuccessful!");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ export class RedisManager {
|
||||||
|
|
||||||
public async initialize(): Promise<void> {
|
public async initialize(): Promise<void> {
|
||||||
if (this.redisClient && this.isConnected) {
|
if (this.redisClient && this.isConnected) {
|
||||||
logger.info("Redis client already initialized and connected");
|
logger.info("REDIS_MANAGER: client already initialized and connected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.connectionPromise) {
|
if (this.connectionPromise) {
|
||||||
logger.info("Redis connection already in progress, waiting...");
|
logger.info("REDIS_MANAGER: Redis connection already in progress, waiting...");
|
||||||
await this.connectionPromise;
|
await this.connectionPromise;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ export class RedisManager {
|
||||||
const redisUrl = this.getRedisUrl();
|
const redisUrl = this.getRedisUrl();
|
||||||
|
|
||||||
if (!redisUrl) {
|
if (!redisUrl) {
|
||||||
logger.warn("No Redis URL provided, Redis functionality will be disabled");
|
logger.warn("REDIS_MANAGER: No Redis URL provided, Redis functionality will be disabled");
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -70,27 +70,27 @@ export class RedisManager {
|
||||||
|
|
||||||
// Set up event listeners
|
// Set up event listeners
|
||||||
this.redisClient.on("connect", () => {
|
this.redisClient.on("connect", () => {
|
||||||
logger.info("Redis client connected");
|
logger.info("REDIS_MANAGER: Redis client connected");
|
||||||
this.isConnected = true;
|
this.isConnected = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.redisClient.on("ready", () => {
|
this.redisClient.on("ready", () => {
|
||||||
logger.info("Redis client ready");
|
logger.info("REDIS_MANAGER: Redis client ready");
|
||||||
this.isConnected = true;
|
this.isConnected = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.redisClient.on("error", (error) => {
|
this.redisClient.on("error", (error) => {
|
||||||
logger.error("Redis client error:", error);
|
logger.error("REDIS_MANAGER: Redis client error:", error);
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.redisClient.on("close", () => {
|
this.redisClient.on("close", () => {
|
||||||
logger.warn("Redis client connection closed");
|
logger.warn("REDIS_MANAGER: Redis client connection closed");
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.redisClient.on("reconnecting", () => {
|
this.redisClient.on("reconnecting", () => {
|
||||||
logger.info("Redis client reconnecting...");
|
logger.info("REDIS_MANAGER: Redis client reconnecting...");
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -99,9 +99,9 @@ export class RedisManager {
|
||||||
|
|
||||||
// Test the connection
|
// Test the connection
|
||||||
await this.redisClient.ping();
|
await this.redisClient.ping();
|
||||||
logger.info("Redis connection test successful");
|
logger.info("REDIS_MANAGER: Redis connection test successful");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to initialize Redis client:", error);
|
logger.error("REDIS_MANAGER: Failed to initialize Redis client:", error);
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -111,7 +111,7 @@ export class RedisManager {
|
||||||
|
|
||||||
public getClient(): Redis | null {
|
public getClient(): Redis | null {
|
||||||
if (!this.redisClient || !this.isConnected) {
|
if (!this.redisClient || !this.isConnected) {
|
||||||
logger.warn("Redis client not available or not connected");
|
logger.warn("REDIS_MANAGER: Redis client not available or not connected");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.redisClient;
|
return this.redisClient;
|
||||||
|
|
@ -125,9 +125,9 @@ export class RedisManager {
|
||||||
if (this.redisClient) {
|
if (this.redisClient) {
|
||||||
try {
|
try {
|
||||||
await this.redisClient.quit();
|
await this.redisClient.quit();
|
||||||
logger.info("Redis client disconnected gracefully");
|
logger.info("REDIS_MANAGER: Redis client disconnected gracefully");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error disconnecting Redis client:", error);
|
logger.error("REDIS_MANAGER: Error disconnecting Redis client:", error);
|
||||||
// Force disconnect if quit fails
|
// Force disconnect if quit fails
|
||||||
this.redisClient.disconnect();
|
this.redisClient.disconnect();
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -150,7 +150,7 @@ export class RedisManager {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error setting Redis key ${key}:`, error);
|
logger.error(`REDIS_MANAGER: Error setting Redis key ${key}:`, error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,7 +162,7 @@ export class RedisManager {
|
||||||
try {
|
try {
|
||||||
return await client.get(key);
|
return await client.get(key);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error getting Redis key ${key}:`, error);
|
logger.error(`REDIS_MANAGER: Error getting Redis key ${key}:`, error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +175,7 @@ export class RedisManager {
|
||||||
await client.del(key);
|
await client.del(key);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error deleting Redis key ${key}:`, error);
|
logger.error(`REDIS_MANAGER: Error deleting Redis key ${key}:`, error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +188,7 @@ export class RedisManager {
|
||||||
const result = await client.exists(key);
|
const result = await client.exists(key);
|
||||||
return result === 1;
|
return result === 1;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error checking Redis key ${key}:`, error);
|
logger.error(`REDIS_MANAGER: Error checking Redis key ${key}:`, error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +201,7 @@ export class RedisManager {
|
||||||
const result = await client.expire(key, ttl);
|
const result = await client.expire(key, ttl);
|
||||||
return result === 1;
|
return result === 1;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error setting expiry for Redis key ${key}:`, error);
|
logger.error(`REDIS_MANAGER: Error setting expiry for Redis key ${key}:`, error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,15 @@ export class Server {
|
||||||
public async initialize(): Promise<void> {
|
public async initialize(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await redisManager.initialize();
|
await redisManager.initialize();
|
||||||
logger.info("Redis setup completed");
|
logger.info("SERVER: Redis setup completed");
|
||||||
const manager = HocusPocusServerManager.getInstance();
|
const manager = HocusPocusServerManager.getInstance();
|
||||||
this.hocuspocusServer = await manager.initialize();
|
this.hocuspocusServer = await manager.initialize();
|
||||||
logger.info("HocusPocus setup completed");
|
logger.info("SERVER: HocusPocus setup completed");
|
||||||
|
|
||||||
this.setupRoutes(this.hocuspocusServer);
|
this.setupRoutes(this.hocuspocusServer);
|
||||||
this.setupNotFoundHandler();
|
this.setupNotFoundHandler();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to initialize live server dependencies:", error);
|
logger.error("SERVER: Failed to initialize live server dependencies:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,10 +89,10 @@ export class Server {
|
||||||
public listen() {
|
public listen() {
|
||||||
this.httpServer = this.app
|
this.httpServer = this.app
|
||||||
.listen(this.app.get("port"), () => {
|
.listen(this.app.get("port"), () => {
|
||||||
logger.info(`Plane Live server has started at port ${this.app.get("port")}`);
|
logger.info(`SERVER: Express server has started at port ${this.app.get("port")}`);
|
||||||
})
|
})
|
||||||
.on("error", (err) => {
|
.on("error", (err) => {
|
||||||
logger.error("Failed to start server:", err);
|
logger.error("SERVER: Failed to start server:", err);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -100,11 +100,11 @@ export class Server {
|
||||||
public async destroy() {
|
public async destroy() {
|
||||||
if (this.hocuspocusServer) {
|
if (this.hocuspocusServer) {
|
||||||
this.hocuspocusServer.closeConnections();
|
this.hocuspocusServer.closeConnections();
|
||||||
logger.info("HocusPocus connections closed gracefully.");
|
logger.info("SERVER: HocusPocus connections closed gracefully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await redisManager.disconnect();
|
await redisManager.disconnect();
|
||||||
logger.info("Redis connection closed gracefully.");
|
logger.info("SERVER: Redis connection closed gracefully.");
|
||||||
|
|
||||||
if (this.httpServer) {
|
if (this.httpServer) {
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
|
@ -112,7 +112,7 @@ export class Server {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
logger.info("Express server closed gracefully.");
|
logger.info("SERVER: Express server closed gracefully.");
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
import { logger } from "@plane/logger";
|
||||||
import { env } from "@/env";
|
import { env } from "@/env";
|
||||||
|
|
||||||
export abstract class APIService {
|
export abstract class APIService {
|
||||||
|
|
@ -13,6 +14,17 @@ export abstract class APIService {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
timeout: 20000,
|
timeout: 20000,
|
||||||
});
|
});
|
||||||
|
this.setupInterceptors();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupInterceptors() {
|
||||||
|
this.axiosInstance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
logger.error("AXIOS_ERROR:", error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setHeader(key: string, value: string) {
|
setHeader(key: string, value: string) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue