فهرست منبع

improve error handling and logging

Richard Köhl 2 سال پیش
والد
کامیت
49d56e56d6

+ 14 - 0
deno.lock

@@ -27,9 +27,23 @@
     "https://deno.land/std@0.191.0/datetime/week_of_year.ts": "4f327c5656959759fac0a0bb642cfd6a0a64440e5a876ab218edbee006d98d20",
     "https://deno.land/std@0.191.0/datetime/week_of_year.ts": "4f327c5656959759fac0a0bb642cfd6a0a64440e5a876ab218edbee006d98d20",
     "https://deno.land/std@0.191.0/dotenv/load.ts": "0636983549b98f29ab75c9a22a42d9723f0a389ece5498fe971e7bb2556a12e2",
     "https://deno.land/std@0.191.0/dotenv/load.ts": "0636983549b98f29ab75c9a22a42d9723f0a389ece5498fe971e7bb2556a12e2",
     "https://deno.land/std@0.191.0/dotenv/mod.ts": "f5a8123741d1561ae8184a7f043bc097b15132c5171c651142b804b6dbc21853",
     "https://deno.land/std@0.191.0/dotenv/mod.ts": "f5a8123741d1561ae8184a7f043bc097b15132c5171c651142b804b6dbc21853",
+    "https://deno.land/std@0.191.0/encoding/base64.ts": "144ae6234c1fbe5b68666c711dc15b1e9ee2aef6d42b3b4345bf9a6c91d70d0d",
     "https://deno.land/std@0.191.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e",
     "https://deno.land/std@0.191.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e",
     "https://deno.land/std@0.191.0/fs/exists.ts": "29c26bca8584a22876be7cb8844f1b6c8fc35e9af514576b78f5c6884d7ed02d",
     "https://deno.land/std@0.191.0/fs/exists.ts": "29c26bca8584a22876be7cb8844f1b6c8fc35e9af514576b78f5c6884d7ed02d",
+    "https://deno.land/std@0.191.0/http/_negotiation/common.ts": "14d1a52427ab258a4b7161cd80e1d8a207b7cc64b46e911780f57ead5f4323c6",
+    "https://deno.land/std@0.191.0/http/_negotiation/encoding.ts": "ff747d107277c88cb7a6a62a08eeb8d56dad91564cbcccb30694d5dc126dcc53",
+    "https://deno.land/std@0.191.0/http/_negotiation/language.ts": "7bcddd8db3330bdb7ce4fc00a213c5547c1968139864201efd67ef2d0d51887d",
+    "https://deno.land/std@0.191.0/http/_negotiation/media_type.ts": "58847517cd549384ad677c0fe89e0a4815be36fe7a303ea63cee5f6a1d7e1692",
+    "https://deno.land/std@0.191.0/http/cookie.ts": "934f92d871d50852dbd7a836d721df5a9527b14381db16001b40991d30174ee4",
+    "https://deno.land/std@0.191.0/http/cookie_map.ts": "d148a5eaf35f19905dd5104126fa47ac71105306dd42f129732365e43108b28a",
+    "https://deno.land/std@0.191.0/http/etag.ts": "6ad8abbbb1045aabf2307959a2c5565054a8bf01c9824ddee836b1ff22706a58",
+    "https://deno.land/std@0.191.0/http/http_errors.ts": "b9a18ef97d6c5966964de95e04d1f9f88a0f8bd8577c26fd402d9d632fb03a42",
+    "https://deno.land/std@0.191.0/http/http_status.ts": "8a7bcfe3ac025199ad804075385e57f63d055b2aed539d943ccc277616d6f932",
+    "https://deno.land/std@0.191.0/http/method.ts": "e66c2a015cb46c21ab0bb3589aa4fca43143a506cb324ffdfd42d2edef7bc0c4",
+    "https://deno.land/std@0.191.0/http/mod.ts": "ca1f014826ff666731325022859d21afece14d6b5be00cd098f30e555671bfae",
+    "https://deno.land/std@0.191.0/http/negotiation.ts": "46e74a6bad4b857333a58dc5b50fe8e5a4d5267e97292293ea65f980bd918086",
     "https://deno.land/std@0.191.0/http/server.ts": "1b23463b5b36e4eebc495417f6af47a6f7d52e3294827a1226d2a1aab23d9d20",
     "https://deno.land/std@0.191.0/http/server.ts": "1b23463b5b36e4eebc495417f6af47a6f7d52e3294827a1226d2a1aab23d9d20",
+    "https://deno.land/std@0.191.0/http/server_sent_event.ts": "f752c9e4f0eba247a3fead8bfe0883f44e04d31c2dcc88776ba5230b7165a460",
     "https://deno.land/std@0.191.0/io/buf_writer.ts": "48c33c8f00b61dcbc7958706741cec8e59810bd307bc6a326cbd474fe8346dfd",
     "https://deno.land/std@0.191.0/io/buf_writer.ts": "48c33c8f00b61dcbc7958706741cec8e59810bd307bc6a326cbd474fe8346dfd",
     "https://deno.land/std@0.191.0/log/handlers.ts": "38871ecbfa67b0d39dc3384210439ac9a13cba6118b912236f9011b5989b9a4d",
     "https://deno.land/std@0.191.0/log/handlers.ts": "38871ecbfa67b0d39dc3384210439ac9a13cba6118b912236f9011b5989b9a4d",
     "https://deno.land/std@0.191.0/log/levels.ts": "6309147664e9e008cd6671610f2505c4c95f181f6bae4816a84b33e0aec66859",
     "https://deno.land/std@0.191.0/log/levels.ts": "6309147664e9e008cd6671610f2505c4c95f181f6bae4816a84b33e0aec66859",

+ 2 - 0
src/deps.ts

@@ -1,5 +1,7 @@
 import 'std/dotenv/load.ts';
 import 'std/dotenv/load.ts';
 
 
 export { serve } from 'std/http/server.ts';
 export { serve } from 'std/http/server.ts';
+export { isSuccessfulStatus } from 'std/http/http_status.ts';
+export { Status as HTTPStatus } from 'std/http/mod.ts';
 export { format as dateFormat } from 'std/datetime/mod.ts';
 export { format as dateFormat } from 'std/datetime/mod.ts';
 export * from 'std/log/mod.ts';
 export * from 'std/log/mod.ts';

+ 51 - 10
src/interfaces/controllers/controller.class.ts

@@ -1,33 +1,63 @@
+import { HTTPStatus } from 'deps';
+import { isSuccessfulStatus } from 'deps';
+
 import { logger } from 'infra/logger.ts';
 import { logger } from 'infra/logger.ts';
 
 
+interface Options {
+  status?: HTTPStatus;
+  json?: boolean;
+}
 export default class Controller {
 export default class Controller {
   constructor(
   constructor(
     public path: string,
     public path: string,
     public method: string,
     public method: string,
-    public handler: (req: Request) => Response | Promise<Response>,
+    public handler: (
+      req: Request,
+      error?: string,
+    ) => Response | Promise<Response>,
     public headers: Record<string, string> = {},
     public headers: Record<string, string> = {},
   ) {}
   ) {}
 
 
-  public static response = (req: Request, body?: string, json = false) => {
+  public static response = (
+    req: Request,
+    body: string,
+    options: Options = {},
+  ) => {
     const url = new URL(req.url);
     const url = new URL(req.url);
     const output = new TextEncoder().encode(body);
     const output = new TextEncoder().encode(body);
-    const response = new Response(output);
+    const response = new Response(output, {
+      status: options.status ?? HTTPStatus.OK,
+    });
     const userName = this.getUser(req);
     const userName = this.getUser(req);
 
 
-    if (json) {
+    if (options.json) {
       response.headers.set('Content-Type', 'application/json');
       response.headers.set('Content-Type', 'application/json');
     }
     }
 
 
-    logger.info(
-      `[${req.method}] ${url.pathname} ${
-        userName ? userName + ' ' : ''
-      }${response.status} ${output.byteLength}`,
+    const logMessage = this.getLogMessage(
+      req.method,
+      url.pathname,
+      response.status,
+      output.byteLength,
+      userName,
     );
     );
+    if (isSuccessfulStatus(response.status)) {
+      logger.info(logMessage);
+    } else {
+      logger.error(logMessage);
+    }
+
     return response;
     return response;
   };
   };
 
 
-  public static responseJSON = (req: Request, body?: unknown) => {
-    return this.response(req, JSON.stringify(body), true);
+  public static responseJSON = (
+    req: Request,
+    body: unknown,
+    options: Options = {},
+  ) => {
+    options.json = true;
+
+    return this.response(req, JSON.stringify(body), options);
   };
   };
 
 
   private static getUser = (req: Request): string | undefined => {
   private static getUser = (req: Request): string | undefined => {
@@ -43,4 +73,15 @@ export default class Controller {
 
 
     return;
     return;
   };
   };
+
+  private static getLogMessage(
+    method: string,
+    path: string,
+    status: number,
+    length: number,
+    userName?: string,
+  ) {
+    const user = userName ? userName + ' ' : '';
+    return `[${method}] ${path} ${user}${status} ${length}`;
+  }
 }
 }

+ 22 - 0
src/interfaces/controllers/errors.controller.ts

@@ -0,0 +1,22 @@
+import { HTTPStatus } from 'deps';
+
+import { logger } from 'infra/logger.ts';
+import Controller from 'if/controllers/controller.class.ts';
+
+export const ControllerErrors = {
+  NotFound: new Controller('*', '*', (req: Request) => {
+    return Controller.response(req, 'not found', {
+      status: HTTPStatus.NotFound,
+    });
+  }),
+  InternalServerError: new Controller(
+    '*',
+    '*',
+    (req: Request, error?: string) => {
+      logger.error(error);
+      return Controller.response(req, 'internal server error', {
+        status: HTTPStatus.InternalServerError,
+      });
+    },
+  ),
+};

+ 7 - 0
src/interfaces/controllers/errors/notfound.controller.ts

@@ -0,0 +1,7 @@
+import { HTTPStatus } from 'deps';
+
+import Controller from 'if/controllers/controller.class.ts';
+
+export default new Controller('*', '*', (req: Request) => {
+  return Controller.response(req, 'not found', { status: HTTPStatus.NotFound });
+});

+ 1 - 1
src/interfaces/controllers/root.controller.ts

@@ -4,6 +4,6 @@ export default new Controller(
   '/',
   '/',
   'GET',
   'GET',
   (req: Request) => {
   (req: Request) => {
-    return Controller.response(req, Deno.env.get('GREETING'));
+    return Controller.response(req, Deno.env.get('GREETING') ?? '');
   },
   },
 );
 );

+ 16 - 8
src/interfaces/router.ts

@@ -1,14 +1,22 @@
 import controllers from 'if/controllers/index.ts';
 import controllers from 'if/controllers/index.ts';
+import { ControllerErrors } from 'if/controllers/errors.controller.ts';
 
 
 export const router = (req: Request) => {
 export const router = (req: Request) => {
-  const url = new URL(req.url);
-  for (const controller of controllers) {
-    if (
-      (controller.path === '*' || controller.path === url.pathname) &&
-      (controller.method === '*' || controller.method === req.method)
-    ) {
-      return controller.handler(req, controller.headers);
+  try {
+    const url = new URL(req.url);
+    for (const controller of controllers) {
+      if (
+        (controller.path === '*' || controller.path === url.pathname) &&
+        (controller.method === '*' || controller.method === req.method)
+      ) {
+        return controller.handler(req);
+      }
     }
     }
+    return ControllerErrors.NotFound.handler(req);
+  } catch (error: unknown) {
+    return ControllerErrors.InternalServerError.handler(
+      req,
+      error instanceof Error ? error.message : 'unknown error!',
+    );
   }
   }
-  return new Response('Not Found', { status: 404 });
 };
 };