Repository navigation
feat: Add structured logging throughout core for plugin developer debuggability - #368
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive structured logging throughout the ObjectStack core packages to improve debuggability for plugin developers. The implementation replaces ad-hoc console.log statements with a structured logger (Pino for Node.js, console fallback for browsers) that provides consistent logging with proper log levels, structured metadata, and contextual information.
Changes:
- Introduced structured logging infrastructure with Logger interface and createLogger factory function that works in both Node.js and browser environments
- Added detailed logging to core packages (objectql, driver-memory, runtime, client, plugin-hono-server, plugin-msw) covering lifecycle events, data operations, HTTP requests, and error conditions
- Added logger dependencies to client and examples/basic packages to support the new logging infrastructure
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Added @objectstack/core dependency to client and examples/basic packages |
| packages/runtime/src/driver-plugin.ts | Replaced console.log with structured logging for driver registration and lifecycle |
| packages/runtime/src/app-plugin.ts | Added detailed logging for app plugin initialization and runtime execution |
| packages/plugins/plugin-msw/src/msw-plugin.ts | Replaced console.log with structured logging for MSW operations and request handling |
| packages/plugins/plugin-hono-server/src/hono-plugin.ts | Added comprehensive logging for HTTP server operations and API route handling |
| packages/plugins/driver-memory/src/memory-driver.ts | Added structured logging for all CRUD operations with statistics |
| packages/objectql/src/plugin.ts | Converted console.log to structured logging for ObjectQL plugin lifecycle |
| packages/objectql/src/engine.ts | Added comprehensive logging for engine initialization, driver operations, and data operations with error tracking |
| packages/client/src/index.ts | Added HTTP request/response logging with optional debug mode |
| packages/client/package.json | Added @objectstack/core dependency |
| examples/basic/package.json | Added @objectstack/core dependency |
| examples/basic/logger-example.ts | Fixed type annotation for service retrieval |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
| return hookContext.result; | ||
| } catch (e) { | ||
| // hookContext.error = e; | ||
| this.logger.error('Find operation failed', e as Error, { object }); |
There was a problem hiding this comment.
Using type assertion e as Error doesn't guarantee that e is actually an Error instance. In JavaScript, catch blocks can catch any value (string, number, object, etc.).
Consider checking the error type:
catch (e) {
const error = e instanceof Error ? e : new Error(String(e));
this.logger.error('Find operation failed', error, { object });
throw error;
}This pattern appears throughout this file in multiple catch blocks (lines 367, 402, 453, 491, 529).
| this.logger.debug('FindOne operation completed', { object, found: !!result }); | ||
| return result; | ||
| } catch (e) { | ||
| this.logger.error('FindOne operation failed', e as Error, { object }); |
There was a problem hiding this comment.
Using type assertion e as Error doesn't guarantee that e is actually an Error instance. Same issue as line 367.
| return hookContext.result; | ||
| return hookContext.result; | ||
| } catch (e) { | ||
| this.logger.error('Insert operation failed', e as Error, { object }); |
There was a problem hiding this comment.
Using type assertion e as Error doesn't guarantee that e is actually an Error instance. Same issue as line 367.
| res.json(result); | ||
| } | ||
| catch(e:any) { | ||
| ctx.logger.error('Data update failed', e, { object: req.params.object, id: req.params.id }); |
There was a problem hiding this comment.
The caught error e (typed as any) is passed directly to the logger without ensuring it's an Error object. This is the same issue as line 100.
|
|
||
| return hookContext.result; | ||
| } catch (e) { | ||
| this.logger.error('Update operation failed', e as Error, { object, id }); |
There was a problem hiding this comment.
Using type assertion e as Error doesn't guarantee that e is actually an Error instance. Same issue as line 367.
| data: result | ||
| }; | ||
| } catch (error) { | ||
| this.logger?.error?.('MSW: Create failed', error, { object }); |
There was a problem hiding this comment.
The caught error is passed directly to the logger without ensuring it's an Error object. In JavaScript, catch blocks can catch any value (string, number, object, etc.), not just Error instances. This could cause issues if the logger expects an Error object.
Consider checking the error type before passing it to the logger:
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
this.logger?.error?.('MSW: Create failed', err, { object });
// ... rest of the code
}| data: result | ||
| }; | ||
| } catch (error) { | ||
| this.logger?.error?.('MSW: Delete failed', error, { object, id }); |
There was a problem hiding this comment.
The caught error is passed directly to the logger without ensuring it's an Error object. In JavaScript, catch blocks can catch any value (string, number, object, etc.), not just Error instances.
Consider checking the error type before passing it to the logger:
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
this.logger?.error?.('MSW: Delete failed', err, { object, id });
// ... rest of the code
}| res.status(201).json(result); | ||
| } | ||
| catch(e:any) { | ||
| ctx.logger.error('Data create failed', e, { object: req.params.object }); |
There was a problem hiding this comment.
The caught error e (typed as any) is passed directly to the logger without ensuring it's an Error object. This is the same issue as line 100.
| res.json(result); | ||
| } | ||
| catch(e:any) { | ||
| ctx.logger.error('Data delete failed', e, { object: req.params.object, id: req.params.id }); |
There was a problem hiding this comment.
The caught error e (typed as any) is passed directly to the logger without ensuring it's an Error object. This is the same issue as line 100.
| this.logger = logger || { | ||
| info: console.log, | ||
| debug: console.debug, | ||
| warn: console.warn, | ||
| error: console.error | ||
| }; |
There was a problem hiding this comment.
The fallback logger maps info to console.log, but this is inconsistent with the typical console API where console.info exists. While this works, it's semantically confusing.
Consider using console.info for consistency:
this.logger = logger || {
info: console.info,
debug: console.debug,
warn: console.warn,
error: console.error
};
Plugin developers lacked visibility into runtime behavior. Most packages used
console.logor had no logging at all.Changes
Logging Infrastructure
console.logwith structured logger (Pino for Node.js, console fallback for browser)objectql,driver-memory,runtime,client,plugin-hono-server,plugin-mswObjectQL Engine (
packages/objectql/src/engine.ts)Driver & Plugins
Example
Before:
After:
Log levels:
debug(operations),info(lifecycle),warn(issues),error(failures with stack traces)Dependencies
Added
@objectstack/coretoclientandexamples/basicpackages (required for logger import).Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.