Skip to content

Commit

Permalink
feat: add request logging (#716)
Browse files Browse the repository at this point in the history
  • Loading branch information
gamemaker1 committed Jul 30, 2022
1 parent 0616ce3 commit 337279c
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 10 deletions.
3 changes: 3 additions & 0 deletions package.json
Expand Up @@ -86,6 +86,9 @@
"source/**/*.ts": [
"eslint --max-warnings 0 --fix",
"vitest related --run"
],
"tests": [
"vitest --run"
]
}
}
4 changes: 2 additions & 2 deletions source/main.ts
Expand Up @@ -114,11 +114,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(
Expand Down
1 change: 1 addition & 0 deletions source/types.ts
Expand Up @@ -75,6 +75,7 @@ export declare interface Options {
'--single': boolean;
'--debug': boolean;
'--config': Path;
'--no-request-logging': boolean;
'--no-clipboard': boolean;
'--no-compression': boolean;
'--no-etag': boolean;
Expand Down
6 changes: 4 additions & 2 deletions source/utilities/cli.ts
Expand Up @@ -38,12 +38,14 @@ const helpText = chalkTemplate`
-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, --no-request-logging Do not log any request information to the console.
-C, --cors Enable CORS, sets \`Access-Control-Allow-Origin\` to \`*\`
-n, --no-clipboard Do not copy the local address to the clipboard
Expand Down
6 changes: 4 additions & 2 deletions source/utilities/logger.ts
Expand Up @@ -5,12 +5,14 @@

import chalk from 'chalk';

const http = (...message: string[]) =>
console.info(chalk.bgBlue.bold(' HTTP '), ...message);
const info = (...message: string[]) =>
console.error(chalk.bgMagenta.bold(' INFO '), ...message);
console.info(chalk.bgMagenta.bold(' INFO '), ...message);
const warn = (...message: string[]) =>
console.error(chalk.bgYellow.bold(' WARN '), ...message);
const error = (...message: string[]) =>
console.error(chalk.bgRed.bold(' ERROR '), ...message);
const log = console.log;

export const logger = { info, warn, error, log };
export const logger = { http, info, warn, error, log };
26 changes: 26 additions & 0 deletions source/utilities/server.ts
Expand Up @@ -7,8 +7,10 @@ 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';
import type { IncomingMessage, ServerResponse } from 'node:http';
import type { AddressInfo } from 'node:net';
import type {
Expand Down Expand Up @@ -46,13 +48,37 @@ export const startServer = async (
type ExpressRequest = Parameters<typeof compress>[0];
type ExpressResponse = Parameters<typeof compress>[1];

// Log the request.
const requestTime = new Date();
const formattedTime = `${requestTime.toLocaleDateString()} ${requestTime.toLocaleTimeString()}`;
const ipAddress =
request.socket.remoteAddress?.replace('::ffff:', '') ?? 'unknown';
const requestUrl = `${request.method ?? 'GET'} ${request.url ?? '/'}`;
if (!args['--no-request-logging'])
logger.http(
chalk.dim(formattedTime),
chalk.yellow(ipAddress),
chalk.cyan(requestUrl),
);

if (args['--cors'])
response.setHeader('Access-Control-Allow-Origin', '*');
if (!args['--no-compression'])
await compress(request as ExpressRequest, response as ExpressResponse);

// 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['--no-request-logging'])
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.
Expand Down
6 changes: 4 additions & 2 deletions tests/__snapshots__/cli.test.ts.snap
Expand Up @@ -27,12 +27,14 @@ exports[`utilities/cli > render help text 1`] = `
-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, --no-request-logging Do not log any request information to the console.
-C, --cors Enable CORS, sets \`Access-Control-Allow-Origin\` to \`*\`
-n, --no-clipboard Do not copy the local address to the clipboard
Expand Down
47 changes: 45 additions & 2 deletions 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/';
Expand Down Expand Up @@ -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, host: '::1' }, 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 = '::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();
});
});

0 comments on commit 337279c

Please sign in to comment.