瀏覽代碼

introduce logging

Richard Köhl 2 年之前
父節點
當前提交
4ba59c9c45

+ 2 - 0
.env.example

@@ -1,3 +1,5 @@
 APPLICATION_NAME="My Deno Application"
 VERSION=0.1.0
+DEBUG_LEVEL=DEBUG # DEBUG, INFO, WARNING, ERROR, CRITICAL
+
 GREETING="hello world!"

+ 1 - 0
deno.jsonc

@@ -4,6 +4,7 @@
     "domain/": "./src/domain/",
     "infra/": "./src/infrastructure/",
     "if/": "./src/interfaces/",
+    "deps": "./src/deps.ts",
     "std/": "https://deno.land/std@0.191.0/"
   },
   "tasks": {

+ 17 - 0
deno.lock

@@ -12,12 +12,29 @@
     "https://deno.land/std@0.191.0/async/pool.ts": "f1b8d3df4d7fd3c73f8cbc91cc2e8b8e950910f1eab94230b443944d7584c657",
     "https://deno.land/std@0.191.0/async/retry.ts": "321ba30b1b39e9dc6bc3545232b5b8610804fa46ccf55fdd7b4016f96e58d356",
     "https://deno.land/std@0.191.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757",
+    "https://deno.land/std@0.191.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219",
     "https://deno.land/std@0.191.0/collections/filter_values.ts": "5b9feaf17b9a6e5ffccdd36cf6f38fa4ffa94cff2602d381c2ad0c2a97929652",
     "https://deno.land/std@0.191.0/collections/without_all.ts": "a89f5da0b5830defed4f59666e188df411d8fece35a5f6ca69be6ca71a95c185",
+    "https://deno.land/std@0.191.0/datetime/_common.ts": "f5c1cb784c616151a3d8198a4ab29f65b7fe5c20a105d8979bde9558c7b52910",
+    "https://deno.land/std@0.191.0/datetime/constants.ts": "b63a6b702e06fa028fb2ffa25e0cf775e3b21cf7f38e53a6f219e9641894dfbb",
+    "https://deno.land/std@0.191.0/datetime/day_of_year.ts": "3c2583cc4ef42e60c60623cf240442bffd32088c29ab7acbdf8aa592b9cbcdb2",
+    "https://deno.land/std@0.191.0/datetime/difference.ts": "7b8915cf467a7868e037938e4345fa439cff8feb2bf4f956bbf03f0c45f2b351",
+    "https://deno.land/std@0.191.0/datetime/format.ts": "2d7a430ca9571e054ac181dcb950faf9ac23445e081dcb230ca37134e6eaad0c",
+    "https://deno.land/std@0.191.0/datetime/is_leap.ts": "706c3579a34d38111eea92c1b8683e859e5c5db9ec05f08a9b611ec888fbd787",
+    "https://deno.land/std@0.191.0/datetime/mod.ts": "8fccadb22ad67775374a375e0077ef008bed80a20ccf8a3285115a071d08f75e",
+    "https://deno.land/std@0.191.0/datetime/parse.ts": "b59f583e7fe5ef2105c6dd4a74825670c9f4e1e35462c047c6f0a207b5653aac",
+    "https://deno.land/std@0.191.0/datetime/to_imf.ts": "8f9c0af8b167031ffe2e03da01a12a3b0672cc7562f89c61942a0ab0129771b2",
+    "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/mod.ts": "f5a8123741d1561ae8184a7f043bc097b15132c5171c651142b804b6dbc21853",
     "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/http/server.ts": "1b23463b5b36e4eebc495417f6af47a6f7d52e3294827a1226d2a1aab23d9d20",
+    "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/levels.ts": "6309147664e9e008cd6671610f2505c4c95f181f6bae4816a84b33e0aec66859",
+    "https://deno.land/std@0.191.0/log/logger.ts": "257ceb47e3f5f872068073de9809b015a7400e8d86dd40563c1d80169e578f71",
+    "https://deno.land/std@0.191.0/log/mod.ts": "36d156ad18de3f1806c6ddafa4965129be99ccafc27f1813de528d65b6c528bf",
     "https://deno.land/std@0.191.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea",
     "https://deno.land/std@0.191.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7",
     "https://deno.land/std@0.191.0/testing/asserts.ts": "e16d98b4d73ffc4ed498d717307a12500ae4f2cbe668f1a215632d19fcffc22f"

+ 2 - 0
src/deps.ts

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

+ 34 - 0
src/infrastructure/logger.ts

@@ -0,0 +1,34 @@
+import * as log from 'deps';
+import { dateFormat } from 'deps';
+
+function isLogLevel(level: string): level is log.LevelName {
+  return Object.keys(log.LogLevels).includes(
+    level,
+  );
+}
+
+function getLogLevel(level: string | undefined): log.LevelName {
+  return level && isLogLevel(level) ? level : 'WARNING';
+}
+
+const logLevel = Deno.env.get('DEBUG_LEVEL') ?? 'WARNING';
+
+log.setup({
+  handlers: {
+    console: new log.handlers.ConsoleHandler(getLogLevel(logLevel), {
+      formatter: (logRecord) => {
+        const date = dateFormat(new Date(), 'yyyy-MM-dd HH:mm:ss.SSS');
+        return `${date}: [${logRecord.levelName}] ${logRecord.msg}`;
+      },
+    }),
+  },
+
+  loggers: {
+    default: {
+      level: getLogLevel(logLevel),
+      handlers: ['console'],
+    },
+  },
+});
+
+export const logger = log.getLogger();

+ 39 - 4
src/interfaces/controllers/controller.class.ts

@@ -1,11 +1,46 @@
+import { logger } from 'infra/logger.ts';
+
 export default class Controller {
   constructor(
     public path: string,
     public method: string,
-    public handler: (
-      req: Request,
-      headers: Record<string, string>,
-    ) => Response | Promise<Response>,
+    public handler: (req: Request) => Response | Promise<Response>,
     public headers: Record<string, string> = {},
   ) {}
+
+  public static response = (req: Request, body?: string, json = false) => {
+    const url = new URL(req.url);
+    const output = new TextEncoder().encode(body);
+    const response = new Response(output);
+    const userName = this.getUser(req);
+
+    if (json) {
+      response.headers.set('Content-Type', 'application/json');
+    }
+
+    logger.info(
+      `[${req.method}] ${url.pathname} ${
+        userName ? userName + ' ' : ''
+      }${response.status} ${output.byteLength}`,
+    );
+    return response;
+  };
+
+  public static responseJSON = (req: Request, body?: unknown) => {
+    return this.response(req, JSON.stringify(body), true);
+  };
+
+  private static getUser = (req: Request): string | undefined => {
+    const authHeader = req.headers.get('Authorization');
+
+    if (authHeader && authHeader.startsWith('Basic ')) {
+      const base64Credentials = authHeader.slice(6);
+      const credentials = atob(base64Credentials);
+      const [userName] = credentials.split(':');
+
+      return userName;
+    }
+
+    return;
+  };
 }

+ 4 - 9
src/interfaces/controllers/health.controller.ts

@@ -3,29 +3,24 @@ import Controller from 'if/controllers/controller.class.ts';
 export default new Controller(
   '/health',
   'GET',
-  (req: Request, headers: Record<string, string>) => {
+  (req: Request) => {
     const url = new URL(req.url);
     const started = Deno.env.get('STARTED');
     let uptime = 0;
     if (started) {
       uptime = new Date().getTime() - new Date(started).getTime();
     }
-    // const uptime = new Date().to - new Date(Deno.env.get("STARTED"));
+
     const body = {
       app: Deno.env.get('APPLICATION_NAME'),
       version: Deno.env.get('VERSION'),
       currentTime: new Date().toISOString(),
       started,
       uptime: uptime / 1000,
-      host: url.hostname,
+      host: url.host,
       status: 'healthy',
     };
 
-    const response = new Response(JSON.stringify(body));
-    for (const [name, value] of Object.entries(headers)) {
-      response.headers.set(name, value);
-    }
-    return response;
+    return Controller.responseJSON(req, body);
   },
-  { 'Content-Type': 'application/json' },
 );

+ 2 - 7
src/interfaces/controllers/metrics.controller.ts

@@ -3,14 +3,9 @@ import Controller from 'if/controllers/controller.class.ts';
 export default new Controller(
   '/metrics',
   'GET',
-  (_req: Request, headers: Record<string, string>) => {
+  (req: Request) => {
     const body = Deno.metrics();
 
-    const response = new Response(JSON.stringify(body));
-    for (const [name, value] of Object.entries(headers)) {
-      response.headers.set(name, value);
-    }
-    return response;
+    return Controller.responseJSON(req, body);
   },
-  { 'Content-Type': 'application/json' },
 );

+ 2 - 3
src/interfaces/controllers/root.controller.ts

@@ -3,8 +3,7 @@ import Controller from 'if/controllers/controller.class.ts';
 export default new Controller(
   '/',
   'GET',
-  (_req: Request) => {
-    const body = new TextEncoder().encode(Deno.env.get('GREETING'));
-    return new Response(body);
+  (req: Request) => {
+    return Controller.response(req, Deno.env.get('GREETING'));
   },
 );

+ 5 - 2
src/main.ts

@@ -1,7 +1,10 @@
-import { serve } from './deps.ts';
+import { serve } from 'deps';
 import { router } from 'if/router.ts';
+import { logger } from 'infra/logger.ts';
 
 if (import.meta.main) {
+  logger.debug('starting application...');
   Deno.env.set('STARTED', new Date().toISOString());
-  serve(router, { port: 1993 });
+
+  await serve(router, { port: 1993 });
 }