From c434b8152a7b91c53b44e978eb67bacf13f4f2d5 Mon Sep 17 00:00:00 2001 From: Vedant K Date: Wed, 13 Jul 2022 21:57:38 +0530 Subject: [PATCH 1/7] feat: add request logging --- source/types.ts | 1 + source/utilities/cli.ts | 8 ++++++-- source/utilities/logger.ts | 6 ++++-- source/utilities/server.ts | 25 +++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/source/types.ts b/source/types.ts index 7ae5cefa..342d0ced 100644 --- a/source/types.ts +++ b/source/types.ts @@ -72,6 +72,7 @@ export declare interface Options { '--single': boolean; '--debug': boolean; '--config': Path; + '--log-requests': boolean; '--no-clipboard': boolean; '--no-compression': boolean; '--no-etag': boolean; diff --git a/source/utilities/cli.ts b/source/utilities/cli.ts index 11969066..df8d9ab0 100644 --- a/source/utilities/cli.ts +++ b/source/utilities/cli.ts @@ -14,6 +14,7 @@ const options = { '--single': Boolean, '--debug': Boolean, '--config': String, + '--log-requests': Boolean, '--no-clipboard': Boolean, '--no-compression': Boolean, '--no-etag': Boolean, @@ -30,6 +31,7 @@ const options = { '-s': '--single', '-d': '--debug', '-c': '--config', + '-L': '--log-requests', '-n': '--no-clipboard', '-u': '--no-compression', '-S': '--symlinks', @@ -66,12 +68,14 @@ const helpText = chalk` -p Specify custom port - -d, --debug Show debugging information - -s, --single Rewrite all not-found requests to \`index.html\` + -d, --debug Show debugging information + -c, --config Specify custom path to \`serve.json\` + -L, --log-requests Log all requests being made to the server + -C, --cors Enable CORS, sets \`Access-Control-Allow-Origin\` to \`*\` -n, --no-clipboard Do not copy the local address to the clipboard diff --git a/source/utilities/logger.ts b/source/utilities/logger.ts index dec9114c..32f62737 100644 --- a/source/utilities/logger.ts +++ b/source/utilities/logger.ts @@ -5,12 +5,14 @@ import chalk from 'chalk'; +const http = (...message: string[]) => + console.info(chalk.blue('HTTP:', ...message)); const info = (...message: string[]) => - console.error(chalk.magenta('INFO:', ...message)); + console.info(chalk.magenta('INFO:', ...message)); const warn = (...message: string[]) => console.error(chalk.yellow('WARNING:', ...message)); const error = (...message: string[]) => console.error(chalk.red('ERROR:', ...message)); const log = console.log; -export const logger = { info, warn, error, log }; +export const logger = { http, info, warn, error, log }; diff --git a/source/utilities/server.ts b/source/utilities/server.ts index 360389c1..1615ce4b 100644 --- a/source/utilities/server.ts +++ b/source/utilities/server.ts @@ -9,6 +9,7 @@ import compression from 'compression'; import isPortReachable from 'is-port-reachable'; import { getNetworkAddress, registerCloseListener } from './http.js'; import { promisify } from './promise.js'; +import { logger } from './logger.js'; import type { IncomingMessage, ServerResponse } from 'node:http'; import type { AddressInfo } from 'node:net'; import type { @@ -18,6 +19,7 @@ import type { Port, ServerAddress, } from '../types.js'; +import chalk from 'chalk'; const compress = promisify(compression()); @@ -46,6 +48,18 @@ export const startServer = async ( type ExpressRequest = Parameters[0]; type ExpressResponse = Parameters[1]; + // Log the request. + const requestTime = new Date(); + const formattedTime = `${requestTime.toLocaleDateString()} ${requestTime.toLocaleTimeString()}`; + const ipAddress = request.socket.remoteAddress; + const requestUrl = `${request.method?.toLowerCase()} ${request.url}`; + if (args['--log-requests']) + logger.http( + chalk.dim(formattedTime), + chalk.yellow(ipAddress), + chalk.cyan(requestUrl), + ); + if (args['--cors']) response.setHeader('Access-Control-Allow-Origin', '*'); if (!args['--no-compression']) @@ -53,6 +67,17 @@ export const startServer = async ( // Let the `serve-handler` module do the rest. await handler(request, response, config); + + // Before returning the response, log the status code and time taken. + const responseTime = Date.now() - requestTime.getTime(); + if (args['--log-requests']) + logger.http( + chalk.dim(formattedTime), + chalk.yellow(ipAddress), + chalk[response.statusCode < 400 ? 'green' : 'red']( + `returned ${response.statusCode} in ${responseTime} ms`, + ), + ); }; // Then we run the async function, and re-throw any errors. From 532c38a2199615fcca36188170380266ea28ad9a Mon Sep 17 00:00:00 2001 From: Vedant K Date: Wed, 13 Jul 2022 22:27:27 +0530 Subject: [PATCH 2/7] feat(utilities/logger): make logs more colorful --- source/main.ts | 2 ++ source/utilities/logger.ts | 8 ++++---- source/utilities/server.ts | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/source/main.ts b/source/main.ts index 1891a456..c250f6ec 100755 --- a/source/main.ts +++ b/source/main.ts @@ -70,6 +70,8 @@ if (!args['--listen']) args['--listen'] = [ [process.env.PORT ? parseInt(process.env.PORT, 10) : 3000], ]; +// Default to logging requests. +if (!args['--log-requests']) args['--log-requests'] = true; // Ensure that the user has passed only one directory to serve. if (args._.length > 1) { logger.error('Please provide one path argument at maximum'); diff --git a/source/utilities/logger.ts b/source/utilities/logger.ts index 32f62737..1da7c505 100644 --- a/source/utilities/logger.ts +++ b/source/utilities/logger.ts @@ -6,13 +6,13 @@ import chalk from 'chalk'; const http = (...message: string[]) => - console.info(chalk.blue('HTTP:', ...message)); + console.info(chalk.bgBlue.bold(' HTTP '), ...message); const info = (...message: string[]) => - console.info(chalk.magenta('INFO:', ...message)); + console.info(chalk.bgMagenta.bold(' INFO '), ...message); const warn = (...message: string[]) => - console.error(chalk.yellow('WARNING:', ...message)); + console.error(chalk.bgYellow.bold(' WARN '), ...message); const error = (...message: string[]) => - console.error(chalk.red('ERROR:', ...message)); + console.error(chalk.bgRed.bold(' ERROR '), ...message); const log = console.log; export const logger = { http, info, warn, error, log }; diff --git a/source/utilities/server.ts b/source/utilities/server.ts index 1615ce4b..f6d99a74 100644 --- a/source/utilities/server.ts +++ b/source/utilities/server.ts @@ -7,6 +7,7 @@ import { readFile } from 'node:fs/promises'; import handler from 'serve-handler'; import compression from 'compression'; import isPortReachable from 'is-port-reachable'; +import chalk from 'chalk'; import { getNetworkAddress, registerCloseListener } from './http.js'; import { promisify } from './promise.js'; import { logger } from './logger.js'; @@ -19,7 +20,6 @@ import type { Port, ServerAddress, } from '../types.js'; -import chalk from 'chalk'; const compress = promisify(compression()); @@ -51,8 +51,8 @@ export const startServer = async ( // Log the request. const requestTime = new Date(); const formattedTime = `${requestTime.toLocaleDateString()} ${requestTime.toLocaleTimeString()}`; - const ipAddress = request.socket.remoteAddress; - const requestUrl = `${request.method?.toLowerCase()} ${request.url}`; + const ipAddress = request.socket.remoteAddress.replace('::ffff:', ''); + const requestUrl = `${request.method ?? 'GET'} ${request.url ?? '/'}`; if (args['--log-requests']) logger.http( chalk.dim(formattedTime), @@ -75,7 +75,7 @@ export const startServer = async ( chalk.dim(formattedTime), chalk.yellow(ipAddress), chalk[response.statusCode < 400 ? 'green' : 'red']( - `returned ${response.statusCode} in ${responseTime} ms`, + `Returned ${response.statusCode} in ${responseTime} ms`, ), ); }; From e2a678af74aefe7af3d977136d77f1e8b3620f71 Mon Sep 17 00:00:00 2001 From: Vedant K Date: Thu, 14 Jul 2022 12:00:15 +0530 Subject: [PATCH 3/7] fix(logging): flip option for request logging - request logging is enabled by default - use `--no-request-logging` to disable it --- source/main.ts | 6 ++---- source/types.ts | 2 +- source/utilities/cli.ts | 6 +++--- source/utilities/server.ts | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/source/main.ts b/source/main.ts index c250f6ec..60bc01a1 100755 --- a/source/main.ts +++ b/source/main.ts @@ -70,8 +70,6 @@ if (!args['--listen']) args['--listen'] = [ [process.env.PORT ? parseInt(process.env.PORT, 10) : 3000], ]; -// Default to logging requests. -if (!args['--log-requests']) args['--log-requests'] = true; // Ensure that the user has passed only one directory to serve. if (args._.length > 1) { logger.error('Please provide one path argument at maximum'); @@ -128,11 +126,11 @@ for (const endpoint of args['--listen']) { let message = chalk.green('Serving!'); if (local) { const prefix = network ? '- ' : ''; - const space = network ? ' ' : ' '; + const space = network ? ' ' : ' '; message += `\n\n${chalk.bold(`${prefix}Local:`)}${space}${local}`; } - if (network) message += `\n${chalk.bold('- On Your Network:')} ${network}`; + if (network) message += `\n${chalk.bold('- Network:')} ${network}`; if (previous) message += chalk.red( `\n\nThis port was picked because ${chalk.underline( diff --git a/source/types.ts b/source/types.ts index 342d0ced..d3a81c13 100644 --- a/source/types.ts +++ b/source/types.ts @@ -72,7 +72,7 @@ export declare interface Options { '--single': boolean; '--debug': boolean; '--config': Path; - '--log-requests': boolean; + '--no-request-logging': boolean; '--no-clipboard': boolean; '--no-compression': boolean; '--no-etag': boolean; diff --git a/source/utilities/cli.ts b/source/utilities/cli.ts index df8d9ab0..45a7b0d6 100644 --- a/source/utilities/cli.ts +++ b/source/utilities/cli.ts @@ -14,7 +14,7 @@ const options = { '--single': Boolean, '--debug': Boolean, '--config': String, - '--log-requests': Boolean, + '--no-request-logging': Boolean, '--no-clipboard': Boolean, '--no-compression': Boolean, '--no-etag': Boolean, @@ -31,7 +31,7 @@ const options = { '-s': '--single', '-d': '--debug', '-c': '--config', - '-L': '--log-requests', + '-L': '--no-request-logging', '-n': '--no-clipboard', '-u': '--no-compression', '-S': '--symlinks', @@ -74,7 +74,7 @@ const helpText = chalk` -c, --config Specify custom path to \`serve.json\` - -L, --log-requests Log all requests being made to the server + -L, --no-request-logging Do not log any request information to the console. -C, --cors Enable CORS, sets \`Access-Control-Allow-Origin\` to \`*\` diff --git a/source/utilities/server.ts b/source/utilities/server.ts index f6d99a74..7f0b2e1f 100644 --- a/source/utilities/server.ts +++ b/source/utilities/server.ts @@ -53,7 +53,7 @@ export const startServer = async ( const formattedTime = `${requestTime.toLocaleDateString()} ${requestTime.toLocaleTimeString()}`; const ipAddress = request.socket.remoteAddress.replace('::ffff:', ''); const requestUrl = `${request.method ?? 'GET'} ${request.url ?? '/'}`; - if (args['--log-requests']) + if (!args['--no-request-logging']) logger.http( chalk.dim(formattedTime), chalk.yellow(ipAddress), @@ -70,7 +70,7 @@ export const startServer = async ( // Before returning the response, log the status code and time taken. const responseTime = Date.now() - requestTime.getTime(); - if (args['--log-requests']) + if (!args['--no-request-logging']) logger.http( chalk.dim(formattedTime), chalk.yellow(ipAddress), From 46de8143d61ac1135b8bdce1c30d868685e822d7 Mon Sep 17 00:00:00 2001 From: Vedant K Date: Thu, 14 Jul 2022 12:03:24 +0530 Subject: [PATCH 4/7] fix(utilities/server): print `unknown` if request ip is undefined --- source/utilities/server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/utilities/server.ts b/source/utilities/server.ts index 7f0b2e1f..78506720 100644 --- a/source/utilities/server.ts +++ b/source/utilities/server.ts @@ -51,7 +51,8 @@ export const startServer = async ( // Log the request. const requestTime = new Date(); const formattedTime = `${requestTime.toLocaleDateString()} ${requestTime.toLocaleTimeString()}`; - const ipAddress = request.socket.remoteAddress.replace('::ffff:', ''); + const ipAddress = + request.socket.remoteAddress?.replace('::ffff:', '') ?? 'unknown'; const requestUrl = `${request.method ?? 'GET'} ${request.url ?? '/'}`; if (!args['--no-request-logging']) logger.http( From e31061465304d29af89255a9b3f0841e1ec48a3c Mon Sep 17 00:00:00 2001 From: Vedant K Date: Wed, 27 Jul 2022 18:57:51 +0530 Subject: [PATCH 5/7] test: add tests for server request logging --- tests/server.test.ts | 47 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/tests/server.test.ts b/tests/server.test.ts index 151ba541..66ee0c8e 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -1,11 +1,12 @@ -// tests/config.test.ts -// Tests for the configuration loader. +// tests/server.test.ts +// Tests for the server creating function. import { afterEach, describe, test, expect, vi } from 'vitest'; import { extend as createFetch } from 'got'; import { loadConfiguration } from '../source/utilities/config.js'; import { startServer } from '../source/utilities/server.js'; +import { logger } from '../source/utilities/logger.js'; // The path to the fixtures for this test file. const fixture = 'tests/__fixtures__/server/'; @@ -54,4 +55,46 @@ describe('utilities/server', () => { const response = await fetch(address.local!); expect(response.ok); }); + + // Make sure the server logs requests by default. + test('log requests to the server by default', async () => { + const consoleSpy = vi.spyOn(logger, 'http'); + const address = await startServer({ port: 3003 }, config, {}); + + const response = await fetch(address.local!); + expect(response.ok); + + expect(consoleSpy).toBeCalledTimes(2); + + const requestLog = consoleSpy.mock.calls[0].join(' '); + const responseLog = consoleSpy.mock.calls[1].join(' '); + + const time = new Date(); + const formattedTime = `${time.toLocaleDateString()} ${time.toLocaleTimeString()}`; + const ip = '127.0.0.1'; + const requestString = 'GET /'; + const status = 200; + + expect(requestLog).toMatch( + new RegExp(`${formattedTime}.*${ip}.*${requestString}`), + ); + expect(responseLog).toMatch( + new RegExp( + `${formattedTime}.*${ip}.*Returned ${status} in [0-9][0-9]? ms`, + ), + ); + }); + + // Make sure the server logs requests by default. + test('log requests to the server by default', async () => { + const consoleSpy = vi.spyOn(logger, 'http'); + const address = await startServer({ port: 3004 }, config, { + '--no-request-logging': true, + }); + + const response = await fetch(address.local!); + expect(response.ok); + + expect(consoleSpy).not.toHaveBeenCalled(); + }); }); From 0cebc07cda6c6bc159e17e45850558c1131afdd8 Mon Sep 17 00:00:00 2001 From: Vedant K Date: Wed, 27 Jul 2022 19:01:00 +0530 Subject: [PATCH 6/7] test: expect ipv6 address on ipv6 host --- tests/server.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/server.test.ts b/tests/server.test.ts index 66ee0c8e..3656bd31 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -59,7 +59,7 @@ describe('utilities/server', () => { // Make sure the server logs requests by default. test('log requests to the server by default', async () => { const consoleSpy = vi.spyOn(logger, 'http'); - const address = await startServer({ port: 3003 }, config, {}); + const address = await startServer({ port: 3003, host: '::1' }, config, {}); const response = await fetch(address.local!); expect(response.ok); @@ -71,7 +71,7 @@ describe('utilities/server', () => { const time = new Date(); const formattedTime = `${time.toLocaleDateString()} ${time.toLocaleTimeString()}`; - const ip = '127.0.0.1'; + const ip = '::1'; const requestString = 'GET /'; const status = 200; From 8c54651a5d3d6289954e29d9526d05b3dbc04c91 Mon Sep 17 00:00:00 2001 From: Vedant K Date: Wed, 27 Jul 2022 19:02:39 +0530 Subject: [PATCH 7/7] chore: run staged test files --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 0e6f638c..a754de2e 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,9 @@ "source/**/*.ts": [ "eslint --max-warnings 0 --fix", "vitest related --run" + ], + "tests": [ + "vitest --run" ] } }