762a240890
- Log system revamp: - Logs to stdout, disabled while testing - Use header `X-Request-Id`, or create a new `uuid` when no present, to identyfy log entries - Be able to set log level from env variable `LOG_LEVEL`, useful while testing: `LOG_LEVEL=info npm test`; even more human-readable: `LOG_LEVEL=info npm t | ./node_modules/.bin/pino-pretty` - Be able to reduce the footprint in the final log file depending on the environment - Use one logger for every service: Queries, Batch Queries (Jobs), and Data Ingestion (CopyTo/CopyFrom) - Stop using headers such as: `X-SQL-API-Log`, `X-SQL-API-Profiler`, and `X-SQL-API-Errors` as a way to log info. - Be able to tag requests with labels as an easier way to provide business metrics - Metro: Add log-collector utility (`metro`), it will be moved to its own repository. Attaching it here fro development purposes. Try it with the following command `LOG_LEVEL=info npm t | node metro` - Metro: Creates `metrics-collector.js` a stream to update Prometheus' counters and histograms and exposes them via Express' app (`:9145/metrics`). Use the ones defined in `grok_exporter` Announcements: - Profiler is always set. No need to check its existence anymore - Unify profiler usage for every endpoint Bug fixes: - Avoid hung requests while fetching user identifier
129 lines
4.5 KiB
JavaScript
129 lines
4.5 KiB
JavaScript
'use strict';
|
|
|
|
const bodyParser = require('../middlewares/body-parser');
|
|
const user = require('../middlewares/user');
|
|
const rateLimits = require('../middlewares/rate-limit');
|
|
const authorization = require('../middlewares/authorization');
|
|
const connectionParams = require('../middlewares/connection-params');
|
|
const timeoutLimits = require('../middlewares/timeout-limits');
|
|
const params = require('../middlewares/params');
|
|
const log = require('../middlewares/log-query');
|
|
const cancelOnClientAbort = require('../middlewares/cancel-on-client-abort');
|
|
const affectedTables = require('../middlewares/affected-tables');
|
|
const accessValidator = require('../middlewares/access-validator');
|
|
const queryMayWrite = require('../middlewares/query-may-write');
|
|
const cacheControl = require('../middlewares/cache-control');
|
|
const cacheChannel = require('../middlewares/cache-channel');
|
|
const surrogateKey = require('../middlewares/surrogate-key');
|
|
const lastModified = require('../middlewares/last-modified');
|
|
const formatter = require('../middlewares/formatter');
|
|
const content = require('../middlewares/content');
|
|
const tag = require('../middlewares/tag');
|
|
|
|
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimits;
|
|
const PSQL = require('cartodb-psql');
|
|
|
|
module.exports = class QueryController {
|
|
constructor (metadataBackend, userDatabaseService, statsdClient, userLimitsService) {
|
|
this.metadataBackend = metadataBackend;
|
|
this.stats = statsdClient;
|
|
this.userDatabaseService = userDatabaseService;
|
|
this.userLimitsService = userLimitsService;
|
|
}
|
|
|
|
route (sqlRouter) {
|
|
const forceToBeMaster = false;
|
|
|
|
const queryMiddlewares = () => {
|
|
return [
|
|
tag({ tags: ['query'] }),
|
|
bodyParser(),
|
|
user(this.metadataBackend),
|
|
rateLimits(this.userLimitsService, RATE_LIMIT_ENDPOINTS_GROUPS.QUERY),
|
|
authorization(this.metadataBackend, forceToBeMaster),
|
|
connectionParams(this.userDatabaseService),
|
|
timeoutLimits(this.metadataBackend),
|
|
params({ strategy: 'query' }),
|
|
log(),
|
|
cancelOnClientAbort(),
|
|
affectedTables(),
|
|
accessValidator(),
|
|
queryMayWrite(),
|
|
cacheControl(),
|
|
cacheChannel(),
|
|
surrogateKey(),
|
|
lastModified(),
|
|
formatter(),
|
|
content(),
|
|
handleQuery({ stats: this.stats })
|
|
];
|
|
};
|
|
|
|
sqlRouter.all('/', queryMiddlewares());
|
|
sqlRouter.all('.:f', queryMiddlewares());
|
|
}
|
|
};
|
|
|
|
function handleQuery ({ stats } = {}) {
|
|
return function handleQueryMiddleware (req, res, next) {
|
|
const { user: username, userDbParams: dbopts, userLimits } = res.locals;
|
|
const { orderBy, sortOrder, limit, offset } = res.locals.params;
|
|
const { sql, skipfields, decimalPrecision, filename, callback } = res.locals.params;
|
|
|
|
let { formatter } = req;
|
|
|
|
try {
|
|
req.profiler.done('init');
|
|
|
|
const opts = {
|
|
username: username,
|
|
dbopts: dbopts,
|
|
sink: res,
|
|
gn: 'the_geom', // TODO: read from configuration FILE,
|
|
dp: decimalPrecision,
|
|
skipfields: skipfields,
|
|
sql: new PSQL.QueryWrapper(sql).orderBy(orderBy, sortOrder).window(limit, offset).query(),
|
|
filename: filename,
|
|
bufferedRows: global.settings.bufferedRows,
|
|
callback: callback,
|
|
timeout: userLimits.timeout
|
|
};
|
|
|
|
if (req.profiler) {
|
|
opts.profiler = req.profiler;
|
|
opts.beforeSink = function () {
|
|
req.profiler.done('beforeSink');
|
|
};
|
|
}
|
|
|
|
if (dbopts.host) {
|
|
res.header('X-Served-By-DB-Host', dbopts.host);
|
|
}
|
|
|
|
formatter.sendResponse(opts, (err) => {
|
|
formatter = null;
|
|
|
|
if (stats) {
|
|
if (err) {
|
|
stats.increment('sqlapi.query.error');
|
|
} else {
|
|
stats.increment('sqlapi.query.success');
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
next(err);
|
|
} else {
|
|
next();
|
|
}
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
|
|
if (stats) {
|
|
stats.increment('sqlapi.query.error');
|
|
}
|
|
}
|
|
};
|
|
}
|