@yachteye/signalk-engineroom-plugin
Version:
Get EngineRoom data from the source (database or other) and add it to the SignalK graph.
199 lines (198 loc) • 7.53 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = init;
exports.log = log;
exports.logTelemetry = logTelemetry;
exports.shutdown = shutdown;
const client_cloudwatch_logs_1 = require("@aws-sdk/client-cloudwatch-logs");
const BUFFER_MAX = 10000;
const CW_MAX_BATCH_EVENTS = 10000;
const CW_MAX_BATCH_BYTES = 1048576; // 1 MB
const CW_EVENT_OVERHEAD = 26;
let client = null;
let buffer = [];
let flushTimer = null;
let enabledLevels = new Set();
let excludedPaths = new Set();
let telemetryMode = 'full';
let lastTelemetrySnapshot = {};
let logGroupName = '';
let logStreamName = '';
let debugFn = () => { };
function init(settings, debug) {
return __awaiter(this, void 0, void 0, function* () {
debugFn = debug;
try {
const clientConfig = {
region: settings.cloudwatchRegion,
};
if (settings.cloudwatchAccessKeyId && settings.cloudwatchSecretAccessKey) {
clientConfig.credentials = {
accessKeyId: settings.cloudwatchAccessKeyId,
secretAccessKey: settings.cloudwatchSecretAccessKey,
};
}
client = new client_cloudwatch_logs_1.CloudWatchLogsClient(clientConfig);
logGroupName = settings.cloudwatchLogGroupName;
logStreamName = settings.cloudwatchLogStreamName;
enabledLevels = new Set(settings.cloudwatchLogLevels);
excludedPaths = new Set(settings.cloudwatchExcludedPaths);
telemetryMode = settings.cloudwatchTelemetryMode;
try {
yield client.send(new client_cloudwatch_logs_1.CreateLogGroupCommand({ logGroupName }));
}
catch (err) {
if (err.name !== 'ResourceAlreadyExistsException') {
debugFn(`[CloudWatch] Failed to create log group '${logGroupName}': ${err.message}`);
client = null;
return;
}
}
try {
yield client.send(new client_cloudwatch_logs_1.CreateLogStreamCommand({ logGroupName, logStreamName }));
}
catch (err) {
if (err.name !== 'ResourceAlreadyExistsException') {
debugFn(`[CloudWatch] Failed to create log stream '${logStreamName}': ${err.message}`);
client = null;
return;
}
}
flushTimer = setInterval(() => {
flush().catch((err) => debugFn(`[CloudWatch] flush() unexpected error: ${err === null || err === void 0 ? void 0 : err.message}`));
}, settings.cloudwatchFlushInterval * 1000);
debugFn(`[CloudWatch] Initialized: group=${logGroupName}, stream=${logStreamName}, flush=${settings.cloudwatchFlushInterval}s, levels=${[...enabledLevels].join(',')}`);
}
catch (err) {
debugFn(`[CloudWatch] init() failed: ${err.message}`);
client = null;
}
});
}
function log(level, message, data) {
try {
if (client === null)
return;
if (!enabledLevels.has(level))
return;
if (buffer.length >= BUFFER_MAX) {
debugFn(`[CloudWatch] Buffer full (${BUFFER_MAX}), dropping log entry`);
return;
}
buffer.push({
timestamp: Date.now(),
level,
message,
data,
});
}
catch (err) {
debugFn(`[CloudWatch] log() error: ${err.message}`);
}
}
function logTelemetry(data) {
try {
if (client === null)
return;
const filtered = {};
for (const key of Object.keys(data)) {
if (!excludedPaths.has(key)) {
filtered[key] = data[key];
}
}
if (telemetryMode === 'changes') {
const changed = {};
for (const key of Object.keys(filtered)) {
if (filtered[key] !== lastTelemetrySnapshot[key]) {
changed[key] = filtered[key];
}
}
lastTelemetrySnapshot = Object.assign({}, filtered);
if (Object.keys(changed).length === 0)
return;
log('info', 'telemetry', changed);
}
else {
if (Object.keys(filtered).length === 0)
return;
log('info', 'telemetry', filtered);
}
}
catch (err) {
debugFn(`[CloudWatch] logTelemetry() error: ${err.message}`);
}
}
function flush() {
return __awaiter(this, void 0, void 0, function* () {
if (client === null || buffer.length === 0)
return;
const entries = buffer;
buffer = [];
const serialized = entries.map((entry) => ({
timestamp: entry.timestamp,
message: JSON.stringify({ level: entry.level, message: entry.message, data: entry.data }),
}));
const batches = [];
let currentBatch = [];
let currentBatchBytes = 0;
for (const event of serialized) {
const eventBytes = Buffer.byteLength(event.message, 'utf8') + CW_EVENT_OVERHEAD;
if (currentBatch.length >= CW_MAX_BATCH_EVENTS || currentBatchBytes + eventBytes > CW_MAX_BATCH_BYTES) {
if (currentBatch.length > 0) {
batches.push(currentBatch);
}
currentBatch = [];
currentBatchBytes = 0;
}
currentBatch.push(event);
currentBatchBytes += eventBytes;
}
if (currentBatch.length > 0) {
batches.push(currentBatch);
}
for (const batch of batches) {
try {
const logEvents = batch;
yield client.send(new client_cloudwatch_logs_1.PutLogEventsCommand({
logGroupName,
logStreamName,
logEvents,
}));
}
catch (err) {
debugFn(`[CloudWatch] flush() failed for batch of ${batch.length} events: ${err.message}`);
}
}
});
}
function shutdown() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (flushTimer !== null) {
clearInterval(flushTimer);
flushTimer = null;
}
yield flush();
if (client !== null) {
client.destroy();
client = null;
}
buffer = [];
lastTelemetrySnapshot = {};
enabledLevels = new Set();
excludedPaths = new Set();
}
catch (err) {
debugFn(`[CloudWatch] shutdown() error: ${err.message}`);
}
});
}