Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion lib/utilities/serverAccessLogger.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,15 @@ function calculateElapsedMS(startTime, onCloseEndTime) {
return Number(onCloseEndTime - startTime) / 1_000_000;
}

function timestampToDateTime643(unixMS) {
if (unixMS === undefined || unixMS === null) {
return null;
}

// clickhouse DateTime64(3) expects "seconds.milliseconds" (string type).
return (unixMS / 1000).toFixed(3);
}

function logServerAccess(req, res) {
if (!req.serverAccessLog || !res.serverAccessLog || !serverAccessLogger) {
return;
Expand Down Expand Up @@ -420,7 +429,7 @@ function logServerAccess(req, res) {
elapsed_ms: calculateElapsedMS(params.startTime, params.onCloseEndTime) ?? undefined,

// AWS access server logs fields https://docs.aws.amazon.com/AmazonS3/latest/userguide/LogFormat.html
startTime: params.startTimeUnixMS ?? undefined, // AWS "Time" field - milliseconds since epoch
startTime: timestampToDateTime643(params.startTimeUnixMS) ?? undefined, // AWS "Time" field
requester: getRequester(authInfo) ?? undefined,
operation: getOperation(req),
requestURI: getURI(req) ?? undefined,
Expand Down Expand Up @@ -480,4 +489,5 @@ module.exports = {
getBytesSent,
calculateTotalTime,
calculateTurnAroundTime,
timestampToDateTime643,
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenko/cloudserver",
"version": "9.2.19",
"version": "9.2.20",
"description": "Zenko CloudServer, an open-source Node.js implementation of a server handling the Amazon S3 protocol",
"main": "index.js",
"engines": {
Expand Down
4 changes: 2 additions & 2 deletions schema/server_access_log.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@
"minimum": 0
},
"startTime": {
"description": "Milliseconds since Unix epoch, recorded when the server first routes the request. Represents the AWS server access log 'Time' field.",
"type": "number"
"description": "Timestamp formatted as: 'seconds.milliseconds', recorded when the server first routes the request. Represents the AWS server access log 'Time' field. String type compatible with Clickhouse DateTime64(3) type.",
"type": "string"
},
"requester": {
"description": "AWS server access log 'Requester' field. From AWS 'The canonical user ID of the requester, or a - for unauthenticated requests. If the requester was an IAM user, this field returns the requester's IAM user name along with the AWS account that the IAM user belongs to. This identifier is the same one used for access control purposes.'. We don't use null instead of '-' when the requester is missing.",
Expand Down
45 changes: 44 additions & 1 deletion tests/unit/utils/serverAccessLogger.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
getBytesSent,
calculateTotalTime,
calculateTurnAroundTime,
timestampToDateTime643,
} = require('../../../lib/utilities/serverAccessLogger');

describe('serverAccessLogger utility functions', () => {
Expand Down Expand Up @@ -679,6 +680,48 @@ describe('serverAccessLogger utility functions', () => {
});
});

describe('timestampToDateTime643', () => {
it('should convert milliseconds to seconds with 3 decimal places', () => {
const startTimeUnixMS = 1234567890000;
const result = timestampToDateTime643(startTimeUnixMS);
assert.strictEqual(result, '1234567890.000');
});

it('should handle timestamp with milliseconds', () => {
const startTimeUnixMS = 1234567890123;
const result = timestampToDateTime643(startTimeUnixMS);
assert.strictEqual(result, '1234567890.123');
});

it('should handle 0', () => {
const startTimeUnixMS = 0;
const result = timestampToDateTime643(startTimeUnixMS);
assert.strictEqual(result, '0.000');
});

it('should return null when startTimeUnixMS is null', () => {
const result = timestampToDateTime643(null);
assert.strictEqual(result, null);
});

it('should return null when startTimeUnixMS is undefined', () => {
const result = timestampToDateTime643(undefined);
assert.strictEqual(result, null);
});

it('should handle small timestamps', () => {
const startTimeUnixMS = 1000;
const result = timestampToDateTime643(startTimeUnixMS);
assert.strictEqual(result, '1.000');
});

it('should handle timestamps with partial milliseconds', () => {
const startTimeUnixMS = 1500;
const result = timestampToDateTime643(startTimeUnixMS);
assert.strictEqual(result, '1.500');
});
});

describe('logServerAccess', () => {
let mockLogger;
let sandbox;
Expand Down Expand Up @@ -831,7 +874,7 @@ describe('serverAccessLogger utility functions', () => {
assert.strictEqual(loggedData.elapsed_ms, 20.5);

// Verify AWS access server log fields
assert.strictEqual(loggedData.startTime, 1234567890000);
assert.strictEqual(loggedData.startTime, '1234567890.000');
assert.strictEqual(loggedData.requester, 'canonical123');
assert.strictEqual(loggedData.operation, 'REST.GET.OBJECT');
assert.strictEqual(loggedData.requestURI, 'GET /test-bucket/test-key.txt HTTP/1.1');
Expand Down
Loading