Fix DI bugs; implement general logging service

This commit is contained in:
garrettmills 2020-06-17 09:48:01 -05:00
parent 6c4696227b
commit eddb4f1fbe
No known key found for this signature in database
GPG Key ID: 6ACD58D6ADACFC6E
13 changed files with 258 additions and 5 deletions

View File

@ -133,11 +133,14 @@ class Container {
return factory.produce(construction_args, params.reverse().all())
}
make<T>(target: Instantiable<T>|DependencyKey, ...parameters: any[]): T {
if ( isInstantiable(target) )
make(target: DependencyKey, ...parameters: any[]) {
if ( this.has_key(target) ) {
return this.resolve_and_create(target, ...parameters)
}
else if ( typeof target !== 'string' )
return this.produce_factory(new Factory(target), parameters)
else
return this.resolve_and_create(target, ...parameters)
throw new TypeError(`Invalid or unknown make target: ${target}`)
}
}

View File

@ -1,11 +1,16 @@
import { container } from '../global.ts'
import { isInstantiable } from '../type/Instantiable.ts'
import { Injectable } from './Injection.ts'
const injectable = Injectable()
const Service = (name?: string): ClassDecorator => {
return (target) => {
if ( isInstantiable(target) ) {
if ( name ) container.register_named(name, target)
else container.register(target)
injectable(target)
}
}
}

15
lib/src/const/status.ts Normal file
View File

@ -0,0 +1,15 @@
const STATUS_STOPPED = Symbol('status stopped')
const STATUS_STARTING = Symbol('status starting')
const STATUS_RUNNING = Symbol('status running')
const STATUS_STOPPING = Symbol('status stopping')
const STATUS_ERROR = Symbol('status error')
const isStatus = (something: any) => [
STATUS_STOPPED,
STATUS_STARTING,
STATUS_RUNNING,
STATUS_STOPPING,
STATUS_ERROR,
].includes(something)
export { STATUS_STOPPED, STATUS_STARTING, STATUS_RUNNING, STATUS_STOPPING, STATUS_ERROR, isStatus }

3
lib/src/external/std.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export { serve } from 'https://deno.land/std/http/server.ts'
export * from 'https://deno.land/std/fmt/colors.ts'
export { config as dotenv } from 'https://deno.land/x/dotenv/mod.ts'

19
lib/src/lifecycle/Unit.ts Normal file
View File

@ -0,0 +1,19 @@
import { STATUS_STOPPED, isStatus } from '../const/status.ts'
export default abstract class LifecycleUnit {
private _status = STATUS_STOPPED
public get status() {
return this._status
}
public set status(status) {
if ( !isStatus(status) )
throw new TypeError('Invalid unit status: '+status.description)
this._status = status
}
public async up(): Promise<void> {};
public async down(): Promise<void> {};
}

View File

@ -0,0 +1,11 @@
import { Service } from '../../../di/src/decorator/Service.ts'
const service = Service()
const Unit = (): ClassDecorator => {
return (target) => {
return service(target)
}
}
export { Unit }

View File

@ -0,0 +1,45 @@
import { LogMessage, LoggingLevel } from './types.ts'
import {make} from "../../../../di/src/global.ts";
import {Logging} from "./Logging.ts";
import {blue, cyan, gray, green, red, yellow} from "../../external/std.ts";
const isLoggerClass = (something: any): something is (typeof Logger) => {
return something.prototype instanceof Logger
}
export { isLoggerClass }
export default abstract class Logger {
public abstract async write(message: LogMessage): Promise<void>;
public static register() {
make(Logging).register_logger(this)
}
public static remove() {
make(Logging).remove_logger(this)
}
protected format_date(date: Date): string {
return `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`
}
protected level_display(level: LoggingLevel): string {
switch(level) {
case LoggingLevel.Success:
return green('success')
case LoggingLevel.Error:
return red('error')
case LoggingLevel.Warning:
return yellow('warning')
case LoggingLevel.Info:
return blue('info')
case LoggingLevel.Debug:
return cyan('debug')
case LoggingLevel.Verbose:
return gray('verbose')
case LoggingLevel.Silent:
return gray('silent')
}
}
}

View File

@ -0,0 +1,74 @@
import {LoggingLevel, LogMessage} from './types.ts'
import Logger from './Logger.ts'
import {Service} from '../../../../di/src/decorator/Service.ts'
import {make} from '../../../../di/src/global.ts'
import {isInstantiable} from '../../../../di/src/type/Instantiable.ts'
@Service()
class Logging {
private _level = LoggingLevel.Warning
private _loggers: Logger[] = []
public get level() {
return this._level
}
public set level(level) {
this._level = level
}
public success(output: any, force = false) {
this.write_log(LoggingLevel.Success, output, force)
}
public error(output: any, force = false) {
this.write_log(LoggingLevel.Error, output, force)
}
public warn(output: any, force = false) {
this.write_log(LoggingLevel.Warning, output, force)
}
public info(output: any, force = false) {
this.write_log(LoggingLevel.Info, output, force)
}
public debug(output: any, force = false) {
this.write_log(LoggingLevel.Debug, output, force)
}
public verbose(output: any, force = false) {
this.write_log(LoggingLevel.Verbose, output, force)
}
protected write_log(level: LoggingLevel, output: any, force = false) {
const message = this.build_message(level, output)
if ( this._level >= level || force ) {
for ( const logger of this._loggers ) {
logger.write(message)
}
}
}
protected build_message(level: LoggingLevel, output: any): LogMessage {
return {
level,
output,
date: new Date,
}
}
public register_logger(logger_class: typeof Logger) {
if ( isInstantiable(logger_class) ) {
const logger = make(logger_class)
if ( !this._loggers.includes(logger) )
this._loggers.push(logger)
}
}
public remove_logger(logger_class: typeof Logger) {
this._loggers = this._loggers.filter(x => !(x instanceof logger_class))
}
}
export { Logging }

View File

@ -0,0 +1,11 @@
import AbstractLogger from './Logger.ts'
import { LogMessage } from './types.ts'
import { gray } from '../../external/std.ts'
export default class StandardLogger extends AbstractLogger {
public async write(message: LogMessage): Promise<void> {
const prefix = this.level_display(message.level)
const text = `${prefix} ${gray(this.format_date(message.date))}`
console.log(text, message.output)
}
}

View File

@ -0,0 +1,33 @@
enum LoggingLevel {
Silent = 0,
Success = 1,
Error = 1,
Warning = 2,
Info = 3,
Debug = 4,
Verbose = 5,
}
const isLoggingLevel = (something: any): something is LoggingLevel => {
return [
LoggingLevel.Silent,
LoggingLevel.Success,
LoggingLevel.Error,
LoggingLevel.Warning,
LoggingLevel.Info,
LoggingLevel.Debug,
LoggingLevel.Verbose
].includes(something)
}
interface LogMessage {
level: LoggingLevel,
date: Date,
output: any,
}
const isLogMessage = (something: any): something is LogMessage => {
return isLoggingLevel(something?.level) && something?.date instanceof Date;
}
export { LoggingLevel, LogMessage, isLoggingLevel, isLogMessage }

View File

@ -0,0 +1,30 @@
import LifecycleUnit from '../lifecycle/Unit.ts'
import { Unit } from '../lifecycle/decorators.ts'
import { dotenv } from '../external/std.ts'
import { Logging } from '../service/logging/Logging.ts'
import StandardLogger from '../service/logging/StandardLogger.ts'
@Unit()
export default class Scaffolding extends LifecycleUnit {
private config = {}
constructor(
protected logger: Logging
) {
super()
}
public refresh_env() {
this.config = dotenv()
}
public setup_logging() {
StandardLogger.register()
this.logger.info('Logging initialized.', true)
}
public async up() {
this.setup_logging()
this.refresh_env()
}
}

5
test.ts Normal file
View File

@ -0,0 +1,5 @@
import { make, container } from "./di/src/global.ts";
import Scaffolding from "./lib/src/unit/Scaffolding.ts";
const scaf = make(Scaffolding)
scaf.up()

View File

@ -1,8 +1,7 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "es2017"
"emitDecoratorMetadata": true
}
}