alfred-logger-sdk
Version:
Production-ready data collection SDK for feeding structured events to LLM Data Agents with auto-capture capabilities
125 lines (101 loc) • 3.3 kB
JavaScript
const axios = require('axios');
class HttpClient {
constructor(config) {
this.endpoint = config.endpoint;
this.apiKey = config.apiKey;
this.timeout = Math.min(config.timeout || 10000, 30000);
this.retryAttempts = Math.min(config.retryAttempts || 3, 5);
this.retryDelay = Math.max(config.retryDelay || 1000, 500);
this.maxRetryDelay = config.maxRetryDelay || 10000;
this.requestCount = 0;
this.lastRequestTime = 0;
this.rateLimitDelay = config.rateLimitDelay || 100;
}
async sendBatch(events) {
if (!events || events.length === 0) {
return;
}
await this.enforceRateLimit();
const payload = {
batchId: this.generateBatchId(),
timestamp: new Date().toISOString(),
eventCount: events.length,
events
};
const payloadSize = JSON.stringify(payload).length;
if (payloadSize > 10 * 1024 * 1024) {
throw new Error(`Payload too large: ${payloadSize} bytes`);
}
return await this.sendWithRetry(payload);
}
async sendWithRetry(payload) {
const headers = {
'Content-Type': 'application/json',
'X-Data-Source': 'alfred-logger-sdk',
'X-Request-ID': this.generateBatchId()
};
if (this.apiKey) {
headers['Authorization'] = `Bearer ${this.apiKey}`;
}
let lastError;
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
try {
const response = await axios.post(this.endpoint, payload, {
headers,
timeout: this.timeout,
validateStatus: (status) => status < 500
});
if (response.status >= 400) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.data;
} catch (error) {
lastError = error;
if (this.isRetryableError(error) && attempt < this.retryAttempts) {
const delay = Math.min(this.retryDelay * Math.pow(2, attempt - 1), this.maxRetryDelay);
await this.delay(delay);
continue;
}
break;
}
}
this.handleFailedSend(payload, lastError);
throw lastError;
}
generateBatchId() {
return `batch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
isRetryableError(error) {
if (error.code === 'ECONNABORTED' || error.code === 'ENOTFOUND') {
return true;
}
if (error.response) {
const status = error.response.status;
return status >= 500 || status === 429;
}
return false;
}
async enforceRateLimit() {
const now = Date.now();
const timeSinceLastRequest = now - this.lastRequestTime;
if (timeSinceLastRequest < this.rateLimitDelay) {
await this.delay(this.rateLimitDelay - timeSinceLastRequest);
}
this.lastRequestTime = Date.now();
this.requestCount++;
}
handleFailedSend(payload, error) {
if (typeof window === 'undefined') {
console.error('Failed to send data batch:', {
batchId: payload.batchId,
eventCount: payload.eventCount,
error: error.message,
timestamp: new Date().toISOString()
});
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
module.exports = HttpClient;