LogBook.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // @ts-nocheck
  2. import config from "config";
  3. export type Log = {
  4. timestamp: number;
  5. message: string;
  6. type?: "info" | "success" | "error" | "debug";
  7. category?: string;
  8. data?: Record<string, unknown>;
  9. };
  10. export type LogFilters = {
  11. include: Partial<Omit<Log, "timestamp">>[];
  12. exclude: Partial<Omit<Log, "timestamp">>[];
  13. };
  14. export type LogOutputOptions = Record<
  15. "timestamp" | "title" | "type" | "message" | "data" | "color",
  16. boolean
  17. > &
  18. Partial<LogFilters>;
  19. export type LogOutputs = {
  20. console: LogOutputOptions;
  21. memory: { enabled: boolean } & Partial<LogFilters>;
  22. };
  23. export default class LogBook {
  24. private logs: Log[];
  25. private default: LogOutputs;
  26. private outputs: LogOutputs;
  27. /**
  28. * Log Book
  29. */
  30. public constructor() {
  31. this.logs = [];
  32. this.default = {
  33. console: {
  34. timestamp: true,
  35. title: true,
  36. type: false,
  37. message: true,
  38. data: false,
  39. color: true,
  40. exclude: [
  41. {
  42. category: "jobs",
  43. type: "success"
  44. },
  45. {
  46. type: "debug"
  47. }
  48. ]
  49. },
  50. memory: {
  51. enabled: false
  52. }
  53. };
  54. if (config.has("logging"))
  55. (["console", "memory"] as (keyof LogOutputs)[]).forEach(output => {
  56. if (config.has(`logging.${output}`))
  57. this.default[output] = {
  58. ...this.default[output],
  59. ...config.get<any>(`logging.${output}`)
  60. };
  61. });
  62. this.outputs = this.default;
  63. }
  64. /**
  65. * log - Add log
  66. *
  67. * @param log - Log message or object
  68. */
  69. public log(log: string | Omit<Log, "timestamp">) {
  70. const logObject: Log = {
  71. timestamp: Date.now(),
  72. ...(typeof log === "string" ? { message: log } : log)
  73. };
  74. const exclude = {
  75. console: false,
  76. memory: false
  77. };
  78. (Object.entries(logObject) as [keyof Log, Log[keyof Log]][]).forEach(
  79. ([key, value]) => {
  80. (
  81. Object.entries(this.outputs) as [
  82. keyof LogOutputs,
  83. LogOutputs[keyof LogOutputs]
  84. ][]
  85. ).forEach(([outputName, output]) => {
  86. if (
  87. (output.include &&
  88. output.include.length > 0 &&
  89. output.include.filter(
  90. filter =>
  91. key !== "timestamp" && filter[key] === value
  92. ).length === 0) ||
  93. (output.exclude &&
  94. output.exclude.filter(
  95. filter =>
  96. key !== "timestamp" && filter[key] === value
  97. ).length > 0)
  98. )
  99. exclude[outputName] = true;
  100. });
  101. }
  102. );
  103. const title =
  104. (logObject.data && logObject.data.jobName) ||
  105. logObject.category ||
  106. undefined;
  107. if (!exclude.memory && this.outputs.memory.enabled)
  108. this.logs.push(logObject);
  109. if (!exclude.console) {
  110. const message = this.formatMessage(logObject, String(title));
  111. const logArgs = this.outputs.console.data
  112. ? [message]
  113. : [message, logObject.data];
  114. switch (logObject.type) {
  115. case "debug": {
  116. console.debug(...logArgs);
  117. break;
  118. }
  119. case "error": {
  120. console.error(...logArgs);
  121. break;
  122. }
  123. default:
  124. console.log(...logArgs);
  125. }
  126. }
  127. }
  128. /**
  129. * formatMessage - Format log to string
  130. *
  131. * @param log - Log
  132. * @param title - Log title
  133. * @returns Formatted log string
  134. */
  135. private formatMessage(log: Log, title: string | undefined): string {
  136. const centerString = (string: string, length: number) => {
  137. const spaces = Array(
  138. Math.floor((length - Math.max(0, string.length)) / 2)
  139. ).join(" ");
  140. return `| ${spaces}${string}${spaces}${
  141. string.length % 2 === 0 ? "" : " "
  142. } `;
  143. };
  144. let message = "";
  145. if (this.outputs.console.color)
  146. switch (log.type) {
  147. case "success":
  148. message += "\x1b[32m";
  149. break;
  150. case "error":
  151. message += "\x1b[31m";
  152. break;
  153. case "debug":
  154. message += "\x1b[33m";
  155. break;
  156. case "info":
  157. default:
  158. message += "\x1b[36m";
  159. break;
  160. }
  161. if (this.outputs.console.timestamp)
  162. message += `| ${new Date(log.timestamp).toISOString()} `;
  163. if (this.outputs.console.title)
  164. message += centerString(title ? title.substring(0, 20) : "", 24);
  165. if (this.outputs.console.type)
  166. message += centerString(
  167. log.type ? log.type.toUpperCase() : "INFO",
  168. 10
  169. );
  170. if (this.outputs.console.message) message += `| ${log.message} `;
  171. if (this.outputs.console.color) message += "\x1b[0m";
  172. return message;
  173. }
  174. /**
  175. * updateOutput - Update output settings
  176. *
  177. * @param output - Output name
  178. * @param key - Output key to update
  179. * @param action - Update action
  180. * @param values - Updated value
  181. */
  182. public async updateOutput(
  183. output: "console" | "memory",
  184. key: keyof LogOutputOptions | "enabled",
  185. action: "set" | "add" | "reset",
  186. values?: LogOutputOptions[keyof LogOutputOptions]
  187. ) {
  188. switch (key) {
  189. case "include":
  190. case "exclude": {
  191. if (action === "set" || action === "add") {
  192. if (!values) throw new Error("No filters provided");
  193. const filters = Array.isArray(values) ? values : [values];
  194. if (action === "set") this.outputs[output][key] = filters;
  195. if (action === "add")
  196. this.outputs[output][key] = [
  197. ...(this.outputs[output][key] || []),
  198. ...filters
  199. ];
  200. } else if (action === "reset") {
  201. this.outputs[output][key] = this.default[output][key] || [];
  202. } else
  203. throw new Error(
  204. `Action "${action}" not found for ${key} in ${output}`
  205. );
  206. break;
  207. }
  208. case "enabled": {
  209. if (output === "memory" && action === "set") {
  210. if (values === undefined)
  211. throw new Error("No value provided");
  212. this.outputs[output][key] = !!values;
  213. } else
  214. throw new Error(
  215. `Action "${action}" not found for ${key} in ${output}`
  216. );
  217. break;
  218. }
  219. default: {
  220. if (output !== "memory" && action === "set") {
  221. if (values === undefined)
  222. throw new Error("No value provided");
  223. this.outputs[output][key] = !!values;
  224. } else if (output !== "memory" && action === "reset") {
  225. this.outputs[output][key] = this.default[output][key];
  226. } else
  227. throw new Error(
  228. `Action "${action}" not found for ${key} in ${output}`
  229. );
  230. }
  231. }
  232. }
  233. }