Skip to content

Commit

Permalink
feat(logger): add configurable global logger
Browse files Browse the repository at this point in the history
This allows users to configure a global logger (currently implmeneted with winston) to give helpful debugging logs. We can continue to add debug logs, as well as identify spots where performance metrics may be helpful
  • Loading branch information
dtfiedler committed Nov 9, 2023
1 parent 8da51cc commit e6f341a
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 17 deletions.
13 changes: 13 additions & 0 deletions src/common/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { TurboUnauthenticatedConfiguration } from '../types.js';
import { TurboWinstonLogger } from './logger.js';
import { TurboUnauthenticatedPaymentService } from './payment.js';
import { TurboUnauthenticatedClient } from './turbo.js';
import { TurboUnauthenticatedUploadService } from './upload.js';

export class TurboBaseFactory {
protected static logger = new TurboWinstonLogger();

static setLogLevel(level: string) {
this.logger.setLogLevel(level);
}

static setLogFormat(format: string) {
this.logger.setLogFormat(format);
}

static unauthenticated({
paymentServiceConfig = {},
uploadServiceConfig = {},
}: TurboUnauthenticatedConfiguration = {}) {
const paymentService = new TurboUnauthenticatedPaymentService({
...paymentServiceConfig,
logger: this.logger,
});
const uploadService = new TurboUnauthenticatedUploadService({
...uploadServiceConfig,
logger: this.logger,
});
return new TurboUnauthenticatedClient({
uploadService,
Expand Down
17 changes: 17 additions & 0 deletions src/common/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,42 @@ import { ReadableStream } from 'stream/web';

import {
TurboHTTPServiceInterface,
TurboLogger,
TurboSignedRequestHeaders,
} from '../types.js';
import { createAxiosInstance } from '../utils/axiosClient.js';
import { FailedRequestError } from '../utils/errors.js';

export class TurboHTTPService implements TurboHTTPServiceInterface {
protected axios: AxiosInstance;
protected logger: TurboLogger;

constructor({
url,
retryConfig,
logger,
}: {
url: string;
retryConfig?: IAxiosRetryConfig;
logger: TurboLogger;
}) {
this.logger = logger;
this.axios = createAxiosInstance({
axiosConfig: {
baseURL: url,
onUploadProgress: (progressEvent) => {
this.logger.debug(`Uploading...`, {
percent: Math.floor((progressEvent.progress ?? 0) * 100),
loaded: `${progressEvent.loaded} bytes`,
total: `${progressEvent.total} bytes`,
});
if (progressEvent.progress === 1) {
this.logger.debug(`Upload complete!`);
}
},
},
retryConfig,
logger: this.logger,
});
}
async get<T>({
Expand Down
77 changes: 77 additions & 0 deletions src/common/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import winston, { createLogger, format, transports } from 'winston';

import { TurboLogger } from '../types.js';
import { version } from '../version.js';

export class TurboWinstonLogger implements TurboLogger {
protected logger: winston.Logger;
constructor({
level = 'info',
logFormat = 'simple',
}: {
level?: 'info' | 'debug' | 'error' | 'none' | undefined;
logFormat?: 'simple' | 'json' | undefined;
} = {}) {
this.logger = createLogger({
level,
defaultMeta: { client: 'turbo-sdk', version },
silent: level === 'none',
format: getLogFormat(logFormat),
transports: [new transports.Console()],
});
}

info(message: string, ...args: any[]) {

Check warning on line 40 in src/common/logger.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 40 in src/common/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
this.logger.info(message, ...args);
}

warn(message: string, ...args: any[]) {

Check warning on line 44 in src/common/logger.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 44 in src/common/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
this.logger.warn(message, ...args);
}

error(message: string, ...args: any[]) {

Check warning on line 48 in src/common/logger.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 48 in src/common/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
this.logger.error(message, ...args);
}

debug(message: string, ...args: any[]) {

Check warning on line 52 in src/common/logger.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 52 in src/common/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
this.logger.debug(message, ...args);
}

setLogLevel(level: string) {
this.logger.level = level;
}

setLogFormat(logFormat: string) {
this.logger.format = getLogFormat(logFormat);
}
}

function getLogFormat(logFormat: string) {
return format.combine(
format((info) => {
if (info.stack && info.level !== 'error') {
delete info.stack;
}
return info;
})(),
format.errors({ stack: true }), // Ensure errors show a stack trace
format.timestamp(),
logFormat === 'json' ? format.json() : format.simple(),
);
}
9 changes: 8 additions & 1 deletion src/common/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
TurboCountriesResponse,
TurboCurrenciesResponse,
TurboFiatToArResponse,
TurboLogger,
TurboPriceResponse,
TurboRatesResponse,
TurboSignedRequestHeaders,
Expand All @@ -35,6 +36,7 @@ import {
TurboWincForFiatResponse,
} from '../types.js';
import { TurboHTTPService } from './http.js';
import { TurboWinstonLogger } from './logger.js';

export const developmentPaymentServiceURL = 'https://payment.ardrive.dev';
export const defaultPaymentServiceURL = 'https://payment.ardrive.io';
Expand All @@ -43,14 +45,18 @@ export class TurboUnauthenticatedPaymentService
implements TurboUnauthenticatedPaymentServiceInterface
{
protected readonly httpService: TurboHTTPService;
protected logger: TurboLogger;

constructor({
url = defaultPaymentServiceURL,
retryConfig,
logger = new TurboWinstonLogger(),
}: TurboUnauthenticatedPaymentServiceConfiguration) {
this.logger = logger;
this.httpService = new TurboHTTPService({
url: `${url}/v1`,
retryConfig,
logger: this.logger,
});
}

Expand Down Expand Up @@ -154,8 +160,9 @@ export class TurboAuthenticatedPaymentService
url = defaultPaymentServiceURL,
retryConfig,
signer,
logger,
}: TurboAuthenticatedPaymentServiceConfiguration) {
super({ url, retryConfig });
super({ url, retryConfig, logger });
this.signer = signer;
}

Expand Down
11 changes: 10 additions & 1 deletion src/common/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import {
TurboAuthenticatedUploadServiceConfiguration,
TurboAuthenticatedUploadServiceInterface,
TurboFileFactory,
TurboLogger,
TurboSignedDataItemFactory,
TurboUnauthenticatedUploadServiceConfiguration,
TurboUnauthenticatedUploadServiceInterface,
TurboUploadDataItemResponse,
TurboWalletSigner,
} from '../types.js';
import { TurboHTTPService } from './http.js';
import { TurboWinstonLogger } from './logger.js';

export const developmentUploadServiceURL = 'https://upload.ardrive.dev';
export const defaultUploadServiceURL = 'https://upload.ardrive.io';
Expand All @@ -34,14 +36,18 @@ export class TurboUnauthenticatedUploadService
implements TurboUnauthenticatedUploadServiceInterface
{
protected httpService: TurboHTTPService;
protected logger: TurboLogger;

constructor({
url = defaultUploadServiceURL,
retryConfig,
logger = new TurboWinstonLogger(),
}: TurboUnauthenticatedUploadServiceConfiguration) {
this.logger = logger;
this.httpService = new TurboHTTPService({
url: `${url}/v1`,
retryConfig,
logger: this.logger,
});
}

Expand All @@ -52,6 +58,7 @@ export class TurboUnauthenticatedUploadService
}: TurboSignedDataItemFactory &
TurboAbortSignal): Promise<TurboUploadDataItemResponse> {
const fileSize = dataItemSizeFactory();
this.logger.debug('Uploading signed data item...');
// TODO: add p-limit constraint or replace with separate upload class
return this.httpService.post<TurboUploadDataItemResponse>({
endpoint: `/tx`,
Expand All @@ -76,8 +83,9 @@ export class TurboAuthenticatedUploadService
url = defaultUploadServiceURL,
retryConfig,
signer,
logger,
}: TurboAuthenticatedUploadServiceConfiguration) {
super({ url, retryConfig });
super({ url, retryConfig, logger });
this.signer = signer;
}

Expand All @@ -94,6 +102,7 @@ export class TurboAuthenticatedUploadService
});
const signedDataItem = dataItemStreamFactory();
const fileSize = dataItemSizeFactory();
this.logger.debug('Uploading signed data item...');
// TODO: add p-limit constraint or replace with separate upload class
return this.httpService.post<TurboUploadDataItemResponse>({
endpoint: `/tx`,
Expand Down
7 changes: 6 additions & 1 deletion src/node/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,19 @@ export class TurboFactory extends TurboBaseFactory {
paymentServiceConfig = {},
uploadServiceConfig = {},
}: TurboAuthenticatedConfiguration) {
const signer = new TurboNodeArweaveSigner({ privateKey });
const signer = new TurboNodeArweaveSigner({
privateKey,
logger: this.logger,
});
const paymentService = new TurboAuthenticatedPaymentService({
...paymentServiceConfig,
signer,
logger: this.logger,
});
const uploadService = new TurboAuthenticatedUploadService({
...uploadServiceConfig,
signer,
logger: this.logger,
});
return new TurboAuthenticatedClient({
uploadService,
Expand Down
16 changes: 13 additions & 3 deletions src/node/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,23 @@ import { randomBytes } from 'node:crypto';
import { Readable } from 'node:stream';

import { JWKInterface } from '../common/jwk.js';
import { StreamSizeFactory, TurboWalletSigner } from '../types.js';
import { StreamSizeFactory, TurboLogger, TurboWalletSigner } from '../types.js';
import { toB64Url } from '../utils/base64.js';

export class TurboNodeArweaveSigner implements TurboWalletSigner {
protected privateKey: JWKInterface;
protected signer: ArweaveSigner; // TODO: replace with internal signer class

protected logger: TurboLogger;
// TODO: replace with internal signer class
constructor({ privateKey }: { privateKey: JWKInterface }) {
constructor({
privateKey,
logger,
}: {
privateKey: JWKInterface;
logger: TurboLogger;
}) {
this.privateKey = privateKey;
this.logger = logger;
this.signer = new ArweaveSigner(this.privateKey);
}

Expand All @@ -44,8 +51,11 @@ export class TurboNodeArweaveSigner implements TurboWalletSigner {
dataItemSizeFactory: StreamSizeFactory;
}> {
// TODO: replace with our own signer implementation
this.logger.debug('Signing data item...');
const [stream1, stream2] = [fileStreamFactory(), fileStreamFactory()];
const signedDataItem = await streamSigner(stream1, stream2, this.signer);
this.logger.debug('Successfully signed data item...');

// TODO: support target, anchor, and tags
const signedDataItemSize = this.calculateSignedDataHeadersSize({
dataSize: fileSizeFactory(),
Expand Down
12 changes: 10 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import { IAxiosRetryConfig } from 'axios-retry';
import { Readable } from 'node:stream';
import { ReadableStream } from 'node:stream/web';
import winston from 'winston';

import { CurrencyMap } from './common/currency.js';
import { JWKInterface } from './common/jwk.js';
Expand Down Expand Up @@ -121,7 +120,7 @@ type TurboAuthConfiguration = {
type TurboServiceConfiguration = {
url?: string;
retryConfig?: IAxiosRetryConfig;
logger?: winston.Logger;
logger?: TurboLogger;
};

export type TurboUnauthenticatedUploadServiceConfiguration =
Expand All @@ -139,6 +138,15 @@ export type TurboUnauthenticatedConfiguration = {
uploadServiceConfig?: TurboUnauthenticatedUploadServiceConfiguration;
};

export interface TurboLogger {
setLogLevel: (level: string) => void;
setLogFormat: (logFormat: string) => void;
info: (message: string, ...args: any[]) => void;

Check warning on line 144 in src/types.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 144 in src/types.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
warn: (message: string, ...args: any[]) => void;

Check warning on line 145 in src/types.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 145 in src/types.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
error: (message: string, ...args: any[]) => void;

Check warning on line 146 in src/types.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 146 in src/types.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
debug: (message: string, ...args: any[]) => void;

Check warning on line 147 in src/types.ts

View workflow job for this annotation

GitHub Actions / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 147 in src/types.ts

View workflow job for this annotation

GitHub Actions / build (20.x, lint)

Unexpected any. Specify a different type
}

export type TurboAuthenticatedConfiguration =
TurboUnauthenticatedConfiguration & {
privateKey: TurboWallet;
Expand Down
15 changes: 10 additions & 5 deletions src/utils/axiosClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import axios, { AxiosInstance, AxiosRequestConfig, CanceledError } from 'axios';
import axiosRetry, { IAxiosRetryConfig } from 'axios-retry';

import { TurboWinstonLogger } from '../common/logger.js';
import { TurboLogger } from '../types.js';
import { version } from '../version.js';

export const defaultRequestHeaders = {
Expand All @@ -27,9 +29,11 @@ export const defaultRequestHeaders = {
export interface AxiosInstanceParameters {
axiosConfig?: Omit<AxiosRequestConfig, 'validateStatus'>;
retryConfig?: IAxiosRetryConfig;
logger?: TurboLogger;
}

export const createAxiosInstance = ({
logger = new TurboWinstonLogger(),
axiosConfig = {},
retryConfig = {
retryDelay: axiosRetry.exponentialDelay,
Expand All @@ -41,15 +45,16 @@ export const createAxiosInstance = ({
);
},
onRetry: (retryCount, error) => {
console.debug(
`Request failed, ${error}. Retry attempt #${retryCount}...`,
);
logger.debug(`Request failed, ${error}. Retry attempt #${retryCount}...`);
},
},
}: AxiosInstanceParameters): AxiosInstance => {
}: AxiosInstanceParameters = {}): AxiosInstance => {
const axiosInstance = axios.create({
...axiosConfig,
headers: defaultRequestHeaders,
headers: {
...axiosConfig.headers,
...defaultRequestHeaders,
},
validateStatus: () => true, // don't throw on non-200 status codes
});

Expand Down
Loading

0 comments on commit e6f341a

Please sign in to comment.