ralphblaster-agent 0.1.1
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/LICENSE +21 -0
- package/README.md +294 -0
- package/bin/agent-dashboard.sh +168 -0
- package/bin/monitor-agent.sh +264 -0
- package/bin/ralphblaster.js +247 -0
- package/package.json +64 -0
- package/postinstall-colored.js +66 -0
- package/src/api-client.js +764 -0
- package/src/claude-plugin/.claude-plugin/plugin.json +9 -0
- package/src/claude-plugin/README.md +42 -0
- package/src/claude-plugin/skills/ralph/SKILL.md +259 -0
- package/src/commands/add-project.js +257 -0
- package/src/commands/init.js +79 -0
- package/src/config-file-manager.js +84 -0
- package/src/config.js +66 -0
- package/src/error-window.js +86 -0
- package/src/executor/claude-runner.js +716 -0
- package/src/executor/error-handler.js +65 -0
- package/src/executor/git-helper.js +196 -0
- package/src/executor/index.js +296 -0
- package/src/executor/job-handlers/clarifying-questions.js +213 -0
- package/src/executor/job-handlers/code-execution.js +145 -0
- package/src/executor/job-handlers/prd-generation.js +259 -0
- package/src/executor/path-helper.js +74 -0
- package/src/executor/prompt-validator.js +51 -0
- package/src/executor.js +4 -0
- package/src/index.js +342 -0
- package/src/logger.js +193 -0
- package/src/logging/README.md +93 -0
- package/src/logging/config.js +179 -0
- package/src/logging/destinations/README.md +290 -0
- package/src/logging/destinations/api-destination-unbatched.js +118 -0
- package/src/logging/destinations/api-destination.js +40 -0
- package/src/logging/destinations/base-destination.js +85 -0
- package/src/logging/destinations/batched-destination.js +198 -0
- package/src/logging/destinations/console-destination.js +172 -0
- package/src/logging/destinations/file-destination.js +208 -0
- package/src/logging/destinations/index.js +29 -0
- package/src/logging/destinations/progress-batch-destination-unbatched.js +92 -0
- package/src/logging/destinations/progress-batch-destination.js +41 -0
- package/src/logging/formatter.js +288 -0
- package/src/logging/log-manager.js +426 -0
- package/src/progress-throttle.js +101 -0
- package/src/system-monitor.js +64 -0
- package/src/utils/format.js +16 -0
- package/src/utils/log-file-helper.js +265 -0
- package/src/utils/progress-parser.js +250 -0
- package/src/worktree-manager.js +255 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging configuration
|
|
3
|
+
*
|
|
4
|
+
* This module consolidates all logging-related configuration from:
|
|
5
|
+
* - src/config.js (logLevel, consoleColors, consoleFormat)
|
|
6
|
+
* - src/setup-log-batcher.js (maxBatchSize, flushInterval, useBatchEndpoint)
|
|
7
|
+
* - Environment variables (RALPHBLASTER_* with RALPH_* fallback for backward compatibility)
|
|
8
|
+
*
|
|
9
|
+
* @module logging/config
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse boolean from environment variable
|
|
14
|
+
* Treats 'false', '0', and empty string as false, everything else as true
|
|
15
|
+
* @param {string} value - Environment variable value
|
|
16
|
+
* @param {boolean} defaultValue - Default if not set
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
function parseEnvBoolean(value, defaultValue) {
|
|
20
|
+
if (value === undefined || value === null) return defaultValue;
|
|
21
|
+
return value !== 'false' && value !== '0' && value !== '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse positive integer from environment variable
|
|
26
|
+
* @param {string} value - Environment variable value
|
|
27
|
+
* @param {number} defaultValue - Default if not set or invalid
|
|
28
|
+
* @returns {number}
|
|
29
|
+
*/
|
|
30
|
+
function parseEnvInt(value, defaultValue) {
|
|
31
|
+
if (value === undefined || value === null) return defaultValue;
|
|
32
|
+
const parsed = parseInt(value, 10);
|
|
33
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
34
|
+
console.warn(`Invalid numeric value "${value}", using default: ${defaultValue}`);
|
|
35
|
+
return defaultValue;
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Valid log levels in order of severity
|
|
42
|
+
* @type {string[]}
|
|
43
|
+
*/
|
|
44
|
+
const VALID_LOG_LEVELS = ['error', 'warn', 'info', 'debug'];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Valid console format options
|
|
48
|
+
* @type {string[]}
|
|
49
|
+
*/
|
|
50
|
+
const VALID_CONSOLE_FORMATS = ['pretty', 'json'];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate that a log level is valid
|
|
54
|
+
* @param {string} level - Log level to validate
|
|
55
|
+
* @returns {boolean} True if valid
|
|
56
|
+
*/
|
|
57
|
+
function isValidLogLevel(level) {
|
|
58
|
+
return VALID_LOG_LEVELS.includes(level);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Validate that a console format is valid
|
|
63
|
+
* @param {string} format - Console format to validate
|
|
64
|
+
* @returns {boolean} True if valid
|
|
65
|
+
*/
|
|
66
|
+
function isValidConsoleFormat(format) {
|
|
67
|
+
return VALID_CONSOLE_FORMATS.includes(format);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Read configuration from environment variables with validation
|
|
71
|
+
const rawLogLevel = process.env.RALPHBLASTER_LOG_LEVEL || 'info';
|
|
72
|
+
const logLevel = isValidLogLevel(rawLogLevel) ? rawLogLevel : 'info';
|
|
73
|
+
|
|
74
|
+
// Warn if invalid log level was provided
|
|
75
|
+
if (!isValidLogLevel(rawLogLevel)) {
|
|
76
|
+
console.warn(`Invalid log level "${rawLogLevel}", using default: info. Valid levels: ${VALID_LOG_LEVELS.join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const rawConsoleFormat = process.env.RALPHBLASTER_CONSOLE_FORMAT || 'pretty';
|
|
80
|
+
const consoleFormat = isValidConsoleFormat(rawConsoleFormat) ? rawConsoleFormat : 'pretty';
|
|
81
|
+
|
|
82
|
+
// Warn if invalid console format was provided
|
|
83
|
+
if (!isValidConsoleFormat(rawConsoleFormat)) {
|
|
84
|
+
console.warn(`Invalid console format "${rawConsoleFormat}", using default: pretty. Valid formats: ${VALID_CONSOLE_FORMATS.join(', ')}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Centralized logging configuration object
|
|
89
|
+
* All logging-related settings are defined here in one place
|
|
90
|
+
*/
|
|
91
|
+
const loggingConfig = {
|
|
92
|
+
// ===== Console Output Settings =====
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Log level - controls which messages are displayed
|
|
96
|
+
* @type {'error' | 'warn' | 'info' | 'debug'}
|
|
97
|
+
* @default 'info'
|
|
98
|
+
*/
|
|
99
|
+
logLevel,
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Enable/disable colored console output
|
|
103
|
+
* Set to 'false' to disable colors (useful for log files or CI environments)
|
|
104
|
+
* @type {boolean}
|
|
105
|
+
* @default true
|
|
106
|
+
*/
|
|
107
|
+
consoleColors: parseEnvBoolean(process.env.RALPHBLASTER_CONSOLE_COLORS, true),
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Console output format
|
|
111
|
+
* - 'pretty': Human-readable format with colors (default)
|
|
112
|
+
* - 'json': Structured JSON format for machine parsing
|
|
113
|
+
* @type {'pretty' | 'json'}
|
|
114
|
+
* @default 'pretty'
|
|
115
|
+
*/
|
|
116
|
+
consoleFormat,
|
|
117
|
+
|
|
118
|
+
// ===== Log Batching Settings =====
|
|
119
|
+
// These settings control how logs are batched before being sent to the API
|
|
120
|
+
// to reduce the number of API calls and improve performance
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Maximum number of logs to batch before forcing a flush
|
|
124
|
+
* Larger values reduce API calls but may delay log visibility
|
|
125
|
+
* Phase 1.3: Increased from 10 to 50 to match progress batch size
|
|
126
|
+
* Expected impact: 80% reduction in log API calls (50 req/s → 10 req/s)
|
|
127
|
+
* @type {number}
|
|
128
|
+
* @default 50
|
|
129
|
+
*/
|
|
130
|
+
maxBatchSize: parseEnvInt(process.env.RALPHBLASTER_MAX_BATCH_SIZE, 50),
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Time in milliseconds between automatic log flushes
|
|
134
|
+
* Smaller values improve log visibility but increase API calls
|
|
135
|
+
* @type {number}
|
|
136
|
+
* @default 2000 (2 seconds)
|
|
137
|
+
*/
|
|
138
|
+
flushInterval: parseEnvInt(process.env.RALPHBLASTER_FLUSH_INTERVAL, 2000),
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Whether to use the batch endpoint for sending logs
|
|
142
|
+
* When true, tries to send logs in a single batch API call
|
|
143
|
+
* Falls back to individual calls if batch endpoint fails
|
|
144
|
+
* @type {boolean}
|
|
145
|
+
* @default true
|
|
146
|
+
*/
|
|
147
|
+
useBatchEndpoint: parseEnvBoolean(process.env.RALPHBLASTER_USE_BATCH_ENDPOINT, true),
|
|
148
|
+
|
|
149
|
+
// ===== Agent Identification =====
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Agent ID for multi-agent support
|
|
153
|
+
* Used to identify which agent instance is running in multi-agent deployments
|
|
154
|
+
* @type {string}
|
|
155
|
+
* @default 'agent-default'
|
|
156
|
+
*/
|
|
157
|
+
agentId: process.env.RALPHBLASTER_AGENT_ID || 'agent-default',
|
|
158
|
+
|
|
159
|
+
// ===== Validation Helpers =====
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Valid log levels
|
|
163
|
+
* @type {string[]}
|
|
164
|
+
* @readonly
|
|
165
|
+
*/
|
|
166
|
+
validLogLevels: VALID_LOG_LEVELS,
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Valid console formats
|
|
170
|
+
* @type {string[]}
|
|
171
|
+
* @readonly
|
|
172
|
+
*/
|
|
173
|
+
validConsoleFormats: VALID_CONSOLE_FORMATS
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Freeze the configuration to prevent accidental modifications
|
|
177
|
+
Object.freeze(loggingConfig);
|
|
178
|
+
|
|
179
|
+
module.exports = loggingConfig;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Log Destinations
|
|
2
|
+
|
|
3
|
+
This directory contains the log destination abstraction layer for Ralph's logging system. Each destination represents a different output target for log messages (console, file, API, etc.).
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
All destinations extend `BaseDestination` and implement a common interface:
|
|
8
|
+
|
|
9
|
+
- `write(level, message, metadata)` - Write a log entry
|
|
10
|
+
- `flush()` - Flush any buffered logs
|
|
11
|
+
- `close()` - Clean up resources and ensure no logs are lost
|
|
12
|
+
- `shouldLog(level)` - Filter logs based on level
|
|
13
|
+
- `handleError(error, level, message)` - Handle write errors gracefully
|
|
14
|
+
|
|
15
|
+
This pluggable architecture allows destinations to be added, removed, or replaced independently.
|
|
16
|
+
|
|
17
|
+
## Available Destinations
|
|
18
|
+
|
|
19
|
+
### BaseDestination
|
|
20
|
+
|
|
21
|
+
Abstract base class defining the common interface. All destinations must extend this class.
|
|
22
|
+
|
|
23
|
+
**Key features:**
|
|
24
|
+
- Defines standard lifecycle methods (write, flush, close)
|
|
25
|
+
- Provides default error handling
|
|
26
|
+
- Manages shutdown state
|
|
27
|
+
|
|
28
|
+
### ConsoleDestination
|
|
29
|
+
|
|
30
|
+
Outputs logs to console (stdout/stderr) with formatting and colors.
|
|
31
|
+
|
|
32
|
+
**Configuration:**
|
|
33
|
+
```javascript
|
|
34
|
+
const destination = new ConsoleDestination({
|
|
35
|
+
colors: true, // Enable ANSI colors
|
|
36
|
+
format: 'pretty', // 'pretty' or 'json'
|
|
37
|
+
minLevel: 'info' // Minimum log level to output
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Features:**
|
|
42
|
+
- Pretty formatting for human readability
|
|
43
|
+
- JSON format for machine parsing
|
|
44
|
+
- Color-coded by log level
|
|
45
|
+
- Redacts sensitive data
|
|
46
|
+
- No buffering - immediate output
|
|
47
|
+
|
|
48
|
+
### FileDestination
|
|
49
|
+
|
|
50
|
+
Writes logs to job-specific files in `.rb-logs/` directory.
|
|
51
|
+
|
|
52
|
+
**Configuration:**
|
|
53
|
+
```javascript
|
|
54
|
+
const destination = new FileDestination({
|
|
55
|
+
workingDir: '/path/to/work',
|
|
56
|
+
job: { id: 123, task_title: 'Generate PRD' },
|
|
57
|
+
startTime: Date.now(),
|
|
58
|
+
jobType: 'PRD Generation',
|
|
59
|
+
useStream: true // Use streams vs batch writes
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Features:**
|
|
64
|
+
- Creates organized log files per job
|
|
65
|
+
- Streaming mode for real-time output
|
|
66
|
+
- Batch mode for complete output
|
|
67
|
+
- Automatic header/footer formatting
|
|
68
|
+
- Safe error handling
|
|
69
|
+
|
|
70
|
+
### ApiDestination
|
|
71
|
+
|
|
72
|
+
Sends logs to API with intelligent batching to reduce overhead.
|
|
73
|
+
|
|
74
|
+
**Configuration:**
|
|
75
|
+
```javascript
|
|
76
|
+
const destination = new ApiDestination({
|
|
77
|
+
apiClient: apiClientInstance,
|
|
78
|
+
jobId: 123,
|
|
79
|
+
maxBatchSize: 10, // Flush after N logs
|
|
80
|
+
flushInterval: 2000, // Flush every N ms
|
|
81
|
+
useBatchEndpoint: true // Try batch endpoint first
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Features:**
|
|
86
|
+
- Batches multiple logs into single API calls
|
|
87
|
+
- Automatic periodic flushing
|
|
88
|
+
- Fallback to individual sends on batch failure
|
|
89
|
+
- Graceful shutdown with final flush
|
|
90
|
+
|
|
91
|
+
## Usage Example
|
|
92
|
+
|
|
93
|
+
### Single Destination
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
const { ConsoleDestination } = require('./logging/destinations');
|
|
97
|
+
|
|
98
|
+
const logger = new ConsoleDestination({
|
|
99
|
+
colors: true,
|
|
100
|
+
format: 'pretty',
|
|
101
|
+
minLevel: 'debug'
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await logger.write('info', 'Job started', { jobId: 123 });
|
|
105
|
+
await logger.write('debug', 'Processing file', { file: 'example.js' });
|
|
106
|
+
await logger.close();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Multiple Destinations (with LogManager)
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const { ConsoleDestination, FileDestination, ApiDestination } = require('./logging/destinations');
|
|
113
|
+
|
|
114
|
+
// Create destinations
|
|
115
|
+
const console = new ConsoleDestination({ minLevel: 'info' });
|
|
116
|
+
const file = new FileDestination({
|
|
117
|
+
workingDir: '/tmp',
|
|
118
|
+
job: { id: 1, task_title: 'Test' },
|
|
119
|
+
startTime: Date.now(),
|
|
120
|
+
jobType: 'Test'
|
|
121
|
+
});
|
|
122
|
+
const api = new ApiDestination({
|
|
123
|
+
apiClient,
|
|
124
|
+
jobId: 1
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Use with LogManager (future implementation)
|
|
128
|
+
const manager = new LogManager([console, file, api]);
|
|
129
|
+
await manager.log('info', 'Message to all destinations');
|
|
130
|
+
await manager.close(); // Closes all destinations
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Creating Custom Destinations
|
|
134
|
+
|
|
135
|
+
Extend `BaseDestination` and implement the required methods:
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
const BaseDestination = require('./base-destination');
|
|
139
|
+
|
|
140
|
+
class CustomDestination extends BaseDestination {
|
|
141
|
+
constructor(config) {
|
|
142
|
+
super(config);
|
|
143
|
+
// Initialize your destination
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async write(level, message, metadata = {}) {
|
|
147
|
+
// Write log to your destination
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async flush() {
|
|
151
|
+
// Flush any buffers
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async close() {
|
|
155
|
+
this.isShuttingDown = true;
|
|
156
|
+
await this.flush();
|
|
157
|
+
// Clean up resources
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = CustomDestination;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Design Principles
|
|
165
|
+
|
|
166
|
+
1. **Independence** - Each destination operates independently without dependencies on other destinations
|
|
167
|
+
2. **Graceful Degradation** - Errors in one destination don't affect others
|
|
168
|
+
3. **No Job Context** - Destinations accept context as parameters, not through global state
|
|
169
|
+
4. **Async Support** - All operations support async/await for I/O operations
|
|
170
|
+
5. **Pluggable** - Destinations can be added/removed without code changes
|
|
171
|
+
6. **Testable** - Pure functions and dependency injection make testing easy
|
|
172
|
+
|
|
173
|
+
## Error Handling
|
|
174
|
+
|
|
175
|
+
Destinations handle their own errors gracefully:
|
|
176
|
+
|
|
177
|
+
- **ConsoleDestination** - Attempts to write error to stderr
|
|
178
|
+
- **FileDestination** - Falls back to console.error
|
|
179
|
+
- **ApiDestination** - Silently fails to prevent cascading errors
|
|
180
|
+
|
|
181
|
+
This prevents log failures from disrupting the application while still providing feedback when possible.
|
|
182
|
+
|
|
183
|
+
### BatchedDestination
|
|
184
|
+
|
|
185
|
+
Generic batching wrapper that can wrap any destination to add batching capabilities.
|
|
186
|
+
|
|
187
|
+
**Configuration:**
|
|
188
|
+
```javascript
|
|
189
|
+
const { BatchedDestination, ConsoleDestination } = require('./logging/destinations');
|
|
190
|
+
|
|
191
|
+
// Wrap any destination with batching
|
|
192
|
+
const console = new ConsoleDestination();
|
|
193
|
+
const batchedConsole = new BatchedDestination(console, {
|
|
194
|
+
maxBatchSize: 10, // Flush after N logs
|
|
195
|
+
flushInterval: 2000, // Flush every N ms
|
|
196
|
+
useBatchSend: true // Try sendBatch() if available
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Features:**
|
|
201
|
+
- Wraps any BaseDestination implementation
|
|
202
|
+
- Buffers logs and sends in batches
|
|
203
|
+
- Automatic periodic flushing
|
|
204
|
+
- Graceful fallback to individual sends
|
|
205
|
+
- Composable - can wrap any destination
|
|
206
|
+
|
|
207
|
+
**How it works:**
|
|
208
|
+
1. Buffers incoming logs until `maxBatchSize` is reached
|
|
209
|
+
2. Flushes automatically every `flushInterval` milliseconds
|
|
210
|
+
3. If destination implements `sendBatch()`, uses it for efficiency
|
|
211
|
+
4. Falls back to individual `write()` calls if batch sending fails
|
|
212
|
+
5. Ensures all logs are flushed on `close()`
|
|
213
|
+
|
|
214
|
+
### ApiDestinationUnbatched
|
|
215
|
+
|
|
216
|
+
Low-level API destination without batching. Used internally by ApiDestination.
|
|
217
|
+
|
|
218
|
+
**Note:** This should not be used directly - use `ApiDestination` instead, which wraps this with `BatchedDestination` for optimal performance.
|
|
219
|
+
|
|
220
|
+
## Batching Pattern
|
|
221
|
+
|
|
222
|
+
The batching pattern is composable and can be applied to any destination:
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
// Example 1: Batched API destination (built-in)
|
|
226
|
+
const api = new ApiDestination({ apiClient, jobId });
|
|
227
|
+
|
|
228
|
+
// Example 2: Manual batching of any destination
|
|
229
|
+
const file = new FileDestination({ workingDir, job, startTime, jobType });
|
|
230
|
+
const batchedFile = new BatchedDestination(file, {
|
|
231
|
+
maxBatchSize: 20,
|
|
232
|
+
flushInterval: 5000
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Example 3: Custom destination with batching
|
|
236
|
+
class SyslogDestination extends BaseDestination {
|
|
237
|
+
async write(level, message, metadata) {
|
|
238
|
+
// Send single log to syslog
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async sendBatch(logs) {
|
|
242
|
+
// Optional: efficient batch send
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const syslog = new SyslogDestination(config);
|
|
247
|
+
const batchedSyslog = new BatchedDestination(syslog, {
|
|
248
|
+
maxBatchSize: 50,
|
|
249
|
+
flushInterval: 1000
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Migration from SetupLogBatcher
|
|
254
|
+
|
|
255
|
+
The old `SetupLogBatcher` class is deprecated. Migrate to the new destination-based approach:
|
|
256
|
+
|
|
257
|
+
**Before:**
|
|
258
|
+
```javascript
|
|
259
|
+
const SetupLogBatcher = require('./setup-log-batcher');
|
|
260
|
+
const batcher = new SetupLogBatcher(apiClient, jobId, {
|
|
261
|
+
maxBatchSize: 10,
|
|
262
|
+
flushInterval: 2000,
|
|
263
|
+
useBatchEndpoint: true
|
|
264
|
+
});
|
|
265
|
+
batcher.add('info', 'message', metadata);
|
|
266
|
+
await batcher.flush();
|
|
267
|
+
await batcher.shutdown();
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**After:**
|
|
271
|
+
```javascript
|
|
272
|
+
const { ApiDestination } = require('./logging/destinations');
|
|
273
|
+
const destination = new ApiDestination({
|
|
274
|
+
apiClient,
|
|
275
|
+
jobId,
|
|
276
|
+
maxBatchSize: 10,
|
|
277
|
+
flushInterval: 2000,
|
|
278
|
+
useBatchEndpoint: true
|
|
279
|
+
});
|
|
280
|
+
await destination.write('info', 'message', metadata);
|
|
281
|
+
await destination.flush();
|
|
282
|
+
await destination.close();
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The old `SetupLogBatcher` is maintained for backward compatibility but internally uses the new destination-based implementation.
|
|
286
|
+
|
|
287
|
+
## Next Steps
|
|
288
|
+
|
|
289
|
+
- **LogManager** (Task #1) - Coordinates multiple destinations
|
|
290
|
+
- **Unit Tests** (Task #7) - Comprehensive test coverage for all destinations
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const BaseDestination = require('./base-destination');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ApiDestinationUnbatched - Sends individual logs to API
|
|
5
|
+
*
|
|
6
|
+
* Provides direct API logging without batching. Designed to be wrapped by
|
|
7
|
+
* BatchedDestination for efficient batched sending.
|
|
8
|
+
*
|
|
9
|
+
* This class should NOT be used directly - use ApiDestination (which wraps
|
|
10
|
+
* this with BatchedDestination) for production use.
|
|
11
|
+
*
|
|
12
|
+
* Supports both single log and batch log endpoints:
|
|
13
|
+
* - write() - sends single log via apiClient.addSetupLog()
|
|
14
|
+
* - sendBatch() - sends multiple logs via apiClient.addSetupLogBatch()
|
|
15
|
+
*/
|
|
16
|
+
class ApiDestinationUnbatched extends BaseDestination {
|
|
17
|
+
/**
|
|
18
|
+
* Create a new unbatched API destination
|
|
19
|
+
* @param {Object} config - Configuration options
|
|
20
|
+
* @param {Object} config.apiClient - API client with addSetupLog() and addSetupLogBatch() methods
|
|
21
|
+
* @param {number} config.jobId - Job ID to associate logs with
|
|
22
|
+
* @param {boolean} [config.useBatchEndpoint=true] - Whether to use batch endpoint for sendBatch()
|
|
23
|
+
*/
|
|
24
|
+
constructor(config) {
|
|
25
|
+
super(config);
|
|
26
|
+
|
|
27
|
+
if (!config.apiClient) {
|
|
28
|
+
throw new Error('ApiDestinationUnbatched requires apiClient in config');
|
|
29
|
+
}
|
|
30
|
+
if (config.jobId === undefined || config.jobId === null) {
|
|
31
|
+
throw new Error('ApiDestinationUnbatched requires jobId in config');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.apiClient = config.apiClient;
|
|
35
|
+
this.jobId = config.jobId;
|
|
36
|
+
this.useBatchEndpoint = config.useBatchEndpoint !== false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Write a single log entry to the API
|
|
41
|
+
* @param {string} level - Log level
|
|
42
|
+
* @param {string} message - Log message
|
|
43
|
+
* @param {Object} [metadata={}] - Structured metadata
|
|
44
|
+
* @returns {Promise<void>}
|
|
45
|
+
*/
|
|
46
|
+
async write(level, message, metadata = {}) {
|
|
47
|
+
try {
|
|
48
|
+
await this.apiClient.addSetupLog(
|
|
49
|
+
this.jobId,
|
|
50
|
+
level,
|
|
51
|
+
message,
|
|
52
|
+
metadata && Object.keys(metadata).length > 0 ? metadata : null
|
|
53
|
+
);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
this.handleError(error, level, message);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Send multiple log entries in a single batch API call
|
|
61
|
+
* This is called by BatchedDestination when flushing buffered logs.
|
|
62
|
+
* @param {Array<Object>} logs - Array of log entries with {timestamp, level, message, metadata}
|
|
63
|
+
* @returns {Promise<void>}
|
|
64
|
+
*/
|
|
65
|
+
async sendBatch(logs) {
|
|
66
|
+
if (logs.length === 0) return;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
if (this.useBatchEndpoint && this.apiClient.addSetupLogBatch) {
|
|
70
|
+
// Use efficient batch endpoint
|
|
71
|
+
await this.apiClient.addSetupLogBatch(this.jobId, logs);
|
|
72
|
+
} else {
|
|
73
|
+
// Fall back to individual sends
|
|
74
|
+
await this.sendIndividually(logs);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// Re-throw to let BatchedDestination handle fallback
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Send logs individually
|
|
84
|
+
* Used as fallback when batch endpoint fails or is unavailable.
|
|
85
|
+
* @param {Array<Object>} logs - Array of log entries
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
* @private
|
|
88
|
+
*/
|
|
89
|
+
async sendIndividually(logs) {
|
|
90
|
+
const promises = logs.map(log =>
|
|
91
|
+
this.apiClient.addSetupLog(this.jobId, log.level, log.message, log.metadata)
|
|
92
|
+
.catch((error) => {
|
|
93
|
+
// Silently fail individual logs to prevent cascading errors
|
|
94
|
+
this.handleError(error, log.level, log.message);
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
await Promise.all(promises);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Handle errors during API operations
|
|
103
|
+
* Silently fails to prevent cascading errors, but could be enhanced
|
|
104
|
+
* to report to a fallback destination (e.g., console or file)
|
|
105
|
+
* @param {Error} error - The error that occurred
|
|
106
|
+
* @param {string} level - Log level of failed write
|
|
107
|
+
* @param {string} message - Log message of failed write
|
|
108
|
+
* @protected
|
|
109
|
+
*/
|
|
110
|
+
handleError(error, level, message) {
|
|
111
|
+
// Silently fail - setup logs are best-effort and shouldn't disrupt job execution
|
|
112
|
+
// Logging errors about logging would create cascading failures and noise
|
|
113
|
+
// If API logging fails, the job continues unaffected
|
|
114
|
+
// console.error(`[ApiDestination Error] Failed to send log: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = ApiDestinationUnbatched;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const BatchedDestination = require('./batched-destination');
|
|
2
|
+
const ApiDestinationUnbatched = require('./api-destination-unbatched');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ApiDestination - Sends logs to API with batching
|
|
6
|
+
*
|
|
7
|
+
* Provides API-based logging with automatic batching to reduce overhead.
|
|
8
|
+
* Wraps ApiDestinationUnbatched with BatchedDestination for efficient sending.
|
|
9
|
+
* Batches multiple log entries and flushes automatically based on buffer size or time interval.
|
|
10
|
+
*
|
|
11
|
+
* This is a convenience factory that creates a properly configured batched API destination.
|
|
12
|
+
*/
|
|
13
|
+
class ApiDestination extends BatchedDestination {
|
|
14
|
+
/**
|
|
15
|
+
* Create a new batched API destination
|
|
16
|
+
* @param {Object} config - Configuration options
|
|
17
|
+
* @param {Object} config.apiClient - API client with addSetupLog() and addSetupLogBatch() methods
|
|
18
|
+
* @param {number} config.jobId - Job ID to associate logs with
|
|
19
|
+
* @param {number} [config.maxBatchSize=10] - Maximum logs to buffer before flushing
|
|
20
|
+
* @param {number} [config.flushInterval=2000] - Interval in ms to auto-flush
|
|
21
|
+
* @param {boolean} [config.useBatchEndpoint=true] - Whether to use batch endpoint
|
|
22
|
+
*/
|
|
23
|
+
constructor(config) {
|
|
24
|
+
// Create unbatched API destination
|
|
25
|
+
const unbatchedDestination = new ApiDestinationUnbatched({
|
|
26
|
+
apiClient: config.apiClient,
|
|
27
|
+
jobId: config.jobId,
|
|
28
|
+
useBatchEndpoint: config.useBatchEndpoint
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Wrap it with batching
|
|
32
|
+
super(unbatchedDestination, {
|
|
33
|
+
maxBatchSize: config.maxBatchSize,
|
|
34
|
+
flushInterval: config.flushInterval,
|
|
35
|
+
useBatchSend: config.useBatchEndpoint
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = ApiDestination;
|