I want to add a dynamic attribute to a pattern I am using with log4js.
I am using some custom pattern, something like this:
"%d{} %-6p[%thread] %c [%x{context}]: %m%n%r"
Context is the dynamic value that I want to set with some unique id generated for each user on the server side.
There is a way to add dynamic value when creation log4js configuration by using "tokens" and "context" attributes.
But in this case values should be set during the log creation.
Is there a way to add some dynamic attribute that is set when the actual message is written to the log and not during the config phase?
Right now I am doing something like this:
log4js.configure(
{
appenders: { "file": { "type": "file", "filename": "service.log", "maxLogSize": 102400, "backups": 5, "category": "com.nextinsurance", "layout": { "type": "pattern", "pattern": "%d{} %-6p[%thread] %c [%x{context}]: %m%n%r", "tokens" : {context: function(logEvent){ return getContextFromData(logEvent) } } } }, "console" : {"type": "console"} },
categories: { "default": { "appenders": ["file", "console"], "level": "info" } }
}
);
But want to inject this value when writing to log, something like
logger.info(Message, {context: context_value})
You can use logEvent data property to fetch context. logEvent data property contains the array of args passed in log event.
Here is the sample code:
var log4js = require("log4js");
log4js.configure({
appenders: {
out: {
type: 'stdout',
layout: {
type: 'pattern',
pattern: '[%d] [%p] [%c] [%x{context}] - %m%n',
tokens: {
context: function(logEvent) {
let returnVal = logEvent.data[1] ? logEvent.data[1].context : undefined;
if (returnVal) {
logEvent.data.pop();
} else {
returnVal = 'null'; // set any default value if context is not passed.
}
return returnVal;
}
}
}
}
},
categories: {
default: {
appenders: ['out'],
level: 'INFO'
}
}
});
log4js.level = 'info';
let logger = log4js.getLogger();
logger.info('Hello', { context: 'context_value'}); // prints [2019-09-13T16:50:48.818] [INFO] [default] [context_value] - Hello
logger.info('Hello'); // prints [2019-09-13T16:50:48.820] [INFO] [default] [null] - Hello
Related
I am using Axios to execute a GET request to a public API. This API return a numeric value as string. I need to cast this value a numeric value. My application runs using tsc, so I expect the result of my object to be a numeric value, but it's not.
Axios Response
[
{
"name": "foo1",
"value": "8123.3000"
},
{
"name": "foo2",
"value": "5132.2003"
},
{
"name": "foo3",
"value": "622.0000"
}
]
Expected Output
[
{
"name": "foo1",
"value": 8123.3
},
{
"name": "foo2",
"value": 5132.2003
},
{
"name": "foo3",
"value": 622
}
]
My code is very simple,
interface MyObj {
myString: string;
myNumber: number;
}
(async () => {
let { data }: AxiosResponse<MyObj> = await axios.get<MyObj>("/public/data");
console.log(data);
})();
I try to use interface, class, the interface Number. Nothing worked.
I leave an example of code to try it.
How can I get the expected output without manually converting each value one by one?
Axios does not change the type of properties in the response. Please verify that the server does not send you the wrong types.
Edit
From your comment, it seems that the server sends you the vslue as string instead of as number. In this case I would suggest working with Ajv (https://github.com/ajv-validator/ajv) so you can create a schema that describes how the response looks like. Ajv cn also transform the value from string to number for you:
const Ajv = require('ajv')
const ajv = new Ajv({
// allow chaning the type of some values from type X to type Y. depends on the source and target type:
// https://ajv.js.org/guide/modifying-data.html#coercing-data-types
// X => Y rules: https://ajv.js.org/coercion.html
coerceTypes: true,
})
const schema = {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'number' },
},
required: ['name', 'value'],
additionalProperties: false,
},
}
const data = { name: 1, value: '1.1' }
console.log(typeof data.value === 'number') // false!
const valid = ajv.validate(schema, data)
if (!valid) {
console.log(ajv.errors)
process.exit(1)
}
console.log(typeof data.value === 'number') // true!
When declaring that your axios request returns a certain type; this will be checked at compile time and syntax checking. It does not however do this at runtime. If you know that the axios request is returning something different than your interface, you need to convert to that format first. You can do this using the second argument of the JSON.parse function like so:
interface Item {
name: string;
value: number;
}
let responseString = `
[
{
"name": "foo1",
"value": "8123.3000"
},
{
"name": "foo2",
"value": "5132.2003"
},
{
"name": "foo3",
"value": "622.0000"
}
]`
const items: Item[] = JSON.parse(responseString, (key, value) => {
const propertiesToCast = ["value"] // Which properties should be converted from string to number
if (propertiesToCast.includes(key)) {
return parseFloat(value)
}
return value
});
console.log(items)
I have the following docs:
export class CustomerServiceResultType{
id: string;
body:{customerRelation: string};
}
export class CustomerType{
id: string;
body:{name: string};
}
I want CustomerServiceResultType to have a relation to CustomerType with the field: customerRelation.
this is my mapping:
await this.elasticsearchService.indices.putMapping({
"index": "db",
"type": "CustomerServiceResultType",
"body" : {
"properties": {
"customerRelation": {
"type": "join",
"relations": {
"CustomerServiceResultType": "CustomerType"
}
}
}
}
});
This is the error I get:
[Nest] 421512 - 11/21/2020, 6:40:42 PM [ExceptionsHandler] illegal_argument_exception +96414ms
ResponseError: illegal_argument_exception
There are no details about this error...
Thanks
There's nothing wrong with your request per-se -- I think it just requires one extra option: include_type_name: true.
It's undefined by default in nodejs but is required in ES 7.x on the server side. More reasoning behind this is here.
So this should do the trick:
await client.indices.putMapping({
include_type_name: true,
index: "db",
type: "CustomerServiceResultType",
body : {
properties: {
customerRelation: {
type: "join",
relations: {
CustomerServiceResultType: "CustomerType"
}
}
}
}
});
Typed indexes will be removed in 8.x so the best approach would actually be:
await client.indices.putMapping({
index: "db",
body : {
properties: {
customerRelation: {
type: "join",
relations: {
CustomerServiceResultType: "CustomerType"
}
}
}
}
});
BTW: your typescript types don't really play a role here because ES is a JSON-only interface and while there's the deprecated type aspect to ES, the two concepts are very distant.
My nodejs code receives a response from a HTTP POST call which is in xml. I convert it to json using xml2js, and now I need to read this so as to get the data of one of the json keys.
This is how a part of my json data looks. I am trying to read this as follows:
var base64encoded = jsonxml."soapenv:Body".runReportResponse.runReportReturn.reportBytes;
However, when I run this i get the error message:
jsonxml."soapenv:Body".runReportResponse.runReportReturn.reportBytes;
^^^^^^^^^^^^^^
SyntaxError: Unexpected string.'
I have also tried by removing the double quotes, but then it gives exception to the colon (:) which appears in the data. How do I read such kind of data?
Entire json:
[
{
"Envelope": {
"$": {
"xmlns:soapenv": "http://schemas.xmlsoap.org/soap/envelope/",
"xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance"
},
"Body": {
"runReportResponse": {
"$": {
"xmlns": "http://xmlns.oracle.com/oxp/service/PublicReportService"
},
"runReportReturn": {
"metaDataList": {
"$": {
"xsi:nil": "true"
}
},
"reportBytes": "MzAwMDAwMDA0Mzk5ODEwLERDT0cgQ29ycG9yYXRlIEJVDQozMDAwMDAwMDk0ODE4MzEsREVMRlRMQUJfVVNfQlVTSU5FU1NfVU5JVA0KMzAwMDAwMDAzMDYyNTI1LERFTEhJVEVDSF9VU19CVVNJTkVTU19VTklUDQozMDAwMDAwMDMwNjE1ODMsREVMTFMgVVMgQlUNCjMwMDAwMDAwMzE3OTE0NixERUxNRkcgVVMgQlUNCjMwMDAwMDAxMDI1NDA1NyxESEMgQ29ycG9yYXRlDQo=",
"reportContentType": "text/plain;charset=UTF-8",
"reportFileID": {
"$": {
"xsi:nil": "true"
}
},
"reportLocale": {
"$": {
"xsi:nil": "true"
}
}
}
}
}
}
}
]
I've updated the answer base on your new JSON (thank you!). It looks like the problem is maybe that each property in the JSON is really an array.
I'd suggest if you don't want this behaviour, to have a look at the explicitArray options in the xml2js options object setting it to false.
e.g. :
explicitArray (default: true): Always put child nodes in an array if true; otherwise an array is created only if there is more than one.
This might simplify accessing properties!
let response = [
{
"Envelope": {
"$": {
"xmlns:soapenv": "http://schemas.xmlsoap.org/soap/envelope/",
"xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance"
},
"Body": {
"runReportResponse": {
"$": {
"xmlns": "http://xmlns.oracle.com/oxp/service/PublicReportService"
},
"runReportReturn": {
"metaDataList": {
"$": {
"xsi:nil": "true"
}
},
"reportBytes": "MzAwMDAwMDA0Mzk5ODEwLERDT0cgQ29ycG9yYXRlIEJVDQozMDAwMDAwMDk0ODE4MzEsREVMRlRMQUJfVVNfQlVTSU5FU1NfVU5JVA0KMzAwMDAwMDAzMDYyNTI1LERFTEhJVEVDSF9VU19CVVNJTkVTU19VTklUDQozMDAwMDAwMDMwNjE1ODMsREVMTFMgVVMgQlUNCjMwMDAwMDAwMzE3OTE0NixERUxNRkcgVVMgQlUNCjMwMDAwMDAxMDI1NDA1NyxESEMgQ29ycG9yYXRlDQo=",
"reportContentType": "text/plain;charset=UTF-8",
"reportFileID": {
"$": {
"xsi:nil": "true"
}
},
"reportLocale": {
"$": {
"xsi:nil": "true"
}
}
}
}
}
}
}
];
console.log("Report bytes: ", response[0].Envelope.Body.runReportResponse.runReportReturn.reportBytes);
How can we override the default loopback REST API model end points? For example, I would like to invoke a custom model method named list when the following GET API is invoked.
I am referring to the documentation https://loopback.io/doc/en/lb2/Exposing-models-over-REST.html
1.API endpoint from loopback explorer: http://localhost:3000/api/Assets
2.Model method definition:
Asset.list = function(cb) {
console.log("called");
}
Asset.remoteMethod('list', {
http: {path: '/'},
returns: {type: 'Object', root: true}
});
If you want to use not default path (not used by default methods) you should add new remote method to JSON model config and define method in JS model file:
"methods": {
"myCustomMethod": {
"accepts": [
{
"arg": "req",
"type": "object",
"http": {
"source": "req"
}
}
],
"returns": [
{
"type": "Array",
"root": true
}
],
"http": {
"verb": "GET",
"path": "/myCustomPath"
}
}
}
Candidate.myCustomMethod = (req) => {//method code}
If you want to override default loopback path (autogenerated methods) you also should disable default method.
Candidate.disableRemoteMethodByName('find');
So now you can change in JSON config "/myCustomPath" to "/" and your remote method would refer to your function instead of default.
Your console.log("called"); should appear in your terminal only, not as a return on your web browser - that's maybe why you aren't seeing it at the moment.
If you want to see something on your web browser, you have to return a value for you callback, like:
module.exports = function (Asset) {
Asset.list = function(cb) {
console.log("called");
cb(false, {'called': true}); // cb(error, returned value(s));
}
Asset.remoteMethod('list', {
http: {verb: 'get'},
returns: {type: 'Object', root: true}
});
}
This file should be in your common/model/asset.js
In your server/model-config.json, do not forget to reference your model:
...
"Asset": {
"dataSource": "yourdatasource", //change this by your datasource name
"public": true
}
...
Currently, I am using express with log4js module like this, in any route file:
var log = require('log4jslogger.js').LOG;
log.info('this is log statement');
logger.js
var log4js = require('log4js');
log4js.configure(__base + "log4jsconfig.json");
var logger = log4js.getLogger('default');
Object.defineProperty(exports, "LOG", {
value : logger,
});
logs4jsconfig.json
{
"appenders": {
"out": {
"type": "stdout"
},
"default": {
"type": "dateFile",
"filename": "logs/default",
"pattern": "-yyyy-MM-dd.log",
"alwaysIncludePattern": true,
"keepFileExt": true
}
},
"categories": {
"default": {
"appenders": ["out",
"default"],
"level": "trace"
}
}
}
I want to add logged [username] or [unauthenticated] in all log statements. which I can get like req.user.id.
How can we do this instead of adding this to all log statements?
In log4js docs, I found a use of layout using a token but did not get clearly how to form AuthLibrary.currentUser()
const currentUser = require("./authLibrary").currentUser;
log4js.configure({
replaceConsole: true,
appenders: {
stdout: {
type: 'console',
layout: {
type: "pattern",
pattern: "%d %p %c %x{user} %m%n",
tokens: {
user: function (logEvent) {
return `[auth:${currentUser().UserName || '-'} / ${currentUser().UserId || '-'}]`;
},
},
},
}
},
categories: { default: { appenders: ['stdout'], level: 'info' }, },
});
// authLibrary.js
let user = {};
const setCurrentUser = (_user) => {
user = _user;
}
const currentUser = () => user;
module.exports = { currentUser, setCurrentUser };
// test
(async (err, req, res, next) => {
const token = req.get('token')
const user = await authService(token)
req.currentUser = user;
require("../utils/authLibrary").setCurrentUser(user);
})(null, { get: (x) => "1697E29ADE8DA9F6F84E214CF66E" }, null, null)