diff --git a/packages/logging/package.json b/packages/logging/package.json index cf4193cdb71..32612c9ddbe 100644 --- a/packages/logging/package.json +++ b/packages/logging/package.json @@ -56,6 +56,7 @@ "@google-cloud/common-grpc": "^0.1.1", "arrify": "^1.0.0", "async": "^2.1.4", + "eventid": "^0.1.0", "extend": "^3.0.0", "google-gax": "^0.12.2", "google-proto-files": "^0.10.0", diff --git a/packages/logging/src/entry.js b/packages/logging/src/entry.js index 5ab9005e9a4..8772ae5c536 100644 --- a/packages/logging/src/entry.js +++ b/packages/logging/src/entry.js @@ -21,10 +21,13 @@ 'use strict'; var commonGrpc = require('@google-cloud/common-grpc'); +var EventId = require('eventid'); var extend = require('extend'); var is = require('is'); var isCircular = require('is-circular'); +var eventId = new EventId(); + /** * Create an entry object to define new data to insert into a log. * @@ -87,6 +90,17 @@ function Entry(metadata, data) { timestamp: new Date() }, metadata); + // JavaScript date has a very coarse granularity (millisecond), which makes + // it quite likely that multiple log entries would have the same timestamp. + // The Logging API doesn't guarantee to preserve insertion order for entries + // with the same timestamp. The service does use `insertId` as a secondary + // ordering for entries with the same timestamp. `insertId` needs to be + // globally unique (within the project) however. + // + // We use a globally unique monotonically increasing EventId as the + // insertId. + this.metadata.insertId = this.metadata.insertId || eventId.new(); + this.data = data; } diff --git a/packages/logging/system-test/logging.js b/packages/logging/system-test/logging.js index 750f481054c..f4419a3c4b8 100644 --- a/packages/logging/system-test/logging.js +++ b/packages/logging/system-test/logging.js @@ -351,8 +351,8 @@ describe('Logging', function() { var entry1 = log.entry('1'); setTimeout(function() { - var entry3 = log.entry('3'); - var entry2 = log.entry({ timestamp: entry3.metadata.timestamp }, '2'); + var entry2 = log.entry('2'); + var entry3 = log.entry({ timestamp: entry2.metadata.timestamp }, '3'); // Re-arrange to confirm the timestamp is sent and honored. log.write([entry2, entry3, entry1], options, function(err) { @@ -369,6 +369,22 @@ describe('Logging', function() { }, 1000); }); + it('should preserve order for sequential write calls', function(done) { + var messages = ['1', '2', '3', '4', '5']; + + messages.forEach(function(message) { + log.write(log.entry(message)); + }); + + setTimeout(function() { + log.getEntries({ pageSize: messages.length }, function(err, entries) { + assert.ifError(err); + assert.deepEqual(entries.reverse().map(prop('data')), messages); + done(); + }); + }, WRITE_CONSISTENCY_DELAY_MS); + }); + it('should write an entry with primitive values', function(done) { var logEntry = log.entry({ when: new Date(), diff --git a/packages/logging/test/entry.js b/packages/logging/test/entry.js index bf3be4c9edf..7a069f819d7 100644 --- a/packages/logging/test/entry.js +++ b/packages/logging/test/entry.js @@ -24,6 +24,13 @@ var util = require('@google-cloud/common').util; function FakeGrpcService() {} +var fakeEventIdNewOverride; + +function FakeEventId() {} +FakeEventId.prototype.new = function() { + return (fakeEventIdNewOverride || util.noop).apply(null, arguments); +}; + describe('Entry', function() { var Entry; var entry; @@ -35,11 +42,13 @@ describe('Entry', function() { Entry = proxyquire('../src/entry.js', { '@google-cloud/common-grpc': { Service: FakeGrpcService - } + }, + 'eventid': FakeEventId }); }); beforeEach(function() { + fakeEventIdNewOverride = null; extend(FakeGrpcService, GrpcService); entry = new Entry(METADATA, DATA); }); @@ -67,6 +76,34 @@ describe('Entry', function() { assert.strictEqual(entry.metadata.timestamp, timestamp); }); + it('should assign insertId to metadata', function() { + var eventId = 'event-id'; + + fakeEventIdNewOverride = function() { + return eventId; + }; + + var entry = new Entry(); + + assert.strictEqual(entry.metadata.insertId, eventId); + }); + + it('should not assign insertId if one is already set', function() { + var eventId = 'event-id'; + + fakeEventIdNewOverride = function() { + return eventId; + }; + + var userDefinedInsertId = 'user-defined-insert-id'; + + var entry = new Entry({ + insertId: userDefinedInsertId + }); + + assert.strictEqual(entry.metadata.insertId, userDefinedInsertId); + }); + it('should localize data', function() { assert.strictEqual(entry.data, DATA); });