I have a MongoDB query that searches all properties for a value defined in the search variable. It works the following way:
db.collection.findOne({
$where: function() {
var search = 'searchstring';
for (var key in this) {
if (this[key] === search) {
return true;
}
return false;
}
}
});
However, I would like to define the search variable outside the query.
But when I do so, I get an error that it is not referenced (i.e. scoping issue):
"ReferenceError: search is not defined near '[key] === search
How can I use or pass the variable to the query filter?
You can try something like this:
var searchstring = 'whatever';
var params = {};
params.$where = 'function() {' +
'var search = "' + searchstring + '";' +
'for (var key in this) {' +
'if (this[key] === search) {' +
'return true;' +
'}' +
'return false;' +
'}' +
'}';
db.collection.findOne(params);
(Stringify your function and concat with external variable)
Worked for me via mongoose
search is a variable that you define in your client, may be the shell, or any client API.
The function that you define for the $where clause, will not be executed on the client side, but on the mongodb server.
so, when the function is being interpreted in the server side and it looks for the search variable, it was never defined on the server and hence you get the error.
In your case, you want the variable search, to be replaced with its content, by the client, before being executed on the server. And this is not possible, unless you build the function content itself in the client side.
The client never really interprets anything you write inside the anonymous function. The server does. The error you see is from the server. You can validate it by firing this query and looking onto the server logs:
2015-12-22T19:03:44.011-0800 I QUERY [conn1] assertion 16722 ReferenceError:
searc is not defined
at _funcs1 (_funcs1:1:39) near 's.key === searc){retu' ns:test.t query:{ $w
here: function (){if(this.key === searc){return true}} }
There is a better way to write what you wish to achieve using the $exists operator.
var search = "title";
var find = {};
find[search] = {$exists:true};
db.collection.findOne(find);
This works, because, you build the query parameter fully on the client side, before passing it on to the findOne() method.
You can solve this problem using mongodb map reduce and scope functionality. Scope allows you to pass variables into map reduce job.
function map() {
for (var key in this) {
if (this[key] === search) {
return emit(this._id, this);
}
}
}
function reduce(key, values) {
return values[0];
}
db.collection.mapReduce(map, reduce, {
out: {inline: 1},
scope: {
search: 'searchstring'
}
}
);
I'm trying to compare a new object with the original using CloudCode beforeSave function. I need to compare a field sent in the update with the existing value. The problem is that I can't fetch the object correctly. When I run the query I always get the value from the sent object.
UPDATE: I tried a different approach and could get the old register ( the one already saved in parse). But the new one, sent in the request, was overridden by the old one. WHAT?! Another issue is that, even thought the code sent a response.success(), the update wasn't saved.
I believe that I'm missing something pretty obvious here. Or I'm facing a bug or something...
NEW APPROACH
Parse.Cloud.beforeSave('Tasks', function(request, response) {
if ( !request.object.isNew() )
{
var Task = Parse.Object.extend("Tasks");
var newTask = request.object;
var oldTask = new Task();
oldTask.set("objectId", request.object.id);
oldTask.fetch()
.then( function( oldTask )
{
console.log(">>>>>> Old Task: " + oldTask.get("name") + " version: " + oldTask.get("version"));
console.log("<<<<<< New Task: " + newTask.get("name") + " version: " + newTask.get("version"));
response.success();
}, function( error ) {
response.error( error.message );
}
);
}
});
OBJ SENT {"name":"LLL", "version":333}
LOG
I2015-10-02T22:04:07.778Z]v175 before_save triggered for Tasks for user tAQf1nCWuz:
Input: {"original":{"createdAt":"2015-10-02T17:47:34.143Z","name":"GGG","objectId":"VlJdk34b2A","updatedAt":"2015-10-02T21:57:37.765Z","version":111},"update":{"name":"LLL","version":333}}
Result: Update changed to {}
I2015-10-02T22:04:07.969Z]>>>>>> Old Task: GGG version: 111
I2015-10-02T22:04:07.970Z]<<<<<< New Task: GGG version: 111
NOTE: I'm testing the login via cURL and in the parse console.
CloudCode beforeSave
Parse.Cloud.beforeSave("Tasks", function( request, response) {
var query = new Parse.Query("Tasks");
query.get(request.object.id)
.then(function (oldObj) {
console.log("-------- OLD Task: " + oldObj.get("name") + " v: " + oldObj.get("version"));
console.log("-------- NEW Task: " + request.object.get("name") + " v: " + request.object.get("version"));
}).then(function () {
response.success();
}, function ( error) {
response.error(error.message);
}
);
});
cURL request
curl -X PUT \
-H "Content-Type: application/json" \
-H "X-Parse-Application-Id: xxxxx" \
-H "X-Parse-REST-API-Key: xxxxx" \
-H "X-Parse-Session-Token: xxxx" \
-d "{\"name\":\"NEW_VALUE\", \"version\":9999}" \
https://api.parse.com/1/classes/Tasks/VlJdk34b2A
JSON Response
"updatedAt": "2015-10-02T19:45:47.104Z"
LOG
The log prints the original and the new value, but I don't know how to access it either.
I2015-10-02T19:57:08.603Z]v160 before_save triggered for Tasks for user tAQf1nCWuz:
Input: {"original":{"createdAt":"2015-10-02T17:47:34.143Z","name":"OLD_VALUE","objectId":"VlJdk34b2A","updatedAt":"2015-10-02T19:45:47.104Z","version":0},"update":{"name":"NEW_VALUE","version":9999}}
Result: Update changed to {"name":"NEW_VALUE","version":9999}
I2015-10-02T19:57:08.901Z]-------- OLD Task: NEW_VALUE v: 9999
I2015-10-02T19:57:08.902Z]-------- NEW Task: NEW_VALUE v: 9999
After a lot test and error I could figure out what was going on.
Turn out that Parse is merging any objects with the same class and id into one instance. That was the reason why I always had either the object registered in DB or the one sent by the user. I honestly can't make sense of such behavior, but anyway...
The Parse javascript sdk offers an method called Parse.Object.disableSingeInstance link that disables this "feature". But, once the method is called, all object already defined are undefined. That includes the sent object. Witch means that you can't neither save the sent object for a later reference.
The only option was to save the key and values of the sent obj and recreate it later. So, I needed to capture the request before calling disableSingleInstance, transform it in a JSON, then disable single instance, fetch the object saved in DB and recreate the sent object using the JSON saved.
Its not pretty and definitely isn't the most efficient code, but I couldn't find any other way. If someone out there have another approach, by all means tell me.
Parse.Cloud.beforeSave('Tasks', function(request, response) {
if ( !request.object.isNew() ) {
var id = request.object.id;
var jsonReq;
var Task = Parse.Object.extend("Tasks");
var newTask = new Task;
var oldTask = new Task;
// getting new Obj
var queryNewTask = new Parse.Query(Task);
queryNewTask.get(id)
.then(function (result) {
newTask = result;
// Saving values as a JSON to later reference
jsonReq = result.toJSON();
// Disable the merge of obj w/same class and id
// It will also undefine all Parse objects,
// including the one sent in the request
Parse.Object.disableSingleInstance();
// getting object saved in DB
oldTask.set("objectId", id);
return oldTask.fetch();
}).then(function (result) {
oldTask = result;
// Recreating new Task sent
for ( key in jsonReq ) {
newTask.set( key, jsonReq[key]);
}
// Do your job here
}, function (error) {
response.error( error.message );
}
);
}
});
If I were you, I would pass in the old value as a parameter to the cloud function so that you can access it under request.params.(name of parameter). I don't believe that there is another way to get the old value. An old SO question said that you can use .get(), but you're claiming that that is not working. Unless you actually already had 9999 in the version...
edit - I guess beforeSave isn't called like a normal function... so create an "update version" function that passes in the current Task and the version you're trying to update to, perhaps?
Rather than performing a query, you can see the modified attributes by checking which keys are dirty, meaning they have been changed but not saved yet.
The JS SDK includes dirtyKeys(), which returns the keys that have been changed. Try this out.
var attributes = request.object.attributes;
var changedAttributes = new Array();
for(var attribute in attributes) {
if(object.dirty(attribute)) {
changedAttributes.push(attribute);
// object.get(attribute) is changed and the key is pushed to the array
}
}
For clarification, to get the original attribute's value, you will have to call get() to load those pre-save values. It should be noted that this will count as another API request.
Hey this worked perfectly for me :
var dirtyKeys = request.object.dirtyKeys();
var query = new Parse.Query("Question");
var clonedData = null;
query.equalTo("objectId", request.object.id);
query.find().then(function(data){
var clonedPatch = request.object.toJSON();
clonedData = data[0];
clonedData = clonedData.toJSON();
console.log("this is the data : ", clonedData, clonedPatch, dirtyKeys);
response.success();
}).then(null, function(err){
console.log("the error is : ", err);
});
For those coming to this thread in 2021-ish, if you have the server data loaded in the client SDK before you save, you can resolve this issue by passing that server data from the client SDK in the context option of the save() function and then use it in the beforeSave afterSave cloud functions.
// eg JS client sdk
const options = {
context: {
before: doc._getServerData() // object data, as loaded
}
}
doc.save(null, options)
// #beforeSave cloud fn
Parse.Cloud.beforeSave(className, async (request) => {
const { before } = request.context
// ... do something with before ...
})
Caveat: this wouldn't help you if you didn't have the attributes loaded in the _getServerData() function in the client
Second Caveat: parse will not handle (un)serialization for you in your cloud function, eg:
{
before: { // < posted as context
status: {
is: 'atRisk',
comment: 'Its all good now!',
at: '2021-04-09T15:39:04.907Z', // string
by: [Object] // pojo
}
},
after: {
status: { // < posted as doc's save data
is: 'atRisk',
comment: 'Its all good now!',
at: 2021-04-09T15:39:04.907Z, // instanceOf Date
by: [ParseUser] // instanceOf ParseUser
}
}
}
Thanks to #asgoth, I am able to use AngularJS $http service to retrieve stock prices from Yahoo as described here: Cannot read response from AngularJS $resource JSONP get from Yahoo Finance
In the "getHistoricalPrice" function, it puts the price inside an array, which is inside an object. From inside that function, I am able to access the price and write it to console.
The function returns the object to where it is called from. From there, I can successfully write the entire object out to console. However, I cannot access the elements of this object. I tried many different ways, but still cannot access the data in the object. You can see the code at http://jsfiddle.net/curt00/LTazR/2/ or below:
angular.module('app', ['ngResource']);
function AppCtrl($scope, $http, $resource) {
var historical_price = getHistoricalPrice("AAPL", 'start date is hard coded', 'end date is hard coded');
console.log("after calling historical price: ", historical_price); // historical_price is an object and all of the correct data is outputted to console here, but I cannot access its elements directly from Javascript.
for(var key in historical_price) {
console.log("key =",key); // this outputs "key = list"
}
console.log("after calling getHistoricalPrice: ", historical_price.list[0][1]); // Cannot access this as browser console gives error: TypeError: Cannot read property '1' of undefined
console.log("after calling getHistoricalPrice: ", historical_price['list'][0][1]); // Cannot access this as browser console gives error: TypeError: Cannot read property '1' of undefined
console.log("after calling getHistoricalPrice: ", historical_price[0][1]); // Cannot access this as browser console gives error: TypeError: Cannot read property '1' of undefined
function getHistoricalPrice(symbol, start, end) {
var query = 'select * from csv where url=\'http://ichart.yahoo.com/table.csv?s=' + symbol + '&a=' + '11' + '&b=' + '19' + '&c=' + '2012' + '&d=' + '11' + '&e=' + '19' + '&f=' + '2012' + '&g=d&ignore=.csv\'';
var url = 'http://query.yahooapis.com/v1/public/yql?q=' + fixedEncodeURIComponent(query) + '&format=json&callback=JSON_CALLBACK';
var histData = {};
$http.jsonp(url, {timeout: 30000}).success(function(json) {
var list = [];
var result = json.query.results.row;
result.shift(); // remove the header (columns) row
angular.forEach(result, function(row) {
list.push([(new Date(row.col0)).getTime()/1000, parseFloat(row.col4)]);
});
list.sort(function(val1, val2) {
return val1[0] - val2[0];
});
histData.list = list;
console.log('Loaded historical data',histData.list[0][1],', for ' + symbol); // This works and gives the price
});
return histData;
}
var fixedEncodeURIComponent = function(str) {
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
};
}
Any help or suggestions to solve this problem is greatly appreciate!
It's a matter of timing.
In lines 12-14 you are trying to access histData.list before it has been populated. This is because this code is run before the success callback to the $http.jsonp function is executed.
Any code that depends on that callback being completed must be in the callback or in a function called in the callback.
See my answer on https://stackoverflow.com/a/13967709/1916258
A great way to debug the Yahoo api is using the YQL Console: http://developer.yahoo.com/yql/console/
Info about the different posibilities (which stock info) can be found on http://www.gummy-stuff.org/Yahoo-data.htm
Edit: there was still a problem with function fixedEncodeURIComponent. It should encode quotes (") too:
var fixedEncodeURIComponent = function(str) {
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A").replace(/\"/g, "%22");
};
BobS is right, you aren't timing things correctly. Also you declared fixedEncodeURIComponent after you had called it. This was resulting in an immediate error when I loaded up the jsfiddle.
While you were passing the callback through to your function correctly, you weren't actually calling it. I stripped out all the post processing of the json as you have some other errors involving the query and just implemented the callback so you can see it working.
After the request is finished and you're still in the success function you need to add
if(typeof(callback) === "function"){
callback();
}
This calls that function you passed in and runs it. Here is a working jsFiddle of it:
http://jsfiddle.net/LTazR/22/
I also updated a new variable i created call output so you can see it changing.
Thanks to everybody for providing suggestions.
I solved the problem by using AngularJS' $scope variable, such as $scope.symbol[user].price. I created this variable before calling the getHistoricalPrice function and then in that function, after the result is returned from $http.jsonp, I put the value into the $scope variable, as such:
$scope.symbol[user].price = row.col4;
I would like to extend the 'console.log' function to add additional information to its output - but I dont want to affect the script name/line number information generated by the browser in the console window. See how if I create my own implementation, I get useless trace information, should I want to locate that region of code... (they all link to the log implementation, not the actual script that caused the log message)
Basically, my application is a very pluggable infrastructure, were any log output may occur within any number of frames.
As such, I want every log message to include a special unique identifier at the beginning of the log message.
I have tried replacing the console.log method with my own, but chrome complains with
Uncaught TypeError: Illegal invocation
this is how I override it
var orig = console.log;
console.log = function( message )
{
orig( (window == top ? '[root]' : '[' + window.name + ']') + ': ' + message );
}
Any ideas?
[EDIT]
Note: After fixing the 'illegal invocation' problem, it seems the filename/linenumber is still 'polluted' by the override...
[EDIT]
It looks like the general answer is - NO - despite some confusing goose chases, the desired functionality is NOT achievable in the current versions of browsers.
Yes, it is possible to add information without messing up the originating line numbers of the log invocation. Some of the other answers here came close, but the trick is to have your custom logging method return the modified logger. Below is a simple example that was only moderately tested that uses the context variant.
log = function() {
var context = "My Descriptive Logger Prefix:";
return Function.prototype.bind.call(console.log, console, context);
}();
This can be used with:
log("A log message...");
Here is a jsfiddle: http://jsfiddle.net/qprro98v/
One could get easily get creative and pass the context variable in, and remove the auto-executing parens from the function definition. i.e. log("DEBUG:")("A debug message"), log("INFO:")("Here is some info"), etc.
The only really import part about the function (in regards to line numbers) is that it returns the logger.
If your use case can deal with a few restrictions, there is a way that this can be made to work. The restrictions are:
The extra log content has to be calculated at bind time; it cannot be time sensitive or depend on the incoming log message in any way.
The extra log content can only be place at the beginning of the log message.
With these restrictions, the following may work for you:
var context = "ALIASED LOG:"
var logalias;
if (console.log.bind === 'undefined') { // IE < 10
logalias = Function.prototype.bind.call(console.log, console, context);
}
else {
logalias = console.log.bind(console, context);
}
logalias('Hello, world!');
http://jsfiddle.net/Wk2mf/
An acceptable solution can be to make your own log-function that returns a console.log function bound with the log arguments.
log = function() {
// Put your extension code here
var args = Array.prototype.slice.call(arguments);
args.unshift(console);
return Function.prototype.bind.apply(console.log, args);
}
// Note the extra () to call the original console.log
log("Foo", {bar: 1})();
This way the console.log call will be made from the correct line, and will be displayed nicely in the console, allowing you to click on it and everything.
It is actually possible in chrome at least. Here is the most relevant. This may vary depending on setup, and how i got the splits was to just log the whole stack, and find the information I needed.
var stack = new Error().stack;
var file = stack.split("\n")[2].split("/")[4].split("?")[0]
var line = stack.split("\n")[2].split(":")[5];
Here is the whole thing, preserving the native object logging.
var orig = console.log
console.log = function(input) {
var isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
if(isChrome){
var stack = new Error().stack;
var file = stack.split("\n")[2].split("/")[4].split("?")[0]
var line = stack.split("\n")[2].split(":")[5];
var append = file + ":" + line;
}
orig.apply(console, [input, append])
}
You need to call the console.log with the correct context (console):
orig.call(console, message);
To complete your function allowing multiple arguments:
var orig = console.log;
console.log = function() {
var msgs = [],
prefix = (window== top ? '[root]' : '[' + window.name + ']');
while(arguments.length) {
msgs.push(prefix + ': ' + [].shift.call(arguments));
}
orig.apply(console, msgs);
};
Demo: http://jsfiddle.net/je2wR/
Remember that you loose the built-in object/array browser in the console when combining objects with strings using the + sign.
I just answered this on a post that helped me answer the original 'alias' question:
(http://stackoverflow.com/a/12942764/401735)
my_log_alias = console.log.bind(console)
Apparently the capacity to do this has been designed in. Tested. Works.
thereafter my_log_alias is the same as console.log and can be called in the same way; Calling this from inside the function will report the line number for that function call, including the line inside of an alias or advice function where applicable.
Specifically, the line number Chrome provides will tell you the file the line is in, so what you are doing may be unneccesary; Consider reporting this as a bug/feature request in chrome that it provide this info in console.log.
Christopher Currie provided an excellent solution. I've expanded it a bit for my needs. Here's the AMD module:
define([], function () {
var enableDebug = true;
var separator = ">";
function bind(f, thisArg, ctx) {
if (f.bind !== 'undefined') { // IE < 10
return Function.prototype.bind.call(f, thisArg, ctx);
}
else {
return f.bind(thisArg, ctx);
}
}
function newConsole(context, parentConsole) {
var log;
var debug;
var warn;
var error;
if (!parentConsole) {
parentConsole = console;
}
context = context + separator;
if (enableDebug) {
debug = bind(console.log, console, context + "DEBUG" + separator);
} else {
debug = function () {
// suppress all debug messages
};
}
log = bind(console.log, console, context);
warn = bind(console.warn, console, context);
error = bind(console.error, console, context);
return {
debug: debug,
info: log,
log: log,
warn: warn,
error: error,
/* access console context information */
context: context,
/* create a new console with nested context */
nest: function (subContext) {
return newConsole(context + subContext, this);
},
parent: parentConsole
};
}
return newConsole("");
});
By default this will output > {message}. You can also add nested context to you logging, e.g. console.nest("my").log("test") will output >my> test.
I've also added a debug function that will indent messages with >DEBUG>
Hope somebody will find it useful.
Not long ago Chrome introduced a feature that can solve your problem without code hacks. It is called "blackbox" which basically allows you to mark files which should be ignored with their tools.
https://gist.github.com/paulirish/c307a5a585ddbcc17242
Yes, this solution is browser specific, but if you are using Chrome you do want this solution.
The solutions with a huge hack around throwing an Error for each log can show the right line, but it will not be a clickable link in your console.
The solutions based on binding/aliasing only enables you to modify the printed text. You will not be able to forward the arguments to a third function for further processing.
I have looked into this several times and always found it was not possible.
My workaround if you are interested is to assign console to another variable and then wrap all my log messages in a function which lets me modify/style/whatever on the message.
It looks nice with CoffeeScript, not sure its practical with plain JS.
I just get into the habit of prefixing everything with x.
logger.debug x 'Foo'
log x 'Bar'
log x('FooBar %o'), obj
Unfrotuantly it's currenlty not possible, In the future we might be able to do it with the Proxy object in ECMAScript 6.
My use case was to auto-prefix console messages with helpful information like the arguments passed and executing method. at the moment the closest I got is using Function.prototype.apply.
A simple approach is to just write your debug statements as such:
console.info('=== LazyLoad.css(', arguments, '): css files are skipped, gives us a clean slate to style within theme\'s CSS.');
A complicated approach is to use helper function as per below, I personally now prefer the simple approach.
/* Debug prefixing function
* ===========================
*
* A helper used to provide useful prefixing information
* when calling `console.log`, `console.debug`, `console.error`.
* But the catch is that to utilize one must leverage the
* `.apply` function as shown in the below examples.
*
* ```
* console.debug.apply(console, _fDebugPrefix(arguments)
* .concat('your message'));
*
* // or if you need to pass non strings
* console.debug.apply(console, _fDebugPrefix(arguments)
* .concat('json response was:', oJson));
*
*
* // if you need to use strict mode ("use strict") one can't
* // extract the function name but following approach works very
* // well; updating the name is just a matter of search and replace
* var aDebugPrefix = ['fYourFunctionName('
* ,Array.prototype.slice.call(arguments, 0),
* ,')'];
* console.debug.apply(console,
* aDebugPrefix.concat(['json response was:', oJson]));
* ```
*/
function _fDebugPrefix(oArguments) {
try {
return [oArguments.callee.name + '('
,Array.prototype.slice.call(oArguments, 0)
, ')'];
}
catch(err) { // are we in "use strict" mode ?
return ['<callee.name unsupported in "use strict">('
,Array.prototype.slice.call(oArguments, 0)
, ')'];
}
}
Reusable class in TS/JS
// File: LogLevel.ts
enum LogLevel {
error = 0,
warn,
info,
debug,
verbose,
}
export default LogLevel;
// File: Logger.js
import LogLevel from "./LogLevel";
export default class Logger {
static id = "App";
static level = LogLevel.info;
constructor(id) {
this.id = id;
const commonPrefix = `[${Logger.id}/${this.id}]`;
const verboseContext = `[V]${commonPrefix}`;
if (console.log.bind === "undefined") {
// IE < 10
this.verbose = Function.prototype.bind.call(console.log, console, verboseContext);
} else {
this.verbose = console.log.bind(console, verboseContext);
}
if (LogLevel.verbose > Logger.level) {
this.verbose = function() {
return // Suppress
};
}
const debugContext = `[D]${commonPrefix}`;
if (console.debug.bind === "undefined") {
// IE < 10
this.debug = Function.prototype.bind.call(console.debug, console, debugContext);
} else {
this.debug = console.debug.bind(console, debugContext);
}
if (LogLevel.debug > Logger.level) {
this.debug = function() {
return // Suppress
};
}
const infoContext = `[I]${commonPrefix}`;
if (console.info.bind === "undefined") {
// IE < 10
this.info = Function.prototype.bind.call(console.info, console, infoContext);
} else {
this.info = console.info.bind(console, infoContext);
}
if (LogLevel.info > Logger.level) {
this.info = function() {
return // Suppress
};
}
const warnContext = `[W]${commonPrefix}`;
if (console.warn.bind === "undefined") {
// IE < 10
this.warn = Function.prototype.bind.call(console.warn, console, warnContext);
} else {
this.warn = console.warn.bind(console, warnContext);
}
if (LogLevel.warn > Logger.level) {
this.warn = function() {
return // Suppress
};
}
const errorContext = `[E]${commonPrefix}`;
if (console.error.bind === "undefined") {
// IE < 10
this.error = Function.prototype.bind.call(console.error, console, errorContext);
} else {
this.error = console.error.bind(console, errorContext);
}
if (LogLevel.error > Logger.level) {
this.error = function() {
return // Suppress
};
}
}
}
Usage (React):
// File: src/index.tsx
// ...
Logger.id = "MCA"
const env = new Env()
if (env.env == Environment.dev) {
Logger.level = LogLevel.verbose
const log = new Logger("Main")
log.info("Environment is 'Development'")
}
///...
// File: src/App/CookieConsent/index.tsx
import React, { useEffect } from "react";
import { useCookies } from "react-cookie";
import "./index.scss";
import Logger from "#lib/Logger" // #lib is just alias configured in webpack.
const cookieName = "mca-cookie-consent";
// const log = new Logger(CookieConsent.name) // IMPORTANT! Don't put log instance here. It is too early! Put inside function.
export default function CookieConsent(): JSX.Element {
const log = new Logger(CookieConsent.name) // IMPORTANT! Have to be inside function, not in global scope (after imports)
useEffect(() => {
log.verbose(`Consent is accepted: ${isAccepted()}`);
}, []);
const [cookie, setCookie] = useCookies([cookieName]);
function isAccepted(): boolean {
return cookie[cookieName] != undefined;
}
function containerStyle(): React.CSSProperties {
return isAccepted() ? { display: "none" } : {};
}
function handleClick() {
const expires = new Date();
expires.setFullYear(expires.getFullYear() + 1);
log.verbose(`Accepted cookie consent. Expiration: ${expires}`)
setCookie(cookieName, true, { path: "/", expires: expires, sameSite: "lax" });
}
return (
<div className="cookieContainer" style={containerStyle()}>
<div className="cookieContent">
<div>
<p className="cookieText">This website uses cookies to enhance the user experience.</p>
</div>
<div>
<button onClick={handleClick} className="cookieButton">
I understand
</button>
</div>
</div>
</div>
);
}
Output in browser console:
20:47:48.190 [I][MCA/Main] Environment is 'Development' index.tsx:19
20:47:48.286 [V][MCA/CookieConsent] Consent is accepted: false index.tsx:13
20:47:52.250 [V][MCA/CookieConsent] Accepted cookie consent. Expiration: Sun Jan 30 2022 20:47:52 GMT+0100 (Central European Standard Time) index.tsx:29
Hope this helps for some of your cases...
const log = console.log;
export default function middleWare(optionalStringExtension = '') {
console.log = (...args) => {
log(...args, optionalStringExtension);
}
}
Either run as middleware, top of file, or first line of function.
I ran into this issue as well about extending console.log() so that the application can extend, control and do fancy stuff with it in addition to logging stuff to the console. Losing the line number information was tantamount to failure, however. After wrestling with the issue, I came up with a long-winded workaround, but at least it's still a "1-liner" to use.
First, define a global class to use or add some methods to your main existing "app" class:
/**
* Log message to our in-app and possibly on-screen console, return args.
* #param {!string} aMsgLevel - one of "log", "debug", "info", "warn", or "error"
* #param {any} aArgs - the arguments to log (not used directly, just documentation helper)
* #returns args so it can be nested within a console.log.apply(console,app.log()) statement.
*/
MyGlobalClassWithLogMethods.prototype.debugLog = function(aMsgLevel, aArgs) {
var s = '';
var args = [];
for (var i=1; i<arguments.length; i++) {
args.push(arguments[i]);
if (arguments[i])
s += arguments[i].toString()+' ';
}
if (typeof this.mLog === 'undefined')
this.mLog = [];
this.mLog.push({level: aMsgLevel, msg: s});
return args;
};
MyGlobalClassWithLogMethods.prototype.log = function() {
var args = ['log'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.debug = function() {
var args = ['debug'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.info = function() {
var args = ['info'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.warn = function() {
var args = ['warn'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.error = function() {
var args = ['error'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
//not necessary, but it is used in my example code, so defining it
MyGlobalClassWithLogMethods.prototype.toString = function() {
return "app: " + JSON.stringify(this);
};
Next, we put those methods to use like so:
//JS line done as early as possible so rest of app can use logging mechanism
window.app = new MyGlobalClassWithLogMethods();
//only way to get "line info" reliably as well as log the msg for actual page display;
// ugly, but works. Any number of params accepted, and any kind of var will get
// converted to str using .toString() method.
console.log.apply(console,app.log('the log msg'));
console.debug.apply(console,app.debug('the log msg','(debug)', app));
console.info.apply(console,app.info('the log msg','(info)'));
console.warn.apply(console,app.warn('the log msg','(warn)'));
console.error.apply(console,app.error('the log msg','(error)'));
Now the console gets log messages with their appropriate line information as well as our app contains an array of log messages that can be put to use. For example, to display your in-app log using HTML, JQuery and some CSS the following simplistic example can be used.
First, the HTML:
<div id="debug_area">
<h4 class="text-center">Debug Log</h4>
<ul id="log_list">
<!-- console log/debug/info/warn/error ('msg') lines will go here -->
</ul>
</div>
some CSS:
.log_level_log {
color: black;
background-color: white;
font-size: x-small;
}
.log_level_debug {
color: #060;
background-color: #80FF80;
font-size: x-small;
}
.log_level_info {
color: #00F;
background-color: #BEF;
font-size: x-small;
}
.log_level_warn {
color: #E65C00;
background-color: #FB8;
font-size: x-small;
}
.log_level_error {
color: #F00;
background-color: #FBB;
font-size: x-small;
}
and some JQuery:
var theLog = app.mLog || [];
if (theLog.length>0) {
var theLogList = $('#log_list');
theLogList.empty();
for (var i=0; i<theLog.length; i++) {
theLogList.prepend($('<li class="log_level_'+theLog[i].level+'"></li>').text(theLog[i].msg));
}
}
This is a simplistic use, but once you have the mechanism in place, you can do whatever your imagination can come up with, including leaving the log lines in the code, but setting a threshold so that only warnings and errors get through. Hopefully this helps others with their projects.
Today you have to use args with rest operator, because as the Mozilla docs says Function.arguments has been deprecated and is not accessible in arrow functions. So simply you can extend it like below:
//#1
const myLog= (...args) =>
console.log.bind(console, ...args);
//myLog("this is my new log")();
//#2
const myNewLog= (...args) =>{
const prefix = "Prefixed: ";
return console.log.bind(console, ...[prefix,...args]);
}
//myNewLog("test")()
And you can make a beautifulLog like this:
//#3
const colorizedLog = (text, color= "#40a7e3", ...args) =>
console.log.bind(
console,
`%c ${text}`,
`font-weight:bold; color:${color}`,
...args
);
//colorizedLog("Title:", "#40a7e3", "This is a working example")();
This snippet apply a prefix to logs for all levels (console.log console.debug console.info ...) :
export const makeConsole = (context: string, cons = console): Console =>
Object.getOwnPropertyNames(cons).reduce((c, lev) => {
if (typeof cons[lev] === "function") {
c[lev] = Function.prototype.bind.call(cons[lev], cons, context);
}
return c;
}, {});
console.debug("Hello world!")
// >> Hello world!
console = makeConsole("[logging is fun]")
// >> [logging is fun] Hello world!
Bonus, for React peeps:
export function useConsole(context: string): Console {
return React.useMemo(() => makeConsole(context), [context]);
}
Try setTimeout(console.log.bind(console,'foo'));