Skip to content

Commit

Permalink
feat: add Logging for summary and detail
Browse files Browse the repository at this point in the history
  • Loading branch information
htdangkhoa committed Nov 11, 2023
1 parent 6cb75d5 commit 73a5d8b
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 3 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
'/build/',
'/src/generated/',
'/src/tests/',
'/src/lib/LoggingInterceptor.ts',
'jest.config.js',
],
testPathIgnorePatterns: ['/node_modules/', '/build/'],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"dependencies": {
"@grpc/grpc-js": "^1.8.5",
"deepmerge": "^4.2.2",
"log4js": "^6.9.1",
"protobufjs": "^7.1.2"
},
"devDependencies": {
Expand Down
185 changes: 185 additions & 0 deletions src/lib/LoggingInterceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import {
InterceptingCall,
InterceptorOptions,
Metadata,
Interceptor as GRPCInterceptor,
StatusObject,
} from '@grpc/grpc-js';
import { Status } from '@grpc/grpc-js/build/src/constants';
import {
FullRequester,
ListenerBuilder,
NextCall,
RequesterBuilder,
} from '@grpc/grpc-js/build/src/client-interceptors';
import log4js from 'log4js';
import type { Logger } from 'log4js';
import { Interceptor, LoggingOptions } from './types';
import { HOST } from './constants';

const cleanEmpty = function (obj: any, defaults = [undefined, null]): any {
if (defaults.includes(obj)) return;

if (Array.isArray(obj))
return obj
.map((v) => (v && typeof v === 'object' ? cleanEmpty(v, defaults) : v))
.filter((v) => !defaults.includes(v));

return Object.entries(obj).length
? Object.entries(obj)
.map(([k, v]) => [
k,
v && typeof v === 'object' ? cleanEmpty(v, defaults) : v,
])
.reduce(
(a, [k, v]) => (defaults.includes(v) ? a : { ...a, [k]: v }),
{},
)
: obj;
};

export class LoggingInterceptor implements Interceptor {
private requestLogging: boolean | LoggingOptions;
private summaryLogger: Logger;
private detailLogger: Logger;

constructor(requestLogging: boolean | LoggingOptions) {
this.requestLogging = requestLogging;

log4js.configure({
appenders: {
out: { type: 'stdout' },
},
categories: {
default: { appenders: ['out'], level: 'info' },
},
});
this.summaryLogger = log4js.getLogger('Google::Ads::GoogleAds::Summary');
this.detailLogger = log4js.getLogger('Google::Ads::GoogleAds::Detail');
}

private logSummary(
responseStatus: StatusObject,
request: any,
options: InterceptorOptions,
responseHeaders: Metadata,
) {
if (
this.requestLogging === true ||
(<LoggingOptions>this.requestLogging).summary === true
) {
const isSuccess = responseStatus.code == Status.OK.valueOf();

const messages = [
`${isSuccess ? 'SUCCESS' : 'FAILURE'} REQUEST SUMMARY.`,
`Host=${HOST}`,
`Method=${options.method_definition.path}`,
`ClientCustomerId=${request.customer_id}`,
`RequestId=${responseHeaders.get('request-id')}`,
`ResponseCode=${responseStatus.code}`,
];

if (isSuccess) {
this.summaryLogger.info(messages.join(' '));
} else {
messages.push(`Fault=${responseStatus.details}`);
this.summaryLogger.warn(messages.join(' '));
}
}
}

private logDetail(
responseStatus: StatusObject,
request: any,
requestHeaders: Metadata,
options: InterceptorOptions,
response: any,
responseHeaders: Metadata,
) {
if (
this.requestLogging === true ||
(<LoggingOptions>this.requestLogging).detail === true
) {
const isSuccess = responseStatus.code == Status.OK.valueOf();

const messages = [
`${isSuccess ? 'SUCCESS' : 'FAILURE'} REQUEST DETAIL.`,
'Request',
'-------',
`MethodName: ${options.method_definition.path}`,
`Host: ${HOST}`,
`Headers: ${JSON.stringify(requestHeaders.getMap())}`,
`Body: ${JSON.stringify(request)}`,
`\nResponse`,
'--------',
`Headers: ${JSON.stringify(responseHeaders.getMap())}`,
`Body: ${JSON.stringify(cleanEmpty(response))}`,
`ResponseCode: ${responseStatus.code}`,
];

if (isSuccess) {
this.detailLogger.debug(messages.join('\n'));
} else {
messages.push(`Fault: ${responseStatus.details}`);
this.detailLogger.info(messages.join('\n'));
}
}
}

interceptCall: GRPCInterceptor = (
options: InterceptorOptions,
nextCall: NextCall,
) => {
let request: any;
let requestHeaders: Metadata;
let response: any;
let responseHeaders: Metadata;

const requester: Partial<FullRequester> = new RequesterBuilder()
.withStart((headers, responseListener, next) => {
requestHeaders = headers;

const listener = new ListenerBuilder()
.withOnReceiveMessage((message, next) => {
response = message;
next(message);
})
.withOnReceiveMetadata((metadata, next) => {
responseHeaders = metadata;
next(metadata);
})
.withOnReceiveStatus((status, next) => {
try {
this.logSummary(status, request, options, responseHeaders);
this.logDetail(
status,
request,
requestHeaders,
options,
response,
responseHeaders,
);
} catch (error) {
} finally {
next(status);
}
})
.build();

next(headers, listener);
})
.withSendMessage((message, next) => {
request = message;
next(message);
})
.withHalfClose((next) => {
next();
})
.withCancel((next) => {
next();
})
.build();

return new InterceptingCall(nextCall(options), requester);
};
}
8 changes: 7 additions & 1 deletion src/lib/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Metadata } from '@grpc/grpc-js';
import { ServiceProvider } from './ServiceProvider';
import { AllServices, ServiceName, ServiceOptions } from './types';
import { getCredentials } from './utils';
import { LoggingInterceptor } from './LoggingInterceptor';
import { HOST } from './constants';

export class Service extends ServiceProvider {
// @ts-expect-error All fields don't need to be set here
Expand All @@ -28,7 +30,11 @@ export class Service extends ServiceProvider {

const credentials = getCredentials(this.options.auth);

const client = new ProtoService('googleads.googleapis.com', credentials);
const client = new ProtoService(HOST, credentials, {
interceptors: [
new LoggingInterceptor(this.options.logging || false).interceptCall,
],
});

this.cachedClients[serviceName] = client;

Expand Down
2 changes: 2 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const HOST = 'googleads.googleapis.com' as const;

export const VERSION = 'v13' as const;

export const FAILURE_KEY = `google.ads.googleads.${VERSION}.errors.googleadsfailure-bin`;
12 changes: 11 additions & 1 deletion src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OAuth2Client } from '@grpc/grpc-js';
import { Interceptor as GRPCInterceptor, OAuth2Client } from '@grpc/grpc-js';
import allProtos from '../generated/google';
import { VERSION } from './constants';

Expand All @@ -8,9 +8,15 @@ export type OptionalExceptFor<T, TRequired extends keyof T> = Partial<T> &
export type AllServices = Omit<typeof allProtos, typeof VERSION>;
export type ServiceName = keyof Omit<typeof allProtos, typeof VERSION>;

export type LoggingOptions = {
summary?: boolean;
detail?: boolean;
};

export interface ServiceOptions {
auth: OAuth2Client;
developer_token: string;
logging?: boolean | LoggingOptions;
}

export interface CustomerOptions {
Expand Down Expand Up @@ -80,3 +86,7 @@ export interface OrderBy {
attribute: string;
direction?: OrderDirection;
}

export interface Interceptor {
interceptCall: GRPCInterceptor;
}
58 changes: 57 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,12 @@ dataloader@^1.4.0:
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8"
integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==

debug@4, debug@^4.1.0, debug@^4.1.1:
date-format@^4.0.14:
version "4.0.14"
resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400"
integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==

debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
Expand Down Expand Up @@ -1376,6 +1381,11 @@ find-yarn-workspace-root@^2.0.0:
dependencies:
micromatch "^4.0.2"

flatted@^3.2.7:
version "3.2.9"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf"
integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==

[email protected]:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
Expand All @@ -1385,6 +1395,15 @@ [email protected]:
combined-stream "^1.0.8"
mime-types "^2.1.12"

fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"

fs-extra@^9.0.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
Expand Down Expand Up @@ -2108,6 +2127,13 @@ json5@^2.2.2, json5@^2.2.3:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==

jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
optionalDependencies:
graceful-fs "^4.1.6"

jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
Expand Down Expand Up @@ -2183,6 +2209,17 @@ [email protected]:
resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8"
integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==

log4js@^6.9.1:
version "6.9.1"
resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6"
integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==
dependencies:
date-format "^4.0.14"
debug "^4.3.4"
flatted "^3.2.7"
rfdc "^1.3.0"
streamroller "^3.1.5"

long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
Expand Down Expand Up @@ -2561,6 +2598,11 @@ resolve@^1.20.0:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"

rfdc@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==

rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
Expand Down Expand Up @@ -2673,6 +2715,15 @@ stack-utils@^2.0.3:
dependencies:
escape-string-regexp "^2.0.0"

streamroller@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff"
integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==
dependencies:
date-format "^4.0.14"
debug "^4.3.4"
fs-extra "^8.1.0"

string-length@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
Expand Down Expand Up @@ -2852,6 +2903,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==

universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==

universalify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
Expand Down

0 comments on commit 73a5d8b

Please sign in to comment.