Skip to content

Commit 294d1e4

Browse files
feat: add getLogs() and getLogsStream() (#692)
1 parent b201100 commit 294d1e4

3 files changed

Lines changed: 416 additions & 33 deletions

File tree

handwritten/logging/src/index.ts

Lines changed: 205 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,30 @@ export interface GetEntriesCallback {
103103
): void;
104104
}
105105

106+
export interface GetLogsRequest {
107+
autoPaginate?: boolean;
108+
gaxOptions?: gax.CallOptions;
109+
maxApiCalls?: number;
110+
maxResults?: number;
111+
pageSize?: number;
112+
pageToken?: string;
113+
}
114+
115+
export type GetLogsResponse = [
116+
Sink[],
117+
google.logging.v2.IListLogsRequest,
118+
google.logging.v2.IListLogsResponse
119+
];
120+
121+
export interface GetLogsCallback {
122+
(
123+
err: Error | null,
124+
entries?: Sink[],
125+
request?: google.logging.v2.IListLogsRequest,
126+
apiResponse?: google.logging.v2.IListLogsResponse
127+
): void;
128+
}
129+
106130
export interface GetSinksRequest {
107131
autoPaginate?: boolean;
108132
gaxOptions?: gax.CallOptions;
@@ -677,6 +701,186 @@ class Logging {
677701
return userStream;
678702
}
679703

704+
/**
705+
* Query object for listing entries.
706+
*
707+
* @typedef {object} GetLogsRequest
708+
* @property {boolean} [autoPaginate=true] Have pagination handled
709+
* automatically.
710+
* @property {object} [gaxOptions] Request configuration options, outlined
711+
* here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions.
712+
* @property {number} [maxApiCalls] Maximum number of API calls to make.
713+
* @property {number} [maxResults] Maximum number of items plus prefixes to
714+
* return.
715+
* @property {number} [pageSize] Maximum number of logs to return.
716+
* @property {string} [pageToken] A previously-returned page token
717+
* representing part of the larger set of results to view.
718+
*/
719+
/**
720+
* @typedef {array} GetLogsResponse
721+
* @property {Log[]} 0 Array of {@link Log} instances.
722+
* @property {object} 1 The full API response.
723+
*/
724+
/**
725+
* @callback GetLogsCallback
726+
* @param {?Error} err Request error, if any.
727+
* @param {Log[]} logs Array of {@link Log} instances.
728+
* @param {object} apiResponse The full API response.
729+
*/
730+
/**
731+
* List the entries in your logs.
732+
*
733+
* @see [logs.list API Documentation]{@link https://cloud.google.com/logging/docs/reference/v2/rest/v2/logs/list}
734+
*
735+
* @param {GetLogsRequest} [query] Query object for listing entries.
736+
* @param {GetLogsCallback} [callback] Callback function.
737+
* @returns {Promise<GetLogsResponse>}
738+
*
739+
* @example
740+
* const {Logging} = require('@google-cloud/logging');
741+
* const logging = new Logging();
742+
*
743+
* logging.getLogs((err, logs) => {
744+
* // `logs` is an array of Stackdriver Logging log objects.
745+
* });
746+
*
747+
* //-
748+
* // To control how many API requests are made and page through the results
749+
* // manually, set `autoPaginate` to `false`.
750+
* //-
751+
* function callback(err, entries, nextQuery, apiResponse) {
752+
* if (nextQuery) {
753+
* // More results exist.
754+
* logging.getLogs(nextQuery, callback);
755+
* }
756+
* }
757+
*
758+
* logging.getLogs({
759+
* autoPaginate: false
760+
* }, callback);
761+
*
762+
* //-
763+
* // If the callback is omitted, we'll return a Promise.
764+
* //-
765+
* logging.getLogs().then(data => {
766+
* const entries = data[0];
767+
* });
768+
*
769+
* @example <caption>include:samples/logs.js</caption>
770+
* region_tag:logging_list_logs
771+
* Another example:
772+
*/
773+
getLogs(options?: GetLogsRequest): Promise<GetLogsResponse>;
774+
getLogs(callback: GetLogsCallback): void;
775+
getLogs(options: GetLogsRequest, callback: GetLogsCallback): void;
776+
async getLogs(
777+
opts?: GetLogsRequest | GetLogsCallback
778+
): Promise<GetLogsResponse> {
779+
const options = opts ? (opts as GetSinksRequest) : {};
780+
this.projectId = await this.auth.getProjectId();
781+
const reqOpts = extend({}, options, {
782+
parent: 'projects/' + this.projectId,
783+
});
784+
delete reqOpts.autoPaginate;
785+
delete reqOpts.gaxOptions;
786+
const gaxOptions = extend(
787+
{
788+
autoPaginate: options.autoPaginate,
789+
},
790+
options.gaxOptions
791+
);
792+
const resp = await this.loggingService.listLogs(reqOpts, gaxOptions);
793+
const [logs] = resp;
794+
if (logs) {
795+
resp[0] = logs.map((logName: string) => this.log(logName));
796+
}
797+
return resp;
798+
}
799+
800+
/**
801+
* List the {@link Log} objects in your project as a readable object stream.
802+
*
803+
* @method Logging#getLogsStream
804+
* @param {GetLogsRequest} [query] Query object for listing entries.
805+
* @returns {ReadableStream} A readable stream that emits {@link Log}
806+
* instances.
807+
*
808+
* @example
809+
* const {Logging} = require('@google-cloud/logging');
810+
* const logging = new Logging();
811+
*
812+
* logging.getLogsStream()
813+
* .on('error', console.error)
814+
* .on('data', log => {
815+
* // `log` is a Stackdriver Logging log object.
816+
* })
817+
* .on('end', function() {
818+
* // All logs retrieved.
819+
* });
820+
*
821+
* //-
822+
* // If you anticipate many results, you can end a stream early to prevent
823+
* // unnecessary processing and API requests.
824+
* //-
825+
* logging.getLogsStream()
826+
* .on('data', log => {
827+
* this.end();
828+
* });
829+
*/
830+
getLogsStream(options: GetLogsRequest = {}) {
831+
options = options || {};
832+
let requestStream: Duplex;
833+
const userStream = streamEvents<Duplex>(pumpify.obj());
834+
(userStream as AbortableDuplex).abort = () => {
835+
if (requestStream) {
836+
(requestStream as AbortableDuplex).abort();
837+
}
838+
};
839+
const toLogStream = through.obj((logName, _, next) => {
840+
next(null, this.log(logName));
841+
});
842+
userStream.once('reading', () => {
843+
this.auth.getProjectId().then(projectId => {
844+
this.projectId = projectId;
845+
const reqOpts = extend({}, options, {
846+
parent: 'projects/' + this.projectId,
847+
});
848+
delete reqOpts.gaxOptions;
849+
const gaxOptions = extend(
850+
{
851+
autoPaginate: options.autoPaginate,
852+
},
853+
options.gaxOptions
854+
);
855+
856+
let gaxStream: ClientReadableStream<Log>;
857+
requestStream = streamEvents<Duplex>(through.obj());
858+
(requestStream as AbortableDuplex).abort = () => {
859+
if (gaxStream && gaxStream.cancel) {
860+
gaxStream.cancel();
861+
}
862+
};
863+
requestStream.once('reading', () => {
864+
try {
865+
gaxStream = this.loggingService.listLogsStream(reqOpts, gaxOptions);
866+
} catch (error) {
867+
requestStream.destroy(error);
868+
return;
869+
}
870+
gaxStream
871+
.on('error', err => {
872+
requestStream.destroy(err);
873+
})
874+
.pipe(requestStream);
875+
return;
876+
});
877+
// tslint:disable-next-line no-any
878+
(userStream as any).setPipeline(requestStream, toLogStream);
879+
});
880+
});
881+
return userStream;
882+
}
883+
680884
/**
681885
* Query object for listing sinks.
682886
*
@@ -1067,7 +1271,7 @@ callbackifyAll(Logging, {
10671271
*
10681272
* These methods can be auto-paginated.
10691273
*/
1070-
paginator.extend(Logging, ['getEntries', 'getSinks']);
1274+
paginator.extend(Logging, ['getEntries', 'getLogs', 'getSinks']);
10711275

10721276
/**
10731277
* {@link Entry} class.

handwritten/logging/system-test/logging.ts

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ nock(HOST_ADDRESS)
3434

3535
describe('Logging', () => {
3636
let PROJECT_ID: string;
37-
const TESTS_PREFIX = 'gcloud-logging-test';
37+
const TESTS_PREFIX = 'nodejs-logging-system-test';
3838
const WRITE_CONSISTENCY_DELAY_MS = 5000;
3939

4040
const bigQuery = new BigQuery();
@@ -67,6 +67,7 @@ describe('Logging', () => {
6767
deleteDatasets(),
6868
deleteTopics(),
6969
deleteSinks(),
70+
deleteLogs(),
7071
]);
7172

7273
async function deleteBuckets() {
@@ -97,6 +98,20 @@ describe('Logging', () => {
9798
await getAndDelete(logging.getSinks.bind(logging));
9899
}
99100

101+
async function deleteLogs() {
102+
const [logs] = await logging.getLogs();
103+
const logsToDelete = logs.filter(log => {
104+
return (
105+
log.name.includes(TESTS_PREFIX) &&
106+
getDateFromGeneratedName(log.name) < oneHourAgo
107+
);
108+
});
109+
110+
for (const log of logsToDelete) {
111+
await log.delete();
112+
}
113+
}
114+
100115
async function getAndDelete(method: Function) {
101116
const [objects] = await method();
102117
return Promise.all(
@@ -210,14 +225,8 @@ describe('Logging', () => {
210225
});
211226

212227
describe('logs', () => {
213-
// tslint:disable-next-line no-any
214-
const logs: any[] = [];
215-
216228
function getTestLog(loggingInstnce = null) {
217-
const log = (loggingInstnce || logging).log(
218-
`system-test-logs-${uuid.v4()}`
219-
);
220-
logs.push(log);
229+
const log = (loggingInstnce || logging).log(generateName());
221230

222231
const logEntries = [
223232
// string data
@@ -281,29 +290,26 @@ describe('Logging', () => {
281290
},
282291
};
283292

284-
after(async () => {
285-
for (const log of logs) {
286-
// attempt to delete log entries multiple times, as they can
287-
// take a variable amount of time to write to the API:
288-
let retries = 0;
289-
while (retries < 3) {
290-
try {
291-
await log.delete();
292-
} catch (_err) {
293-
if (_err.code === 5) {
294-
break;
295-
}
296-
retries++;
297-
console.warn(
298-
`delete of ${log.name} failed retries = ${retries}`,
299-
_err.message
300-
);
301-
await new Promise(r => setTimeout(r, WRITE_CONSISTENCY_DELAY_MS));
302-
continue;
303-
}
304-
break;
305-
}
306-
}
293+
describe('listing logs', () => {
294+
before(async () => {
295+
const {log, logEntries} = getTestLog();
296+
await log.write(logEntries, options);
297+
});
298+
299+
it('should list logs', async () => {
300+
const [logs] = await logging.getLogs();
301+
assert(logs.length > 0);
302+
});
303+
304+
it('should list logs as a stream', done => {
305+
const stream: Duplex = logging
306+
.getLogsStream({pageSize: 1})
307+
.on('error', done)
308+
.once('data', () => {
309+
stream.end();
310+
done();
311+
});
312+
});
307313
});
308314

309315
it('should list log entries', done => {

0 commit comments

Comments
 (0)