refactor: removing static references to TsLogger

This commit is contained in:
ppedziwiatr
2021-09-07 15:32:40 +02:00
committed by Piotr Pędziwiatr
parent a020359a27
commit b20278e82b
18 changed files with 236 additions and 108 deletions

4
.gitignore vendored
View File

@@ -15,3 +15,7 @@ yarn-error.log
cache/
.experiments/
yalc.lock
.yalc/

View File

@@ -4,7 +4,6 @@ import BSON from 'bson';
import { BlockHeightCacheResult, BlockHeightKey, BlockHeightSwCache } from '@smartweave/cache';
import { Benchmark, LoggerFactory } from '@smartweave/logging';
const logger = LoggerFactory.INST.create(__filename);
/**
* An implementation of {@link BlockHeightSwCache} that stores its data in BSON files.
* Data is flushed to disk every 10 new cache entries.
@@ -32,6 +31,8 @@ const logger = LoggerFactory.INST.create(__filename);
* Note: this is not performance-optimized for reading LARGE amount of contracts ;-)
*/
export class BsonFileBlockHeightSwCache<V = any> implements BlockHeightSwCache<V> {
private readonly logger = LoggerFactory.INST.create('BsonFileBlockHeightSwCache');
// TODO: not sure why I'm using "string" as type for blockHeight...:-)
// probably because of some issues with BSON parser...
private readonly storage: { [key: string]: { [blockHeight: string]: V } };
@@ -71,9 +72,9 @@ export class BsonFileBlockHeightSwCache<V = any> implements BlockHeightSwCache<V
this.storage[directory][height] = cache as V;
});
logger.debug(`loading cache for ${directory}`, benchmark.elapsed());
this.logger.debug(`loading cache for ${directory}`, benchmark.elapsed());
});
logger.debug('Storage keys', Object.keys(this.storage));
this.logger.debug('Storage keys', Object.keys(this.storage));
process.on('exit', () => {
this.saveCache();
@@ -93,7 +94,7 @@ export class BsonFileBlockHeightSwCache<V = any> implements BlockHeightSwCache<V
// TODO: switch to async, as currently writing cache files may slow down contract execution.
try {
logger.debug(`==== Storing cache update [${Object.keys(this.updatedStorage).length}] ====`);
this.logger.debug(`==== Storing cache update [${Object.keys(this.updatedStorage).length}] ====`);
const directoryPath = this.basePath;
Object.keys(this.updatedStorage).forEach((key) => {
const directory = key;

View File

@@ -21,8 +21,6 @@ import {
import { TransactionStatusResponse } from 'arweave/node/transactions';
import { NetworkInfoInterface } from 'arweave/node/network';
const logger = LoggerFactory.INST.create(__filename);
/**
* An implementation of {@link Contract} that is backwards compatible with current style
* of writing SW contracts (ie. using the "handle" function).
@@ -30,6 +28,8 @@ const logger = LoggerFactory.INST.create(__filename);
* It requires {@link ExecutorFactory} that is using {@link HandlerApi} generic type.
*/
export class HandlerBasedContract<State> implements Contract<State> {
private readonly logger = LoggerFactory.INST.create('HandlerBasedContract');
private wallet?: ArWallet;
private evaluationOptions: EvaluationOptions = new DefaultEvaluationOptions();
public networkInfo?: NetworkInfoInterface = null;
@@ -69,16 +69,16 @@ export class HandlerBasedContract<State> implements Contract<State> {
blockHeight?: number,
currentTx?: { interactionTxId: string; contractTxId: string }[]
): Promise<EvalStateResult<State>> {
logger.info('Read state for', this.contractTxId);
this.logger.info('Read state for', this.contractTxId);
this.maybeClearNetworkInfo();
const { stateEvaluator } = this.smartweave;
const benchmark = Benchmark.measure();
const executionContext = await this.createExecutionContext(this.contractTxId, blockHeight);
logger.debug('context', benchmark.elapsed());
this.logger.debug('context', benchmark.elapsed());
benchmark.reset();
const result = await stateEvaluator.eval(executionContext, currentTx || []);
logger.debug('state', benchmark.elapsed());
this.logger.debug('state', benchmark.elapsed());
return result as EvalStateResult<State>;
}
@@ -89,10 +89,10 @@ export class HandlerBasedContract<State> implements Contract<State> {
tags: Tags = [],
transfer: ArTransfer = emptyTransfer
): Promise<InteractionResult<State, View>> {
logger.info('View state for', this.contractTxId);
this.logger.info('View state for', this.contractTxId);
this.maybeClearNetworkInfo();
if (!this.wallet) {
logger.warn('Wallet not set.');
this.logger.warn('Wallet not set.');
}
const { arweave, stateEvaluator } = this.smartweave;
// create execution context
@@ -121,7 +121,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
// eval current state
const evalStateResult = await stateEvaluator.eval<State>(executionContext, []);
logger.debug('Creating new interaction for view state');
this.logger.debug('Creating new interaction for view state');
// create interaction transaction
const interaction: ContractInteraction<Input> = {
@@ -129,7 +129,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
caller: executionContext.caller
};
logger.trace('interaction', interaction);
this.logger.trace('interaction', interaction);
// TODO: what is the best/most efficient way of creating a transaction in this case?
// creating a real transaction, with multiple calls to Arweave, seems like a huge waste.
@@ -153,7 +153,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
);
if (handleResult.type !== 'ok') {
logger.fatal('Error while interacting with contract', {
this.logger.fatal('Error while interacting with contract', {
type: handleResult.type,
error: handleResult.errorMessage
});
@@ -163,7 +163,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
}
async viewStateForTx<Input, View>(input: Input, transaction: InteractionTx): Promise<InteractionResult<State, View>> {
logger.info(`Vies state for ${this.contractTxId}`, transaction);
this.logger.info(`Vies state for ${this.contractTxId}`, transaction);
this.maybeClearNetworkInfo();
const { stateEvaluator } = this.smartweave;
@@ -209,15 +209,15 @@ export class HandlerBasedContract<State> implements Contract<State> {
const response = await arweave.transactions.post(interactionTx);
if (response.status !== 200) {
logger.error('Error while posting transaction', response);
this.logger.error('Error while posting transaction', response);
return null;
}
if (this.evaluationOptions.waitForConfirmation) {
logger.info('Waiting for confirmation of', interactionTx.id);
this.logger.info('Waiting for confirmation of', interactionTx.id);
const benchmark = Benchmark.measure();
await this.waitForConfirmation(interactionTx.id);
logger.info('Transaction confirmed after', benchmark.elapsed());
this.logger.info('Transaction confirmed after', benchmark.elapsed());
}
return interactionTx.id;
}
@@ -228,11 +228,11 @@ export class HandlerBasedContract<State> implements Contract<State> {
const status = await arweave.transactions.getStatus(transactionId);
if (status.confirmed === null) {
logger.info(`Transaction ${transactionId} not yet confirmed. Waiting another 20 seconds before next check.`);
this.logger.info(`Transaction ${transactionId} not yet confirmed. Waiting another 20 seconds before next check.`);
await sleep(20000);
await this.waitForConfirmation(transactionId);
} else {
logger.info(`Transaction ${transactionId} confirmed`, status);
this.logger.info(`Transaction ${transactionId} confirmed`, status);
return status;
}
}
@@ -248,12 +248,12 @@ export class HandlerBasedContract<State> implements Contract<State> {
const benchmark = Benchmark.measure();
// if this is a "root" call (ie. original call from SmartWeave's client)
if (this.callingContract == null) {
logger.debug('Reading network info for root call');
this.logger.debug('Reading network info for root call');
currentNetworkInfo = await arweave.network.getInfo();
this.networkInfo = currentNetworkInfo;
} else {
// if that's a call from within contract's source code
logger.debug('Reusing network info from the calling contract');
this.logger.debug('Reusing network info from the calling contract');
// note: the whole execution tree should use the same network info!
// this requirement was not fulfilled in the "v1" SDK - each subsequent
@@ -266,7 +266,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
if (blockHeight == null) {
blockHeight = currentNetworkInfo.height;
}
logger.debug('network info', benchmark.elapsed());
this.logger.debug('network info', benchmark.elapsed());
benchmark.reset();
const [contractDefinition, interactions] = await Promise.all([
@@ -282,7 +282,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
// TODO: this could be further optimized to always load interactions only up to the "root's" call requested height
interactionsLoader.load(contractTxId, 0, this.networkInfo.height)
]);
logger.debug('contract and interactions load', benchmark.elapsed());
this.logger.debug('contract and interactions load', benchmark.elapsed());
const sortedInteractions = await interactionsSorter.sort(interactions);
const handler = (await executorFactory.create(contractDefinition)) as HandlerApi<State>;
@@ -315,7 +315,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
const sortedInteractions = await interactionsSorter.sort(interactions);
const handler = (await executorFactory.create(contractDefinition)) as HandlerApi<State>;
logger.debug('Creating execution context from tx:', benchmark.elapsed());
this.logger.debug('Creating execution context from tx:', benchmark.elapsed());
return {
contractDefinition,
@@ -332,7 +332,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
private maybeClearNetworkInfo() {
if (this.callingContract == null) {
logger.debug('Clearing network info for the root contract');
this.logger.debug('Clearing network info for the root contract');
this.networkInfo = null;
}
}

View File

@@ -2,9 +2,9 @@ import { ContractDefinition, DefinitionLoader, getTag, LoggerFactory, SmartWeave
import Arweave from 'arweave';
import Transaction from 'arweave/web/lib/transaction';
const logger = LoggerFactory.INST.create(__filename);
export class ContractDefinitionLoader implements DefinitionLoader {
private readonly logger = LoggerFactory.INST.create('ContractDefinitionLoader');
constructor(
private readonly arweave: Arweave,
// TODO: cache should be removed from the core layer and implemented in a wrapper of the core implementation
@@ -13,7 +13,7 @@ export class ContractDefinitionLoader implements DefinitionLoader {
async load<State>(contractTxId: string, forcedSrcTxId?: string): Promise<ContractDefinition<State>> {
if (!forcedSrcTxId && this.cache?.contains(contractTxId)) {
logger.debug('ContractDefinitionLoader: Hit from cache!');
this.logger.debug('ContractDefinitionLoader: Hit from cache!');
return Promise.resolve(this.cache?.get(contractTxId) as ContractDefinition<State>);
}

View File

@@ -29,9 +29,9 @@ interface ReqVariables {
after?: string;
}
const logger = LoggerFactory.INST.create(__filename);
export class ContractInteractionsLoader implements InteractionsLoader {
private readonly logger = LoggerFactory.INST.create('ContractInteractionsLoader');
private static readonly query = `query Transactions($tags: [TagFilter!]!, $blockFilter: BlockFilter!, $first: Int!, $after: String) {
transactions(tags: $tags, block: $blockFilter, first: $first, sort: HEIGHT_ASC, after: $after) {
pageInfo {
@@ -100,7 +100,7 @@ export class ContractInteractionsLoader implements InteractionsLoader {
txInfos.push(...transactions.edges.filter((tx) => !tx.node.parent || !tx.node.parent.id));
}
logger.debug('All loaded interactions:', {
this.logger.debug('All loaded interactions:', {
from: fromBlockHeight,
to: toBlockHeight,
loaded: txInfos.length
@@ -115,10 +115,10 @@ export class ContractInteractionsLoader implements InteractionsLoader {
query: ContractInteractionsLoader.query,
variables
});
logger.debug('GQL page load:', benchmark.elapsed());
this.logger.debug('GQL page load:', benchmark.elapsed());
while (response.status === 403) {
logger.debug(`GQL rate limiting, waiting ${ContractInteractionsLoader._30seconds}ms before next try.`);
this.logger.debug(`GQL rate limiting, waiting ${ContractInteractionsLoader._30seconds}ms before next try.`);
await sleep(ContractInteractionsLoader._30seconds);
@@ -133,7 +133,7 @@ export class ContractInteractionsLoader implements InteractionsLoader {
}
if (response.data.errors) {
logger.error(response.data.errors);
this.logger.error(response.data.errors);
throw new Error('Error while loading interaction transactions');
}

View File

@@ -15,10 +15,10 @@ import {
} from '@smartweave';
import Arweave from 'arweave';
const logger = LoggerFactory.INST.create(__filename);
// FIXME: currently this is tightly coupled with the HandlerApi
export class DefaultStateEvaluator implements StateEvaluator {
private readonly logger = LoggerFactory.INST.create('DefaultStateEvaluator');
constructor(
private readonly arweave: Arweave,
private readonly executionContextModifiers: ExecutionContextModifier[] = []
@@ -48,21 +48,21 @@ export class DefaultStateEvaluator implements StateEvaluator {
let currentState = baseState.state;
const validity = JSON.parse(JSON.stringify(baseState.validity));
logger.info(
this.logger.info(
`Evaluating state for ${executionContext.contractDefinition.txId} [${missingInteractions.length} non-cached of ${executionContext.sortedInteractions.length} all]`
);
logger.trace(
this.logger.trace(
'missingInteractions',
missingInteractions.map((int) => {
return int.node.id;
})
);
logger.trace('Init state', JSON.stringify(baseState.state));
this.logger.trace('Init state', JSON.stringify(baseState.state));
for (const missingInteraction of missingInteractions) {
logger.debug(
this.logger.debug(
`${missingInteraction.node.id}: ${missingInteractions.indexOf(missingInteraction) + 1}/${
missingInteractions.length
} [of all:${executionContext.sortedInteractions.length}]`
@@ -72,13 +72,13 @@ export class DefaultStateEvaluator implements StateEvaluator {
const inputTag = this.findInputTag(missingInteraction, executionContext);
if (!inputTag || inputTag.name !== SmartWeaveTags.INPUT) {
logger.error(`Skipping tx with missing or invalid Input tag - ${currentInteraction.id}`);
this.logger.error(`Skipping tx with missing or invalid Input tag - ${currentInteraction.id}`);
continue;
}
const input = this.parseInput(inputTag);
if (!input) {
logger.error(`Skipping tx with missing or invalid Input tag - ${currentInteraction.id}`);
this.logger.error(`Skipping tx with missing or invalid Input tag - ${currentInteraction.id}`);
continue;
}
@@ -115,7 +115,7 @@ export class DefaultStateEvaluator implements StateEvaluator {
executionContext = await modify<State>(currentState, executionContext);
currentState = stateCopy;
}
logger.debug('Interaction evaluation', singleInteractionBenchmark.elapsed());
this.logger.debug('Interaction evaluation', singleInteractionBenchmark.elapsed());
await this.onStateUpdate<State>(
currentInteraction,
@@ -123,16 +123,16 @@ export class DefaultStateEvaluator implements StateEvaluator {
new EvalStateResult(currentState, validity)
);
}
logger.debug('State evaluation total:', stateEvaluationBenchmark.elapsed());
this.logger.debug('State evaluation total:', stateEvaluationBenchmark.elapsed());
return new EvalStateResult<State>(currentState, validity);
}
private logResult<State>(result: InteractionResult<State, unknown>, currentTx: GQLNodeInterface) {
if (result.type === 'exception') {
logger.error(`Executing of interaction: ${currentTx.id} threw exception:`, `${result.errorMessage}`);
this.logger.error(`Executing of interaction: ${currentTx.id} threw exception:`, `${result.errorMessage}`);
}
if (result.type === 'error') {
logger.warn(`Executing of interaction: ${currentTx.id} returned error:`, result.errorMessage);
this.logger.warn(`Executing of interaction: ${currentTx.id} returned error:`, result.errorMessage);
}
}
@@ -140,7 +140,7 @@ export class DefaultStateEvaluator implements StateEvaluator {
try {
return JSON.parse(inputTag.value);
} catch (e) {
logger.error(e);
this.logger.error(e);
return null;
}
}

View File

@@ -23,8 +23,6 @@ export interface HandlerApi<State> {
): Promise<InteractionResult<State, Result>>;
}
const logger = LoggerFactory.INST.create(__filename);
/**
* A factory that produces handlers that are compatible with the "current" style of
* writing SW contracts (ie. using "handle" function).
@@ -32,7 +30,12 @@ const logger = LoggerFactory.INST.create(__filename);
* First candidate for the refactor!
*/
export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknown>> {
constructor(private readonly arweave: Arweave) {}
private readonly logger = LoggerFactory.INST.create('HandlerExecutorFactory');
constructor(private readonly arweave: Arweave) {
this.assignReadContractState = this.assignReadContractState.bind(this);
this.assignViewContractState = this.assignViewContractState.bind(this);
}
async create<State>(contractDefinition: ContractDefinition<State>): Promise<HandlerApi<State>> {
const normalizedSource = HandlerExecutorFactory.normalizeContractSource(contractDefinition.src);
@@ -58,7 +61,7 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
const handler = contractFunction(swGlobal, BigNumber, clarity) as HandlerFunction<State, Input, Result>;
const stateCopy = JSON.parse(JSON.stringify(state));
swGlobal._activeTx = interactionTx;
logger.debug(`SmartWeave.contract.id:`, swGlobal.contract.id);
self.logger.debug(`SmartWeave.contract.id:`, swGlobal.contract.id);
self.assignReadContractState<Input, State>(swGlobal, contractDefinition, executionContext, currentTx);
self.assignViewContractState<Input, State>(swGlobal, contractDefinition, executionContext);
@@ -107,8 +110,7 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
executionContext: ExecutionContext<State>
) {
swGlobal.contracts.viewContractState = async <View>(contractTxId: string, input: any) => {
throw new Error('TODO implement');
logger.debug('swGlobal.viewContractState call:', {
this.logger.debug('swGlobal.viewContractState call:', {
from: contractDefinition.txId,
to: contractTxId,
input
@@ -126,7 +128,7 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
currentTx: { interactionTxId: string; contractTxId: string }[]
) {
swGlobal.contracts.readContractState = async (contractTxId: string, height?: number, returnValidity?: boolean) => {
logger.debug('swGlobal.readContractState call:', {
this.logger.debug('swGlobal.readContractState call:', {
from: contractDefinition.txId,
to: contractTxId
});

View File

@@ -1,19 +1,28 @@
import { ConsoleLoggerFactory, LogLevel, RedStoneLogger } from '@smartweave';
import { ISettingsParam } from 'tslog';
import { TsLogFactory } from './node/TsLogFactory';
import { LogLevel, RedStoneLogger } from '@smartweave/logging';
import { ConsoleLoggerFactory } from './web/ConsoleLoggerFactory';
export class LoggerFactory {
static readonly INST: LoggerFactory = typeof window === 'undefined' ? new TsLogFactory() : new ConsoleLoggerFactory();
export interface ILoggerFactory {
setOptions(newOptions: any, moduleName?: string): void;
getOptions(moduleName?: string): any;
logLevel(level: LogLevel, moduleName?: string): void;
create(moduleName?: string): RedStoneLogger;
}
export class LoggerFactory implements ILoggerFactory {
static INST: ILoggerFactory = new ConsoleLoggerFactory();
private constructor() {
// not instantiable from outside
}
setOptions(newOptions: ISettingsParam, moduleName?: string): void {
setOptions(newOptions: any, moduleName?: string): void {
LoggerFactory.INST.setOptions(newOptions, moduleName);
}
getOptions(moduleName?: string): ISettingsParam {
getOptions(moduleName?: string): any {
return LoggerFactory.INST.getOptions(moduleName);
}
@@ -24,4 +33,8 @@ export class LoggerFactory {
create(moduleName?: string): RedStoneLogger {
return LoggerFactory.INST.create(moduleName);
}
static use(logger: ILoggerFactory) {
LoggerFactory.INST = logger;
}
}

View File

@@ -0,0 +1,23 @@
export const LogLevelOrder = {
silly: 0,
trace: 1,
debug: 2,
info: 3,
warn: 4,
error: 5,
fatal: 6
};
/**
* Log level names (silly - fatal)
* // FIXME: generate from LogLevelOrder with some TS trickery..
*/
export type LogLevel = 'silly' | 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
export interface LoggerSettings {
minLevel: LogLevel;
}
export function lvlToOrder(logLevel: LogLevel) {
return LogLevelOrder[logLevel];
}

View File

@@ -1,5 +1,3 @@
export type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silly';
export interface RedStoneLogger {
fatal(message?: any, ...optionalParams: any[]);

View File

@@ -2,4 +2,5 @@ export * from './web/ConsoleLogger';
export * from './web/ConsoleLoggerFactory';
export * from './RedStoneLogger';
export * from './LoggerFactory';
export * from './LoggerSettings';
export * from './Benchmark';

View File

@@ -1,13 +1,14 @@
import path from 'path';
import { ISettingsParam, Logger } from 'tslog';
import { LogLevel, RedStoneLogger } from '../RedStoneLogger';
import { RedStoneLogger } from '../RedStoneLogger';
import { ILoggerFactory, LogLevel } from '@smartweave';
export const defaultLoggerOptions: ISettingsParam = {
displayFunctionName: false,
displayFilePath: 'hidden',
displayLoggerName: true,
dateTimeTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
minLevel: 'debug',
minLevel: 'info',
overwriteConsole: false
};
@@ -15,7 +16,7 @@ export const defaultLoggerOptions: ISettingsParam = {
* A wrapper around "tslog" logging library that allows to change logging settings at runtime
* (for each registered module independently, or globally - for all loggers).
*/
export class TsLogFactory {
export class TsLogFactory implements ILoggerFactory {
private readonly registeredLoggers: { [moduleName: string]: Logger } = {};
private readonly registeredOptions: { [moduleName: string]: ISettingsParam } = {};
@@ -32,7 +33,10 @@ export class TsLogFactory {
// if moduleName not specified
if (!moduleName) {
// update default options
this.defaultOptions = newOptions;
this.defaultOptions = {
...this.defaultOptions,
...newOptions
};
// update options for all already registered loggers
Object.keys(this.registeredLoggers).forEach((key: string) => {
this.registeredLoggers[key].setSettings({

View File

@@ -1,35 +1,67 @@
import { RedStoneLogger } from '@smartweave';
import { LoggerSettings, LogLevel, lvlToOrder, RedStoneLogger } from '@smartweave';
export class ConsoleLogger implements RedStoneLogger {
constructor(private readonly moduleName, public settings: LoggerSettings) {}
trace(message?: any, ...optionalParams: any[]) {
console.debug(message, optionalParams);
if (this.shouldLog('trace')) {
// note: no 'trace' for console logger
console.debug(this.message('trace', message), optionalParams);
}
}
error(message?: any, ...optionalParams: any[]) {
console.error(message, optionalParams);
if (this.shouldLog('error')) {
console.error(this.message('error', message), optionalParams);
}
}
info(message?: any, ...optionalParams: any[]) {
console.info(message, optionalParams);
if (this.shouldLog('info')) {
console.info(this.message('info', message), optionalParams);
}
}
silly(message?: any, ...optionalParams: any[]) {
console.debug(message, optionalParams);
if (this.shouldLog('silly')) {
// note: no silly level for console logger
console.debug(this.message('silly', message), optionalParams);
}
}
debug(message?: any, ...optionalParams: any[]) {
console.debug(message, optionalParams);
if (this.shouldLog('debug')) {
console.debug(this.message('debug', message), optionalParams);
}
}
warn(message?: any, ...optionalParams: any[]) {
console.warn(message, optionalParams);
if (this.shouldLog('warn')) {
console.warn(this.message('warn', message), optionalParams);
}
}
log(message?: any, ...optionalParams: any[]) {
console.info(message, optionalParams);
if (this.shouldLog('info')) {
console.info(this.message('info', message), optionalParams);
}
}
fatal(message?: any, ...optionalParams: any[]) {
console.error(message, optionalParams);
if (this.shouldLog('fatal')) {
console.error(this.message('fatal', message), optionalParams);
}
}
shouldLog(logLevel: LogLevel) {
return lvlToOrder(logLevel) >= lvlToOrder(this.settings.minLevel);
}
setSettings(settings: LoggerSettings) {
this.settings = settings;
}
message(lvl: LogLevel, message: string) {
return `${new Date().toISOString()} ${lvl.toUpperCase()} [${this.moduleName}] ${message}`;
}
}

View File

@@ -1,8 +1,15 @@
import { LogLevel, RedStoneLogger } from '@smartweave';
import { ILoggerFactory, LoggerSettings, RedStoneLogger } from '@smartweave';
import { ConsoleLogger } from './ConsoleLogger';
import { ISettingsParam } from 'tslog';
import { LogLevel } from '../LoggerSettings';
export class ConsoleLoggerFactory implements ILoggerFactory {
private registeredLoggers: { [moduleName: string]: ConsoleLogger } = {};
private readonly registeredOptions: { [moduleName: string]: LoggerSettings } = {};
private defOptions: LoggerSettings = {
minLevel: 'info'
};
export class ConsoleLoggerFactory {
constructor() {
this.setOptions = this.setOptions.bind(this);
this.getOptions = this.getOptions.bind(this);
@@ -10,19 +17,62 @@ export class ConsoleLoggerFactory {
this.logLevel = this.logLevel.bind(this);
}
setOptions(newOptions: ISettingsParam, moduleName?: string): void {
// noop
setOptions(newOptions: LoggerSettings, moduleName?: string): void {
// FIXME: c/p from TsLogFactory...
// if moduleName not specified
if (!moduleName) {
// update default options
this.defOptions = newOptions;
// update options for all already registered loggers
Object.keys(this.registeredLoggers).forEach((key: string) => {
this.registeredLoggers[key].setSettings({
...this.registeredLoggers[key].settings,
...newOptions
});
});
} else {
// if logger already registered
if (this.registeredLoggers[moduleName]) {
// update its options
this.registeredLoggers[moduleName].setSettings({
...this.registeredLoggers[moduleName].settings,
...newOptions
});
} else {
// if logger not yet registered - save options that will be used for its creation
this.registeredOptions[moduleName] = {
...this.defOptions,
...newOptions
};
}
}
}
getOptions(moduleName?: string): ISettingsParam {
return {};
getOptions(moduleName?: string): LoggerSettings {
// FIXME: c/p from TsLogFactory...
if (!moduleName) {
return this.defOptions;
} else {
if (this.registeredLoggers[moduleName]) {
return this.registeredLoggers[moduleName].settings;
} else if (this.registeredOptions[moduleName]) {
return this.registeredOptions[moduleName];
} else {
return this.defOptions;
}
}
}
logLevel(level: LogLevel, moduleName?: string) {
// noop
// FIXME: c/p from TsLogFactory...
this.setOptions({ minLevel: level }, moduleName);
}
create(moduleName = 'SWC'): RedStoneLogger {
return new ConsoleLogger();
if (!Object.prototype.hasOwnProperty.call(this.registeredLoggers, moduleName)) {
this.registeredLoggers[moduleName] = new ConsoleLogger(moduleName, this.getOptions(moduleName));
}
return this.registeredLoggers[moduleName];
}
}

View File

@@ -1,20 +1,20 @@
import { BlockHeightKey, BlockHeightSwCache, GQLEdgeInterface, InteractionsLoader, LoggerFactory } from '@smartweave';
const logger = LoggerFactory.INST.create(__filename);
/**
* This implementation InteractionsLoader tries to limit the amount of interactions
* with GraphQL endpoint. Additionally, it is downloading only the missing interactions
* (starting from the latest already cached) - to additionally limit the amount of "paging".
*/
export class CacheableContractInteractionsLoader implements InteractionsLoader {
private readonly logger = LoggerFactory.INST.create('CacheableContractInteractionsLoader');
constructor(
private readonly baseImplementation: InteractionsLoader,
private readonly cache: BlockHeightSwCache<GQLEdgeInterface[]>
) {}
async load(contractId: string, fromBlockHeight: number, toBlockHeight: number): Promise<GQLEdgeInterface[]> {
logger.debug('Loading interactions', {
this.logger.debug('Loading interactions', {
contractId,
fromBlockHeight,
toBlockHeight
@@ -26,7 +26,7 @@ export class CacheableContractInteractionsLoader implements InteractionsLoader {
};
if (cachedHeight >= toBlockHeight) {
logger.debug('Reusing interactions cached at higher block height:', cachedHeight);
this.logger.debug('Reusing interactions cached at higher block height:', cachedHeight);
return cachedValue.filter(
(interaction: GQLEdgeInterface) =>
interaction.node.block.height >= fromBlockHeight && interaction.node.block.height <= toBlockHeight
@@ -41,7 +41,7 @@ export class CacheableContractInteractionsLoader implements InteractionsLoader {
const valueToCache = cachedValue.concat(missingInteractions);
logger.debug('Interactions load result:', {
this.logger.debug('Interactions load result:', {
cached: cachedValue.length,
missing: missingInteractions.length,
total: valueToCache.length,

View File

@@ -3,12 +3,12 @@ import { ContractDefinition, ExecutorFactory } from '@smartweave/core';
import { SwCache } from '@smartweave/cache';
import { LoggerFactory } from '@smartweave/logging';
const logger = LoggerFactory.INST.create(__filename);
/**
* An implementation of ExecutorFactory that adds caching capabilities
*/
export class CacheableExecutorFactory<Api> implements ExecutorFactory<Api> {
private readonly logger = LoggerFactory.INST.create('CacheableExecutorFactory');
constructor(
arweave: Arweave,
private readonly baseImplementation: ExecutorFactory<Api>,
@@ -25,7 +25,7 @@ export class CacheableExecutorFactory<Api> implements ExecutorFactory<Api> {
// as "evolve" feature changes the srcTxId for the given txId...
const cacheKey = `${contractDefinition.txId}_${contractDefinition.srcTxId}`;
if (!this.cache.contains(cacheKey)) {
logger.debug('Updating executor factory cache');
this.logger.debug('Updating executor factory cache');
const handler = await this.baseImplementation.create(contractDefinition);
this.cache.put(cacheKey, handler);
}

View File

@@ -10,12 +10,12 @@ import Arweave from 'arweave';
import { GQLNodeInterface } from '@smartweave/legacy';
import { LoggerFactory } from '@smartweave/logging';
const logger = LoggerFactory.INST.create(__filename);
/**
* An implementation of DefaultStateEvaluator that adds caching capabilities
*/
export class CacheableStateEvaluator extends DefaultStateEvaluator {
private readonly cLogger = LoggerFactory.INST.create('CacheableStateEvaluator');
constructor(
arweave: Arweave,
private readonly cache: BlockHeightSwCache<EvalStateResult<unknown>>,
@@ -29,7 +29,7 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
currentTx: { interactionTxId: string; contractTxId: string }[]
): Promise<EvalStateResult<State>> {
const requestedBlockHeight = executionContext.blockHeight;
logger.debug(`Requested state block height: ${requestedBlockHeight}`);
this.cLogger.debug(`Requested state block height: ${requestedBlockHeight}`);
let cachedState: BlockHeightCacheResult<EvalStateResult<State>> | null = null;
@@ -48,7 +48,7 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
)) as BlockHeightCacheResult<EvalStateResult<State>>;
if (cachedState != null) {
logger.debug(`Cached state for ${executionContext.contractDefinition.txId}`, {
this.cLogger.debug(`Cached state for ${executionContext.contractDefinition.txId}`, {
block: cachedState.cachedHeight,
requestedBlockHeight
});
@@ -60,7 +60,7 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
);
}
logger.debug(`Interactions until [${requestedBlockHeight}]`, {
this.cLogger.debug(`Interactions until [${requestedBlockHeight}]`, {
total: sortedInteractionsUpToBlock.length,
cached: sortedInteractionsUpToBlock.length - missingInteractions.length
});
@@ -74,7 +74,7 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
if (entry.contractTxId === executionContext.contractDefinition.txId) {
const index = missingInteractions.findIndex((tx) => tx.node.id === entry.interactionTxId);
if (index !== -1) {
logger.debug('Inf. Loop fix - removing interaction', {
this.cLogger.debug('Inf. Loop fix - removing interaction', {
contractTxId: entry.contractTxId,
interactionTxId: entry.interactionTxId
});
@@ -85,7 +85,7 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
// if cache is up-to date - return immediately to speed-up the whole process
if (missingInteractions.length === 0 && cachedState) {
logger.debug(`State up to requested height [${requestedBlockHeight}] fully cached!`);
this.cLogger.debug(`State up to requested height [${requestedBlockHeight}] fully cached!`);
return cachedState.cachedValue;
}
}

View File

@@ -15,8 +15,6 @@ export interface EvolveCompatibleState {
evolve: string; // the transaction id of the Arweave transaction with the updated source code. odd naming convention..
}
const logger = LoggerFactory.INST.create(__filename);
/*
...I'm still not fully convinced to the whole "evolve" idea.
@@ -46,6 +44,8 @@ function isEvolveCompatible(state: any): state is EvolveCompatibleState {
}
export class Evolve implements ExecutionContextModifier {
private readonly logger = LoggerFactory.INST.create('Evolve');
constructor(
private readonly definitionLoader: DefinitionLoader,
private readonly executorFactory: ExecutorFactory<HandlerApi<unknown>>
@@ -58,9 +58,9 @@ export class Evolve implements ExecutionContextModifier {
executionContext: ExecutionContext<State, HandlerApi<State>>
): Promise<ExecutionContext<State, HandlerApi<State>>> {
const contractTxId = executionContext.contractDefinition.txId;
logger.debug(`trying to evolve for: ${contractTxId}`);
this.logger.debug(`trying to evolve for: ${contractTxId}`);
if (!isEvolveCompatible(state)) {
logger.debug('State is not evolve compatible');
this.logger.debug('State is not evolve compatible');
return executionContext;
}
const currentSrcTxId = executionContext.contractDefinition.srcTxId;
@@ -77,7 +77,7 @@ export class Evolve implements ExecutionContextModifier {
canEvolve = true;
}
if (evolve && /[a-z0-9_-]{43}/i.test(evolve) && canEvolve) {
logger.debug('Checking evolve:', {
this.logger.debug('Checking evolve:', {
current: currentSrcTxId,
evolve
});
@@ -86,7 +86,7 @@ export class Evolve implements ExecutionContextModifier {
try {
// note: that's really nasty IMO - loading original contract definition,
// but forcing different sourceTxId...
logger.info('Evolving to: ', evolve);
this.logger.info('Evolving to: ', evolve);
const newContractDefinition = await this.definitionLoader.load<State>(contractTxId, evolve);
const newHandler = (await this.executorFactory.create<State>(newContractDefinition)) as HandlerApi<State>;
@@ -95,7 +95,7 @@ export class Evolve implements ExecutionContextModifier {
contractDefinition: newContractDefinition,
handler: newHandler
};
logger.debug('evolved to:', {
this.logger.debug('evolved to:', {
txId: modifiedContext.contractDefinition.txId,
srcTxId: modifiedContext.contractDefinition.srcTxId
});