Skip to content

Commit

Permalink
fix: improved custom logger
Browse files Browse the repository at this point in the history
  • Loading branch information
riipandi committed Sep 6, 2024
1 parent 533faf8 commit 8c2573a
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 37 deletions.
12 changes: 6 additions & 6 deletions app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ export const loader: LoaderFunction = async ({ request }) => {
const domain = process.env.APP_DOMAIN || 'example.com'
const baseDomain = process.env.NODE_ENV === 'development' ? 'localhost:3000' : domain

logger('DEBUG', 'Original host:', host)
logger.debug('Original host:', host)

// Check if it's a subdomain
const isSubdomain = host.endsWith(`.${baseDomain}`) && !host.startsWith('www.')

if (!isSubdomain) {
logger('DEBUG', 'Not a subdomain, returning default page')
logger.debug('Not a subdomain, returning default page')

const meta = [
{ title: 'Remix Start' },
Expand All @@ -33,13 +33,13 @@ export const loader: LoaderFunction = async ({ request }) => {
// Handle localhost by replacing 'localhost' with the domain name
if (host.includes('localhost')) {
host = host.replace(/localhost:\d+/, domain)
logger('DEBUG', 'Transformed host for localhost:', host)
logger.debug('Transformed host for localhost:', host)
}

// Extract the subdomain (slug) from the host
if (host.endsWith(`.${domain}`)) {
host = host.replace(`.${domain}`, '')
logger('DEBUG', 'Subdomain (slug):', host)
logger.debug('Subdomain (slug):', host)
}

// Sample list of sites; ideally, this would come from a database
Expand All @@ -66,11 +66,11 @@ export const loader: LoaderFunction = async ({ request }) => {
const currentSite = sites.find((site) => site.slug === host)

if (!currentSite) {
logger('DEBUG', 'Site not found for slug:', host)
logger.debug('Site not found for slug:', host)
throw new Response(null, { status: 404, statusText: 'Not Found' })
}

logger('DEBUG', 'Returning data for site:', currentSite)
logger.debug('Returning data for site:', currentSite)

const meta = [{ title: `@${currentSite.slug} on Remix Start` }] satisfies MetaDescriptor[]

Expand Down
2 changes: 1 addition & 1 deletion app/routes/healthz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
])
return new Response('🫡 All is well!')
} catch (error: unknown) {
logger('ERROR', 'healthcheck ❌', { error })
logger.error('healthcheck ❌', { error })
return new Response('🔥 Unhealthy', { status: 500 })
}
}
56 changes: 49 additions & 7 deletions app/utils/common.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import pico from 'picocolors'

export type EnumValues<Type> = Type[keyof Type]

export type LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG' | 'QUERY'

export function logTimestamp(date?: Date, localtime = true): string {
/**
* Generates a formatted timestamp string.
* @param date - Optional Date object. Defaults to current date/time.
* @param localtime - Whether to use local time. Defaults to true.
* @returns Formatted timestamp string.
*/
function logTimestamp(date?: Date, localtime = true): string {
const now = date ?? new Date()
const useUTC = !localtime

Expand All @@ -18,7 +22,12 @@ export function logTimestamp(date?: Date, localtime = true): string {
return pico.dim(`[${year}-${month}-${day} ${hours}:${minutes}:${seconds}]`)
}

// TODO replace with `node:utils.styleText` (requires node >= 21)
/**
* TODO replace with `node:utils.styleText` (requires node >= 21)
* Parses the log level and returns a colored string representation.
* @param level - The log level.
* @returns Colored string representation of the log level.
*/
const parseLogLevel = (level: LogLevel): string => {
const colors = {
INFO: pico.green,
Expand All @@ -30,7 +39,13 @@ const parseLogLevel = (level: LogLevel): string => {
return colors[level] ? colors[level](level) : pico.gray(level)
}

export function logger(level: LogLevel, message: string, ...args: unknown[]): void {
/**
* Logs a message with the specified log level.
* @param level - The log level.
* @param message - The message to log.
* @param args - Additional arguments to log.
*/
function log(level: LogLevel, message: string, ...args: unknown[]): void {
const logPrefix = `${logTimestamp()} ${parseLogLevel(level)}`
const logMethod = {
INFO: console.info,
Expand All @@ -42,10 +57,37 @@ export function logger(level: LogLevel, message: string, ...args: unknown[]): vo
const logFunc = logMethod[level] || console.log
const logMessage = level === 'INFO' || level === 'WARN' ? ` ${message}` : message

// If log level === DEBUG and env is not development, don't print the log
if (level === 'DEBUG' && process.env.APP_LOG_LEVEL?.toLowerCase() !== 'debug') {
if (level === 'DEBUG' && process.env.APP_LOG_LEVEL?.toLowerCase() === 'silent') {
return
}

logFunc(logPrefix, logMessage, ...args)
}

/**
* An object that provides methods for logging messages with different log levels.
*
* The available log levels are:
* - `info`: For informational messages.
* - `warn`: For warning messages.
* - `error`: For error messages.
* - `debug`: For debug messages.
* - `query`: For logging SQL queries.
* - `silent`: Suppresses all logging.
*
* Example usage:
* logger.info('This is an info message');
* logger.debug('This is a debug message');
* logger.error('This is an error message', { someError: 'details' });
* logger.warn('This is a warning');
* logger.query('SELECT * FROM users');
*
* Each method takes a message string and optional additional arguments to be logged.
*/
export const logger = {
info: (message: string, ...args: unknown[]) => log('INFO', message, ...args),
warn: (message: string, ...args: unknown[]) => log('WARN', message, ...args),
error: (message: string, ...args: unknown[]) => log('ERROR', message, ...args),
debug: (message: string, ...args: unknown[]) => log('DEBUG', message, ...args),
query: (message: string, ...args: unknown[]) => log('QUERY', message, ...args),
}
4 changes: 2 additions & 2 deletions app/utils/env.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ declare global {
export function init() {
try {
const parsed = v.parse(EnvSchema, process.env)
logger('DEBUG', 'EnvSchema', parsed)
logger.debug('EnvSchema', parsed)
} catch (error) {
logger('ERROR', '❌ Invalid environment variables:', error)
logger.error('❌ Invalid environment variables:', error)
throw new Error('Invalid environment variables')
}
}
Expand Down
61 changes: 45 additions & 16 deletions server/logger.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { default as pico } from 'picocolors'
import pico from 'picocolors'

/**
* Formats the current timestamp for logs.
* @param {Date} [date] Optional date to use for the timestamp. Defaults to the current date.
* @param {boolean} [localtime=true] Whether to use local time or UTC. Defaults to local time.
* Generates a formatted timestamp string.
* @param {Date} [date] - Optional Date object. Defaults to current date/time.
* @param {boolean} [localtime=true] - Whether to use local time. Defaults to true.
* @returns {string} Formatted timestamp string.
*/
export function logTimestamp(date, localtime = true) {
function logTimestamp(date, localtime = true) {
const now = date ?? new Date()
const useUTC = !localtime

Expand All @@ -21,10 +21,10 @@ export function logTimestamp(date, localtime = true) {
}

/**
* Parse the log level to return a color-coded string.
* TODO: Replace with `node:utils.styleText` for Node >= 21.
* @param {string} level The log level to parse.
* @returns {string} A color-coded log level string.
* TODO replace with `node:utils.styleText` (requires node >= 21)
* Parses the log level and returns a colored string representation.
* @param {string} level - The log level.
* @returns {string} Colored string representation of the log level.
*/
const parseLogLevel = (level) => {
const colors = {
Expand All @@ -38,12 +38,12 @@ const parseLogLevel = (level) => {
}

/**
* Logs a message with a given log level and optional arguments.
* @param {string} level The log level (INFO, WARN, ERROR, DEBUG, QUERY).
* @param {string} message The message to log.
* @param {...any} args Additional arguments to log.
* Logs a message with the specified log level.
* @param {string} level - The log level.
* @param {string} message - The message to log.
* @param {...any} args - Additional arguments to log.
*/
export default function logger(level, message, ...args) {
function log(level, message, ...args) {
const logPrefix = `${logTimestamp()} ${parseLogLevel(level)}`
const logMethod = {
INFO: console.info,
Expand All @@ -55,10 +55,39 @@ export default function logger(level, message, ...args) {
const logFunc = logMethod[level] || console.log
const logMessage = level === 'INFO' || level === 'WARN' ? ` ${message}` : message

// If log level is DEBUG and environment is not set to 'debug', do not print the log.
if (level === 'DEBUG' && process.env.APP_LOG_LEVEL?.toLowerCase() !== 'debug') {
if (level === 'DEBUG' && process.env.APP_LOG_LEVEL?.toLowerCase() === 'silent') {
return
}

logFunc(logPrefix, logMessage, ...args)
}

/**
* An object that provides methods for logging messages with different log levels.
*
* The available log levels are:
* - `info`: For informational messages.
* - `warn`: For warning messages.
* - `error`: For error messages.
* - `debug`: For debug messages.
* - `query`: For logging SQL queries.
* - `silent`: Suppresses all logging.
*
* Example usage:
* logger.info('This is an info message');
* logger.debug('This is a debug message');
* logger.error('This is an error message', { someError: 'details' });
* logger.warn('This is a warning');
* logger.query('SELECT * FROM users');
*
* Each method takes a message string and optional additional arguments to be logged.
*/
const logger = {
info: (message, ...args) => log('INFO', message, ...args),
warn: (message, ...args) => log('WARN', message, ...args),
error: (message, ...args) => log('ERROR', message, ...args),
debug: (message, ...args) => log('DEBUG', message, ...args),
query: (message, ...args) => log('QUERY', message, ...args),
}

export default logger
10 changes: 5 additions & 5 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This script is used to start the Remix development server.
* Based on https://github.com/kentcdodds/nonce-remix-issue
*
* ╰─➤ $ pnpm add @remix-run/express express compression morgan helmet express-rate-limit
* ╰─➤ $ pnpm add @remix-run/express express compression morgan helmet express-rate-limit picocolors
* ╰─➤ $ pnpm add -D @types/express @types/compression @types/morgan
*/

Expand Down Expand Up @@ -98,7 +98,7 @@ app.use(express.static(PUBLIC_DIR, { maxAge: '1h' }))
app.use(
morgan('short', {
stream: {
write: (message) => logger('INFO', message.trim()),
write: (message) => logger.info(message.trim()),
},
})
)
Expand Down Expand Up @@ -153,7 +153,7 @@ app.use(
})
)
app.use((err, _req, res, _next) => {
logger('ERROR', err.stack)
logger.error(err.stack)
res.status(500).send(`Something went wrong: ${err.message}`)
})

Expand Down Expand Up @@ -182,9 +182,9 @@ const onListen = () => {
const localUrl = `http://localhost:${PORT}`
const networkUrl = address ? `http://${address}:${PORT}` : null
if (networkUrl) {
logger('INFO', `[remix-express] ${localUrl} (${networkUrl})`)
logger.info(`[remix-express] ${localUrl} (${networkUrl})`)
} else {
logger('INFO', `[remix-express] ${localUrl}`)
logger.info(`[remix-express] ${localUrl}`)
}
}

Expand Down

0 comments on commit 8c2573a

Please sign in to comment.