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 5, 2024
1 parent 6b9132a commit 252c8ee
Show file tree
Hide file tree
Showing 18 changed files with 146 additions and 62 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
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.
5 changes: 5 additions & 0 deletions packages/@ama-sdk/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@angular-devkit/schematics": "~17.0.0",
"@angular/cli": "~17.0.0",
"@angular/common": "~17.0.2",
"@o3r/core": "workspace:^",
"@o3r/schematics": "workspace:^",
"@schematics/angular": "~17.0.0",
"chokidar": "^3.5.2",
Expand All @@ -76,6 +77,9 @@
"@angular/common": {
"optional": true
},
"@o3r/core": {
"optional": true
},
"@o3r/schematics": {
"optional": true
},
Expand All @@ -101,6 +105,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,11 @@ export class BotProtectionFingerprintRequest implements RequestPlugin {
this.hasPolled = true;
}

public load(): PluginRunner<RequestOptions, RequestOptions> {
/** @inheritdoc */
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
11 changes: 7 additions & 4 deletions packages/@ama-sdk/core/src/plugins/core/fetch-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { ApiClient } from '../../fwk/core/api-client';
import { Plugin, PluginAsyncRunner } from './plugin';
import { RequestOptions } from './request-plugin';
import type { Plugin, PluginAsyncRunner, PluginContext } from './plugin';
import type { RequestOptions } from './request-plugin';

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 Expand Up @@ -39,6 +39,9 @@ export interface PluginAsyncStarter {
* The plugin will be run around the Fetch call
*/
export interface FetchPlugin extends Plugin<Response, FetchCall> {
/** Load the plugin with the context */
/**
* Load the plugin with the context
* @param context Context of fetch plugin
*/
load(context: FetchPluginContext): PluginAsyncRunner<Response, FetchCall> & PluginAsyncStarter;
}
19 changes: 17 additions & 2 deletions 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,25 @@ 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 the plugin with the context
* @param context Context of plugin
*/
load(context?: PluginContext): PluginRunner<T, V>;
}

/**
Expand Down
13 changes: 8 additions & 5 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 type { ApiTypes } from '../../fwk/api';
import type { ReviverType } from '../../fwk/Reviver';
import type { 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 Expand Up @@ -40,6 +40,9 @@ export interface ReplyPluginContext<T> {
* The plugin will be run on the reply of a call
*/
export interface ReplyPlugin<T, V = {[key: string]: any}> extends Plugin<T, V> {
/** Load the plugin with the context */
/**
* Load the plugin with the context
* @param context Context of reply plugin
*/
load<K>(context: ReplyPluginContext<K>): PluginRunner<T | K, V | undefined>;
}
14 changes: 11 additions & 3 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 type { Plugin, PluginContext, PluginRunner } from './plugin';

export type RequestBody = string | FormData;

Expand Down Expand Up @@ -52,11 +52,19 @@ 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 the plugin with the context
* @param context Context of request plugin
*/
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
Expand Up @@ -20,6 +20,7 @@ export class MockInterceptRequest implements RequestPlugin {
};
}

/** @inheritdoc */
public load(): PluginRunner<RequestOptions, RequestOptions> {
return {
transform: async (data: RequestOptions) => {
Expand Down
Loading

0 comments on commit 252c8ee

Please sign in to comment.