Separate log levels - javascript

I am looking the way how can I post out only "INFO" level (for user) to console, and "DEBUG" level post to the file. Currently I found only one one work solution - using few "getLogger()" functions. For example:
log4js.getLogger('debug');
log4js.getLogger('info');
where each parameter of the function separate category of the configuration from configure() function.
but I don't like it and suppose that there is better option with one getLogger() function.
Last think I tried was:
log4js.configure({
appenders: {
everything:{ type: 'stdout' },
file_log: { type: 'file' ,filename: 'all-the-logs.log' },
logLevelFilter: { type:'logLevelFilter',level: 'debug', appender: 'file_log' }
},
categories: {
default: {
appenders: [ 'logLevelFilter','everything'], level: 'info'},
}
});
I see in console out - only "INFO" level, but in file I also see only "INFO" level. In case If I add to appender - level "ALL" - I will see in console out all levels and the same will be in file

log4js provided special append typed categoryFilter and logLevelFilter, see Log4js - Appenders, step as below
Add appender and category based on each log level;
Add extra appender typed logLevelFilter pointing to the related appender;
Change config.categories.default.appenders using appenders in step 2;
Finally use log4js.getLogger() create logger and logging everywhere, log file separated by love level
Written in typescript
import {Appender, Configuration, LogLevelFilterAppender} from "log4js";
const fs = require('fs');
const path = require('path');
let levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
let conf: Configuration = {
appenders : {
default: {
type : "console",
layout: {
type : "colored",
pattern: "%m"
},
}
},
categories: {
default: {
appenders: levels,
level : 'all'
}
},
};
let logs = path.join(__dirname, 'logs');
if (!fs.existsSync(logs)) {
fs.mkdirSync(logs);
}
for (let level of levels) {
let appender: Appender = {
type : "file",
filename : path.join(logs, level + '.log'),
maxLogSize: 65536,
backups : 10,
layout : {
type : "pattern",
pattern: "%d{dd/MM hh:mm} %-5p %m"
}
};
conf.appenders[level] = appender;
conf.categories[level] = {
appenders: [level],
level : level.toUpperCase(),
}
}
let prefix = 'only-';
for (let level of levels) {
let appender: LogLevelFilterAppender = {
type : "logLevelFilter",
appender: level,
level : level,
maxLevel: level,
};
let name = prefix + level;
conf.appenders[name] = appender;
conf.categories[name] = {
appenders: [level],
level : level.toUpperCase(),
}
}
conf.categories.default.appenders = levels.map(x => prefix + x);
//console.log(JSON.stringify(conf, null, 2));
export default conf;
sample usage
// const log4js = require('log4js');
import * as log4js from 'log4js';
import log4jsConfig from './log4js.config';
log4js.configure(log4jsConfig );
(function () {
const logger = log4js.getLogger();
logger.trace('Entering cheese testing');
logger.debug('Got cheese.');
logger.info('Cheese is Gouda.');
logger.warn('Cheese is quite smelly.');
logger.error('Cheese is too ripe!');
logger.fatal('Cheese was breeding ground for listeria.');
log4js.shutdown(console.log);
})();

Related

Electron 19 showOpenDialog "Cannot read properties of undefined (reading 'showOpenDialog')"

I have updated electron to version 19 but I have an error:
Cannot read properties of undefined (reading 'showOpenDialog')
I have a function to save file:
window.SaveFile = function (options, callBack) {
const {
name, //the name showed for the extension/s
extensions,
fileName
} = options;
let filters = [{ name: name, extensions: Array.isArray(extensions) ? extensions : [extensions] }];
dialog.showSaveDialog(null, { properties: ['saveFile'], name: [name], filters: filters, icon: 'main.ico', defaultPath: (fileName || name) + '.' + extensions }, (returnValue) => {
callBack(returnValue);
});
}
But dialog is undefined.
In index.js I have set the:
webPreferences: {
enableRemoteModule: true,
nodeIntegration: true
}
My required in electron is
const { ipcRenderer, dialog, remote, ipcMain } = require('electron');
but dialog and remote are undefined.
How can I solve this?
A while back, the remote module was deprecated (see Electron's breaking changes document). Because dialog is a main process module, you'll now have to open the dialog from the main process.
This, of course, complicates things a little bit, but with ipcRenderer and ipcMain this should be easy to achieve.
Assuming you have const { ipcRenderer } = require("electron"); in your renderer process:
window.SaveFile = function (options, callBack) {
ipcRenderer.invoke ("save-file", options).then (returnValue => {
callBack (returnValue);
});
}
And then in your main process:
ipcMain.handle ("save-file", async (event, options) => {
const {
name, //the name showed for the extension/s
extensions,
fileName
} = options;
let filters = [{ name: name, extensions: Array.isArray(extensions) ? extensions : [extensions] }];
var returnValue = await dialog.showSaveDialog(
null,
{
properties: ['saveFile'],
name: [name],
filters: filters,
icon: 'main.ico',
defaultPath: (fileName || name) + '.' + extensions
}
);
return returnValue;
});

Export a variable that is unique only first time it is referenced

Some context, I am trying to create a global variable that is set once using Math.random().toString(36).slice(2); or similar however whenever referenced again does not generate a new random string but returns the already generated random string.
However within parallel.conf.js and then in runner.js I have two different strings.
I have tried the logic of if localIdentifier is undefined generate a random string and if not return the already generated string by referencing where it has been inserted.
I have added the configuration I have setup relating to nightwatch + browserstack for context but don't think it should matter.
If unclear do let me know and I can provide more context.
parallel.conf.js
let localIdentifier;
let defineLocalIdentifier = (function(){
if (localIdentifier === 'undefined') {
localIdentifier = nightwatch_config.common_capabilities["browserstack.localIdentifier"];
return localIdentifier;
} else {
localIdentifier = Math.random().toString(36).slice(2);
return localIdentifier;
}
})();
nightwatch_config = {
common_capabilities: {
'browserstack.user': <user>,
'browserstack.key': <key>,
'browserstack.tunnel': true,
'browserstack.localIdentifier': defineLocalIdentifier,
'browserstack.local': true,
'name': 'Bstack-[Nightwatch] Parallel Test',
acceptInsecureCerts: true,
javascriptEnabled: true,
acceptSslCerts: true,
}
module.exports = nightwatch_config;
module.exports.bs = {
localIdentifier : defineLocalIdentifier
}
runner.js
const nightwatchConfig = require('./parallel.conf').bs.localIdentifier;
function BrowserStack () {
const browserStackArgs = {
key: apiKey,
'force': 'true',
'forceLocal': 'true',
'verbose': 'true',
'localIdentifier': nightwatchConfig
};

ESlint Error when using map - workaround?

What is the workaround to update the dataLine when using data.Items.map()
I am getting eslint error:
Assignment to property of function parameter 'dataLine'
You can see I am deleting Other property and modifying dataLine.Config
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
console.log(JSON.stringify(newItems, null, 2));
About eslint, I think it's a missing piece, because if you write your function in an equivalent way:
data.Items.map((dataLine) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
you won't receive any warning. Maybe it's the case of open an issue there.
You could pass {props : true}, like GProst said, but this will enforce you to not make the assignment of any property of the parameter, which is a good thing, for example:
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = { // not allowed with props : true
Size: "L"
};
delete dataLine.Other; // not allowed with props : true
}
return dataLine;
});
Why eslint have such a rule?
You are modifying the properties of data.Items, this will cause side effects on the external environment of the callback function on map. In some cases this will put you in bad situation, like not knowing which piece of code removed some property.
A suggestion about how you can deal with this safely is return an entire new object to make your data.Items immutable in your case:
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
const dataLineCopy = JSON.parse(JSON.stringify(dataLine))
if (data.Type == "API") {
dataLineCopy.Config = {
Size: "L"
};
delete dataLineCopy.Other;
}
return dataLineCopy;
});
console.log(JSON.stringify(newItems, null, 2));
Edit no-param-reassign rule in eslint config, set option props to false:
"no-param-reassign": ["error", { "props": false }]

nodejs add line number to winston based logger

How can I add line number to this following winston-based logger?
So that the line number appears at the end of the file path.
const winston = require('winston');
winston.emitErrs = true;
const logger = function (module) {
let parts, path;
parts = module.filename.split('\\');
path = parts[parts.length - 2] + '\\' + parts.pop();
return new winston.Logger({
transports: [
new winston.transports.Console({
level: 'debug',
handleExceptions: true,
json: false,
colorize: true,
label: path,
timestamp: true
})
],
exitOnError: false
});
};
module.exports = logger;
update
current output example is like:
2018-06-10T00:13:33.344Z - info: [app\main.js] Here is my log
the desired format is:
2018-06-10T00:13:33.344Z - info: [app\main.js:150] Here is my log
in which 150 is appended to the file path.
You can use this code in a new file winston.js. and the Use it by requiring it anywhere.
var winston = require('winston')
var path = require('path')
var PROJECT_ROOT = path.join(__dirname, '..')
var appRoot = require('app-root-path');
const options = {
file: {
level: 'info',
filename: `${appRoot}/logs/app.log`,
handleExceptions: true,
json: true,
maxsize: 5242880, // 5MB
maxFiles: 5,
colorize: false,
timestamp: true
},
console: {
level: 'debug',
handleExceptions: true,
json: true,
colorize: true,
timestamp: true
}
};
var logger = new winston.Logger({
transports: [
new winston.transports.File(options.file),
new winston.transports.Console(options.console)
],
exitOnError: false // do not exit on handled exceptions
});
logger.stream = {
write: function (message) {
logger.info(message)
}
}
// A custom logger interface that wraps winston, making it easy to instrument
// code and still possible to replace winston in the future.
module.exports.debug = module.exports.log = function () {
logger.debug.apply(logger, formatLogArguments(arguments))
}
module.exports.info = function () {
logger.info.apply(logger, formatLogArguments(arguments))
}
module.exports.warn = function () {
logger.warn.apply(logger, formatLogArguments(arguments))
}
module.exports.error = function () {
logger.error.apply(logger, formatLogArguments(arguments))
}
module.exports.stream = logger.stream
/**
* Attempts to add file and line number info to the given log arguments.
*/
function formatLogArguments (args) {
args = Array.prototype.slice.call(args)
var stackInfo = getStackInfo(1)
if (stackInfo) {
// get file path relative to project root
var calleeStr = '(' + stackInfo.relativePath + ':' + stackInfo.line + ')'
if (typeof (args[0]) === 'string') {
args[0] = calleeStr + ' ' + args[0]
} else {
args.unshift(calleeStr)
}
}
return args
}
/**
* Parses and returns info about the call stack at the given index.
*/
function getStackInfo (stackIndex) {
// get call stack, and analyze it
// get all file, method, and line numbers
var stacklist = (new Error()).stack.split('\n').slice(3)
// stack trace format:
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
// do not remove the regex expresses to outside of this method (due to a BUG in node.js)
var stackReg = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/gi
var stackReg2 = /at\s+()(.*):(\d*):(\d*)/gi
var s = stacklist[stackIndex] || stacklist[0]
var sp = stackReg.exec(s) || stackReg2.exec(s)
if (sp && sp.length === 5) {
return {
method: sp[1],
relativePath: path.relative(PROJECT_ROOT, sp[2]),
line: sp[3],
pos: sp[4],
file: path.basename(sp[2]),
stack: stacklist.join('\n')
}
}
}

Swagger-codegen-js not finding not translating the response schema defined on the api endpoint

Summary
I am using swagger-codegen-js to generate typescript files according to the swagger.json defined by an external api.
packages used
"swagger-js-codegen": "^1.12.0",
Alleged Problem
The return type on the method listMovies on the generated TS file is simply Promise<Request.Response> and not Promise<Array<Movie>>, i did expect it to be array of movies as the response clearly state the schema and thought/assumed that would be translated.
Given a json along the lines of the following, the
"/movies": {
"get": {
"description": "Lists movies",
"operationId": "listMovies",
"responses": {
"200": {
"description": "Movie",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Movie"
}
}
},
"default": {
"$ref": "#/responses/genericError"
}
}
},
"definitions": {
"Movie": {
"description": "something",
"type": "object",
"properties": {
"archivedAt": {
"description": "When the movie was archived",
"type": "string",
"format": "nullable-date-time",
"x-go-name": "ArchivedAt",
"readOnly": true
}
}
}
Generated TS Method
/**
* Lists movies
* #method
* #name Api#listMovies
*/
listMovies(parameters: {
$queryParameters ? : any,
$domain ? : string
}): Promise <request.Response> {
const domain = parameters.$domain ? parameters.$domain : this.domain;
let path = '/movies';
.
.
.
this.request('GET', domain + path, body, headers, queryParameters, form, reject, resolve);
});
}
The script i use to generate the above ts file is straight from the github sample
const generateTSFilesUsingSwaggerJsCodegen = function () {
var fs = require('fs');
var CodeGen = require('swagger-js-codegen').CodeGen;
var file = 'build/sample.json';
var swagger = JSON.parse(fs.readFileSync(file, 'UTF-8'));
var tsSourceCode = CodeGen.getTypescriptCode({ className: 'Api', swagger: swagger, imports: ['../../typings/tsd.d.ts'] });
fs.writeFileSync('src/api/api.ts', tsSourceCode)
}
Am i missing something on the wire up/options passed or is this the expected generated file given the json file and that i need to write custom script to get what I want?
It is only possible to edit the codegen to change this.
But you can just use the body of the return value
<restMethod>(<paramters>).then(respose: <request.Response>) {
let responseObject: Array<ListMovies> = response.body as Array<ListMovies>;
...
}
If you want to adapt the codegen, pull it from git and change the following files:
lib/codegen.js:
var getViewForSwagger2 = function(opts, type){
var swagger = opts.swagger;
var methods = [];
var authorizedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'COPY', 'HEAD', 'OPTIONS', 'LINK', 'UNLIK', 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND'];
var data = {
isNode: type === 'node' || type === 'react',
isES6: opts.isES6 || type === 'react',
description: swagger.info.description,
isSecure: swagger.securityDefinitions !== undefined,
moduleName: opts.moduleName,
className: opts.className,
imports: opts.imports,
domain: (swagger.schemes && swagger.schemes.length > 0 && swagger.host && swagger.basePath) ? swagger.schemes[0] + '://' + swagger.host + swagger.basePath.replace(/\/+$/g,'') : '',
methods: [],
definitions: []
};
_.forEach(swagger.definitions, function(definition, name){
data.definitions.push({
name: name,
description: definition.description,
tsType: ts.convertType(definition, swagger)
});
});
the last _.forEach is moved from the bottom of the method to here.
var method = {
path: path,
className: opts.className,
methodName: methodName,
method: M,
isGET: M === 'GET',
isPOST: M === 'POST',
summary: op.description || op.summary,
externalDocs: op.externalDocs,
isSecure: swagger.security !== undefined || op.security !== undefined,
isSecureToken: secureTypes.indexOf('oauth2') !== -1,
isSecureApiKey: secureTypes.indexOf('apiKey') !== -1,
isSecureBasic: secureTypes.indexOf('basic') !== -1,
parameters: [],
responseSchema: {},
headers: []
};
if (op.responses && op.responses["200"]) {
method.responseSchema = ts.convertType(op.responses["200"], swagger);
} else {
method.responseSchema = {
"tsType": "any"
}
}
this is starting at line 102. add responseSchema to method and append the if / else
templates/typescript-class.mustache (line 68)
resolve(response.body);
templates/typescript-method.mustache (line 69)
}): Promise<{{#responseSchema.isRef}}{{responseSchema.target}}{{/responseSchema.isRef}}{{^responseSchema.isRef}}{{responseSchema.tsType}}{{/responseSchema.isRef}}{{#responseSchema.isArray}}<{{responseSchema.elementType.target}}>{{/responseSchema.isArray}}> {
That only works for simple types, Object types and arrays of simple / object types. I did not implement the enum types.
That seems like the expected behaviour.
The TypeScript Mustache template outputs a fixed value:
...
}): Promise<request.Response> {
...

Categories

Resources