项目地址:node-utils
该项目是我使用的一个小工具类,也是我自己编写的,但是还不是很完善,目前只针对日志进行处理,实现了
- yml文件解析
- 控制台日志输出
- 日志之持久化存储等功能
类图
测试文件
import {Logger} from '../src/index'
const logger = new Logger.Logger()
const testDemo:Array<any> = [
"string",
12313123,
false,
null,
undefined,
[1,2,3,4,5],
{name:"test"},
Symbol("test")
]
setInterval(()=>{
testDemo.forEach((item)=>{
logger.info(item)
})
logger.info("测试")
logger.warn("测试")
logger.error("测试")
},500)
可输出的结果
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : false
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : 初始化node.utils.config.yml
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : { log: { pid: true, console: true, time: true } }
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : { pid: true, console: true, time: true }
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : string
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : 12313123
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : false
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : null
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : undefined
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : [ 1, 2, 3, 4, 5 ]
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : { name: 'test' }
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : Symbol(test)
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) : 测试
[2022/1/11 下午3:28:37 WARN ] (pid: 12068) : 测试
[2022/1/11 下午3:28:37 ERROR ] (pid: 12068) : 测试
使用到的依赖
"moment": "^2.29.1" 日期处理库
"yamljs": "^0.3.0" yaml文件解析库
目录文件结构
├── src
│ ├── config 配置类
│ │ ├── abstractConfig.ts 抽象配置类
│ │ ├── configHandler.ts 配置处理类
│ │ ├── config.ts 配置处理接口定义
│ │ └── configYaml.ts 针对yaml文件的操作配置
│ ├── index.ts
│ └── modules
│ └── log
│ ├── color.ts
│ ├── index.ts
│ └── log.ts
├── test
│ └── test.ts
该项目使用typescript作为基础编程语言,分为了三大块,分别是配置处理,模块集,测试
配置处理模块
- abstractConfig.ts
该类主要是作为主要的抽象继承类,包含了一些路径初始化的声明,配置文件处理和获取配置文件。这样做的目的是为了兼容和扩展更多的配置文件类型,并且不需要修改高层抽象模块,只需要针对相应的模块单独编写实现类即可,这也是开闭原则和依赖倒置原则的体现
export abstract class abstractLogConfig<T> {
// 配置url
protected path !: string;
constructor(path: string){
this.path = path
}
abstract handler():this
abstract get(): T|null
}
2 configYaml.ts
该代码文件是针对yaml实现的解析,可以看到该逻辑非常简单由于使用了yamljs方法,就不需要自己单独实现处理,而直接解析即可
export class LogConfigYaml extends abstractLogConfig<ConfigI> {
private config ?:ConfigI
constructor(path:string) {
super(path)
this.init()
}
init(){
const val = yamljs.load(this.path)
if(val) {
this.config=val
}
}
handler(): this {
throw new Error("Method not implemented.");
}
get(): ConfigI | null {
return this.config || null
}
}
- configHandler.ts
该文件是对config的一些处理函数,暂时只用到了isNodeUtilsConfigYml这一个函数,目的是判断文件路径是否有权限操作,这里用到了read和write连个权限等级
export class configHandler{
static isNodeUtilsConfigYml(filePath: string): boolean{
try {
fs.accessSync(filePath,constants.R_OK|constants.W_OK)
return true
} catch {
return false
}
}
}
这部分的判断逻辑是比较有意思的,它是利用一个变量存储,通过位或运算拼接成一个变量,具体操作请看fsconstants文档
constants.R_OK 4 对应的二进制为 0100
constants.W_OK 2 对应的二进制为 0010
位或运算得到 6 也就是 0110
这种实现在现在的vue3和Linux内核的文件系统模块中经常使用
- config.ts
该文件主要是定义了输入输出,日志配置,配置实现等接口声明
export interface OutConfigI {
/**
* 文件名格式
*/
name?: string,
/**
* 输出路径
*/
path?: string,
/**
* 输出文件后缀
*/
ext?: string
}
export interface LoggerConfig{
/**
* 输出时间
*/
time?: boolean,
/**
* 输出到控制台
*/
console?: boolean,
/**
* 输出pid编号
*/
pid?: boolean,
/**
* 日志输出文件路径
*/
out?: OutConfigI
}
export interface ConfigI {
/**
* 日志配置
*/
log?: LoggerConfig
}
modules模块
- color.ts
该模块定义了日志在不同级别下的控制台样式
export enum STYLE_COLOR {
'bold' = '\x1B[1m%s\x1B[22m',
'italic' = '\x1B[3m%s\x1B[23m',
'underline' = '\x1B[4m%s\x1B[24m',
'inverse' = '\x1B[7m%s\x1B[27m',
'strikethrough' = '\x1B[9m%s\x1B[29m',
'white' = '\x1B[37m%s\x1B[39m',
'grey' = '\x1B[90m%s\x1B[39m',
'black' = '\x1B[30m%s\x1B[39m',
'blue' = '\x1B[34m%s\x1B[39m',
'cyan' = '\x1B[36m%s\x1B[39m',
'green' = '\x1B[32m%s\x1B[39m',
'magenta' = '\x1B[35m%s\x1B[39m',
'red' = '\x1B[31m%s\x1B[39m',
'yellow' = '\x1B[33m%s\x1B[39m',
'whiteBG' = '\x1B[47m%s\x1B[49m',
'greyBG' = '\x1B[49;5;8m%s\x1B[49m',
'blackBG' = '\x1B[40m%s\x1B[49m',
'blueBG' = '\x1B[44m%s\x1B[49m',
'cyanBG' = '\x1B[46m%s\x1B[49m',
'greenBG' = '\x1B[42m%s\x1B[49m',
'magentaBG' = '\x1B[45m%s\x1B[49m',
'redBG' = '\x1B[41m%s\x1B[49m',
'yellowBG' = '\x1B[43m%s\x1B[49m'
}
- log.ts
这个代码主要实现了日志的输出和针对yaml实现配置的类,说实话写的不算好,像文件文件存储操作完全可以分离出去单独作为一个文件扩展,虽然现在也是实现了,但是在一个文件内写入,还是有点膈应,应该作为模块引入。
该模块主要实现了日志的打印输出,指定了三种日志格式(这三种日志格式也可以作为日志类引入,这样以后扩展的时候就不需要修改该文件了)
该文件包含了三大块内容
- 初始化配置文件
- 日志打印输出
- 日志持久化输出
日志持久化是通过定义写入流实现数据写入,避免了重复打开文件带来的IO开销和进程资源调度开销
export interface LoggerOutPutI {
info (...args: any): void
warn (...args: any): void
error(...args: any): void
}
export enum LOG_TYPE{
start = "START",
info = "INFO",
warn = "WARN",
error = "ERROR"
}
// 获取命令所在的路径
const base_path = resolve("./",'node.utils.config.yml');
export class Logger implements LoggerOutPutI{
config: LoggerConfig = {
time: true,
console: true,
pid : true
}
writeFile :WriteFile|undefined;
constructor(config ?:LoggerConfig|undefined) {
this.start(typeof config === 'string')
if (typeof config === 'object'){
this.config = config
} else if(!config && configHandler.isNodeUtilsConfigYml(base_path)) { // 如果不存在就对其取反
this.initConfigYml()
}
this.initConfig()
}
// 初始化本类配置
initConfig(){
if(this.config){
if(this.config.out){
this.writeFile = new WriteFile(this.config.out)
}
}
}
// 初始化yml
initConfigYml(){
try{
this.start("初始化node.utils.config.yml")
const configYml = new LogConfigYaml(base_path).handler().get()
if(configYml?.log) {
this.config = Object.assign(this.config,configYml.log)
}
} catch (e) {
this.error(e)
this.warn("初始化失败,赋值为默认")
}
}
// 构建log
private log(type: LOG_TYPE, ...args: any): void {
let time = this.config.time? new Date().toLocaleString(): '';
let color: string= '';
let pid = process.pid;
switch(type){
case LOG_TYPE.info:
color = STYLE_COLOR.green
break;
case LOG_TYPE.warn:
color = STYLE_COLOR.yellow
break;
case LOG_TYPE.error:
color = STYLE_COLOR.red
break;
case LOG_TYPE.start:
color = STYLE_COLOR.magenta
default:
break;
}
if(this.config.console){
if (this.config.pid) {
console.log(`[${time} ${color} ] (pid: %s) : `,type, pid , ...args)
if(this.writeFile){
this.writeFile.write(`[${time} ${type} ] (pid: ${pid}) : ${JSON.stringify(args)}`)
}
} else {
console.log(`[${time} ${color} ] : `,type , ...args)
if(this.writeFile){
this.writeFile.write(`[${time} ${type} ] : ${args}`)
}
}
}
}
private start(...args: any): void {
this.log(LOG_TYPE.start,...args)
}
info(...args: any): void {
this.log(LOG_TYPE.info,...args)
}
warn(...args: any): void {
this.log(LOG_TYPE.warn,...args)
}
error(...args: any): void {
this.log(LOG_TYPE.error,...args)
}
}
class WriteFile {
private config!:OutConfigI
private wStream !:WriteStream
constructor(config:OutConfigI) {
this.config = config
this.init()
}
private init(){
if(this.config){
const outPut = this.config.path|| resolve(base_path,"./logs")
const name = moment().format(this.config.name)
const ext = this.config.ext || 'log'
try {
mkdirSync(outPut)
} catch (error) {
}
this.wStream = createWriteStream(resolve(outPut,+''+name+'.'+ext),{
encoding:"utf-8",
})
}
}
public write(args:any){
this.wStream.write(args+'\r\n')
}
}
- index.ts
模块统一出口
import * as logger from './log'
export const Logger = {
...logger
}
总结
整个工具包还算简单。不复杂,但想要真正写好还需不断学习,提升自身编程设计能力和编码设计能力