[go: nahoru, domu]

Skip to content

Commit

Permalink
fix: Logging to stdout in Cloud Run creates a JSON object as "message" (
Browse files Browse the repository at this point in the history
#1305)

* fix: Logging to stdout in Cloud Run creates a JSON object as "message"

* Add test

* Add comments to code

* Add more tests to cover different data types
  • Loading branch information
losalex committed Jul 11, 2022
1 parent 7f778db commit cb5f424
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 8 deletions.
43 changes: 37 additions & 6 deletions src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export interface StructuredJson {
// Properties not supported by all agents (e.g. Cloud Run, Functions)
logName?: string;
resource?: object;
// Properties to be stored in jsonPayload when running in serverless (e.g. Cloud Run , Functions)
[key: string]: unknown;
}

export interface ToJsonOptions {
Expand Down Expand Up @@ -202,7 +204,7 @@ class Entry {
toJSON(options: ToJsonOptions = {}, projectId = '') {
const entry: EntryJson = extend(true, {}, this.metadata) as {} as EntryJson;
// Format log message
if (Object.prototype.toString.call(this.data) === '[object Object]') {
if (this.isObject(this.data)) {
entry.jsonPayload = objToStruct(this.data, {
removeCircular: !!options.removeCircular,
stringify: true,
Expand Down Expand Up @@ -243,7 +245,7 @@ class Entry {
* Serialize an entry to a standard format for any transports, e.g. agents.
* Read more: https://cloud.google.com/logging/docs/structured-logging
*/
toStructuredJSON(projectId = '') {
toStructuredJSON(projectId = '', useMessageField = true) {
const meta = this.metadata;
// Mask out the keys that need to be renamed.
/* eslint-disable @typescript-eslint/no-unused-vars */
Expand All @@ -260,7 +262,7 @@ class Entry {
...validKeys
} = meta;
/* eslint-enable @typescript-eslint/no-unused-vars */
const entry: StructuredJson = extend(true, {}, validKeys) as {};
let entry: StructuredJson = extend(true, {}, validKeys) as {};
// Re-map keys names.
entry[LABELS_KEY] = meta.labels
? Object.assign({}, meta.labels)
Expand All @@ -273,9 +275,29 @@ class Entry {
? meta.traceSampled
: undefined;
// Format log payload.
entry.message =
meta.textPayload || meta.jsonPayload || meta.protoPayload || undefined;
entry.message = this.data || entry.message;
const data =
this.data ||
meta.textPayload ||
meta.jsonPayload ||
meta.protoPayload ||
undefined;
if (useMessageField) {
/** If useMessageField is set, we add the payload to {@link StructuredJson#message} field.*/
entry.message = data;
} else {
/** useMessageField is false, we add the structured payload to {@link StructuredJson} key-value map.
* It could be especially useful for serverless environments like Cloud Run/Functions when stdout transport is used.
* Note that text still added to {@link StructuredJson#message} field for text payload since it does not have fields within. */
if (data !== undefined && data !== null) {
if (this.isObject(data)) {
entry = extend(true, {}, entry, data);
} else if (typeof data === 'string') {
entry.message = data;
} else {
entry.message = JSON.stringify(data);
}
}
}
// Format timestamp
if (meta.timestamp instanceof Date) {
entry.timestamp = meta.timestamp.toISOString();
Expand Down Expand Up @@ -333,6 +355,15 @@ class Entry {
}
return serializedEntry;
}

/**
* Determines whether `value` is a JavaScript object.
* @param value The value to be checked
* @returns true if `value` is a JavaScript object, false otherwise
*/
private isObject(value: unknown): value is object {
return Object.prototype.toString.call(value) === '[object Object]';
}
}

/**
Expand Down
21 changes: 19 additions & 2 deletions src/log-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ import {
WriteOptions,
} from './utils/log-common';

export interface LogSyncOptions {
// The flag indicating if "message" field should be used to store structured,
// non-text data inside jsonPayload field. By default this value is true
useMessageField?: boolean;
}

/**
* A logSync is a named collection of entries in structured log format. In Cloud
* Logging, structured logs refer to log entries that use the jsonPayload field
Expand Down Expand Up @@ -61,9 +67,16 @@ class LogSync implements LogSeverityFunctions {
logging: Logging;
name: string;
transport: Writable;
useMessageField_: boolean;

// not projectId, formattedname is expected
constructor(logging: Logging, name: string, transport?: Writable) {
constructor(
logging: Logging,
name: string,
transport?: Writable,
options?: LogSyncOptions
) {
options = options || {};
this.formattedName_ = formatLogName(logging.projectId, name);
this.logging = logging;
/**
Expand All @@ -73,6 +86,7 @@ class LogSync implements LogSeverityFunctions {
this.name = this.formattedName_.split('/').pop()!;
// Default to writing to stdout
this.transport = transport || process.stdout;
this.useMessageField_ = options.useMessageField ?? true;
}

/**
Expand Down Expand Up @@ -417,7 +431,10 @@ class LogSync implements LogSeverityFunctions {
if (!(entry instanceof Entry)) {
entry = this.entry(entry);
}
return entry.toStructuredJSON(this.logging.projectId);
return entry.toStructuredJSON(
this.logging.projectId,
this.useMessageField_
);
});
for (const entry of structuredEntries) {
entry.logName = this.formattedName_;
Expand Down
23 changes: 23 additions & 0 deletions test/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,5 +368,28 @@ describe('Entry', () => {
assert.strictEqual(json[entryTypes.SPAN_ID_KEY], '1');
assert.strictEqual(json[entryTypes.TRACE_SAMPLED_KEY], false);
});

it('should add message field for structured data', () => {
entry.data = {message: 'message', test: 'test'};
let json = entry.toStructuredJSON();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
assert(((json.message as any).message = 'message'));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
assert(((json.message as any).test = 'test'));
json = entry.toStructuredJSON(undefined, false);
assert((json.message = 'message'));
assert((json.test = 'test'));
});

it('should add message field only when needed', () => {
entry.data = 1;
let json = entry.toStructuredJSON();
assert((json.message = '1'));
json = entry.toStructuredJSON(undefined, false);
assert((json.message = '1'));
entry.data = 'test';
json = entry.toStructuredJSON(undefined, false);
assert((json.message = 'test'));
});
});
});

0 comments on commit cb5f424

Please sign in to comment.