Merge branch 'master' into pythonTimeout
This commit is contained in:
commit
6e45c39b1f
1
NEWS.md
1
NEWS.md
@ -7,6 +7,7 @@ Announcements:
|
|||||||
* Change work in progress jobs endpoint from `[..]/job/wip` to `[..]/jobs-wip`
|
* Change work in progress jobs endpoint from `[..]/job/wip` to `[..]/jobs-wip`
|
||||||
* Documentation updates for Docs repo issue #840, GPKG Export.
|
* Documentation updates for Docs repo issue #840, GPKG Export.
|
||||||
* Fix SHP exports, now it uses "the_geom" column by default when a dataset has more than one geometry column.
|
* Fix SHP exports, now it uses "the_geom" column by default when a dataset has more than one geometry column.
|
||||||
|
* Logging all errors
|
||||||
|
|
||||||
|
|
||||||
## 1.47.1
|
## 1.47.1
|
||||||
|
@ -32,6 +32,8 @@ module.exports = function handleException(err, res) {
|
|||||||
res.header('X-SQLAPI-Profiler', req.profiler.toJSONString());
|
res.header('X-SQLAPI-Profiler', req.profiler.toJSONString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setErrorHeader(msg, pgErrorHandler.getStatus(), res);
|
||||||
|
|
||||||
res.header('Content-Type', 'application/json; charset=utf-8');
|
res.header('Content-Type', 'application/json; charset=utf-8');
|
||||||
res.status(getStatusError(pgErrorHandler, req));
|
res.status(getStatusError(pgErrorHandler, req));
|
||||||
if (req.query && req.query.callback) {
|
if (req.query && req.query.callback) {
|
||||||
@ -56,3 +58,35 @@ function getStatusError(pgErrorHandler, req) {
|
|||||||
|
|
||||||
return statusError;
|
return statusError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setErrorHeader(err, statusCode, res) {
|
||||||
|
let errorsLog = Object.assign({}, err);
|
||||||
|
|
||||||
|
errorsLog.statusCode = statusCode || 200;
|
||||||
|
errorsLog.message = errorsLog.error[0];
|
||||||
|
delete errorsLog.error;
|
||||||
|
|
||||||
|
res.set('X-SQLAPI-Errors', stringifyForLogs(errorsLog));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove problematic nested characters
|
||||||
|
* from object for logs RegEx
|
||||||
|
*
|
||||||
|
* @param {Object} object
|
||||||
|
*/
|
||||||
|
function stringifyForLogs(object) {
|
||||||
|
Object.keys(object).map(key => {
|
||||||
|
if(typeof object[key] === 'string') {
|
||||||
|
object[key] = object[key].replace(/[^a-zA-Z0-9]/g, ' ');
|
||||||
|
} else if (typeof object[key] === 'object') {
|
||||||
|
stringifyForLogs(object[key]);
|
||||||
|
} else if (object[key] instanceof Array) {
|
||||||
|
for (let element of object[key]) {
|
||||||
|
stringifyForLogs(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return JSON.stringify(object);
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ module.exports.base_url = '(?:/api/:version|/user/:user/api/:version)';
|
|||||||
// X-SQLAPI-Profile header containing elapsed timing for various
|
// X-SQLAPI-Profile header containing elapsed timing for various
|
||||||
// steps taken for producing the response.
|
// steps taken for producing the response.
|
||||||
module.exports.useProfiler = true;
|
module.exports.useProfiler = true;
|
||||||
module.exports.log_format = '[:date] :remote-addr :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-SQLAPI-Profiler])';
|
module.exports.log_format = '[:date] :remote-addr :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-SQLAPI-Profiler]) (:res[X-SQLAPI-Errors])';
|
||||||
// If log_filename is given logs will be written there, in append mode. Otherwise stdout is used (default).
|
// If log_filename is given logs will be written there, in append mode. Otherwise stdout is used (default).
|
||||||
// Log file will be re-opened on receiving the HUP signal
|
// Log file will be re-opened on receiving the HUP signal
|
||||||
module.exports.log_filename = 'logs/cartodb-sql-api.log';
|
module.exports.log_filename = 'logs/cartodb-sql-api.log';
|
||||||
|
@ -8,7 +8,7 @@ module.exports.base_url = '(?:/api/:version|/user/:user/api/:version)';
|
|||||||
// X-SQLAPI-Profile header containing elapsed timing for various
|
// X-SQLAPI-Profile header containing elapsed timing for various
|
||||||
// steps taken for producing the response.
|
// steps taken for producing the response.
|
||||||
module.exports.useProfiler = true;
|
module.exports.useProfiler = true;
|
||||||
module.exports.log_format = '[:date] :remote-addr :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-SQLAPI-Profiler])';
|
module.exports.log_format = '[:date] :remote-addr :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-SQLAPI-Profiler]) (:res[X-SQLAPI-Errors])';
|
||||||
// If log_filename is given logs will be written there, in append mode. Otherwise stdout is used (default).
|
// If log_filename is given logs will be written there, in append mode. Otherwise stdout is used (default).
|
||||||
// Log file will be re-opened on receiving the HUP signal
|
// Log file will be re-opened on receiving the HUP signal
|
||||||
module.exports.log_filename = 'logs/cartodb-sql-api.log';
|
module.exports.log_filename = 'logs/cartodb-sql-api.log';
|
||||||
|
@ -8,7 +8,7 @@ module.exports.base_url = '(?:/api/:version|/user/:user/api/:version)';
|
|||||||
// X-SQLAPI-Profile header containing elapsed timing for various
|
// X-SQLAPI-Profile header containing elapsed timing for various
|
||||||
// steps taken for producing the response.
|
// steps taken for producing the response.
|
||||||
module.exports.useProfiler = true;
|
module.exports.useProfiler = true;
|
||||||
module.exports.log_format = '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-SQLAPI-Profiler])';
|
module.exports.log_format = '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-SQLAPI-Profiler]) (:res[X-SQLAPI-Errors])';
|
||||||
// If log_filename is given logs will be written there, in append mode. Otherwise stdout is used (default).
|
// If log_filename is given logs will be written there, in append mode. Otherwise stdout is used (default).
|
||||||
// Log file will be re-opened on receiving the HUP signal
|
// Log file will be re-opened on receiving the HUP signal
|
||||||
module.exports.log_filename = 'logs/cartodb-sql-api.log';
|
module.exports.log_filename = 'logs/cartodb-sql-api.log';
|
||||||
|
@ -8,7 +8,7 @@ module.exports.base_url = '(?:/api/:version|/user/:user/api/:version)';
|
|||||||
// X-SQLAPI-Profile header containing elapsed timing for various
|
// X-SQLAPI-Profile header containing elapsed timing for various
|
||||||
// steps taken for producing the response.
|
// steps taken for producing the response.
|
||||||
module.exports.useProfiler = true;
|
module.exports.useProfiler = true;
|
||||||
module.exports.log_format = '[:date] :remote-addr :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-SQLAPI-Profiler])';
|
module.exports.log_format = '[:date] :remote-addr :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-SQLAPI-Profiler]) (:res[X-SQLAPI-Errors])';
|
||||||
// If log_filename is given logs will be written there, in append mode. Otherwise stdout is used (default).
|
// If log_filename is given logs will be written there, in append mode. Otherwise stdout is used (default).
|
||||||
// Log file will be re-opened on receiving the HUP signal
|
// Log file will be re-opened on receiving the HUP signal
|
||||||
// module.exports.log_filename = 'logs/cartodb-sql-api.log';
|
// module.exports.log_filename = 'logs/cartodb-sql-api.log';
|
||||||
|
@ -77,3 +77,21 @@ https://{username}.carto.com/api/v2/sql?format=csv&q=SELECT+*+FROM+tm_world_bord
|
|||||||
```
|
```
|
||||||
|
|
||||||
The response creates a direct dataset URL that you can download for use offline.
|
The response creates a direct dataset URL that you can download for use offline.
|
||||||
|
|
||||||
|
### Why can't I see my created tables in my CARTO account?
|
||||||
|
|
||||||
|
The SQL API automatically displays tables in CARTO if you follow these steps:
|
||||||
|
|
||||||
|
- Create a table that has been "cartodbfied", which prepares your table to be compatible with CARTO. View [Creating Tables with the SQL API](https://carto.com/docs/carto-engine/sql-api/creating-tables/#creating-tables-with-the-sql-api) to learn about this function.
|
||||||
|
|
||||||
|
- After creating your "cartodbfied" table, you must login to your CARTO account and open _Your datasets_ dashboard. Logging in initiates a check between the database and your account.
|
||||||
|
|
||||||
|
**Note:** There is an expected refresh time while the database is checking your account and your tables may not appear at this time, especially if there are a lot of tables or tables with a large amount of rows.
|
||||||
|
|
||||||
|
- Once the database updates, CARTO will display your created or changed tables as connected datasets!
|
||||||
|
|
||||||
|
### What happens if I remove a table that is used in an existing map?
|
||||||
|
|
||||||
|
If you [drop](https://carto.com/docs/carto-engine/sql-api/creating-tables/#remove-a-table) a table using the SQL API, be mindful that there is no warning that the table may be used in an existing map. This is by design. Any maps using a removed table will be missing data and thus, will be deleted.
|
||||||
|
|
||||||
|
If you are unsure about which tables are connected to maps, it is suggested to remove tables from _Your datasets_ dashboard in CARTO, which automatically notifies you of any connected maps in use.
|
||||||
|
31
test/acceptance/error-handler.js
Normal file
31
test/acceptance/error-handler.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
var server = require('../../app/server')();
|
||||||
|
var assert = require('../support/assert');
|
||||||
|
|
||||||
|
describe('error handler', function () {
|
||||||
|
it('should returns a errors header', function (done) {
|
||||||
|
const errorHeader = {
|
||||||
|
detail: undefined,
|
||||||
|
hint: undefined,
|
||||||
|
context: undefined,
|
||||||
|
statusCode: 400,
|
||||||
|
message: 'You must indicate a sql query'
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.response(server, {
|
||||||
|
url: '/api/v1/sql',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
'X-SQLAPI-Errors': JSON.stringify(errorHeader)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(err){
|
||||||
|
assert.ifError(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
137
test/unit/error_handler.test.js
Normal file
137
test/unit/error_handler.test.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var errorHandler = require('../../app/utils/error_handler');
|
||||||
|
|
||||||
|
describe('error-handler', function() {
|
||||||
|
it('should return a header with errors', function (done) {
|
||||||
|
let error = new Error('error test');
|
||||||
|
error.detail = 'test detail';
|
||||||
|
error.hint = 'test hint';
|
||||||
|
error.context = 'test context';
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
req: {},
|
||||||
|
headers: {},
|
||||||
|
set (key, value) {
|
||||||
|
this.headers[key] = value;
|
||||||
|
},
|
||||||
|
header (key, value) {
|
||||||
|
this.set(key, value);
|
||||||
|
},
|
||||||
|
statusCode: 0,
|
||||||
|
status (status) {
|
||||||
|
this.statusCode = status;
|
||||||
|
},
|
||||||
|
json () {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorHeader = {
|
||||||
|
detail: error.detail,
|
||||||
|
hint: error.hint,
|
||||||
|
context: error.context,
|
||||||
|
statusCode: 400,
|
||||||
|
message: error.message
|
||||||
|
};
|
||||||
|
|
||||||
|
errorHandler(error, res);
|
||||||
|
|
||||||
|
assert.ok(res.headers['X-SQLAPI-Errors'].length > 0);
|
||||||
|
assert.deepEqual(
|
||||||
|
res.headers['X-SQLAPI-Errors'],
|
||||||
|
JSON.stringify(errorHeader)
|
||||||
|
);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('JSONP should return a header with error statuscode', function (done) {
|
||||||
|
let error = new Error('error test');
|
||||||
|
error.detail = 'test detail';
|
||||||
|
error.hint = 'test hint';
|
||||||
|
error.context = 'test context';
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
req: {
|
||||||
|
query: { callback: true }
|
||||||
|
},
|
||||||
|
headers: {},
|
||||||
|
set (key, value) {
|
||||||
|
this.headers[key] = value;
|
||||||
|
},
|
||||||
|
header (key, value) {
|
||||||
|
this.set(key, value);
|
||||||
|
},
|
||||||
|
statusCode: 0,
|
||||||
|
status (status) {
|
||||||
|
this.statusCode = status;
|
||||||
|
},
|
||||||
|
jsonp () {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorHeader = {
|
||||||
|
detail: error.detail,
|
||||||
|
hint: error.hint,
|
||||||
|
context: error.context,
|
||||||
|
statusCode: 400,
|
||||||
|
message: error.message
|
||||||
|
};
|
||||||
|
|
||||||
|
errorHandler(error, res);
|
||||||
|
|
||||||
|
assert.ok(res.headers['X-SQLAPI-Errors'].length > 0);
|
||||||
|
assert.deepEqual(
|
||||||
|
res.headers['X-SQLAPI-Errors'],
|
||||||
|
JSON.stringify(errorHeader)
|
||||||
|
);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should escape chars that broke logs regex', function (done) {
|
||||||
|
const badString = 'error: ( ) = " \" \' * $ & |';
|
||||||
|
const escapedString = 'error ';
|
||||||
|
|
||||||
|
let error = new Error(badString);
|
||||||
|
error.detail = badString;
|
||||||
|
error.hint = badString;
|
||||||
|
error.context = badString;
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
req: {
|
||||||
|
query: { callback: true }
|
||||||
|
},
|
||||||
|
headers: {},
|
||||||
|
set (key, value) {
|
||||||
|
this.headers[key] = value;
|
||||||
|
},
|
||||||
|
header (key, value) {
|
||||||
|
this.set(key, value);
|
||||||
|
},
|
||||||
|
statusCode: 0,
|
||||||
|
status (status) {
|
||||||
|
this.statusCode = status;
|
||||||
|
},
|
||||||
|
jsonp () {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorHeader = {
|
||||||
|
detail: escapedString,
|
||||||
|
hint: escapedString,
|
||||||
|
context: escapedString,
|
||||||
|
statusCode: 400,
|
||||||
|
message: escapedString
|
||||||
|
};
|
||||||
|
|
||||||
|
errorHandler(error, res);
|
||||||
|
|
||||||
|
assert.ok(res.headers['X-SQLAPI-Errors'].length > 0);
|
||||||
|
assert.deepEqual(
|
||||||
|
res.headers['X-SQLAPI-Errors'],
|
||||||
|
JSON.stringify(errorHeader)
|
||||||
|
);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user