Skip to content

Commit deda29b

Browse files
committed
Add API spec for logger client integration into cdp-client
Specifies two changes to integrate the CDP Logger Client into the main JavaScript CDP Client package: 1. Bundle logger client as studio.logger — move the cdplogger-client code into the cdp-client package so users only need one require() 2. Add client.logger(path) method — auto-discovers the logger's WebSocket endpoint by reading ServerPort and ServerIP from the node's children, then creates and caches a logger client connection Uses ServerPort tree navigation for discovery (works with all CDP versions). Service-based discovery and proxy transport via ServicesNotification are documented as future work for CDP 5.1+. Includes 5 use case examples, failure/timeout semantics, caching and lifecycle behavior, and migration notes.
1 parent ea72cfd commit deda29b

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

spec-logger-integration.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# JavaScript CDP Client: Logger Integration — API Spec
2+
3+
## Problem
4+
5+
Connecting to a CDP Logger requires manual discovery boilerplate:
6+
7+
```javascript
8+
const client = new studio.api.Client('host:7689', listener);
9+
client.find("App.CDPLogger").then(logger => {
10+
logger.subscribeToChildValues("ServerPort", port => {
11+
const loggerClient = new cdplogger.Client("host:" + port);
12+
// Now use loggerClient separately...
13+
});
14+
});
15+
```
16+
17+
This requires knowing the `ServerPort` child convention, extracting the hostname, and managing two independent client lifecycles. The logger client (`cdplogger-client`) is a separate npm package with its own protobuf schema and WebSocket connection, making the setup even more cumbersome.
18+
19+
## Proposed API Changes
20+
21+
### 1. Bundle logger client as `studio.logger`
22+
23+
The logger client code moves into the `cdp-client` package. Users get both clients from a single `require()`:
24+
25+
```javascript
26+
const studio = require('cdp-client');
27+
28+
// Standalone usage (same as before, but no separate package)
29+
const loggerClient = new studio.logger.Client('127.0.0.1:17000');
30+
loggerClient.requestLoggedNodes().then(nodes => { ... });
31+
```
32+
33+
### 2. `client.logger()` auto-discovers and connects
34+
35+
New method on `studio.api.Client` that encapsulates the discovery boilerplate:
36+
37+
```javascript
38+
const client = new studio.api.Client('host:7689', listener);
39+
const loggerClient = await client.logger("App.CDPLogger");
40+
// loggerClient is ready to use — requestLoggedNodes(), requestDataPoints(), etc.
41+
```
42+
43+
**API alternatives considered:**
44+
45+
- **Instance getter (recommended):** `client.logger(path)` — matches `client.root()` and `client.find()` patterns. Logger lifecycle coupled to the client via `close()`. Cached per path. Users who need independent lifecycle can use `studio.logger.Client` directly.
46+
- **Factory function:** `studio.api.createLoggerClient(client, path)` — independent lifecycle, no caching, caller manages disconnect. More boilerplate, less discoverable. The standalone `studio.logger.Client` already serves this role.
47+
- **INode-level:** `loggerNode.loggerClient()` — method on any INode. Pollutes the generic node interface with logger-specific knowledge, sets a precedent for feature-specific methods on INode.
48+
49+
**Behavior:**
50+
51+
- `client.logger(loggerNodePath)` navigates to the given node via `find()`, reads `ServerPort` and `ServerIP` from the node's children, and creates a logger client connection to `ServerIP:ServerPort`. If `ServerIP` is empty or absent (e.g., on a LogServer node), it falls back to the hostname from the `studioURL` the main client was constructed with.
52+
- The logger client is created with `autoReconnect=false``client.close()` disconnects it, but main client reconnects do not invalidate it (see below).
53+
- Returns `Promise<LoggerClient>`.
54+
55+
**Caching:**
56+
57+
- Repeated calls with the same path return the same cached instance (or the same pending promise if discovery is still in progress).
58+
- A cached logger client that is no longer connected is evicted — the next call triggers fresh discovery.
59+
60+
**Error handling:**
61+
62+
- If the node path doesn't exist, `find()` waits for the app to appear (same as any other `find()` call).
63+
- If the node exists but `ServerPort` is not available (wrong node type, not yet configured), the promise rejects after a timeout.
64+
- `client.close()` calls `disconnect()` on all cached logger clients, rejects any pending `client.logger()` promises, and clears the cache.
65+
- After `client.close()`, calls on a disconnected logger client reject immediately with `"Client is disconnected"`.
66+
67+
**Main client reconnect:**
68+
69+
- Cached logger clients are NOT automatically invalidated when the main StudioAPI connection reconnects. A dropped logger WebSocket stays disconnected. The next call to `client.logger()` for the same path creates a fresh connection.
70+
71+
---
72+
73+
## Use Case Examples
74+
75+
### Example 1: Query historical data
76+
77+
```javascript
78+
const studio = require('cdp-client');
79+
const client = new studio.api.Client('127.0.0.1:7689');
80+
81+
const logger = await client.logger('App.CDPLogger');
82+
const limits = await logger.requestLogLimits();
83+
const points = await logger.requestDataPoints(
84+
['Temperature', 'Pressure'],
85+
limits.startS, limits.endS, 200 /* max points */, 0 /* no batch limit */
86+
);
87+
points.forEach(p => {
88+
const temp = p.value['Temperature'];
89+
console.log(new Date(p.timestamp * 1000), 'min:', temp.min, 'max:', temp.max);
90+
});
91+
```
92+
93+
### Example 2: Query events
94+
95+
```javascript
96+
const logger = await client.logger('App.CDPLogger');
97+
const events = await logger.requestEvents({
98+
limit: 100,
99+
flags: studio.logger.Client.EventQueryFlags.NewestFirst
100+
});
101+
events.forEach(e => console.log(e.sender, e.data.Text));
102+
```
103+
104+
### Example 3: Standalone usage (no discovery)
105+
106+
For users who already know the logger endpoint:
107+
108+
```javascript
109+
const studio = require('cdp-client');
110+
const loggerClient = new studio.logger.Client('127.0.0.1:17000');
111+
loggerClient.requestLoggedNodes().then(nodes => console.log(nodes));
112+
```
113+
114+
### Example 4: Timeout on wrong node
115+
116+
```javascript
117+
client.logger('App.SomeComponent') // not a logger
118+
.catch(err => console.log(err.message));
119+
// "Timeout: logger not available on App.SomeComponent"
120+
```
121+
122+
### Example 5: Post-close rejection
123+
124+
```javascript
125+
const logger = await client.logger('App.CDPLogger');
126+
client.close();
127+
logger.requestApiVersion()
128+
.catch(err => console.log(err.message));
129+
// "Client is disconnected"
130+
```
131+
132+
---
133+
134+
## Code Organization
135+
136+
```
137+
JavascriptCDPClient/
138+
index.js
139+
studioapi.proto.js
140+
logger/
141+
logger-client.js
142+
container-pb.js
143+
package.json
144+
```
145+
146+
The two protobuf schemas are independent (`StudioAPI.Proto.Container` vs `DBMessaging.Protobuf.Container`). They share `CDPValueType` and `VariantValue` type definitions but never exchange messages — each goes to a separate WebSocket endpoint.
147+
148+
## Future: Service Discovery and Proxy Transport (CDP 5.1+)
149+
150+
Both CDPLogger and LogServer register as `websocketproxy` services with `proxy_type: "logserver"` via their shared `ServerRunner` class (see `ServerRunner::RegisterToWebsocketProxy`). The JS client already receives these services via `ServicesNotification` and stores them in `availableServices`. Two features can build on this:
151+
152+
### Service-based discovery
153+
154+
Instead of requiring callers to know the logger node path, a path-less API could enumerate all available loggers:
155+
156+
```javascript
157+
const loggers = await client.loggers(); // returns all discovered logger services
158+
const logger = loggers[0]; // or find by name/metadata
159+
```
160+
161+
The service metadata includes `node_path`, `node_model`, `ip_address`, and `port` — everything needed to connect without tree navigation. The JS client's `findProxyService()` currently filters for `proxy_type === 'studioapi'`; extending it to support `'logserver'` would enable this.
162+
163+
### Proxy transport
164+
165+
Instead of opening a direct WebSocket to the logger port, the logger connection could tunnel through the existing StudioAPI WebSocket — the same way sibling app connections work via `connectViaProxy()`. This avoids firewall and port-opening issues when the logger port is not directly reachable from the client.
166+
167+
Service-based discovery and proxy transport are the preferred approach for CDP 5.1+ and should be added in a follow-up. The `ServerPort` approach is implemented first for backward compatibility with all CDP versions.
168+
169+
The C++ client already supports service-based logger discovery via `ProxySocketContext::setProxyType("logserver")` combined with `addServicesMonitor()`. Neither the C++ nor Python clients have a `client.logger(path)` convenience method — discovery is manual in both.
170+
171+
## Migration Notes
172+
173+
No breaking changes. For `cdplogger-client` users migrating to the bundled version:
174+
175+
```javascript
176+
// Before (separate package):
177+
const cdplogger = require('cdplogger-client');
178+
const loggerClient = new cdplogger.Client('127.0.0.1:17000');
179+
180+
// After (bundled):
181+
const studio = require('cdp-client');
182+
const loggerClient = new studio.logger.Client('127.0.0.1:17000');
183+
```
184+
185+
## Summary of Changes
186+
187+
| Change | What it does |
188+
|--------|-------------|
189+
| `studio.logger.Client` | Logger client bundled into main package |
190+
| `client.logger(path)` | Auto-discovers logger port, returns connected logger client |
191+
| `client.close()` | Also disconnects cached logger clients; post-close calls reject immediately |

0 commit comments

Comments
 (0)