From 893f88b20df8af5e91071c8208f5c9c44d0712c6 Mon Sep 17 00:00:00 2001 From: David Kebler Date: Mon, 4 Feb 2019 15:48:46 -0800 Subject: [PATCH] Moved so single main environment variable UCI_ENV now checks for development and production. Production will write a file to default location if none given Example can be run in numerous ways from npm scripts add readme.md documenatation. need to add mocha tests --- example/example.js | 41 ++++++++++++++++----- package.json | 21 ++++++----- readme.md | 88 +++++++++++++++++++++++++++++++++++++++++----- src/logger.js | 80 +++++++++++++++++++++++++++-------------- 4 files changed, 178 insertions(+), 52 deletions(-) diff --git a/example/example.js b/example/example.js index f0978ea..8764e49 100644 --- a/example/example.js +++ b/example/example.js @@ -1,19 +1,44 @@ import logger from '../src/logger' let log = {} -class LOGTEST { +class LogTest { constructor(opts) { log = logger({ - name: 'test', - id: opts.id, - file: 'example/example.js', - class: 'LOGTEST' + // pretty: {translateTime:true, colorize:true, levelFirst:true } // options for pino pretty printer + // env:'', // 'dev' or 'pro' -- can be use to set/override UCI_ENV environment variable + // enForce: false, // only used with .evn. if true will override UCI_ENV if it is set, otherwise no + // level:'info', // info is default level, set level to lowest visible + // clear: false, // true for log files will clear the current log file on restart + // logFileName:'test', // if not supplied log filename will be generated from timestamp + // below properties are optional and are passed to child logger instance and will be part of json log entry + // libraryName: 'UCI', // will be logged as name: can be used to identify logs of related packages, can be set via UCI_LOG_NAME env variable + appName:'uci-example-logger', //will be used for logging directory name if supplied and will be logged as well + package: '@uci/logger', // name of package with scope per package.json + // repo: // will use scope-name from package prop if not supplied + id: opts.id, // can pass a unique if for easy later search/filtering + // file: 'example/example.js', // path (to repo root) of actual file running this logger, default is ./src/.js + class: 'LogTest', // The class that created this logger if any + // one can pass through additional props to log for every log + // should not use any keys above or + // level,time,msg,pid,hostname + // logPath,instanceCreatedHR,instanceCreated as keys will be merged + // NOTE: more props can be added at run time by passing an object of props + additional: {anotherprop:'test'} // should do not use any keys above or 'level,logPath,package,instanceCreatedHR,instanceCreated' as keys will be merged }) } + logit() { + log.trace('this is a trace level logged message') + log.debug('this is a debug level logged message') + log.info({runtimeprop:'somevalue', msg:'this is a info level logged message'}) + log.warn('this is a warn level logged message') + log.error('this is a error level logged message') + log.fatal('this is a fatal level logged message') + log.clear('This would clear the log') + } + } -let test = new LOGTEST({id:'process-test1'}) +let test = new LogTest({id:'logtest-via-new'}) -log.info({test:'filter', msg:'this is a logged message'}) -log.fatal('this is a fatal logged message') +test.logit() diff --git a/package.json b/package.json index a6d86b3..fbe5c52 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,16 @@ "description": "Parent Logger for all UCI modules", "main": "src/logger", "scripts": { - "testw": "mocha -r esm test/*.test.mjs --watch --recurse --watch-extensions mjs", - "test": "mocha -r esm test/*.test.mjs", + "testd": "./node_modules/.bin/nodemon --exec './node_modules/.bin/mocha -r esm --timeout 30000'", + "test": "./node_modules/.bin/mocha -r esm --timeout 30000 || exit 0", "testci": "istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -R spec --recursive && codecov || true", - "example": "node -r esm example/example", - "dev": "UCI_DEV=true ./node_modules/.bin/nodemon -r esm example/example", - "log": "UCI_LOG=true ./node_modules/.bin/nodemon -r esm example/example", - "pro": "UCI_PROD=./test/logs/uci.log node -r esm example/example" + "none": "node -r esm example/example", + "dev": "UCI_ENV=dev ./node_modules/.bin/nodemon -r esm example/example", + "dev:info": "UCI_ENV=dev UCI_LOG_SEARCH='level==`30`' ./node_modules/.bin/nodemon -r esm example/example", + "dev:fatal": "UCI_ENV=dev UCI_LOG_SEARCH='level==`60`' ./node_modules/.bin/nodemon -r esm example/example", + "json": "UCI_ENV=dev UCI_LOG_JSON=true ./node_modules/.bin/nodemon -r esm example/example", + "pro": "UCI_ENV='pro' node -r esm example/example", + "pro:alt": "UCI_ENV='pro' UCI_LOG_PATH=./example/example.log node -r esm example/example" }, "author": "David Kebler", "license": "MIT", @@ -28,16 +31,18 @@ }, "homepage": "https://github.com/uCOMmandIt/uci-changeme#readme", "dependencies": { + "env-paths": "^2.0.0", + "make-dir": "^1.3.0", "pino": "^5.11.1", "pino-pretty": "^2.5.0" }, "devDependencies": { "chai": "^4.2.0", - "chai-as-promised": "^7.1.1", "codecov": "^3.1.0", "esm": "^3.1.2", "istanbul": "^0.4.5", "mocha": "^5.2.0", - "nodemon": "^1.18.9" + "nodemon": "^1.18.9", + "test-console": "^1.1.0" } } diff --git a/readme.md b/readme.md index e274a84..cc94c29 100644 --- a/readme.md +++ b/readme.md @@ -1,17 +1,87 @@ -# uCOMmandIt JSON Logger +`# uCOMmandIt JSON Logger -runtime module logger based on pino +### json logger based on pino. -run example as either +## How to by Example -`npm run log` -for json logging +see [example](./example/example.js) for explanation of options and use in a es6 class allowing logging in any method of the class -or for pretty output `npm run dev` +```javascript + // pretty: {translateTime:true, colorize:true, levelFirst:true } // options for pino pretty printer + // env:'', // 'dev' or 'pro' -- can be use to set/override UCI_ENV environment variable + // enForce: false, // only used with .evn. if true will override UCI_ENV if it is set, otherwise no + // level:'info', // info is default level, set level to lowest visible + // clear: false, // true for log files will clear the current log file on restart + // logFileName:'test', // if not supplied log filename will be generated from timestamp + // below properties are optional and are passed to child logger instance and will be part of json log entry + // libraryName: 'UCI', // will be logged as name: can be used to identify logs of related packages, can be set via UCI_LOG_NAME env variable + appName:'uci-example-logger', //will be used for logging directory name if supplied and will be logged as well + package: '@uci/logger', // name of package with scope per package.json + // repo: // will use scope-name from package prop if not supplied + id: opts.id, // can pass a unique if for easy later search/filtering + // file: 'example/example.js', // path (to repo root) of actual file running this logger, default is ./src/.js + class: 'LogTest', // The class that created this logger if any + // one can pass through additional props to log for every log + // should not use any keys above or + // level,time,msg,pid,hostname + // logPath,instanceCreatedHR,instanceCreated as keys will be merged + // NOTE: more props can be added at run time by passing an object of props + additional: {anotherprop:'test'} // should do not use any keys above or 'level,logPath,package,instanceCreatedHR,instanceCreated' as keys will be merged +``` + +to run the example clone reop and then `npm install` from root + +then + +`npm run [script]` + +available scripts for example file + +- `none`: no logs to console or file +- `dev`: all levels pretty printed to console/stdout +- `dev:info`: show/filter only level 30 (info) logs pretty printed to console +- `dev:fatal`: show/filer only level 60 (fatal) logs pretty printed to console +- `json`: log to console but as raw json +- `pro`: log to a file in the userspace default log directory location +- `pro:alt`: log to a supplied/alternate file (path) location in this case `./example/example.log` + +## Environment Variables + +- `UCI_ENV='dev'|'pro'|'node'`, `dev` outputs to console, `pro` outputs to file, `node` will follow whatever `NODE_ENV` is set to. +- `UCI_LOG_PATH=` force logging to go to some particular file, also setable via options, see above +- `UCI_LOG_JSON=true` will not pretty output to console but rather as raw json. Useful for piping to a json log processor such as pino-colada +- `UCI_LOG_PRETTY`={} the pretty option for pino pretty, also setable via options, see above +- `UCI_LOG_SEARCH='key == `\``value`\``'` will filter pretty/dev logs. _note:_ ALL search values regardless of type MUST be escaped with `` + +## Usage + +standalone (not in a class constructor as in example) +lilke this. + +import logger from '../src/logger' +opts = {} // see above +let log = logger(opts) + +once you have created a Logger as a `global` in your module you can log per http://getpino.io/#/docs/api?id=logger + +like so +```javascript +log.trace('this is a trace level logged message') +log.debug('this is a debug level logged message') +log.info({runtimeprop:'somevalue', msg:'this is a info level logged message'}) +log.warn('this is a warn level logged message') +log.error('this is a error level logged message') +log.fatal('this is a fatal level logged message') +``` -for search/filter use environment variable +you can change visible levels on the fly http://getpino.io/#/docs/api?id=level -`UCI_LOG_SEARCH='key == `\`value\``' npm run dev` +like so -note: ALL search values regardless of type MUST be escaped with `` +`log.level('debug')` or in the options, see above + + +## More Help + +this package/model being a simple extension of the pino json logger check their documentation http://getpino.io/#/ diff --git a/src/logger.js b/src/logger.js index d9cb036..1bdc05d 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,39 +1,65 @@ import pino from 'pino' -import { homedir } from 'os' +import envPaths from 'env-paths' +import { sync as mkdir } from 'make-dir' +import { dirname, basename } from 'path' -let pretty = false -if(process.env.UCI_DEV) { - pretty = process.env.UCI_LOG_PRETTY || {translateTime:true, colorize:true, levelFirst:true } - pretty.search = process.env.UCI_LOG_SEARCH -} +function child (opts) { -let LOG = process.env.UCI_LOG || process.env.UCI_DEV || process.env.UCI_PROD -let LOG_PATH = (process.env.UCI_PROD===true) ? homedir()+'/logs/uci.log' : process.env.UCI_PROD + if (opts.env) (opts.envForce) ? (process.env.UCI_ENV = opts.env) : (process.env.UCI_ENV = process.env.UCI_ENV || opts.env) -const logger = pino({ - name: 'UCI', - enabled: !!LOG, - safe: true, - serializers: { - req: pino.stdSerializers.req, - res: pino.stdSerializers.res + if (process.env.UCI_ENV === 'node') process.env.UCI_ENV = process.env.NODE_ENV + + let pretty = false + if(process.env.UCI_ENV === 'dev' || process.env.UCI_ENV === 'development') { + if (process.env.UCI_LOG_JSON !=='true') { + pretty = process.env.UCI_LOG_PRETTY || opts.pretty || {translateTime:true, colorize:true, levelFirst:true } + pretty.search = process.env.UCI_LOG_SEARCH + } + } + + const DATE_TIME = new Date(Date.now()).toISOString().replace( /[:.]+/g, '-' ) + + const LOG_PATH = (process.env.UCI_ENV=== 'production' || process.env.UCI_ENV=== 'pro' || process.env.UCI_ENV=== 'logfile' ) + ? ( process.env.UCI_LOG_PATH || `${envPaths(opts.appName || opts.name || 'default').log}/${opts.logFileName || DATE_TIME}.log`) : null + + if (LOG_PATH) { + try { mkdir(dirname(LOG_PATH)) } + catch(err) { throw err } + } + + // console.log('making logger (env,json,pretty,pretty-search,path)',process.env.UCI_ENV,process.env.UCI_LOG_JSON,pretty,process.env.UCI_LOG_SEARCH,LOG_PATH) + const logger = pino({ + name: opts.libraryName || process.env.UCI_LOG_NAME || 'UCI', + enabled: !!process.env.UCI_ENV, + safe: true, + serializers: { + req: pino.stdSerializers.req, + res: pino.stdSerializers.res + }, + prettyPrint: pretty }, - prettyPrint: pretty -}, -// if production not enabled then LOG_PATH is empty and logs go to stdout/stderr and can be piped from there -LOG_PATH -) + // if production not enabled then LOG_PATH is empty and logs go to stdout/stderr and can be piped from there + LOG_PATH + ) -function child (opts) { - const LOG_OPTS = { - repo: opts.repo || 'uci-'+ opts.name, - package: opts.package || '@uci/'+ opts.name, - file: opts.file || `src/${opts.name}.js`, + logger.clear = value => {console.log(value)} + + let logOpts = { + level:opts.level, + logPath: LOG_PATH, // if logging to file + appName: opts.appName, + repo: opts.repo || (opts.package) ? opts.package.replace( /[@]+/g, '' ).replace( /[/]+/g, '-' ) : null, + package: opts.package, + file: opts.file || `src/${basename(opts.package)}.js`, class: opts.class || (opts.name.charAt(0).toUpperCase() + opts.name.slice(1)), id: opts.id || opts.name || 'none', - instance_created:new Date().getTime() + instanceCreatedHR:DATE_TIME, + instanceCreated:new Date().getTime() } - return logger.child(LOG_OPTS) + + logOpts = Object.assign(logOpts,opts.additional) + + return logger.child(logOpts) } export default child