From 581abd8d40ee239e36e712422692e9249f55a8e8 Mon Sep 17 00:00:00 2001 From: Josh Faigan Date: Tue, 8 Oct 2024 14:40:10 -0400 Subject: [PATCH] add status codes and event methods to log requests This commit moves the logRequestLine function into a separate file and adds status codes and event methods. It grabs both get and post requests --- .changeset/purple-games-collect.md | 5 ++ .../cli/utilities/log-request-line.test.ts | 53 ++++++++++++++++++ .../src/cli/utilities/log-request-line.ts | 56 +++++++++++++++++++ .../cli/utilities/theme-environment/html.ts | 23 +------- .../cli/utilities/theme-environment/proxy.ts | 7 ++- packages/theme/src/cli/utilities/theme-fs.ts | 4 +- 6 files changed, 122 insertions(+), 26 deletions(-) create mode 100644 .changeset/purple-games-collect.md create mode 100644 packages/theme/src/cli/utilities/log-request-line.test.ts create mode 100644 packages/theme/src/cli/utilities/log-request-line.ts diff --git a/.changeset/purple-games-collect.md b/.changeset/purple-games-collect.md new file mode 100644 index 0000000000..76491ed551 --- /dev/null +++ b/.changeset/purple-games-collect.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli': patch +--- + +Improve user logging by adding status codes and event methods diff --git a/packages/theme/src/cli/utilities/log-request-line.test.ts b/packages/theme/src/cli/utilities/log-request-line.test.ts new file mode 100644 index 0000000000..fe60018c59 --- /dev/null +++ b/packages/theme/src/cli/utilities/log-request-line.test.ts @@ -0,0 +1,53 @@ +import {shouldLog} from './log-request-line.js' +import {createEvent} from 'h3' +import {describe, test, expect} from 'vitest' +import {IncomingMessage, ServerResponse} from 'node:http' +import {Socket} from 'node:net' + +function createH3Event(method = 'GET', path = '/', headers = {}) { + const req = new IncomingMessage(new Socket()) + const res = new ServerResponse(req) + + req.method = method + req.url = path + req.headers = headers + + return createEvent(req, res) +} + +describe('shouldLog', () => { + test('returns false for paths with ignored prefixes', () => { + const event = createH3Event('GET', '/checkouts/some-path') + expect(shouldLog(event)).toBe(false) + }) + + test('returns false for paths with ignored extensions', () => { + const event = createH3Event('GET', '/assets/styles.css') + expect(shouldLog(event)).toBe(false) + }) + + test('returns true for paths without ignored prefixes or extensions', () => { + const event = createH3Event('GET', '/products/some-product') + expect(shouldLog(event)).toBe(true) + }) + + test('returns false for paths with query parameters and ignored extensions', () => { + const event = createH3Event('GET', '/assets/script.js?version=1.2.3') + expect(shouldLog(event)).toBe(false) + }) + + test('returns true for paths with query parameters and no ignored extensions', () => { + const event = createH3Event('GET', '/products/some-product?variant=123') + expect(shouldLog(event)).toBe(true) + }) + + test('returns false for paths with EXTENSION_CDN_PREFIX', () => { + const event = createH3Event('GET', '/cdn/extension/some-path') + expect(shouldLog(event)).toBe(false) + }) + + test('returns false for paths with VANITY_CDN_PREFIX', () => { + const event = createH3Event('GET', '/cdn/vanity/some-path') + expect(shouldLog(event)).toBe(false) + }) +}) diff --git a/packages/theme/src/cli/utilities/log-request-line.ts b/packages/theme/src/cli/utilities/log-request-line.ts new file mode 100644 index 0000000000..149503f573 --- /dev/null +++ b/packages/theme/src/cli/utilities/log-request-line.ts @@ -0,0 +1,56 @@ +import {EXTENSION_CDN_PREFIX, VANITY_CDN_PREFIX} from './theme-environment/proxy.js' +import {timestampDateFormat} from '../constants.js' +import {Response as CliKitResponse} from '@shopify/cli-kit/node/http' +import {outputContent, outputInfo, outputToken} from '@shopify/cli-kit/node/output' +import {H3Event} from 'h3' +import {extname} from '@shopify/cli-kit/node/path' + +const CHARACTER_TRUNCATION_LIMIT = 80 + +export function logRequestLine(event: H3Event, response: CliKitResponse | Response) { + if (shouldLog(event) === false) return + + const truncatedPath = + event.path.length > CHARACTER_TRUNCATION_LIMIT + ? `${event.path.substring(0, CHARACTER_TRUNCATION_LIMIT)}...` + : event.path + const serverTiming = response.headers.get('server-timing') + const requestDuration = serverTiming?.match(/cfRequestDuration;dur=([\d.]+)/)?.[1] + const durationString = requestDuration ? `${Math.round(Number(requestDuration))}ms` : '' + + const statusColor = getColorizeStatus(response.status) + + const eventMethodAligned = event.method.padStart(6) + + outputInfo( + outputContent`• ${timestampDateFormat.format(new Date())} Request ${outputToken.raw( + '»', + )} ${eventMethodAligned} ${statusColor(String(response.status))} ${truncatedPath} ${outputToken.gray( + `${durationString}`, + )}`, + ) +} + +export function shouldLog(event: H3Event) { + const ignoredPathPrefixes = [EXTENSION_CDN_PREFIX, VANITY_CDN_PREFIX, '/checkouts', '/payments'] + const ignoredExtensions = ['.js', '.css', '.json', '.map'] + + if (ignoredPathPrefixes.some((prefix) => event.path.startsWith(prefix))) return false + + const [pathname] = event.path.split('?') as [string] + const extension = extname(pathname) + + if (ignoredExtensions.includes(extension)) return false + + return true +} + +function getColorizeStatus(status: number) { + if (status < 300) { + return outputToken.green + } else if (status < 400) { + return outputToken.yellow + } else { + return outputToken.errorText + } +} diff --git a/packages/theme/src/cli/utilities/theme-environment/html.ts b/packages/theme/src/cli/utilities/theme-environment/html.ts index 56cc4c86f4..8e6097da65 100644 --- a/packages/theme/src/cli/utilities/theme-environment/html.ts +++ b/packages/theme/src/cli/utilities/theme-environment/html.ts @@ -2,17 +2,14 @@ import {getProxyStorefrontHeaders, patchRenderingResponse} from './proxy.js' import {getInMemoryTemplates, injectHotReloadScript} from './hot-reload/server.js' import {render} from './storefront-renderer.js' import {getExtensionInMemoryTemplates} from '../theme-ext-environment/theme-ext-server.js' -import {timestampDateFormat} from '../../constants.js' -import {defineEventHandler, getCookie, H3Event, setResponseHeader, setResponseStatus, type H3Error} from 'h3' +import {logRequestLine} from '../log-request-line.js' +import {defineEventHandler, getCookie, setResponseHeader, setResponseStatus, type H3Error} from 'h3' import {renderError, renderFatalError} from '@shopify/cli-kit/node/ui' -import {outputContent, outputInfo, outputToken} from '@shopify/cli-kit/node/output' import {AbortError} from '@shopify/cli-kit/node/error' import type {Response} from '@shopify/cli-kit/node/http' import type {Theme} from '@shopify/cli-kit/node/themes/types' import type {DevServerContext} from './types.js' -const CHARACTER_TRUNCATION_LIMIT = 80 - export function getHtmlHandler(theme: Theme, ctx: DevServerContext) { return defineEventHandler((event) => { const [browserPathname = '/', browserSearch = ''] = event.path.split('?') @@ -68,22 +65,6 @@ export function getHtmlHandler(theme: Theme, ctx: DevServerContext) { }) } -function logRequestLine(event: H3Event, response: Response) { - const truncatedPath = - event.path.length > CHARACTER_TRUNCATION_LIMIT - ? `${event.path.substring(0, CHARACTER_TRUNCATION_LIMIT)}...` - : event.path - const serverTiming = response.headers.get('server-timing') - const requestDuration = serverTiming?.match(/cfRequestDuration;dur=([\d.]+)/)?.[1] - const durationString = requestDuration ? `${Math.round(Number(requestDuration))}ms` : '' - - outputInfo( - outputContent`• ${timestampDateFormat.format(new Date())} Request ${outputToken.raw('»')} ${outputToken.gray( - `${event.method} ${truncatedPath} ${durationString}`, - )}`, - ) -} - function getErrorPage(options: {title: string; header: string; message: string; code: string}) { const html = String.raw diff --git a/packages/theme/src/cli/utilities/theme-environment/proxy.ts b/packages/theme/src/cli/utilities/theme-environment/proxy.ts index 76dab08809..6b7c9401f0 100644 --- a/packages/theme/src/cli/utilities/theme-environment/proxy.ts +++ b/packages/theme/src/cli/utilities/theme-environment/proxy.ts @@ -1,4 +1,5 @@ import {buildCookies} from './storefront-renderer.js' +import {logRequestLine} from '../log-request-line.js' import {renderWarning} from '@shopify/cli-kit/node/ui' import { defineEventHandler, @@ -23,8 +24,8 @@ import type {Response as NodeResponse} from '@shopify/cli-kit/node/http' import type {DevServerContext} from './types.js' const CART_PREFIX = '/cart/' -const VANITY_CDN_PREFIX = '/cdn/' -const EXTENSION_CDN_PREFIX = '/ext/cdn/' +export const VANITY_CDN_PREFIX = '/cdn/' +export const EXTENSION_CDN_PREFIX = '/ext/cdn/' const IGNORED_ENDPOINTS = [ '/.well-known', '/shopify/monorail', @@ -254,6 +255,8 @@ function proxyStorefrontRequest(event: H3Event, ctx: DevServerContext) { redirect: 'manual', }, async onResponse(event, response) { + logRequestLine(event, response) + patchProxiedResponseHeaders(ctx, event, response) const fileName = url.pathname.split('/').at(-1) diff --git a/packages/theme/src/cli/utilities/theme-fs.ts b/packages/theme/src/cli/utilities/theme-fs.ts index bf125bbe6e..c64e3de6d3 100644 --- a/packages/theme/src/cli/utilities/theme-fs.ts +++ b/packages/theme/src/cli/utilities/theme-fs.ts @@ -398,8 +398,6 @@ function dirPath(filePath: string) { function outputSyncResult(action: 'update' | 'delete', fileKey: string): void { outputInfo( - outputContent`• ${timestampDateFormat.format(new Date())} Synced ${outputToken.raw('»')} ${outputToken.gray( - `${action} ${fileKey}`, - )}`, + outputContent`• ${timestampDateFormat.format(new Date())} Synced ${outputToken.raw('»')} ${action} ${fileKey}`, ) }