Add metadata logging to Agheera push functions and implement logging for pushPosition
This commit is contained in:
parent
12364bcb44
commit
5212bbad71
@ -167,7 +167,12 @@ const pushLocationToAgheera = async ({ latitude, longitude, dni, tripId, measure
|
|||||||
longitude,
|
longitude,
|
||||||
vehicleId: licensePlate,
|
vehicleId: licensePlate,
|
||||||
licensePlate,
|
licensePlate,
|
||||||
measurementTime
|
measurementTime,
|
||||||
|
metadata: {
|
||||||
|
source: 'location',
|
||||||
|
trip_id: tripId,
|
||||||
|
dni
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -284,7 +284,13 @@ const pushTripStatusPositionToAgheera = async ({
|
|||||||
longitude: longitud,
|
longitude: longitud,
|
||||||
vehicleId: licensePlate,
|
vehicleId: licensePlate,
|
||||||
licensePlate,
|
licensePlate,
|
||||||
measurementTime
|
measurementTime,
|
||||||
|
metadata: {
|
||||||
|
source: 'trip_status',
|
||||||
|
request_id: requestId,
|
||||||
|
flow,
|
||||||
|
trip_id: tripId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
appendTripStatusDebugLog({
|
appendTripStatusDebugLog({
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const DEFAULT_AGHEERA_PUSH_URL = 'https://push-test.agheera.com/Telematics/Positions';
|
const DEFAULT_AGHEERA_PUSH_URL = 'https://push-test.agheera.com/Telematics/Positions';
|
||||||
|
const DEFAULT_AGHEERA_PUSH_LOG_PATH = '/var/log/agheera_push.log';
|
||||||
|
|
||||||
let httpClientOverride = null;
|
let httpClientOverride = null;
|
||||||
|
|
||||||
@ -8,6 +12,41 @@ const getPushUrl = () =>
|
|||||||
const getApiKey = () =>
|
const getApiKey = () =>
|
||||||
String(process.env.AGHEERA_API_KEY || '').trim();
|
String(process.env.AGHEERA_API_KEY || '').trim();
|
||||||
|
|
||||||
|
const getPushLogPath = () =>
|
||||||
|
String(process.env.AGHEERA_PUSH_LOG_PATH || DEFAULT_AGHEERA_PUSH_LOG_PATH).trim();
|
||||||
|
|
||||||
|
const appendPushLog = async ({ metadata, url, payload, success, status, responseBody, error }) => {
|
||||||
|
if (process.env.AGHEERA_PUSH_LOGS === '0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstVehicle = Array.isArray(payload?.Vehicles) ? payload.Vehicles[0] : null;
|
||||||
|
const entry = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
...(metadata || {}),
|
||||||
|
url,
|
||||||
|
vehicleId: firstVehicle?.vehicleId ?? null,
|
||||||
|
licensePlate: firstVehicle?.licensePlate ?? null,
|
||||||
|
latitude: firstVehicle?.latitude ?? null,
|
||||||
|
longitude: firstVehicle?.longitude ?? null,
|
||||||
|
measurementTime: firstVehicle?.measurementTime ?? null,
|
||||||
|
payload,
|
||||||
|
success,
|
||||||
|
http_status: status ?? null,
|
||||||
|
response_body: responseBody || '',
|
||||||
|
error: error || null
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const logPath = getPushLogPath();
|
||||||
|
await fs.promises.mkdir(path.dirname(logPath), { recursive: true });
|
||||||
|
await fs.promises.appendFile(logPath, `${JSON.stringify(entry)}
|
||||||
|
`);
|
||||||
|
} catch (logError) {
|
||||||
|
console.error('Failed to append Agheera push log:', { message: logError.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const formatMeasurementTime = (dateValue) => {
|
const formatMeasurementTime = (dateValue) => {
|
||||||
const date = dateValue instanceof Date ? dateValue : new Date(dateValue);
|
const date = dateValue instanceof Date ? dateValue : new Date(dateValue);
|
||||||
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
||||||
@ -53,18 +92,9 @@ const pushPosition = async ({
|
|||||||
longitude,
|
longitude,
|
||||||
vehicleId,
|
vehicleId,
|
||||||
licensePlate,
|
licensePlate,
|
||||||
measurementTime
|
measurementTime,
|
||||||
|
metadata
|
||||||
}) => {
|
}) => {
|
||||||
const httpClient = getHttpClient();
|
|
||||||
if (!httpClient) {
|
|
||||||
throw new Error('Agheera HTTP client unavailable');
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiKey = getApiKey();
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error('Agheera API key missing');
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = getPushUrl();
|
const url = getPushUrl();
|
||||||
const payload = buildPositionPayload({
|
const payload = buildPositionPayload({
|
||||||
latitude,
|
latitude,
|
||||||
@ -74,7 +104,23 @@ const pushPosition = async ({
|
|||||||
measurementTime
|
measurementTime
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await httpClient(url, {
|
const httpClient = getHttpClient();
|
||||||
|
if (!httpClient) {
|
||||||
|
const message = 'Agheera HTTP client unavailable';
|
||||||
|
await appendPushLog({ metadata, url, payload, success: false, error: message });
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = getApiKey();
|
||||||
|
if (!apiKey) {
|
||||||
|
const message = 'Agheera API key missing';
|
||||||
|
await appendPushLog({ metadata, url, payload, success: false, error: message });
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = await httpClient(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
apiKey,
|
apiKey,
|
||||||
@ -82,15 +128,43 @@ const pushPosition = async ({
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await appendPushLog({
|
||||||
|
metadata,
|
||||||
|
url,
|
||||||
|
payload,
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
const responseBody = typeof response?.text === 'function' ? await response.text() : '';
|
const responseBody = typeof response?.text === 'function' ? await response.text() : '';
|
||||||
if (!response?.ok) {
|
if (!response?.ok) {
|
||||||
const error = new Error('Agheera push failed');
|
const error = new Error('Agheera push failed');
|
||||||
error.status = response?.status || null;
|
error.status = response?.status || null;
|
||||||
error.body = responseBody;
|
error.body = responseBody;
|
||||||
|
await appendPushLog({
|
||||||
|
metadata,
|
||||||
|
url,
|
||||||
|
payload,
|
||||||
|
success: false,
|
||||||
|
status: error.status,
|
||||||
|
responseBody,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await appendPushLog({
|
||||||
|
metadata,
|
||||||
|
url,
|
||||||
|
payload,
|
||||||
|
success: true,
|
||||||
|
status: response.status,
|
||||||
|
responseBody
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
body: responseBody
|
body: responseBody
|
||||||
@ -107,6 +181,7 @@ const __resetHttpClientForTests = () => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
DEFAULT_AGHEERA_PUSH_URL,
|
DEFAULT_AGHEERA_PUSH_URL,
|
||||||
|
DEFAULT_AGHEERA_PUSH_LOG_PATH,
|
||||||
pushPosition,
|
pushPosition,
|
||||||
__setHttpClientForTests,
|
__setHttpClientForTests,
|
||||||
__resetHttpClientForTests
|
__resetHttpClientForTests
|
||||||
|
|||||||
72
test/agheera-push-client.log.test.js
Normal file
72
test/agheera-push-client.log.test.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const test = require('node:test');
|
||||||
|
|
||||||
|
const agheeraPushClient = require('../src/services/agheeraPushClient');
|
||||||
|
|
||||||
|
let originalApiKey;
|
||||||
|
let originalPushLogPath;
|
||||||
|
let originalPushLogs;
|
||||||
|
|
||||||
|
test.before(() => {
|
||||||
|
originalApiKey = process.env.AGHEERA_API_KEY;
|
||||||
|
originalPushLogPath = process.env.AGHEERA_PUSH_LOG_PATH;
|
||||||
|
originalPushLogs = process.env.AGHEERA_PUSH_LOGS;
|
||||||
|
});
|
||||||
|
|
||||||
|
test.after(() => {
|
||||||
|
process.env.AGHEERA_API_KEY = originalApiKey;
|
||||||
|
process.env.AGHEERA_PUSH_LOG_PATH = originalPushLogPath;
|
||||||
|
process.env.AGHEERA_PUSH_LOGS = originalPushLogs;
|
||||||
|
agheeraPushClient.__resetHttpClientForTests();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterEach(() => {
|
||||||
|
agheeraPushClient.__resetHttpClientForTests();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pushPosition escribe log dedicado sin apiKey', async () => {
|
||||||
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agheera-log-'));
|
||||||
|
const logPath = path.join(tempDir, 'agheera_push.log');
|
||||||
|
|
||||||
|
process.env.AGHEERA_API_KEY = 'secret-api-key';
|
||||||
|
process.env.AGHEERA_PUSH_LOG_PATH = logPath;
|
||||||
|
delete process.env.AGHEERA_PUSH_LOGS;
|
||||||
|
|
||||||
|
agheeraPushClient.__setHttpClientForTests(async () => ({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
text: async () => 'Messages received.'
|
||||||
|
}));
|
||||||
|
|
||||||
|
await agheeraPushClient.pushPosition({
|
||||||
|
latitude: '40.416775',
|
||||||
|
longitude: '-3.703790',
|
||||||
|
vehicleId: '6599LCN',
|
||||||
|
licensePlate: '6599LCN',
|
||||||
|
measurementTime: '2026-06-01T13:38:31Z',
|
||||||
|
metadata: {
|
||||||
|
source: 'test',
|
||||||
|
trip_id: 306075
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n');
|
||||||
|
assert.equal(lines.length, 1);
|
||||||
|
|
||||||
|
const entry = JSON.parse(lines[0]);
|
||||||
|
assert.equal(entry.source, 'test');
|
||||||
|
assert.equal(entry.trip_id, 306075);
|
||||||
|
assert.equal(entry.vehicleId, '6599LCN');
|
||||||
|
assert.equal(entry.licensePlate, '6599LCN');
|
||||||
|
assert.equal(entry.latitude, 40.416775);
|
||||||
|
assert.equal(entry.longitude, -3.70379);
|
||||||
|
assert.equal(entry.measurementTime, '2026-06-01T13:38:31Z');
|
||||||
|
assert.equal(entry.success, true);
|
||||||
|
assert.equal(entry.http_status, 200);
|
||||||
|
assert.equal(entry.response_body, 'Messages received.');
|
||||||
|
assert.equal(entry.error, null);
|
||||||
|
assert.equal(JSON.stringify(entry).includes('secret-api-key'), false);
|
||||||
|
});
|
||||||
@ -719,7 +719,7 @@ test('POST /api/trips/:id/status modo dual replica foto a SFTP y mantiene local'
|
|||||||
const recorder = createSftpRecorder();
|
const recorder = createSftpRecorder();
|
||||||
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
@ -796,7 +796,7 @@ test('POST /api/trips/:id/status modo dual con fallo SFTP mantiene fallback loca
|
|||||||
createFakeSftpClientFactory(recorder, { failPut: true })
|
createFakeSftpClientFactory(recorder, { failPut: true })
|
||||||
);
|
);
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
@ -862,7 +862,7 @@ test('POST /api/trips/:id/status payload inválido tras upload limpia remoto y l
|
|||||||
const recorder = createSftpRecorder();
|
const recorder = createSftpRecorder();
|
||||||
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
@ -2795,7 +2795,7 @@ test('DELETE /api/trips/:id/status en modo dual no borra foto en remoto ni local
|
|||||||
const recorder = createSftpRecorder();
|
const recorder = createSftpRecorder();
|
||||||
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user