Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve ama-sdk plugins logging #1184

Merged
merged 1 commit into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -21,6 +21,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 @@ -43,12 +44,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 @@ -122,7 +124,8 @@ export function registerCustomComponents(): Map<string, any> {
}
}
},
{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/@ama-sdk/core/src/fwk/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: 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be simplified to

Suggested change
opts = await plugin.load({logger: this.options.logger}).transform(opts);
opts = await plugin.load(this.options).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 '../logger';
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
1 change: 1 addition & 0 deletions packages/@ama-sdk/core/src/fwk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export * from './core/index';
export * from './date';
export * from './errors';
export * from './ignore-enum.type';
export * from './logger';
export * from './mocks/index';
export * from './Reviver';
42 changes: 42 additions & 0 deletions packages/@ama-sdk/core/src/fwk/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Logger Client interface.
*
* Duplicate from [@o3r/core]{@link https://github.com/AmadeusITGroup/otter/blob/main/packages/%40o3r/core/src/log/logger.ts}
* All modifications should be made in both interfaces
*/
export interface Logger {
/**
* Log an error.
* @param message Message to log
* @param optionalParams Optional parameters to log
*/
error(message?: any, ...optionalParams: any[]): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it could be valuable to have the this: Logger as first param to avoid le context loosing usage


/**
* Log a warning.
* @param message Message to log
* @param optionalParams Optional parameters to log
*/
warn(message?: any, ...optionalParams: any[]): void;

/**
* Log a message.
* @param message Message to log
* @param optionalParams Optional parameters to log
*/
info?(message?: any, ...optionalParams: any[]): void;

/**
* Log a message.
* @param message Message to log
* @param optionalParams Optional parameters to log
*/
log(message?: any, ...optionalParams: any[]): void;

/**
* Log a debug message.
* @param message Message to log
* @param optionalParams Optional parameters to log
*/
debug?(message?: any, ...optionalParams: any[]): void;
}
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 '../../fwk/logger';

/**
* 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 @@ -232,15 +231,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 @@ -252,10 +251,11 @@ export class BotProtectionFingerprintRequest implements RequestPlugin {
this.hasPolled = true;
}

public load(): PluginRunner<RequestOptions, RequestOptions> {
/** @inheritdoc */
public load(context?: RequestPluginContext): PluginRunner<RequestOptions, RequestOptions> {
return {
sdo-1A marked this conversation as resolved.
Show resolved Hide resolved
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 '../../fwk/logger';

/**
* 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>;
}
sdo-1A marked this conversation as resolved.
Show resolved Hide resolved
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
Loading