Skip to content

Commit cec6f85

Browse files
author
Luke Sneeringer
committed
Merge branch 'public-master' into issue-2553
2 parents 58a3c67 + 5c7035b commit cec6f85

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+7482
-3413
lines changed

README.md

Lines changed: 122 additions & 92 deletions
Large diffs are not rendered by default.

packages/common/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@google-cloud/common",
3-
"version": "0.13.4",
3+
"version": "0.13.5",
44
"author": "Google Inc.",
55
"description": "Common components for Cloud APIs Node.js Client Libraries",
66
"contributors": [

packages/common/src/service.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ var PROJECT_ID_TOKEN = '{{projectId}}';
4343
* @param {object} config - Configuration object.
4444
* @param {string} config.baseUrl - The base URL to make API requests to.
4545
* @param {string[]} config.scopes - The scopes required for the request.
46-
* @param {object} options - [Configuration object](#/docs).
46+
* @param {object=} options - [Configuration object](#/docs).
4747
*/
4848
function Service(config, options) {
49+
options = options || {};
50+
4951
var reqCfg = extend({}, config, {
5052
credentials: options.credentials,
5153
keyFile: options.keyFilename,

packages/common/test/service.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ describe('Service', function() {
6565
});
6666

6767
describe('instantiation', function() {
68+
it('should not require options', function() {
69+
assert.doesNotThrow(function() {
70+
new Service(CONFIG);
71+
});
72+
});
73+
6874
it('should create an authenticated request factory', function() {
6975
var authenticatedRequest = {};
7076

packages/logging-bunyan/src/index.js

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ var BUNYAN_TO_STACKDRIVER = {
4040
10: 'DEBUG'
4141
};
4242

43+
/**
44+
* Key to use in the Bunyan payload to allow users to indicate a trace for the
45+
* request, and to store as an intermediate value on the log entry before it
46+
* gets written to the Stackdriver logging API.
47+
*/
48+
var LOGGING_TRACE_KEY = 'logging.googleapis.com/trace';
49+
4350
/**
4451
* This module provides support for streaming your Bunyan logs to
4552
* [Stackdriver Logging](https://cloud.google.com/logging).
@@ -56,6 +63,15 @@ var BUNYAN_TO_STACKDRIVER = {
5663
* automatically, but you may optionally specify a specific monitored
5764
* resource. For more information, see the
5865
* [official documentation]{@link https://cloud.google.com/logging/docs/api/reference/rest/v2/MonitoredResource}
66+
* @param {object=} options.serviceContext - For logged errors, we provide this
67+
* as the service context. For more information see
68+
* [this guide]{@link https://cloud.google.com/error-reporting/docs/formatting-error-messages}
69+
* and the [official documentation]{@link https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext}.
70+
* @param {string} options.serviceContext.service - An identifier of the
71+
* service, such as the name of the executable, job, or Google App Engine
72+
* service name.
73+
* @param {string=} options.serviceContext.version - Represents the version of
74+
* the service.
5975
*
6076
* @example
6177
* var bunyan = require('bunyan');
@@ -85,6 +101,7 @@ function LoggingBunyan(options) {
85101

86102
this.logName_ = options.logName || 'bunyan_log';
87103
this.resource_ = options.resource;
104+
this.serviceContext_ = options.serviceContext;
88105

89106
this.log_ = logging(options).log(this.logName_, {
90107
removeCircular: true
@@ -133,8 +150,6 @@ LoggingBunyan.prototype.formatEntry_ = function(record) {
133150
);
134151
}
135152

136-
record = extend({}, record);
137-
138153
// Stackdriver Log Viewer picks up the summary line from the 'message' field
139154
// of the payload. Unless the user has provided a 'message' property also,
140155
// move the 'msg' to 'message'.
@@ -144,11 +159,9 @@ LoggingBunyan.prototype.formatEntry_ = function(record) {
144159
// higher). In this case we leave the 'msg' property intact.
145160
// https://cloud.google.com/error-reporting/docs/formatting-error-messages
146161
//
147-
// TODO(ofrobots): when resource.type is 'global' we need to additionally
148-
// provide serviceContext.service as part of the entry for Error Reporting
149-
// to automatically pick up the error.
150162
if (record.err && record.err.stack) {
151163
record.message = record.err.stack;
164+
record.serviceContext = this.serviceContext_;
152165
} else if (record.msg) {
153166
// Simply rename `msg` to `message`.
154167
record.message = record.msg;
@@ -173,9 +186,58 @@ LoggingBunyan.prototype.formatEntry_ = function(record) {
173186
delete record.httpRequest;
174187
}
175188

189+
if (record[LOGGING_TRACE_KEY]) {
190+
entryMetadata.trace = record[LOGGING_TRACE_KEY];
191+
delete record[LOGGING_TRACE_KEY];
192+
}
193+
176194
return this.log_.entry(entryMetadata, record);
177195
};
178196

197+
/**
198+
* Gets the current fully qualified trace ID when available from the
199+
* @google-cloud/trace-agent library in the LogEntry.trace field format of:
200+
* "projects/[PROJECT-ID]/traces/[TRACE-ID]".
201+
*/
202+
function getCurrentTraceFromAgent() {
203+
var agent = global._google_trace_agent;
204+
if (!agent || !agent.getCurrentContextId || !agent.getWriterProjectId) {
205+
return null;
206+
}
207+
208+
var traceId = agent.getCurrentContextId();
209+
if (!traceId) {
210+
return null;
211+
}
212+
213+
var traceProjectId = agent.getWriterProjectId();
214+
if (!traceProjectId) {
215+
return null;
216+
}
217+
218+
return `projects/${traceProjectId}/traces/${traceId}`;
219+
}
220+
221+
/**
222+
* Intercept log entries as they are written so we can attempt to add the trace
223+
* ID in the same continuation as the function that wrote the log, because the
224+
* trace agent currently uses continuation local storage for the trace context.
225+
*
226+
* By the time the Writable stream buffer gets flushed and _write gets called
227+
* we may well be in a different continuation.
228+
*/
229+
LoggingBunyan.prototype.write = function(record, encoding, callback) {
230+
record = extend({}, record);
231+
if (!record[LOGGING_TRACE_KEY]) {
232+
var trace = getCurrentTraceFromAgent();
233+
if (trace) {
234+
record[LOGGING_TRACE_KEY] = trace;
235+
}
236+
}
237+
238+
Writable.prototype.write.call(this, record, encoding, callback);
239+
};
240+
179241
/**
180242
* Relay a log entry to the logging agent. This is called by bunyan through
181243
* Writable#write.

packages/logging-bunyan/test/index.js

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ describe('logging-bunyan', function() {
5656

5757
var OPTIONS = {
5858
logName: 'log-name',
59-
resource: {}
59+
resource: {},
60+
serviceContext: {
61+
service: 'fake-service'
62+
}
6063
};
6164

6265
var RECORD = {
@@ -98,6 +101,10 @@ describe('logging-bunyan', function() {
98101
assert.strictEqual(loggingBunyan.resource_, OPTIONS.resource);
99102
});
100103

104+
it('should localize the provided service context', function() {
105+
assert.strictEqual(loggingBunyan.serviceContext_, OPTIONS.serviceContext);
106+
});
107+
101108
it('should localize Log instance using provided name', function() {
102109
assert.strictEqual(fakeLoggingOptions_, OPTIONS);
103110
assert.strictEqual(fakeLogName_, OPTIONS.logName);
@@ -174,7 +181,8 @@ describe('logging-bunyan', function() {
174181
err: {
175182
stack: 'the stack'
176183
},
177-
message: 'the stack'
184+
message: 'the stack',
185+
serviceContext: OPTIONS.serviceContext
178186
}, RECORD);
179187

180188
loggingBunyan.log_.entry = function(entryMetadata, record_) {
@@ -223,6 +231,135 @@ describe('logging-bunyan', function() {
223231

224232
loggingBunyan.formatEntry_(recordWithRequest);
225233
});
234+
235+
it('should promote prefixed trace property to metadata', function(done) {
236+
var recordWithRequest = extend({
237+
'logging.googleapis.com/trace': 'trace1'
238+
}, RECORD);
239+
240+
loggingBunyan.log_.entry = function(entryMetadata, record) {
241+
assert.deepStrictEqual(entryMetadata, {
242+
resource: loggingBunyan.resource_,
243+
timestamp: RECORD.time,
244+
severity: LoggingBunyan.BUNYAN_TO_STACKDRIVER[RECORD.level],
245+
trace: 'trace1'
246+
});
247+
assert.deepStrictEqual(record, RECORD);
248+
done();
249+
};
250+
251+
loggingBunyan.formatEntry_(recordWithRequest);
252+
});
253+
});
254+
255+
describe('write', function() {
256+
var oldWritableWrite;
257+
var oldTraceAgent;
258+
259+
beforeEach(function() {
260+
oldWritableWrite = FakeWritable.prototype.write;
261+
oldTraceAgent = global._google_trace_agent;
262+
});
263+
264+
afterEach(function() {
265+
FakeWritable.prototype.write = oldWritableWrite;
266+
global._google_trace_agent = oldTraceAgent;
267+
});
268+
269+
it('should not set trace property if trace unavailable', function(done) {
270+
global._google_trace_agent = undefined;
271+
FakeWritable.prototype.write = function(record, encoding, callback) {
272+
assert.deepStrictEqual(record, RECORD);
273+
assert.strictEqual(encoding, 'encoding');
274+
assert.strictEqual(callback, assert.ifError);
275+
assert.strictEqual(this, loggingBunyan);
276+
done();
277+
};
278+
279+
loggingBunyan.write(RECORD, 'encoding', assert.ifError);
280+
});
281+
282+
it('should set prefixed trace property if trace available', function(done) {
283+
global._google_trace_agent = {
284+
getCurrentContextId: function() { return 'trace1'; },
285+
getWriterProjectId: function() { return 'project1'; }
286+
};
287+
const recordWithoutTrace = extend({}, RECORD);
288+
const recordWithTrace = extend({
289+
'logging.googleapis.com/trace': 'projects/project1/traces/trace1'
290+
}, RECORD);
291+
292+
FakeWritable.prototype.write = function(record, encoding, callback) {
293+
// Check that trace field added to record before calling Writable.write
294+
assert.deepStrictEqual(record, recordWithTrace);
295+
296+
// Check that the original record passed in was not mutated
297+
assert.deepStrictEqual(recordWithoutTrace, RECORD);
298+
299+
assert.strictEqual(encoding, 'encoding');
300+
assert.strictEqual(callback, assert.ifError);
301+
assert.strictEqual(this, loggingBunyan);
302+
done();
303+
};
304+
305+
loggingBunyan.write(recordWithoutTrace, 'encoding', assert.ifError);
306+
});
307+
308+
it('should leave prefixed trace property as is if set', function(done) {
309+
var oldTraceAgent = global._google_trace_agent;
310+
global._google_trace_agent = {
311+
getCurrentContextId: function() { return 'trace-from-agent'; },
312+
getWriterProjectId: function() { return 'project1'; }
313+
};
314+
const recordWithTraceAlreadySet = extend({
315+
'logging.googleapis.com/trace': 'trace-already-set'
316+
}, RECORD);
317+
318+
FakeWritable.prototype.write = function(record, encoding, callback) {
319+
assert.deepStrictEqual(record, recordWithTraceAlreadySet);
320+
assert.strictEqual(encoding, '');
321+
assert.strictEqual(callback, assert.ifError);
322+
assert.strictEqual(this, loggingBunyan);
323+
done();
324+
};
325+
326+
loggingBunyan.write(recordWithTraceAlreadySet, '', assert.ifError);
327+
328+
global._google_trace_agent = oldTraceAgent;
329+
});
330+
});
331+
332+
it('should not set prefixed trace property if trace unavailable', function() {
333+
FakeWritable.prototype.write = function(record, encoding, callback) {
334+
assert.deepStrictEqual(record, RECORD);
335+
assert.strictEqual(encoding, '');
336+
assert.strictEqual(callback, assert.ifError);
337+
assert.strictEqual(this, loggingBunyan);
338+
};
339+
var oldTraceAgent = global._google_trace_agent;
340+
341+
global._google_trace_agent = {};
342+
loggingBunyan.write(RECORD, '', assert.ifError);
343+
344+
global._google_trace_agent = {
345+
getCurrentContextId: function() { return null; },
346+
getWriterProjectId: function() { return null; }
347+
};
348+
loggingBunyan.write(RECORD, '', assert.ifError);
349+
350+
global._google_trace_agent = {
351+
getCurrentContextId: function() { return null; },
352+
getWriterProjectId: function() { return 'project1'; }
353+
};
354+
loggingBunyan.write(RECORD, '', assert.ifError);
355+
356+
global._google_trace_agent = {
357+
getCurrentContextId: function() { return 'trace1'; },
358+
getWriterProjectId: function() { return null; }
359+
};
360+
loggingBunyan.write(RECORD, '', assert.ifError);
361+
362+
global._google_trace_agent = oldTraceAgent;
226363
});
227364

228365
describe('_write', function() {
@@ -303,4 +440,4 @@ describe('logging-bunyan', function() {
303440
});
304441
});
305442
});
306-
});
443+
});

0 commit comments

Comments
 (0)