RocketHooks: Pro Logging With Vite Optimization
Hey guys! Let's dive into how we can seriously level up our logging system in RocketHooks. We're talking about ditching those repetitive process.env.NODE_ENV
checks and moving to a professional setup that not only boosts our developer experience (DX) but also optimizes our production builds. This means cleaner code, better performance, and a more robust application. Buckle up; it's gonna be a fun ride!
๐ The Big Picture: Overview
Our main goal here is to replace the current, somewhat clunky, manual way of handling console logs with a sleek, automated system. Think about it: no more worrying about accidentally shipping those debug statements to production! We're aiming for a solution that:
- Automatically removes console statements in production builds.
- Provides a way better experience for us developers.
- Gives us structured logging that's easy to manage.
Current Pain Points
Right now, we're dealing with:
- Repetitive
process.env.NODE_ENV === 'development'
checks: It's like Groundhog Day, but with more code. - Manual management of console statements: Who has time for that?
- No centralized logging strategy: It's a bit like the Wild West out there.
- Risk of shipping console logs to production: Oops! ๐
Our Awesome Solution
We're going for a hybrid approach that combines the best of both worlds:
vite-plugin-remove-console
: This bad boy will automatically strip out console statements in production. ๐- Custom logger utility: For a smoother DX and structured logging that makes sense.
- TypeScript support: Because type-safe logging is the way to go. ๐
๐๏ธ Let's Build It: Implementation Plan
We're breaking this down into phases so it's super manageable. Let's get started!
Phase 1: Install Dependencies
First up, we need to add vite-plugin-remove-console
to our project. Pop open your terminal and run:
yarn add -D vite-plugin-remove-console
Package Deets:
- NPM: https://www.npmjs.com/package/vite-plugin-remove-console
- Weekly Downloads: ~40K (It's popular for a reason!)
- Bundle Size: 2.5KB (Tiny and mighty)
- License: MIT (Free and open-source!)
Phase 2: Tweak the Vite Config
Now, let's tell Vite about our new plugin. Open up vite.config.ts
and make it look something like this:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import removeConsole from 'vite-plugin-remove-console'
import path from 'path'
export default defineConfig(({ mode }) => ({
plugins: [
react(),
tailwindcss(),
// Only remove console in production, keep errors and warnings
mode === 'production' && removeConsole({
exclude: ['error', 'warn']
}),
].filter(Boolean),
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 3000,
open: true,
},
build: {
target: 'esnext',
sourcemap: true,
},
}))
What's going on here?
- We're importing
removeConsole
from our new package. - We're adding it to the
plugins
array, but only inproduction
mode. This is key! ๐ - We're telling it to
exclude
error
andwarn
logs. We still want those in production, right?
Phase 3: Craft Our Logger Utility
This is where the magic happens. We're going to create a custom logger that's super flexible and easy to use. Create a file called src/utils/logger.ts
and paste this in:
/**
* Professional logging utility for RocketHooks application
*
* Features:
* - Automatic removal in production builds via vite-plugin-remove-console
* - Namespaced logging for better organization
* - Type-safe logging methods
* - Performance tracking utilities
* - Structured data logging
*/
// Check if we're in development mode using Vite's import.meta
const isDev = import.meta.env.DEV;
const isProd = import.meta.env.PROD;
// Color codes for different log levels (browser console)
const LogColors = {
DEBUG: 'color: #9CA3AF',
INFO: 'color: #3B82F6',
SUCCESS: 'color: #10B981',
WARNING: 'color: #F59E0B',
ERROR: 'color: #EF4444',
} as const;
interface LoggerOptions {
namespace?: string;
showTimestamp?: boolean;
collapsed?: boolean;
}
class Logger {
private namespace: string;
private showTimestamp: boolean;
constructor(options: LoggerOptions = {}) {
this.namespace = options.namespace || '';
this.showTimestamp = options.showTimestamp ?? isDev;
}
private format(level: string, message: string): string {
const timestamp = this.showTimestamp
? `[${new Date().toISOString().split('T')[1].slice(0, -1)}]`
: '';
const ns = this.namespace ? `[${this.namespace}]` : '';
return `${timestamp}${ns} ${message}`;
}
// Core logging methods - will be removed in production by vite-plugin-remove-console
log(message: string, ...args: any[]): void {
console.log(this.format('LOG', message), ...args);
}
debug(message: string, ...args: any[]): void {
if (isDev) {
console.debug(`%c${this.format('DEBUG', message)}`, LogColors.DEBUG, ...args);
}
}
info(message: string, ...args: any[]): void {
console.info(`%c${this.format('INFO', message)}`, LogColors.INFO, ...args);
}
success(message: string, ...args: any[]): void {
console.log(`%c${this.format('SUCCESS', message)}`, LogColors.SUCCESS, ...args);
}
// These will remain in production (configured in vite.config.ts)
warn(message: string, ...args: any[]): void {
console.warn(`%c${this.format('WARN', message)}`, LogColors.WARNING, ...args);
}
error(message: string, error?: Error | unknown, ...args: any[]): void {
console.error(`%c${this.format('ERROR', message)}`, LogColors.ERROR, error, ...args);
// Optionally send to error tracking service in production
if (isProd && error instanceof Error) {
// TODO: Send to Sentry/LogRocket/etc
// errorTracker.captureException(error);
}
}
// Utility methods
table(data: any, columns?: string[]): void {
if (isDev) {
console.log(this.format('TABLE', ''))
console.table(data, columns);
}
}
group(label: string, collapsed: boolean = false): void {
const formatted = this.format('GROUP', label);
if (collapsed) {
console.groupCollapsed(formatted);
} else {
console.group(formatted);
}
}
groupEnd(): void {
console.groupEnd();
}
time(label: string): void {
if (isDev) {
console.time(this.format('TIMER', label));
}
}
timeEnd(label: string): void {
if (isDev) {
console.timeEnd(this.format('TIMER', label));
}
}
// Performance tracking
measure<T>(label: string, fn: () => T): T {
if (!isDev) return fn();
const start = performance.now();
const result = fn();
const duration = performance.now() - start;
this.debug(`${label} took ${duration.toFixed(2)}ms`);
return result;
}
async measureAsync<T>(label: string, fn: () => Promise<T>): Promise<T> {
if (!isDev) return fn();
const start = performance.now();
const result = await fn();
const duration = performance.now() - start;
this.debug(`${label} took ${duration.toFixed(2)}ms`);
return result;
}
// Create a child logger with namespace
child(namespace: string): Logger {
const childNamespace = this.namespace
? `${this.namespace}:${namespace}`
: namespace;
return new Logger({
namespace: childNamespace,
showTimestamp: this.showTimestamp,
});
}
}
// Main logger instance
export const logger = new Logger();
// Pre-configured namespaced loggers for common modules
export const loggers = {
onboarding: new Logger({ namespace: 'Onboarding' }),
auth: new Logger({ namespace: 'Auth' }),
api: new Logger({ namespace: 'API' }),
graphql: new Logger({ namespace: 'GraphQL' }),
store: new Logger({ namespace: 'Store' }),
router: new Logger({ namespace: 'Router' }),
ui: new Logger({ namespace: 'UI' }),
performance: new Logger({ namespace: 'Performance' }),
validation: new Logger({ namespace: 'Validation' }),
} as const;
// Convenience exports
export default logger;
// Type exports for better IDE support
export type { Logger, LoggerOptions };
Whoa, that's a lot of code! Let's break it down:
- Namespaces: We can create loggers for specific parts of our app (like "Auth" or "API"). This keeps things organized.
- Colored output: Because who doesn't love a colorful console? ๐
- Different log levels:
debug
,info
,warn
,error
, and more! - Performance tracking: We can easily measure how long functions take to run. โฑ๏ธ
- TypeScript awesomeness: Type safety for all the things!
- Automatic removal in production: Thanks to
vite-plugin-remove-console
,console.log
andconsole.debug
statements will disappear in production builds. ๐ - Error tracking integration: A placeholder for sending errors to services like Sentry or LogRocket.
This logger utility is designed to give us maximum flexibility and control over our logging. With features like namespaced logging, performance tracking, and structured data logging, we can create a logging strategy that truly enhances our development workflow. The automatic removal of logs in production ensures that our builds are optimized for performance and security, while the type-safe methods provide a robust and reliable logging experience. The integration with Vite's environment variables makes it seamless to switch between development and production modes, and the potential for extension with error tracking services makes this utility a solid foundation for our application's observability strategy.
Phase 4: TypeScript Declarations
To make TypeScript happy, we need to add some declarations. Open or create src/types/global.d.ts
and add this:
/// <reference types="vite/client" />
declare global {
// Vite environment variables
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string;
readonly VITE_GRAPHQL_URL: string;
readonly VITE_CLERK_PUBLISHABLE_KEY: string;
// Add other env variables as needed
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
}
export {};
This tells TypeScript about the import.meta.env
variables that Vite provides. Super important!
Phase 5: Let's Migrate! (Examples)
Okay, time to start using our fancy new logger. Let's look at some examples.
Before (The Old Way):
// In src/store/onboarding/machine.ts
if (process.env.NODE_ENV === 'development') {
console.warn(
`[Onboarding] No transition found for event ${event} in state ${currentState}`
);
}
After (The New Hotness):
// In src/store/onboarding/machine.ts
import { loggers } from '@/utils/logger';
const logger = loggers.onboarding;
// This will automatically be removed in production
logger.warn(`No transition found for event ${event} in state ${currentState}`);
See how much cleaner that is? No more process.env.NODE_ENV
checks! ๐
Advanced Usage Examples:
Our new logger is packed with features. Let's check out some advanced use cases.
// Performance tracking
const result = logger.measure('Heavy computation', () => {
return performHeavyComputation();
});
// Grouped logging
logger.group('User Session Started', true);
logger.info('User ID:', userId);
logger.info('Organization:', organizationId);
logger.debug('Session metadata:', metadata);
logger.groupEnd();
// Async operations
await logger.measureAsync('API Call', async () => {
return await fetchUserData();
});
// Child loggers for components
const componentLogger = loggers.ui.child('Button');
componentLogger.debug('Rendered with props:', props);
// Error tracking with context
try {
await riskyOperation();
} catch (error) {
logger.error('Failed to perform operation', error, {
userId,
context: getCurrentContext(),
});
}
From measuring performance to grouping logs and tracking errors with context, our logger is equipped to handle a wide array of logging needs. The ability to create child loggers for components is particularly useful for maintaining a clear and organized logging structure as our application grows. Error tracking with context allows us to capture detailed information about the state of our application when errors occur, making debugging significantly easier. The comprehensive feature set of our new logger ensures that we have the tools we need to create a robust and informative logging system.
๐ Migration Checklist: Time to Get Organized
Okay, let's make a plan to migrate our existing code. Here's a checklist to keep us on track.
Files to Update:
- [ ]
vite.config.ts
- Addvite-plugin-remove-console
- [ ]
src/utils/logger.ts
- Create logger utility - [ ]
src/types/global.d.ts
- Add TypeScript declarations - [ ]
package.json
- Addvite-plugin-remove-console
dependency
Modules to Migrate (Priority Order):
- High Priority (Most
console.log
usage):- [ ]
src/store/onboarding/machine.ts
- [ ]
src/store/onboarding/transitions.ts
- [ ]
src/hooks/useOnboarding.ts
- [ ]
src/store/onboarding/hooks.ts
- [ ]
- Medium Priority:
- [ ] Auth-related modules
- [ ] API/GraphQL modules
- [ ] Route guards
- Low Priority:
- [ ] UI components
- [ ] Utility functions
๐ฏ What Success Looks Like
- [ ] No more
process.env.NODE_ENV
checks for console logging. Hallelujah! - [ ] All
console.log
/debug
statements gone from the production bundle. ๐ป - [ ]
console.error
andconsole.warn
still kicking in production. ๐ช - [ ] Type-safe logging throughout the app. โ
- [ ] Improved DX with namespaced, colored logging. โจ
- [ ] Bundle size reduction in production. ๐ฆ
- [ ] Performance tracking capabilities added. ๐
- [ ] No breaking changes to existing functionality. ๐
๐ง Let's Test This Thing: Testing Plan
Testing is crucial to ensure our new logging system works seamlessly across different environments. We need to verify that our development experience is enhanced, production builds are optimized, and all logging functionalities perform as expected. Hereโs a comprehensive testing plan to guide us:
-
Development Mode:
- Verify all logging appears in the console: Ensure that all log statements, including debug, info, warn, and error messages, are correctly displayed in the console during development.
- Check namespace formatting: Confirm that the namespace prefixes are correctly applied to log messages, making it easier to trace logs back to their origin.
- Test performance tracking utilities: Use the
logger.measure
andlogger.measureAsync
methods to track the execution time of functions and verify that the performance metrics are accurately logged. - Verify colored output: Ensure that the colored output for different log levels (debug, info, warn, error) is displayed correctly in the console, enhancing readability.
-
Production Build:
- Build with
yarn build
: Create a production build of the application using theyarn build
command. - Verify
console.log
/debug
removed from bundle: Inspect the bundled JavaScript files to confirm that allconsole.log
anddebug
statements have been removed byvite-plugin-remove-console
. - Confirm
console.error
/warn
still present: Ensure thatconsole.error
andconsole.warn
statements are still included in the production bundle, as these are important for monitoring application health. - Check bundle size reduction: Compare the bundle size of the production build with and without the logger to quantify the reduction achieved by removing debug logs.
- Test error tracking integration (if applicable): If integrated with an error tracking service like Sentry or LogRocket, trigger error conditions and verify that errors are correctly captured and reported by the service.
- Build with
-
Migration Testing:
- Test each migrated module: After migrating a module to use the new logger, thoroughly test the module to ensure that all logging functionalities are working as expected.
- Verify no functional regressions: Confirm that the changes made to implement the new logger have not introduced any regressions or broken existing functionality.
- Check TypeScript types: Ensure that all TypeScript types are correctly handled and that there are no type-related issues after the migration.
๐ Don't Forget the Docs: Documentation Updates
Good documentation is essential for making our logging system easy to understand and use. This ensures consistency across the team and helps new members get up to speed quickly. Here are the key areas we need to update in our documentation:
- Update contributing guide with logging guidelines:
- Add a section to the contributing guide that outlines the best practices for using the new logging system.
- Explain the importance of using appropriate log levels (debug, info, warn, error) and provide examples of when to use each level.
- Describe how to use namespaces to organize logs and make them easier to filter and analyze.
- Emphasize the importance of including relevant context in log messages to aid in debugging.
- Add logger utility to developer documentation:
- Create a dedicated section in the developer documentation that describes the logger utility in detail.
- Explain the purpose of the logger and its key features, such as automatic log removal in production, colored output, and performance tracking.
- Provide a comprehensive API reference for the logger, including descriptions of all methods and options.
- Create examples for common logging patterns:
- Include examples of common logging scenarios, such as logging errors, tracking performance, and debugging complex logic.
- Show how to use the logger in different parts of the application, such as components, services, and event handlers.
- Provide examples of using child loggers for namespaced logging in components and modules.
- Document environment-specific behavior:
- Clearly document how the logging system behaves in different environments (development, production, testing).
- Explain how
vite-plugin-remove-console
automatically removesconsole.log
andconsole.debug
statements in production builds. - Describe how
console.warn
andconsole.error
statements are preserved in production for critical error reporting.
By keeping our documentation up-to-date, we ensure that our team can effectively use the new logging system, leading to more efficient development and debugging processes.
๐ Helpful Links: References
๐ก Bonus Round: Additional Considerations
- Error Tracking Integration: We should think about hooking this up to Sentry or LogRocket for production error tracking. Super helpful!
- Log Levels: We could add configurable log levels (DEBUG, INFO, WARN, ERROR) for even more control.
- Remote Logging: For those tricky production bugs, remote logging could be a lifesaver.
- Performance Monitoring: Our logger could send performance metrics to monitoring services. ๐
๐ The Perks: Benefits
- DX Improvement: Logging is now way more pleasant with namespaces and colors. ๐
- Performance: Debug code vanishes in production. ๐จ
- Type Safety: TypeScript keeps us honest. ๐ค
- Maintainability: Centralized logging config FTW. ๐
- Flexibility: We can easily add new features. ๐ช
- Production Safety: No accidental console logs escaping into the wild. ๐ป
Estimated Effort: 4-6 hours Priority: Medium-High Breaking Changes: None Dependencies: vite-plugin-remove-console
/cc @adnene