drtrace 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -4
- package/agents/CONTRIBUTING.md +296 -0
- package/agents/README.md +174 -0
- package/agents/daemon-method-selection.md +370 -0
- package/agents/integration-guides/cpp-best-practices.md +218 -0
- package/agents/integration-guides/cpp-ros-integration.md +88 -0
- package/agents/log-analysis.md +218 -0
- package/agents/log-help.md +226 -0
- package/agents/log-init.md +933 -0
- package/agents/log-it.md +1126 -0
- package/bin/init.js +4 -4
- package/dist/bin/init.js +31 -0
- package/dist/browser.d.ts +28 -0
- package/dist/browser.js +91 -0
- package/dist/config-schema.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/init.d.ts +44 -2
- package/dist/init.js +460 -30
- package/dist/logger.d.ts +7 -0
- package/dist/logger.js +30 -4
- package/dist/node.d.ts +13 -0
- package/dist/node.js +67 -0
- package/dist/resources/agents/CONTRIBUTING.md +296 -0
- package/dist/resources/agents/README.md +174 -0
- package/dist/resources/agents/daemon-method-selection.md +370 -0
- package/dist/resources/agents/integration-guides/cpp-best-practices.md +218 -0
- package/dist/resources/agents/integration-guides/cpp-ros-integration.md +88 -0
- package/dist/resources/agents/log-analysis.md +218 -0
- package/dist/resources/agents/log-help.md +226 -0
- package/dist/resources/agents/log-init.md +933 -0
- package/dist/resources/agents/log-it.md +1126 -0
- package/dist/resources/cpp/drtrace_sink.hpp +1249 -0
- package/dist/transport.js +5 -1
- package/dist/types.d.ts +8 -2
- package/package.json +28 -4
- package/.eslintrc.js +0 -20
- package/jest.config.js +0 -11
- package/src/client.ts +0 -68
- package/src/config-schema.ts +0 -115
- package/src/config.ts +0 -326
- package/src/index.ts +0 -3
- package/src/init.ts +0 -451
- package/src/logger.ts +0 -56
- package/src/queue.ts +0 -105
- package/src/transport.ts +0 -60
- package/src/types.ts +0 -20
- package/tests/client.test.ts +0 -66
- package/tests/config-schema.test.ts +0 -198
- package/tests/config.test.ts +0 -456
- package/tests/queue.test.ts +0 -72
- package/tests/transport.test.ts +0 -52
- package/tsconfig.json +0 -18
package/tests/config.test.ts
DELETED
|
@@ -1,456 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for DrTrace configuration loader (JavaScript/TypeScript)
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import * as os from 'os';
|
|
8
|
-
import { ConfigLoader, ConfigSchema, loadConfig } from '../src/config';
|
|
9
|
-
|
|
10
|
-
describe('ConfigSchema', () => {
|
|
11
|
-
describe('getDefault', () => {
|
|
12
|
-
it('should return valid default configuration', () => {
|
|
13
|
-
const defaults = ConfigSchema.getDefault();
|
|
14
|
-
expect(defaults.project.name).toBe('my-app');
|
|
15
|
-
expect(defaults.drtrace.applicationId).toBe('my-app');
|
|
16
|
-
expect(defaults.drtrace.enabled).toBe(true);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should return a deep copy', () => {
|
|
20
|
-
const defaults1 = ConfigSchema.getDefault();
|
|
21
|
-
const defaults2 = ConfigSchema.getDefault();
|
|
22
|
-
defaults1.project.name = 'modified';
|
|
23
|
-
expect(defaults2.project.name).toBe('my-app');
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('validate', () => {
|
|
28
|
-
it('should accept valid default config', () => {
|
|
29
|
-
const config = ConfigSchema.getDefault();
|
|
30
|
-
const validated = ConfigSchema.validate(config);
|
|
31
|
-
expect(validated.project.name).toBe('my-app');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should reject missing project section', () => {
|
|
35
|
-
const config = ConfigSchema.getDefault();
|
|
36
|
-
delete (config as any).project;
|
|
37
|
-
expect(() => ConfigSchema.validate(config)).toThrow('Missing required section: project');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should reject missing project name', () => {
|
|
41
|
-
const config = ConfigSchema.getDefault();
|
|
42
|
-
config.project.name = '' as any;
|
|
43
|
-
expect(() => ConfigSchema.validate(config)).toThrow('Missing required field: project.name');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should reject invalid log level', () => {
|
|
47
|
-
const config = ConfigSchema.getDefault();
|
|
48
|
-
config.drtrace.logLevel = 'invalid' as any;
|
|
49
|
-
expect(() => ConfigSchema.validate(config)).toThrow('Invalid logLevel');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should accept valid log levels', () => {
|
|
53
|
-
for (const level of ['debug', 'info', 'warn', 'error']) {
|
|
54
|
-
const config = ConfigSchema.getDefault();
|
|
55
|
-
config.drtrace.logLevel = level as any;
|
|
56
|
-
expect(() => ConfigSchema.validate(config)).not.toThrow();
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should reject invalid agent framework', () => {
|
|
61
|
-
const config = ConfigSchema.getDefault();
|
|
62
|
-
config.agent!.framework = 'invalid-framework' as any;
|
|
63
|
-
expect(() => ConfigSchema.validate(config)).toThrow('Invalid agent framework');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should accept valid agent frameworks', () => {
|
|
67
|
-
for (const framework of ['bmad', 'langchain', 'other']) {
|
|
68
|
-
const config = ConfigSchema.getDefault();
|
|
69
|
-
config.agent!.framework = framework as any;
|
|
70
|
-
expect(() => ConfigSchema.validate(config)).not.toThrow();
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should reject invalid boolean for enabled', () => {
|
|
75
|
-
const config = ConfigSchema.getDefault();
|
|
76
|
-
config.drtrace.enabled = 'true' as any;
|
|
77
|
-
expect(() => ConfigSchema.validate(config)).toThrow('Invalid type for drtrace.enabled');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should reject invalid type for batchSize', () => {
|
|
81
|
-
const config = ConfigSchema.getDefault();
|
|
82
|
-
config.drtrace.batchSize = 'fifty' as any;
|
|
83
|
-
expect(() => ConfigSchema.validate(config)).toThrow('Invalid type for drtrace.batchSize');
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe('ConfigLoader', () => {
|
|
89
|
-
let tmpDir: string;
|
|
90
|
-
|
|
91
|
-
beforeEach(() => {
|
|
92
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'drtrace-config-test-'));
|
|
93
|
-
// Clean up any existing env vars
|
|
94
|
-
delete process.env.NODE_ENV;
|
|
95
|
-
delete process.env.PYTHON_ENV;
|
|
96
|
-
delete process.env.DRTRACE_APPLICATION_ID;
|
|
97
|
-
delete process.env.DRTRACE_DAEMON_URL;
|
|
98
|
-
delete process.env.DRTRACE_ENABLED;
|
|
99
|
-
delete process.env.DRTRACE_LOG_LEVEL;
|
|
100
|
-
delete process.env.DRTRACE_BATCH_SIZE;
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
afterEach(() => {
|
|
104
|
-
// Clean up temp directory
|
|
105
|
-
if (fs.existsSync(tmpDir)) {
|
|
106
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
107
|
-
}
|
|
108
|
-
// Clean up env vars
|
|
109
|
-
delete process.env.NODE_ENV;
|
|
110
|
-
delete process.env.PYTHON_ENV;
|
|
111
|
-
delete process.env.DRTRACE_APPLICATION_ID;
|
|
112
|
-
delete process.env.DRTRACE_DAEMON_URL;
|
|
113
|
-
delete process.env.DRTRACE_ENABLED;
|
|
114
|
-
delete process.env.DRTRACE_LOG_LEVEL;
|
|
115
|
-
delete process.env.DRTRACE_BATCH_SIZE;
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('load', () => {
|
|
119
|
-
it('should load defaults when no config files exist', () => {
|
|
120
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
121
|
-
expect(config.project.name).toBe('my-app');
|
|
122
|
-
expect(config.drtrace.applicationId).toBe('my-app');
|
|
123
|
-
expect(config.drtrace.daemonUrl).toBe('http://localhost:8001');
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should load from _drtrace/config.json', () => {
|
|
127
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
128
|
-
fs.mkdirSync(configDir);
|
|
129
|
-
|
|
130
|
-
const configData = {
|
|
131
|
-
project: { name: 'my-custom-app' },
|
|
132
|
-
drtrace: { applicationId: 'custom-app' },
|
|
133
|
-
};
|
|
134
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
135
|
-
|
|
136
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
137
|
-
expect(config.project.name).toBe('my-custom-app');
|
|
138
|
-
expect(config.drtrace.applicationId).toBe('custom-app');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should load environment-specific override', () => {
|
|
142
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
143
|
-
fs.mkdirSync(configDir);
|
|
144
|
-
|
|
145
|
-
const baseConfig = {
|
|
146
|
-
project: { name: 'my-app' },
|
|
147
|
-
drtrace: {
|
|
148
|
-
applicationId: 'my-app',
|
|
149
|
-
daemonUrl: 'http://localhost:8001',
|
|
150
|
-
enabled: true,
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
|
|
154
|
-
|
|
155
|
-
const prodConfig = {
|
|
156
|
-
drtrace: { daemonUrl: 'https://production.example.com' },
|
|
157
|
-
};
|
|
158
|
-
fs.writeFileSync(path.join(configDir, 'config.production.json'), JSON.stringify(prodConfig));
|
|
159
|
-
|
|
160
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir, environment: 'production' });
|
|
161
|
-
expect(config.drtrace.daemonUrl).toBe('https://production.example.com');
|
|
162
|
-
expect(config.drtrace.applicationId).toBe('my-app'); // From base
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should load environment subsection from base config', () => {
|
|
166
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
167
|
-
fs.mkdirSync(configDir);
|
|
168
|
-
|
|
169
|
-
const configData = {
|
|
170
|
-
project: { name: 'my-app' },
|
|
171
|
-
drtrace: {
|
|
172
|
-
applicationId: 'my-app',
|
|
173
|
-
daemonUrl: 'http://localhost:8001',
|
|
174
|
-
enabled: true,
|
|
175
|
-
},
|
|
176
|
-
environment: {
|
|
177
|
-
production: {
|
|
178
|
-
daemonUrl: 'https://production.example.com',
|
|
179
|
-
enabled: false,
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
184
|
-
|
|
185
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir, environment: 'production' });
|
|
186
|
-
expect(config.drtrace.daemonUrl).toBe('https://production.example.com');
|
|
187
|
-
expect(config.drtrace.enabled).toBe(false);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should detect environment from NODE_ENV', () => {
|
|
191
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
192
|
-
fs.mkdirSync(configDir);
|
|
193
|
-
|
|
194
|
-
const baseConfig = {
|
|
195
|
-
project: { name: 'my-app' },
|
|
196
|
-
drtrace: { applicationId: 'my-app' },
|
|
197
|
-
};
|
|
198
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
|
|
199
|
-
|
|
200
|
-
const prodConfig = { drtrace: { enabled: false } };
|
|
201
|
-
fs.writeFileSync(path.join(configDir, 'config.production.json'), JSON.stringify(prodConfig));
|
|
202
|
-
|
|
203
|
-
process.env.NODE_ENV = 'production';
|
|
204
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
205
|
-
expect(config.drtrace.enabled).toBe(false);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('should detect environment from PYTHON_ENV', () => {
|
|
209
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
210
|
-
fs.mkdirSync(configDir);
|
|
211
|
-
|
|
212
|
-
const baseConfig = {
|
|
213
|
-
project: { name: 'my-app' },
|
|
214
|
-
drtrace: { applicationId: 'my-app' },
|
|
215
|
-
};
|
|
216
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
|
|
217
|
-
|
|
218
|
-
const devConfig = { drtrace: { logLevel: 'debug' } };
|
|
219
|
-
fs.writeFileSync(path.join(configDir, 'config.development.json'), JSON.stringify(devConfig));
|
|
220
|
-
|
|
221
|
-
process.env.PYTHON_ENV = 'development';
|
|
222
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
223
|
-
expect(config.drtrace.logLevel).toBe('debug');
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('should throw on invalid JSON', () => {
|
|
227
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
228
|
-
fs.mkdirSync(configDir);
|
|
229
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), '{ invalid json }');
|
|
230
|
-
|
|
231
|
-
expect(() => ConfigLoader.load({ projectRoot: tmpDir })).toThrow('Invalid JSON');
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
describe('environment variable overrides', () => {
|
|
236
|
-
it('should override applicationId from DRTRACE_APPLICATION_ID', () => {
|
|
237
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
238
|
-
fs.mkdirSync(configDir);
|
|
239
|
-
|
|
240
|
-
const configData = {
|
|
241
|
-
project: { name: 'my-app' },
|
|
242
|
-
drtrace: { applicationId: 'from-file' },
|
|
243
|
-
};
|
|
244
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
245
|
-
|
|
246
|
-
process.env.DRTRACE_APPLICATION_ID = 'from-env';
|
|
247
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
248
|
-
expect(config.drtrace.applicationId).toBe('from-env');
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('should override daemonUrl from DRTRACE_DAEMON_URL', () => {
|
|
252
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
253
|
-
fs.mkdirSync(configDir);
|
|
254
|
-
|
|
255
|
-
const configData = {
|
|
256
|
-
project: { name: 'my-app' },
|
|
257
|
-
drtrace: {
|
|
258
|
-
applicationId: 'my-app',
|
|
259
|
-
daemonUrl: 'http://localhost:8001',
|
|
260
|
-
},
|
|
261
|
-
};
|
|
262
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
263
|
-
|
|
264
|
-
process.env.DRTRACE_DAEMON_URL = 'http://custom:9000';
|
|
265
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
266
|
-
expect(config.drtrace.daemonUrl).toBe('http://custom:9000');
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('should override enabled from DRTRACE_ENABLED=true', () => {
|
|
270
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
271
|
-
fs.mkdirSync(configDir);
|
|
272
|
-
|
|
273
|
-
const configData = {
|
|
274
|
-
project: { name: 'my-app' },
|
|
275
|
-
drtrace: { applicationId: 'my-app', enabled: false },
|
|
276
|
-
};
|
|
277
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
278
|
-
|
|
279
|
-
process.env.DRTRACE_ENABLED = 'true';
|
|
280
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
281
|
-
expect(config.drtrace.enabled).toBe(true);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it('should override enabled from DRTRACE_ENABLED=false', () => {
|
|
285
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
286
|
-
fs.mkdirSync(configDir);
|
|
287
|
-
|
|
288
|
-
const configData = {
|
|
289
|
-
project: { name: 'my-app' },
|
|
290
|
-
drtrace: { applicationId: 'my-app', enabled: true },
|
|
291
|
-
};
|
|
292
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
293
|
-
|
|
294
|
-
process.env.DRTRACE_ENABLED = 'false';
|
|
295
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
296
|
-
expect(config.drtrace.enabled).toBe(false);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it('should handle DRTRACE_ENABLED=1 as true', () => {
|
|
300
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
301
|
-
fs.mkdirSync(configDir);
|
|
302
|
-
|
|
303
|
-
const configData = {
|
|
304
|
-
project: { name: 'my-app' },
|
|
305
|
-
drtrace: { applicationId: 'my-app', enabled: false },
|
|
306
|
-
};
|
|
307
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
308
|
-
|
|
309
|
-
process.env.DRTRACE_ENABLED = '1';
|
|
310
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
311
|
-
expect(config.drtrace.enabled).toBe(true);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('should override batchSize from DRTRACE_BATCH_SIZE', () => {
|
|
315
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
316
|
-
fs.mkdirSync(configDir);
|
|
317
|
-
|
|
318
|
-
const configData = {
|
|
319
|
-
project: { name: 'my-app' },
|
|
320
|
-
drtrace: { applicationId: 'my-app', batchSize: 50 },
|
|
321
|
-
};
|
|
322
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
323
|
-
|
|
324
|
-
process.env.DRTRACE_BATCH_SIZE = '100';
|
|
325
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
326
|
-
expect(config.drtrace.batchSize).toBe(100);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('should throw on invalid DRTRACE_BATCH_SIZE', () => {
|
|
330
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
331
|
-
fs.mkdirSync(configDir);
|
|
332
|
-
|
|
333
|
-
const configData = {
|
|
334
|
-
project: { name: 'my-app' },
|
|
335
|
-
drtrace: { applicationId: 'my-app' },
|
|
336
|
-
};
|
|
337
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
338
|
-
|
|
339
|
-
process.env.DRTRACE_BATCH_SIZE = 'not-a-number';
|
|
340
|
-
expect(() => ConfigLoader.load({ projectRoot: tmpDir })).toThrow('must be an integer');
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it('should override agent framework from DRTRACE_AGENT_FRAMEWORK', () => {
|
|
344
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
345
|
-
fs.mkdirSync(configDir);
|
|
346
|
-
|
|
347
|
-
const configData = {
|
|
348
|
-
project: { name: 'my-app' },
|
|
349
|
-
drtrace: { applicationId: 'my-app' },
|
|
350
|
-
agent: { framework: null },
|
|
351
|
-
};
|
|
352
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
353
|
-
|
|
354
|
-
process.env.DRTRACE_AGENT_FRAMEWORK = 'langchain';
|
|
355
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
356
|
-
expect(config.agent?.framework).toBe('langchain');
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it('should apply environment variables with highest priority', () => {
|
|
360
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
361
|
-
fs.mkdirSync(configDir);
|
|
362
|
-
|
|
363
|
-
const baseConfig = {
|
|
364
|
-
project: { name: 'my-app' },
|
|
365
|
-
drtrace: {
|
|
366
|
-
applicationId: 'from-file',
|
|
367
|
-
daemonUrl: 'http://file-daemon:8001',
|
|
368
|
-
},
|
|
369
|
-
};
|
|
370
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
|
|
371
|
-
|
|
372
|
-
const prodConfig = { drtrace: { applicationId: 'from-prod-file' } };
|
|
373
|
-
fs.writeFileSync(path.join(configDir, 'config.production.json'), JSON.stringify(prodConfig));
|
|
374
|
-
|
|
375
|
-
process.env.DRTRACE_APPLICATION_ID = 'from-env';
|
|
376
|
-
process.env.DRTRACE_DAEMON_URL = 'http://env-daemon:9000';
|
|
377
|
-
|
|
378
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir, environment: 'production' });
|
|
379
|
-
expect(config.drtrace.applicationId).toBe('from-env');
|
|
380
|
-
expect(config.drtrace.daemonUrl).toBe('http://env-daemon:9000');
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
describe('convenience function', () => {
|
|
385
|
-
it('should work with loadConfig function', () => {
|
|
386
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
387
|
-
fs.mkdirSync(configDir);
|
|
388
|
-
|
|
389
|
-
const configData = {
|
|
390
|
-
project: { name: 'my-app' },
|
|
391
|
-
drtrace: { applicationId: 'test-app' },
|
|
392
|
-
};
|
|
393
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
394
|
-
|
|
395
|
-
const config = loadConfig({ projectRoot: tmpDir });
|
|
396
|
-
expect(config.project.name).toBe('my-app');
|
|
397
|
-
expect(config.drtrace.applicationId).toBe('test-app');
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
describe('loading priority', () => {
|
|
402
|
-
it('should respect complete loading priority', () => {
|
|
403
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
404
|
-
fs.mkdirSync(configDir);
|
|
405
|
-
|
|
406
|
-
const baseConfig = {
|
|
407
|
-
project: { name: 'from-base' },
|
|
408
|
-
drtrace: {
|
|
409
|
-
applicationId: 'from-base',
|
|
410
|
-
daemonUrl: 'http://localhost:8001',
|
|
411
|
-
logLevel: 'info' as const,
|
|
412
|
-
},
|
|
413
|
-
};
|
|
414
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
|
|
415
|
-
|
|
416
|
-
const prodConfig = { drtrace: { logLevel: 'debug' as const } };
|
|
417
|
-
fs.writeFileSync(path.join(configDir, 'config.production.json'), JSON.stringify(prodConfig));
|
|
418
|
-
|
|
419
|
-
process.env.DRTRACE_DAEMON_URL = 'http://env-daemon:9000';
|
|
420
|
-
|
|
421
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir, environment: 'production' });
|
|
422
|
-
|
|
423
|
-
// From env var (highest)
|
|
424
|
-
expect(config.drtrace.daemonUrl).toBe('http://env-daemon:9000');
|
|
425
|
-
|
|
426
|
-
// From env-specific override
|
|
427
|
-
expect(config.drtrace.logLevel).toBe('debug');
|
|
428
|
-
|
|
429
|
-
// From base config
|
|
430
|
-
expect(config.drtrace.applicationId).toBe('from-base');
|
|
431
|
-
|
|
432
|
-
// From defaults (for fields not specified anywhere)
|
|
433
|
-
expect(config.drtrace.batchSize).toBe(50);
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
describe('missing config files', () => {
|
|
438
|
-
it('should use defaults when config files are missing', () => {
|
|
439
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
440
|
-
expect(config.project.name).toBe('my-app');
|
|
441
|
-
expect(config.drtrace.enabled).toBe(true);
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
it('should use defaults for missing drtrace section', () => {
|
|
445
|
-
const configDir = path.join(tmpDir, '_drtrace');
|
|
446
|
-
fs.mkdirSync(configDir);
|
|
447
|
-
|
|
448
|
-
const configData = { project: { name: 'my-app' } };
|
|
449
|
-
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
|
|
450
|
-
|
|
451
|
-
const config = ConfigLoader.load({ projectRoot: tmpDir });
|
|
452
|
-
expect(config.project.name).toBe('my-app');
|
|
453
|
-
expect(config.drtrace.applicationId).toBe('my-app'); // From defaults
|
|
454
|
-
});
|
|
455
|
-
});
|
|
456
|
-
});
|
package/tests/queue.test.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { LogQueue } from '../src/queue';
|
|
2
|
-
import { Transport } from '../src/transport';
|
|
3
|
-
import type { LogEvent } from '../src/types';
|
|
4
|
-
|
|
5
|
-
jest.useFakeTimers();
|
|
6
|
-
|
|
7
|
-
class MockTransport extends Transport {
|
|
8
|
-
public sent: LogEvent[][] = [];
|
|
9
|
-
constructor() {
|
|
10
|
-
super({ daemonUrl: 'http://localhost:8001' });
|
|
11
|
-
}
|
|
12
|
-
async sendBatch(events: LogEvent[]): Promise<void> {
|
|
13
|
-
this.sent.push(events);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('LogQueue', () => {
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
process.removeAllListeners('SIGINT');
|
|
20
|
-
process.removeAllListeners('SIGTERM');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('flushes when batchSize is reached', async () => {
|
|
24
|
-
const transport = new MockTransport();
|
|
25
|
-
const queue = new LogQueue({ transport, batchSize: 3, flushIntervalMs: 1000 });
|
|
26
|
-
queue.start();
|
|
27
|
-
|
|
28
|
-
const mk = (msg: string): LogEvent => ({ timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: msg });
|
|
29
|
-
queue.push(mk('a'));
|
|
30
|
-
queue.push(mk('b'));
|
|
31
|
-
expect(transport.sent.length).toBe(0);
|
|
32
|
-
queue.push(mk('c'));
|
|
33
|
-
expect(transport.sent.length).toBe(1);
|
|
34
|
-
expect(transport.sent[0].map(e => e.message)).toEqual(['a', 'b', 'c']);
|
|
35
|
-
queue.stop();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('flushes periodically based on flushIntervalMs', async () => {
|
|
39
|
-
const transport = new MockTransport();
|
|
40
|
-
const queue = new LogQueue({ transport, batchSize: 10, flushIntervalMs: 500 });
|
|
41
|
-
queue.start();
|
|
42
|
-
|
|
43
|
-
const mk = (msg: string): LogEvent => ({ timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: msg });
|
|
44
|
-
queue.push(mk('x'));
|
|
45
|
-
|
|
46
|
-
// Advance timers to trigger scheduled flush
|
|
47
|
-
jest.advanceTimersByTime(500);
|
|
48
|
-
// Allow flush promise to resolve
|
|
49
|
-
await Promise.resolve();
|
|
50
|
-
|
|
51
|
-
expect(transport.sent.length).toBe(1);
|
|
52
|
-
expect(transport.sent[0].map(e => e.message)).toEqual(['x']);
|
|
53
|
-
queue.stop();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('enforces maxQueueSize and drops oldest with warning', async () => {
|
|
57
|
-
const transport = new MockTransport();
|
|
58
|
-
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
59
|
-
const queue = new LogQueue({ transport, batchSize: 100, flushIntervalMs: 1000, maxQueueSize: 2 });
|
|
60
|
-
queue.start();
|
|
61
|
-
|
|
62
|
-
const mk = (msg: string): LogEvent => ({ timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: msg });
|
|
63
|
-
queue.push(mk('a'));
|
|
64
|
-
queue.push(mk('b'));
|
|
65
|
-
queue.push(mk('c')); // exceeds maxQueueSize
|
|
66
|
-
|
|
67
|
-
expect(queue as any).toBeDefined();
|
|
68
|
-
expect(warnSpy).toHaveBeenCalled();
|
|
69
|
-
warnSpy.mockRestore();
|
|
70
|
-
queue.stop();
|
|
71
|
-
});
|
|
72
|
-
});
|
package/tests/transport.test.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { Transport } from '../src/transport';
|
|
2
|
-
|
|
3
|
-
jest.useFakeTimers();
|
|
4
|
-
|
|
5
|
-
// Mock fetch globally
|
|
6
|
-
const mockFetch = jest.fn(async () => ({ ok: true, body: { pipe: () => {} }, text: async () => '' } as any));
|
|
7
|
-
(global as any).fetch = mockFetch as any;
|
|
8
|
-
|
|
9
|
-
describe('Transport', () => {
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
mockFetch.mockClear();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
afterAll(() => {
|
|
15
|
-
jest.useRealTimers();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('sends batched logs via fetch', async () => {
|
|
19
|
-
const t = new Transport({ daemonUrl: 'http://daemon' });
|
|
20
|
-
await t.sendBatch([
|
|
21
|
-
{ timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: 'hello' },
|
|
22
|
-
]);
|
|
23
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
24
|
-
const url = (mockFetch.mock.calls[0] as any)[0];
|
|
25
|
-
expect(url).toBe('http://daemon/logs/ingest');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('retries on failure with backoff', async () => {
|
|
29
|
-
const seq = [
|
|
30
|
-
Promise.reject(new Error('fail1')),
|
|
31
|
-
Promise.resolve({ ok: false, body: null, text: async () => '' } as any),
|
|
32
|
-
Promise.resolve({ ok: true, body: null, text: async () => '' } as any),
|
|
33
|
-
];
|
|
34
|
-
mockFetch.mockImplementation(() => seq.shift()!);
|
|
35
|
-
|
|
36
|
-
const t = new Transport({ daemonUrl: 'http://daemon', maxRetries: 2, timeoutMs: 1000 });
|
|
37
|
-
const sendPromise = t.sendBatch([
|
|
38
|
-
{ timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: 'hello' },
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
jest.advanceTimersByTime(1000);
|
|
42
|
-
await jest.runAllTimersAsync();
|
|
43
|
-
await sendPromise;
|
|
44
|
-
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('ignores empty batches', async () => {
|
|
48
|
-
const t = new Transport({ daemonUrl: 'http://daemon' });
|
|
49
|
-
await t.sendBatch([]);
|
|
50
|
-
expect(mockFetch).toHaveBeenCalledTimes(0);
|
|
51
|
-
});
|
|
52
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"lib": ["ES2020"],
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"rootDir": "./src",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"resolveJsonModule": true,
|
|
14
|
-
"moduleResolution": "node"
|
|
15
|
-
},
|
|
16
|
-
"include": ["src/**/*"],
|
|
17
|
-
"exclude": ["node_modules", "dist", "tests"]
|
|
18
|
-
}
|