diff --git a/src/putility/src/features/PropertiesFeature.js b/src/putility/src/features/PropertiesFeature.js index 56e27db6..53a8a207 100644 --- a/src/putility/src/features/PropertiesFeature.js +++ b/src/putility/src/features/PropertiesFeature.js @@ -19,7 +19,7 @@ module.exports = { name: 'Properties', depends: ['Listeners'], - install_in_instance: (instance) => { + install_in_instance: (instance, { parameters }) => { const properties = instance._get_merged_static_object('PROPERTIES'); instance.onchange = (name, callback) => { @@ -39,6 +39,9 @@ module.exports = { let spec = null; if ( typeof properties[k] === 'object' ) { spec = properties[k]; + if ( spec.factory ) { + spec.value = spec.factory({ parameters }); + } } else if ( typeof properties[k] === 'function' ) { spec = {}; spec.value = properties[k](); @@ -59,9 +62,14 @@ module.exports = { }); } const old_value = instance[k]; + const intermediate_value = value; + if ( spec.adapt ) { + value = spec.adapt(value); + } state.value = value; if ( spec.post_set ) { spec.post_set.call(instance, value, { + intermediate_value, old_value, }); } @@ -69,6 +77,13 @@ module.exports = { }); state.value = spec.value; + + if ( properties[k].construct ) { + const k_cons = typeof properties[k].construct === 'string' + ? properties[k].construct + : k; + instance[k] = parameters[k_cons]; + } } } } diff --git a/src/putility/src/libs/log.js b/src/putility/src/libs/log.js new file mode 100644 index 00000000..8406f54b --- /dev/null +++ b/src/putility/src/libs/log.js @@ -0,0 +1,142 @@ +const { AdvancedBase } = require("../.."); +const { TLogger } = require("../traits/traits"); + +class ArrayLogger extends AdvancedBase { + static PROPERTIES = { + buffer: { + factory: () => [] + } + } + static IMPLEMENTS = { + [TLogger]: { + log (level, message, fields, values) { + this.buffer.push({ level, message, fields, values }); + } + } + } +} + +class ConsoleLogger extends AdvancedBase { + static MODULES = { + util: require('util'), + } + static PROPERTIES = { + console: { + construct: true, + factory: () => console + }, + format: () => ({ + info: { + ansii: '\x1b[32;1m', + }, + warn: { + ansii: '\x1b[33;1m', + }, + error: { + ansii: '\x1b[31;1m', + err: true, + }, + debug: { + ansii: '\x1b[34;1m', + }, + }), + } + static IMPLEMENTS = { + [TLogger]: { + log (level, message, fields, values) { + const require = this.require; + const util = require('util'); + const l = this.format[level]; + let str = ''; + str += `${l.ansii}[${level.toUpperCase()}]\x1b[0m `; + str += message; + + // values + if (values.length) { + str += ' '; + str += values + .map(v => util.inspect(v)) + .join(' '); + } + + // fields + if (Object.keys(fields).length) { + str += ' '; + str += Object.entries(fields) + .map(([k, v]) => `\n ${k}=${util.inspect(v)}`) + .join(' '); + } + + this.console[l.err ? 'error' : 'log'](str); + } + } + } +} + +class FieldsLogger extends AdvancedBase { + static PROPERTIES = { + fields: { + construct: true, + factory: () => ({}) + }, + delegate: { + construct: true, + value: null + } + } + + static IMPLEMENTS = { + [TLogger]: { + log (level, message, fields, values) { + return this.delegate.log( + level, message, + Object.assign({}, this.fields, fields), + values, + ); + } + } + } +} + +class LoggerFacade extends AdvancedBase { + static PROPERTIES = { + impl: { + value: () => { + return new ConsoleLogger(); + }, + adapt: v => { + return v.as(TLogger); + }, + construct: true, + }, + } + + static IMPLEMENTS = { + [TLogger]: { + log (level, message, fields, values) { + console.log() + } + } + } + + fields (fields) { + const new_delegate = new FieldsLogger({ + fields, + delegate: this.impl, + }); + return new LoggerFacade({ + impl: new_delegate, + }); + } + + info (message, ...values) { + this.impl.log('info', message, {}, values); + } +} + +module.exports = { + ArrayLogger, + ConsoleLogger, + FieldsLogger, + LoggerFacade, +}; diff --git a/src/putility/src/traits/traits.js b/src/putility/src/traits/traits.js index 2416e6d3..03654fb7 100644 --- a/src/putility/src/traits/traits.js +++ b/src/putility/src/traits/traits.js @@ -1,4 +1,5 @@ module.exports = { TTopics: Symbol('TTopics'), TDetachable: Symbol('TDetachable'), + TLogger: Symbol('TLogger'), }; diff --git a/src/putility/test/log.test.js b/src/putility/test/log.test.js new file mode 100644 index 00000000..18a04546 --- /dev/null +++ b/src/putility/test/log.test.js @@ -0,0 +1,48 @@ +const { LoggerFacade, ArrayLogger, ConsoleLogger } = require("../src/libs/log"); +const { expect } = require('chai'); + +describe('log', () => { + it('facade logger', () => { + const array_logger = new ArrayLogger(); + + let logger = new LoggerFacade({ + impl: array_logger, + }); + + logger.info('test message only'); + logger.info('test message and values', 1, 2); + logger = logger.fields({ a: 1 }); + logger.info('test fields', 3, 4); + + const logs = array_logger.buffer; + expect(logs).to.have.length(3); + + expect(logs[0].level).to.equal('info'); + expect(logs[0].message).to.equal('test message only'); + expect(logs[0].fields).to.eql({}); + expect(logs[0].values).to.eql([]); + + expect(logs[1].level).to.equal('info'); + expect(logs[1].message).to.equal('test message and values'); + expect(logs[1].fields).to.eql({}); + expect(logs[1].values).to.eql([1, 2]); + + expect(logs[2].level).to.equal('info'); + expect(logs[2].message).to.equal('test fields'); + expect(logs[2].fields).to.eql({ a: 1 }); + expect(logs[2].values).to.eql([3, 4]); + }); + it('console logger', () => { + let logger = new ConsoleLogger({ + console: console, + }); + logger = new LoggerFacade({ + impl: logger, + }); + + logger.fields({ + token: 'asdf', + user: 'joe', + }).info('Hello, world!', 'v1', 'v2', { a: 1 }); + }); +});