Skip to content

Commit 625769c

Browse files
author
Benjamin E. Coe
authored
feat: introduce maxEntrySize, for enabling error message truncation (#607)
1 parent a15fac9 commit 625769c

2 files changed

Lines changed: 108 additions & 4 deletions

File tree

handwritten/logging/src/log.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {Response} from 'teeny-request';
2424
import {google} from '../proto/logging';
2525

2626
import {GetEntriesCallback, GetEntriesResponse, Logging} from '.';
27-
import {Entry, LogEntry} from './entry';
27+
import {Entry, EntryJson, LogEntry} from './entry';
2828
import {getDefaultResource} from './metadata';
2929

3030
const snakeCaseKeys = require('snakecase-keys');
@@ -44,6 +44,7 @@ export interface GetEntriesRequest {
4444

4545
export interface LogOptions {
4646
removeCircular?: boolean;
47+
maxEntrySize?: number; // see: https://cloud.google.com/logging/quotas
4748
}
4849

4950
export type ApiResponse = [Response];
@@ -103,12 +104,14 @@ type LogSeverityFunctions = {
103104
class Log implements LogSeverityFunctions {
104105
formattedName_: string;
105106
removeCircular_: boolean;
107+
maxEntrySize?: number;
106108
logging: Logging;
107109
name: string;
108110
constructor(logging: Logging, name: string, options?: LogOptions) {
109111
options = options || {};
110112
this.formattedName_ = Log.formatName_(logging.projectId, name);
111113
this.removeCircular_ = options.removeCircular === true;
114+
this.maxEntrySize = options.maxEntrySize;
112115
this.logging = logging;
113116
/**
114117
* @name Log#name
@@ -828,6 +831,7 @@ class Log implements LogSeverityFunctions {
828831
} catch (err) {
829832
// Ignore errors (the API will speak up if it has an issue).
830833
}
834+
self.truncateEntries(decoratedEntries);
831835
const projectId = await self.logging.auth.getProjectId();
832836
self.formattedName_ = Log.formatName_(projectId, self.name);
833837
const reqOpts = extend(
@@ -855,7 +859,7 @@ class Log implements LogSeverityFunctions {
855859
* @returns {object[]} Serialized entries.
856860
* @throws if there is an error during serialization.
857861
*/
858-
decorateEntries_(entries: Entry[]) {
862+
decorateEntries_(entries: Entry[]): EntryJson[] {
859863
return entries.map(entry => {
860864
if (!(entry instanceof Entry)) {
861865
entry = this.entry(entry);
@@ -866,6 +870,48 @@ class Log implements LogSeverityFunctions {
866870
});
867871
}
868872

873+
/**
874+
* Truncate log entries at maxEntrySize, so that error is not thrown, see:
875+
* https://cloud.google.com/logging/quotas
876+
*
877+
* @private
878+
*
879+
* @param {object|string} the JSON log entry.
880+
* @returns {object|string} truncated JSON log entry.
881+
*/
882+
private truncateEntries(entries: EntryJson[]) {
883+
return entries.forEach(entry => {
884+
if (this.maxEntrySize === undefined) return;
885+
886+
const payloadSize = JSON.stringify(entry).length;
887+
if (payloadSize < this.maxEntrySize) return;
888+
889+
const delta = payloadSize - this.maxEntrySize;
890+
if (entry.textPayload) {
891+
entry.textPayload = entry.textPayload.slice(
892+
0,
893+
Math.max(entry.textPayload.length - delta, 0)
894+
);
895+
} else {
896+
// Stackdriver Log Viewer picks up the summary line from the
897+
// 'message' field.
898+
if (
899+
entry.jsonPayload &&
900+
entry.jsonPayload.fields &&
901+
entry.jsonPayload.fields.message &&
902+
entry.jsonPayload.fields.message.stringValue
903+
) {
904+
const text: string | null | undefined =
905+
entry.jsonPayload.fields.message.stringValue;
906+
entry.jsonPayload.fields.message.stringValue = text.slice(
907+
0,
908+
Math.max(text.length - delta, 0)
909+
);
910+
}
911+
}
912+
});
913+
}
914+
869915
/**
870916
* Return an array of log entries with the desired severity assigned.
871917
*

handwritten/logging/test/log.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const fakeCallbackify = extend({}, callbackify, {
3333

3434
import {Entry} from '../src';
3535
import {EntryJson} from '../src/entry';
36+
import {LogOptions} from '../src/log';
3637

3738
const originalGetDefaultResource = async () => {
3839
return 'very-fake-resource';
@@ -76,6 +77,10 @@ describe('Log', () => {
7677
});
7778

7879
beforeEach(() => {
80+
log = createLogger();
81+
});
82+
83+
function createLogger(maxEntrySize?: number) {
7984
assignSeverityToEntriesOverride = null;
8085

8186
LOGGING = {
@@ -86,8 +91,13 @@ describe('Log', () => {
8691
auth: util.noop,
8792
};
8893

89-
log = new Log(LOGGING, LOG_NAME);
90-
});
94+
const options: LogOptions = {};
95+
if (maxEntrySize) {
96+
options.maxEntrySize = maxEntrySize;
97+
}
98+
99+
return new Log(LOGGING, LOG_NAME, options);
100+
}
91101

92102
describe('instantiation', () => {
93103
it('should callbackify all the things', () => {
@@ -441,6 +451,54 @@ describe('Log', () => {
441451

442452
await log.write(ENTRY);
443453
});
454+
455+
it('should not truncate entries by default', async () => {
456+
const logger = createLogger();
457+
const entry = new Entry({}, 'hello world'.padEnd(300000, '.'));
458+
459+
logger.logging.loggingService.writeLogEntries = (reqOpts, _gaxOpts) => {
460+
assert.strictEqual(reqOpts.entries[0].textPayload.length, 300000);
461+
};
462+
463+
await logger.write(entry);
464+
});
465+
466+
it('should truncate string entry if maxEntrySize hit', async () => {
467+
const truncatingLogger = createLogger(200);
468+
const entry = new Entry({}, 'hello world'.padEnd(2000, '.'));
469+
470+
truncatingLogger.logging.loggingService.writeLogEntries = (
471+
reqOpts,
472+
_gaxOpts
473+
) => {
474+
const text = reqOpts.entries[0].textPayload;
475+
assert.ok(text.startsWith('hello world'));
476+
assert.ok(text.length < 300);
477+
};
478+
479+
await truncatingLogger.write(entry);
480+
});
481+
482+
it('should truncate message field, on object entry, if maxEntrySize hit', async () => {
483+
const truncatingLogger = createLogger(200);
484+
const entry = new Entry(
485+
{},
486+
{
487+
message: 'hello world'.padEnd(2000, '.'),
488+
}
489+
);
490+
491+
truncatingLogger.logging.loggingService.writeLogEntries = (
492+
reqOpts,
493+
_gaxOpts
494+
) => {
495+
const text = reqOpts.entries[0].jsonPayload.fields.message.stringValue;
496+
assert.ok(text.startsWith('hello world'));
497+
assert.ok(text.length < 300);
498+
};
499+
500+
await truncatingLogger.write(entry);
501+
});
444502
});
445503

446504
describe('severity shortcuts', () => {

0 commit comments

Comments
 (0)