Skip to content

Commit

Permalink
feat: improve ama-sdk plugins logging
Browse files Browse the repository at this point in the history
  • Loading branch information
sdo-1A committed Jan 4, 2024
1 parent 6b9132a commit 5118e9f
Show file tree
Hide file tree
Showing 17 changed files with 133 additions and 50 deletions.
9 changes: 6 additions & 3 deletions apps/showcase/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
translateLoaderProvider,
TranslateMessageFormatLazyCompiler
} from '@o3r/localization';
import { ConsoleLogger, Logger, LOGGER_CLIENT_TOKEN, LoggerService } from '@o3r/logger';
import { RulesEngineRunnerModule } from '@o3r/rules-engine';
import { HIGHLIGHT_OPTIONS } from 'ngx-highlightjs';
import { ScrollBackTopPresComponent, SidenavPresComponent } from '../components/index';
Expand All @@ -40,12 +41,13 @@ const runtimeChecks: Partial<RuntimeChecks> = {
registerLocaleData(localeEN, 'en-GB');
registerLocaleData(localeFR, 'fr-FR');

function petApiFactory() {
function petApiFactory(logger: Logger) {
const apiConfig: ApiClient = new ApiFetchClient(
{
basePath: 'https://petstore3.swagger.io/api/v3',
requestPlugins: [],
fetchPlugins: []
fetchPlugins: [],
logger
}
);
return new PetApi(apiConfig);
Expand Down Expand Up @@ -109,7 +111,8 @@ export function localizationConfigurationFactory(): Partial<LocalizationConfigur
}
}
},
{provide: PetApi, useFactory: petApiFactory}
{provide: LOGGER_CLIENT_TOKEN, useValue: new ConsoleLogger()},
{provide: PetApi, useFactory: petApiFactory, deps: [LoggerService]}
],
bootstrap: [AppComponent]
})
Expand Down
14 changes: 14 additions & 0 deletions packages/@ama-sdk/core/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ module.exports = {
],
'sourceType': 'module'
},
'overrides': [
{
'files': [
'**/package.json'
],
'rules': {
'@nx/dependency-checks': ['error', {
'buildTargets': ['build', 'compile', 'test'],
'ignoredDependencies': ['@o3r/core'],
'checkObsoleteDependencies': false
}]
}
}
],
'extends': [
'../../../.eslintrc.js'
]
Expand Down
24 changes: 24 additions & 0 deletions packages/@ama-sdk/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,27 @@ A list of API Clients are provided by this package:
| ApiFetchClient | @ama-sdk/core | Default API Client based on the browser FetchApi |
| ApiBeaconClient | @ama-sdk/core | API Client based on the browser BeaconApi, it is processing synchronous call |
| ApiAngularClient | @ama-sdk/core/clients/api-angular-client | API Client using the HttpClient exposed by the `@angular/common` package |

### Logs

In order to ease the logging in the ama-sdk plugins, it is possible to connect to third-party logging services.
This can be achieved by adding a `Logger` [implementation](/packages/@o3r/core/src/log/logger.ts) to the options of an API client.

For example, in the Otter showcase application, we could add a `ConsoleLogger` (from `@o3r/core`) as a parameter to the ApiFetchClient:

```typescript
const logger = new ConsoleLogger();
function petApiFactory() {
const apiConfig: ApiClient = new ApiFetchClient(
{
basePath: 'https://petstore3.swagger.io/api/v3',
requestPlugins: [new SessionIdRequest()],
fetchPlugins: [],
logger
}
);
return new PetApi(apiConfig);
}
```

> *Note*: Adding a third-party logging service is optional. If undefined, the fallback is the console logger.
1 change: 1 addition & 0 deletions packages/@ama-sdk/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"@nx/eslint-plugin": "~17.1.1",
"@nx/jest": "~17.1.1",
"@o3r/build-helpers": "workspace:^",
"@o3r/core": "workspace:^",
"@o3r/eslint-plugin": "workspace:^",
"@schematics/angular": "~17.0.3",
"@swc/cli": "^0.1.57",
Expand Down
5 changes: 3 additions & 2 deletions packages/@ama-sdk/core/src/clients/api-angular-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ApiAngularClient implements ApiClient {
let opts = options;
if (this.options.requestPlugins) {
for (const plugin of this.options.requestPlugins) {
opts = await plugin.load().transform(opts);
opts = await plugin.load({logger: this.options.logger}).transform(opts);
}
}

Expand Down Expand Up @@ -142,7 +142,8 @@ export class ApiAngularClient implements ApiClient {
exception,
operationId,
url,
origin
origin,
logger: this.options.logger
})) : [];

let parsedData = root;
Expand Down
2 changes: 1 addition & 1 deletion packages/@ama-sdk/core/src/clients/api-beacon-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class ApiBeaconClient implements ApiClient {
let opts = options;
if (this.options.requestPlugins) {
for (const plugin of this.options.requestPlugins) {
const changedOpt = plugin.load().transform(opts);
const changedOpt = plugin.load({logger: this.options.logger}).transform(opts);
if (isPromise(changedOpt)) {
throw new Error(`Request plugin ${plugin.constructor.name} has async transform method. Only sync methods are supported with the Beacon client.`);
} else {
Expand Down
7 changes: 4 additions & 3 deletions packages/@ama-sdk/core/src/clients/api-fetch-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class ApiFetchClient implements ApiClient {
let opts = options;
if (this.options.requestPlugins) {
for (const plugin of this.options.requestPlugins) {
opts = await plugin.load().transform(opts);
opts = await plugin.load({logger: this.options.logger}).transform(opts);
}
}

Expand Down Expand Up @@ -120,7 +120,7 @@ export class ApiFetchClient implements ApiClient {
}
const loadedPlugins: (PluginAsyncRunner<Response, FetchCall> & PluginAsyncStarter)[] = [];
if (this.options.fetchPlugins) {
loadedPlugins.push(...this.options.fetchPlugins.map((plugin) => plugin.load({url, options, fetchPlugins: loadedPlugins, controller, apiClient: this})));
loadedPlugins.push(...this.options.fetchPlugins.map((plugin) => plugin.load({url, options, fetchPlugins: loadedPlugins, controller, apiClient: this, logger: this.options.logger})));
}

const canStart = await Promise.all(loadedPlugins.map((plugin) => !plugin.canStart || plugin.canStart()));
Expand Down Expand Up @@ -164,7 +164,8 @@ export class ApiFetchClient implements ApiClient {
exception,
operationId,
url,
origin
origin,
logger: this.options.logger
})) : [];

let parsedData = root;
Expand Down
3 changes: 3 additions & 0 deletions packages/@ama-sdk/core/src/fwk/core/base-api-constructor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Logger } from '@o3r/core';
import { ReplyPlugin, RequestPlugin } from '../../plugins';

/** Interface of the constructor configuration object */
Expand All @@ -15,6 +16,8 @@ export interface BaseApiClientOptions {
enableTokenization?: boolean;
/** Disable the fallback on the first success code reviver if the response returned by the API does not match the list of expected success codes */
disableFallback?: boolean;
/** Logger (optional, fallback to console logger if undefined) */
logger?: Logger;
}

/** Interface of the constructor configuration object */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {PluginRunner, RequestOptions, RequestPlugin} from '../core';
import {PluginRunner, RequestOptions, RequestPlugin, RequestPluginContext} from '../core';
import type {Logger} from '@o3r/core';

/**
* Function that returns the value of the fingerprint if available.
*/
export type BotProtectionFingerprintRetriever = () => string | undefined | Promise<string | undefined>;
export type BotProtectionFingerprintRetriever = (logger?: Logger) => string | undefined | Promise<string | undefined>;

/**
* Represents the object exposed by Imperva for the integration of their Advanced Bot Protection script with Singe Page Apps.
Expand Down Expand Up @@ -52,22 +53,20 @@ If the application runs on a domain that is not protected by Imperva, this plugi
});
};

return async () => {
return async (logger?: Logger) => {
if (!protection) {
try {
protection = await getProtection();
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
(logger || console).error(e);
return;
}
}

try {
return await protection.token(tokenTimeout);
} catch (e) {
// eslint-disable-next-line no-console
console.error('[SDK][Plug-in][BotProtectionFingerprintRequest] Timeout: no Token was received in time.');
(logger || console).error('[SDK][Plug-in][BotProtectionFingerprintRequest] Timeout: no Token was received in time.');
return;
}
};
Expand Down Expand Up @@ -228,15 +227,15 @@ export class BotProtectionFingerprintRequest implements RequestPlugin {
*
* If pollOnlyOnce is set to true, the poller won't be executed again after it has been fully executed once.
*/
private async waitForFingerprint() {
private async waitForFingerprint(logger?: Logger) {
const pollerOptions = this.options.pollerOptions;

if (pollerOptions === undefined || this.options.pollOnlyOnce !== false && this.hasPolled) {
return this.options.fingerprintRetriever();
return this.options.fingerprintRetriever(logger);
}

for (let i = pollerOptions.maximumTries - 1; i >= 0; i--) {
const fingerprint = await this.options.fingerprintRetriever();
const fingerprint = await this.options.fingerprintRetriever(logger);
if (fingerprint) {
this.hasPolled = true;
return fingerprint;
Expand All @@ -248,10 +247,14 @@ export class BotProtectionFingerprintRequest implements RequestPlugin {
this.hasPolled = true;
}

public load(): PluginRunner<RequestOptions, RequestOptions> {
/**
* Load the plugin with the context
* @param context Context of request plugin
*/
public load(context?: RequestPluginContext): PluginRunner<RequestOptions, RequestOptions> {
return {
transform: async (requestOptions: RequestOptions) => {
const fingerprint = await this.waitForFingerprint();
const fingerprint = await this.waitForFingerprint(context?.logger);
if (fingerprint) {
requestOptions.headers.set(this.options.destinationHeaderName, fingerprint);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/@ama-sdk/core/src/plugins/core/fetch-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ApiClient } from '../../fwk/core/api-client';
import { Plugin, PluginAsyncRunner } from './plugin';
import { Plugin, PluginAsyncRunner, PluginContext } from './plugin';
import { RequestOptions } from './request-plugin';

export type FetchCall = Promise<Response>;
Expand All @@ -8,7 +8,7 @@ export type FetchCall = Promise<Response>;
* Interface of an SDK reply plugin.
* The plugin will be run on the reply of a call
*/
export interface FetchPluginContext {
export interface FetchPluginContext extends PluginContext{
/** URL targeted */
url: string;

Expand Down
14 changes: 13 additions & 1 deletion packages/@ama-sdk/core/src/plugins/core/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Logger } from '@o3r/core';

/**
* Interface of a runnable plugin
*/
Expand All @@ -22,12 +24,22 @@ export interface PluginSyncRunner<T, V> {
transform(data: V): T;
}

/**
* Interface of a plugin context
*/
export interface PluginContext {
/** Plugin context properties */
[key: string]: any;
/** Logger (optional, fallback to console logger if undefined) */
logger?: Logger;
}

/**
* Interface of an SDK plugin
*/
export interface Plugin<T, V> {
/** Load the plugin with the context */
load(context?: Record<string, any>): PluginRunner<T, V>;
load(context?: PluginContext): PluginRunner<T, V>;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/@ama-sdk/core/src/plugins/core/reply-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ApiTypes } from '../../fwk/api';
import { ReviverType } from '../../fwk/Reviver';
import { Plugin, PluginRunner } from './plugin';
import { Plugin, PluginContext, PluginRunner } from './plugin';

/**
* Interface of an SDK reply plugin.
* The plugin will be run on the reply of a call
*/
export interface ReplyPluginContext<T> {
export interface ReplyPluginContext<T> extends PluginContext {
/** Reply reviver function */
reviver?: ReviverType<T>;

Expand Down
9 changes: 7 additions & 2 deletions packages/@ama-sdk/core/src/plugins/core/request-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Plugin, PluginRunner } from './plugin';
import { Plugin, PluginContext, PluginRunner } from './plugin';

export type RequestBody = string | FormData;

Expand Down Expand Up @@ -52,11 +52,16 @@ export interface RequestOptions extends RequestInit {
method: NonNullable<RequestInit['method']>;
}

/**
* Interface of an SDK request plugin context.
*/
export interface RequestPluginContext extends PluginContext {}

/**
* Interface of an SDK request plugin.
* The plugin will be run on the request of a call
*/
export interface RequestPlugin extends Plugin<RequestOptions, RequestOptions> {
/** Load the plugin with the context */
load(): PluginRunner<RequestOptions, RequestOptions>;
load(context?: RequestPluginContext): PluginRunner<RequestOptions, RequestOptions>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ export class MockInterceptFetch implements FetchPlugin {
return responsePromise.then(() => response);

} catch {
// eslint-disable-next-line no-console
console.error(`Failed to retrieve the latest mock for Operation ID ${operationId}, fallback to default mock`);
(context.logger || console).error(`Failed to retrieve the latest mock for Operation ID ${operationId}, fallback to default mock`);
return responsePromise;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createJweEncoder, createJwtEncoder } from '../../utils/json-token';
import { PluginRunner, RequestOptions, RequestPlugin } from '../core';
import { PluginRunner, RequestOptions, RequestPlugin, RequestPluginContext } from '../core';
import type { Logger } from '@o3r/core';

/**
* Creates a JWT encoding function which transforms the provided token-value associations as a unsecured JWT format https://tools.ietf.org/html/rfc7519#section-6
Expand Down Expand Up @@ -192,21 +193,25 @@ export class PiiTokenizerRequest implements RequestPlugin {
/**
* Append the generated token based on the request options to the tokens header
* @param requestOptions Request options to generate the token
* @param logger Logger (optional, fallback to console logger if undefined)
*/
private async appendEncodedToken(requestOptions: RequestOptions) {
private async appendEncodedToken(requestOptions: RequestOptions, logger?: Logger) {
try {
return await this.tokenEncoder(requestOptions.tokenizedOptions!.values);
} catch (e) {
if (this.silent) {
// eslint-disable-next-line no-console
console.error('Couldn\'t encode the token');
(logger || console).error('Couldn\'t encode the token');
} else {
throw new Error('Couldn\'t encode the token');
}
}
}

public load(): PluginRunner<RequestOptions, RequestOptions> {
/**
* Load the plugin with the context
* @param context Context of request plugin
*/
public load(context?: RequestPluginContext): PluginRunner<RequestOptions, RequestOptions> {
return {
transform: async (data: RequestOptions) => {
if (data.metadata?.deepLinkOptions) {
Expand All @@ -217,13 +222,12 @@ export class PiiTokenizerRequest implements RequestPlugin {
}
}
else if (!data.tokenizedOptions) {
// eslint-disable-next-line no-console
console.error('No tokenized options found. Please make sure tokenization is enabled on your ApiClient');
(context?.logger || console).error('No tokenized options found. Please make sure tokenization is enabled on your ApiClient');
}
else if (Object.keys(data.tokenizedOptions.values).length > 0) {
data.basePath = data.tokenizedOptions.url;
data.queryParams = {...data.queryParams, ...data.tokenizedOptions.queryParams};
const token = await this.appendEncodedToken(data);
const token = await this.appendEncodedToken(data, context?.logger);
if (token) {
data.headers.append(this.tokensHeader, token);
}
Expand Down
Loading

0 comments on commit 5118e9f

Please sign in to comment.