Skip to content

Commit 8ed86de

Browse files
Copilothotlong
andcommitted
Add logging to HTTP plugins and client
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 21d4614 commit 8ed86de

3 files changed

Lines changed: 189 additions & 42 deletions

File tree

packages/client/src/index.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { QueryAST, SortNode, AggregationNode, WindowFunctionNode } from '@objectstack/spec/data';
2+
import { Logger, createLogger } from '@objectstack/core';
23

34
export interface ClientConfig {
45
baseUrl: string;
@@ -7,6 +8,14 @@ export interface ClientConfig {
78
* Custom fetch implementation (e.g. node-fetch or for Next.js caching)
89
*/
910
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
11+
/**
12+
* Logger instance for debugging
13+
*/
14+
logger?: Logger;
15+
/**
16+
* Enable debug logging
17+
*/
18+
debug?: boolean;
1019
}
1120

1221
export interface DiscoveryResult {
@@ -41,27 +50,44 @@ export class ObjectStackClient {
4150
private token?: string;
4251
private fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
4352
private routes?: DiscoveryResult['routes'];
53+
private logger: Logger;
4454

4555
constructor(config: ClientConfig) {
4656
this.baseUrl = config.baseUrl.replace(/\/$/, ''); // Remove trailing slash
4757
this.token = config.token;
4858
this.fetchImpl = config.fetch || globalThis.fetch.bind(globalThis);
59+
60+
// Initialize logger
61+
this.logger = config.logger || createLogger({
62+
level: config.debug ? 'debug' : 'info',
63+
format: 'pretty'
64+
});
65+
66+
this.logger.debug('ObjectStack client created', { baseUrl: this.baseUrl });
4967
}
5068

5169
/**
5270
* Initialize the client by discovering server capabilities and routes.
5371
*/
5472
async connect() {
73+
this.logger.debug('Connecting to ObjectStack server', { baseUrl: this.baseUrl });
74+
5575
try {
5676
// Connect to the discovery endpoint
5777
// During boot, we might not know routes, so we check convention /api/v1 first
5878
const res = await this.fetch(`${this.baseUrl}/api/v1`);
5979

6080
const data = await res.json();
6181
this.routes = data.routes;
82+
83+
this.logger.info('Connected to ObjectStack server', {
84+
routes: Object.keys(data.routes || {}),
85+
capabilities: data.capabilities
86+
});
87+
6288
return data as DiscoveryResult;
6389
} catch (e) {
64-
console.error('Failed to connect to ObjectStack Server', e);
90+
this.logger.error('Failed to connect to ObjectStack server', e as Error, { baseUrl: this.baseUrl });
6591
throw e;
6692
}
6793
}
@@ -225,6 +251,12 @@ export class ObjectStackClient {
225251
}
226252

227253
private async fetch(url: string, options: RequestInit = {}): Promise<Response> {
254+
this.logger.debug('HTTP request', {
255+
method: options.method || 'GET',
256+
url,
257+
hasBody: !!options.body
258+
});
259+
228260
const headers: Record<string, string> = {
229261
'Content-Type': 'application/json',
230262
...(options.headers as Record<string, string> || {}),
@@ -236,13 +268,28 @@ export class ObjectStackClient {
236268

237269
const res = await this.fetchImpl(url, { ...options, headers });
238270

271+
this.logger.debug('HTTP response', {
272+
method: options.method || 'GET',
273+
url,
274+
status: res.status,
275+
ok: res.ok
276+
});
277+
239278
if (!res.ok) {
240279
let errorBody;
241280
try {
242281
errorBody = await res.json();
243282
} catch {
244283
errorBody = { message: res.statusText };
245284
}
285+
286+
this.logger.error('HTTP request failed', undefined, {
287+
method: options.method || 'GET',
288+
url,
289+
status: res.status,
290+
error: errorBody
291+
});
292+
246293
throw new Error(`[ObjectStack] Request failed: ${res.status} ${JSON.stringify(errorBody)}`);
247294
}
248295

@@ -252,7 +299,10 @@ export class ObjectStackClient {
252299
private getRoute(key: keyof DiscoveryResult['routes']): string {
253300
if (!this.routes) {
254301
// Fallback for strictness, but we allow bootstrapping
255-
console.warn(`[ObjectStackClient] Accessing ${key} route before connect(). Using default /api/v1/${key}`);
302+
this.logger.warn('Accessing route before connect()', {
303+
route: key,
304+
fallback: `/api/v1/${key}`
305+
});
256306
return `/api/v1/${key}`;
257307
}
258308
return this.routes[key] || `/api/v1/${key}`;

packages/plugins/plugin-hono-server/src/hono-plugin.ts

Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,81 +32,152 @@ export class HonoServerPlugin implements Plugin {
3232
* Init phase - Setup HTTP server and register as service
3333
*/
3434
async init(ctx: PluginContext) {
35+
ctx.logger.debug('Initializing Hono server plugin', {
36+
port: this.options.port,
37+
staticRoot: this.options.staticRoot
38+
});
39+
3540
// Register HTTP server service as IHttpServer
3641
ctx.registerService('http-server', this.server);
37-
ctx.logger.log('[HonoServerPlugin] HTTP server service registered');
42+
ctx.logger.info('HTTP server service registered', { serviceName: 'http-server' });
3843
}
3944

4045
/**
4146
* Start phase - Bind routes and start listening
4247
*/
4348
async start(ctx: PluginContext) {
49+
ctx.logger.debug('Starting Hono server plugin');
50+
4451
// Get protocol implementation instance
4552
let protocol: IObjectStackProtocol | null = null;
4653

4754
try {
4855
protocol = ctx.getService<IObjectStackProtocol>('protocol');
56+
ctx.logger.debug('Protocol service found, registering protocol routes');
4957
} catch (e) {
50-
ctx.logger.log('[HonoServerPlugin] Protocol service not found, skipping protocol routes');
58+
ctx.logger.warn('Protocol service not found, skipping protocol routes');
5159
}
5260

5361
// Register protocol routes if available
5462
if (protocol) {
5563
const p = protocol!;
56-
this.server.get('/api/v1', (req, res) => res.json(p.getDiscovery()));
64+
65+
ctx.logger.debug('Registering API routes');
66+
67+
this.server.get('/api/v1', (req, res) => {
68+
ctx.logger.debug('API discovery request');
69+
res.json(p.getDiscovery());
70+
});
5771

5872
// Meta Protocol
59-
this.server.get('/api/v1/meta', (req, res) => res.json(p.getMetaTypes()));
60-
this.server.get('/api/v1/meta/:type', (req, res) => res.json(p.getMetaItems(req.params.type)));
73+
this.server.get('/api/v1/meta', (req, res) => {
74+
ctx.logger.debug('Meta types request');
75+
res.json(p.getMetaTypes());
76+
});
77+
this.server.get('/api/v1/meta/:type', (req, res) => {
78+
ctx.logger.debug('Meta items request', { type: req.params.type });
79+
res.json(p.getMetaItems(req.params.type));
80+
});
6181
this.server.get('/api/v1/meta/:type/:name', (req, res) => {
82+
ctx.logger.debug('Meta item request', { type: req.params.type, name: req.params.name });
6283
try {
6384
res.json(p.getMetaItem(req.params.type, req.params.name));
6485
} catch(e:any) {
86+
ctx.logger.warn('Meta item not found', { type: req.params.type, name: req.params.name });
6587
res.status(404).json({error: e.message});
6688
}
6789
});
6890

6991
// Data Protocol
7092
this.server.get('/api/v1/data/:object', async (req, res) => {
71-
try { res.json(await p.findData(req.params.object, req.query)); }
72-
catch(e:any) { res.status(400).json({error:e.message}); }
93+
ctx.logger.debug('Data find request', { object: req.params.object, query: req.query });
94+
try {
95+
const result = await p.findData(req.params.object, req.query);
96+
ctx.logger.debug('Data find completed', { object: req.params.object, count: result?.length ?? 0 });
97+
res.json(result);
98+
}
99+
catch(e:any) {
100+
ctx.logger.error('Data find failed', e, { object: req.params.object });
101+
res.status(400).json({error:e.message});
102+
}
73103
});
74104
this.server.get('/api/v1/data/:object/:id', async (req, res) => {
75-
try { res.json(await p.getData(req.params.object, req.params.id)); }
76-
catch(e:any) { res.status(404).json({error:e.message}); }
105+
ctx.logger.debug('Data get request', { object: req.params.object, id: req.params.id });
106+
try {
107+
const result = await p.getData(req.params.object, req.params.id);
108+
ctx.logger.debug('Data get completed', { object: req.params.object, id: req.params.id });
109+
res.json(result);
110+
}
111+
catch(e:any) {
112+
ctx.logger.warn('Data get failed - not found', { object: req.params.object, id: req.params.id });
113+
res.status(404).json({error:e.message});
114+
}
77115
});
78116
this.server.post('/api/v1/data/:object', async (req, res) => {
79-
try { res.status(201).json(await p.createData(req.params.object, req.body)); }
80-
catch(e:any) { res.status(400).json({error:e.message}); }
117+
ctx.logger.debug('Data create request', { object: req.params.object });
118+
try {
119+
const result = await p.createData(req.params.object, req.body);
120+
ctx.logger.info('Data created', { object: req.params.object, id: result?.id });
121+
res.status(201).json(result);
122+
}
123+
catch(e:any) {
124+
ctx.logger.error('Data create failed', e, { object: req.params.object });
125+
res.status(400).json({error:e.message});
126+
}
81127
});
82128
this.server.patch('/api/v1/data/:object/:id', async (req, res) => {
83-
try { res.json(await p.updateData(req.params.object, req.params.id, req.body)); }
84-
catch(e:any) { res.status(400).json({error:e.message}); }
129+
ctx.logger.debug('Data update request', { object: req.params.object, id: req.params.id });
130+
try {
131+
const result = await p.updateData(req.params.object, req.params.id, req.body);
132+
ctx.logger.info('Data updated', { object: req.params.object, id: req.params.id });
133+
res.json(result);
134+
}
135+
catch(e:any) {
136+
ctx.logger.error('Data update failed', e, { object: req.params.object, id: req.params.id });
137+
res.status(400).json({error:e.message});
138+
}
85139
});
86140
this.server.delete('/api/v1/data/:object/:id', async (req, res) => {
87-
try { res.json(await p.deleteData(req.params.object, req.params.id)); }
88-
catch(e:any) { res.status(400).json({error:e.message}); }
141+
ctx.logger.debug('Data delete request', { object: req.params.object, id: req.params.id });
142+
try {
143+
const result = await p.deleteData(req.params.object, req.params.id);
144+
ctx.logger.info('Data deleted', { object: req.params.object, id: req.params.id, success: result });
145+
res.json(result);
146+
}
147+
catch(e:any) {
148+
ctx.logger.error('Data delete failed', e, { object: req.params.object, id: req.params.id });
149+
res.status(400).json({error:e.message});
150+
}
89151
});
90152

91153
// UI Protocol
92154
// @ts-ignore
93155
this.server.get('/api/v1/ui/view/:object', (req, res) => {
156+
const viewType = (req.query.type) || 'list';
157+
const qt = Array.isArray(viewType) ? viewType[0] : viewType;
158+
ctx.logger.debug('UI view request', { object: req.params.object, viewType: qt });
94159
try {
95-
const viewType = (req.query.type) || 'list';
96-
const qt = Array.isArray(viewType) ? viewType[0] : viewType;
97160
res.json(p.getUiView(req.params.object, qt as any));
98161
}
99-
catch(e:any) { res.status(404).json({error:e.message}); }
162+
catch(e:any) {
163+
ctx.logger.warn('UI view not found', { object: req.params.object, viewType: qt });
164+
res.status(404).json({error:e.message});
165+
}
100166
});
167+
168+
ctx.logger.info('All API routes registered');
101169
}
102170

103171
// Start server on kernel:ready hook
104172
ctx.hook('kernel:ready', async () => {
105173
const port = this.options.port || 3000;
106-
ctx.logger.log('[HonoServerPlugin] Starting server...');
174+
ctx.logger.info('Starting HTTP server', { port });
107175

108176
await this.server.listen(port);
109-
ctx.logger.log(`✅ Server is running on http://localhost:${port}`);
177+
ctx.logger.info('HTTP server started successfully', {
178+
port,
179+
url: `http://localhost:${port}`
180+
});
110181
});
111182
}
112183

@@ -115,6 +186,7 @@ export class HonoServerPlugin implements Plugin {
115186
*/
116187
async destroy() {
117188
this.server.close();
189+
// Note: Can't use ctx.logger here since we're in destroy
118190
console.log('[HonoServerPlugin] Server stopped');
119191
}
120192
}

0 commit comments

Comments
 (0)