Skip to content

Commit

Permalink
Merge pull request #3182 from huridocs/error_message_multitenant
Browse files Browse the repository at this point in the history
[HOTFIX] formatMessage takes into account current tenant
  • Loading branch information
RafaPolit authored Sep 16, 2020
2 parents 2b48d67 + 9122603 commit 4d14825
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 47 deletions.
1 change: 1 addition & 0 deletions app/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const config = {
multiTenant: process.env.MULTI_TENANT || false,
clusterMode: CLUSTER_MODE,
defaultTenant: <Tenant>{
name: 'default',
dbName: process.env.DATABASE_NAME || 'uwazi_development',
indexName: process.env.INDEX_NAME || 'uwazi_development',
uploadedDocuments: UPLOADS_FOLDER || `${rootPath}/uploaded_documents/`,
Expand Down
13 changes: 7 additions & 6 deletions app/api/log/errorLog.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ const createFileTransport = () =>
format: winston.format.combine(winston.format.timestamp(), formatter),
});

const consoleTransport = new winston.transports.Console({
handleExceptions: true,
level: 'error',
format: winston.format.combine(winston.format.timestamp(), formatter),
});
const createConsoleTransport = () =>
new winston.transports.Console({
handleExceptions: true,
level: 'error',
format: winston.format.combine(winston.format.timestamp(), formatter),
});

export const createErrorLog = () => {
DATABASE_NAME = process.env.DATABASE_NAME ? process.env.DATABASE_NAME : 'localhost';
LOGS_DIR = process.env.LOGS_DIR ? process.env.LOGS_DIR : './log';

const logger = winston.createLogger({
transports: [createFileTransport(), consoleTransport],
transports: [createFileTransport(), createConsoleTransport()],
});

logger.closeGraylog = (cb = () => {}) => {
Expand Down
11 changes: 9 additions & 2 deletions app/api/log/formatMessage.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { tenants } from 'api/tenants';
import { config } from 'api/config';

export default function formatMessage(info, instanceName) {
const message = info.message && info.message.join ? info.message.join('\n') : info.message;

const result = `${info.timestamp} [${instanceName}] ${message}`;
let tenantName = instanceName;
if (info.shouldBeMultiTenantContext) {
const tenant = tenants.current();
tenantName = tenant.name === config.defaultTenant.name ? instanceName : tenant.name;
}

return result;
return `${info.timestamp} [${tenantName}] ${message}`;
}
8 changes: 6 additions & 2 deletions app/api/log/specs/GrayLogTransport.spec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { tenants } from 'api/tenants';
import GrayLogTransport from '../GrayLogTransport';

describe('GrayLogTransport', () => {
Expand All @@ -10,13 +11,16 @@ describe('GrayLogTransport', () => {
expect(aTransport.graylog.constructor.name).toBe('graylog');
});

it('should pass log call to graylog2 instance', () => {
it('should pass log call to graylog2 instance', async () => {
const aTransport = new GrayLogTransport({
instance_name: 'some_name',
});

spyOn(aTransport.graylog, 'log');
aTransport.log('message', () => {});

await tenants.run(async () => {
aTransport.log('message', () => {});
});

expect(aTransport.graylog.log).toHaveBeenCalled();
});
Expand Down
46 changes: 33 additions & 13 deletions app/api/log/specs/errorLog.spec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { tenants } from 'api/tenants';
import { createErrorLog } from '../errorLog';

describe('errorLog', () => {
Expand All @@ -9,47 +10,66 @@ describe('errorLog', () => {
expect(anErrorLog.transports.length).toBe(2);
});

it('should call 2 transports with instance name and message', () => {
it('should call 2 transports with instanceName and message', () => {
const anErrorLog = createErrorLog();

spyOn(anErrorLog.transports[0], 'log');
spyOn(anErrorLog.transports[1], 'log');

anErrorLog.error('a message');

let fileArgs = anErrorLog.transports[0].log.calls.mostRecent().args[0];
fileArgs = Object.getOwnPropertySymbols(fileArgs).map(s => fileArgs[s]);
const fileArgs = anErrorLog.transports[0].log.calls.mostRecent().args[0];
expect(fileArgs[Symbol.for('message')]).toContain('[localhost] a message');

expect(fileArgs[1]).toContain('a message');
expect(fileArgs[1]).toContain('[localhost]');
const consoleArgs = anErrorLog.transports[1].log.calls.mostRecent().args[0];
expect(consoleArgs[Symbol.for('message')]).toContain('[localhost] a message');
});

describe('when passing multitenant flag', () => {
it('should call 2 transports with tenant name and message', async () => {
const anErrorLog = createErrorLog();

spyOn(anErrorLog.transports[0], 'log');
spyOn(anErrorLog.transports[1], 'log');

tenants.add({ name: 'tenant' });

let consoleArgs = anErrorLog.transports[1].log.calls.mostRecent().args[0];
consoleArgs = Object.getOwnPropertySymbols(consoleArgs).map(s => consoleArgs[s]);
await tenants.run(async () => {
anErrorLog.error('a message', { shouldBeMultiTenantContext: true });
}, 'tenant');

expect(consoleArgs[1]).toContain('a message');
expect(consoleArgs[1]).toContain('[localhost]');
const fileArgs = anErrorLog.transports[0].log.calls.mostRecent().args[0];
expect(fileArgs[Symbol.for('message')]).toContain('[tenant] a message');

const consoleArgs = anErrorLog.transports[1].log.calls.mostRecent().args[0];
expect(consoleArgs[Symbol.for('message')]).toContain('[tenant] a message');
});
});

it('should overwritte logs path from env vars', () => {
it('should overwritte logs path from env vars', async () => {
process.env.LOGS_DIR = './some_dir';

const anErrorLog = createErrorLog();

expect(anErrorLog.transports[0].dirname).toBe('./some_dir');
expect(anErrorLog.transports[0].filename).toBe('error.log');

anErrorLog.error('a message');
await tenants.run(async () => {
anErrorLog.error('a message');
});
});

it('should overwritte instance name from env vars', () => {
it('should overwritte instance name from env vars', async () => {
process.env.DATABASE_NAME = 'my_instance';

const anErrorLog = createErrorLog();

spyOn(anErrorLog.transports[0], 'log');
spyOn(anErrorLog.transports[1], 'log');

anErrorLog.error('a message');
await tenants.run(async () => {
anErrorLog.error('a message');
});

let calledArgs = anErrorLog.transports[0].log.calls.mostRecent().args[0];
calledArgs = Object.getOwnPropertySymbols(calledArgs).map(s => calledArgs[s]);
Expand Down
2 changes: 1 addition & 1 deletion app/api/socketio/setupSockets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const setupSockets = (server: Server, app: Application) => {
const io: SocketIoServer = socketIo(server);

io.on('connection', socket => {
socket.join(socket.request.headers.tenant || tenants.defaultTenantName);
socket.join(socket.request.headers.tenant || config.defaultTenant.name);
});

io.emitToCurrentTenant = (event, ...args) => {
Expand Down
22 changes: 14 additions & 8 deletions app/api/socketio/specs/socketClusterMode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,21 @@ describe('socket middlewares setup', () => {
return events;
};

const requestTestRoute = async (tenant: string = '') =>
request(server)
.get('/api/test')
.set('tenant', tenant)
.expect(response => {
if (response.status !== 200) {
throw new Error(response.text);
}
const requestTestRoute = async (tenant?: string) => {
const req = request(server).get('/api/test');

if (tenant) {
req.set('tenant', tenant).catch(e => {
throw e;
});
}

return req.expect(response => {
if (response.status !== 200) {
throw new Error(response.text);
}
});
};

describe('when performing a request to tenant1', () => {
it('should only emit socket events to tenant1 sockets', async () => {
Expand Down
1 change: 1 addition & 0 deletions app/api/tenants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { tenants } from './tenantContext';
11 changes: 6 additions & 5 deletions app/api/tenants/tenantContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ export type Tenant = {
class Tenants {
storage = new AsyncLocalStorage<string>();

defaultTenantName = 'default';

tenants: { [k: string]: Tenant };

constructor() {
this.tenants = {
[this.defaultTenantName]: { name: this.defaultTenantName, ...config.defaultTenant },
[config.defaultTenant.name]: config.defaultTenant,
};
}

Expand All @@ -42,9 +40,12 @@ class Tenants {
});
}

async run(cb: () => Promise<void>, tenantName?: string): Promise<void> {
async run(
cb: () => Promise<void>,
tenantName: string = config.defaultTenant.name
): Promise<void> {
return new Promise((resolve, reject) => {
this.storage.run(tenantName || this.defaultTenantName, () => {
this.storage.run(tenantName, () => {
cb()
.then(resolve)
.catch(reject);
Expand Down
9 changes: 7 additions & 2 deletions app/api/utils/handleError.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const prettifyError = (error, { req = {}, uncaught = false } = {}) => {
return result;
};

export default (_error, { req = {}, uncaught = false } = {}) => {
export default (_error, { req = undefined, uncaught = false } = {}) => {
const error = _error || new Error('undefined error occurred');
const responseToClientError = error.json;

Expand All @@ -89,8 +89,13 @@ export default (_error, { req = {}, uncaught = false } = {}) => {

const result = prettifyError(error, { req, uncaught });

let errorOptions = {};
if (req) {
errorOptions.shouldBeMultiTenantContext = true;
}

if (result.code === 500) {
errorLog.error(result.prettyMessage);
errorLog.error(result.prettyMessage, errorOptions);
} else if (result.code === 400) {
debugLog.debug(result.prettyMessage);
}
Expand Down
12 changes: 8 additions & 4 deletions app/api/utils/specs/error_handling_middleware.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,23 @@ describe('Error handling middleware', () => {
req.originalUrl = 'url';
middleware(error, req, res, next);

expect(errorLog.error).toHaveBeenCalledWith('\nurl: url\nerror');
expect(errorLog.error).toHaveBeenCalledWith('\nurl: url\nerror', {
shouldBeMultiTenantContext: true,
});
});

it('should log the error body', () => {
const error = { message: 'error', code: 500 };
req.body = { param: 'value', param2: 'value2' };
middleware(error, req, res, next);
expect(errorLog.error).toHaveBeenCalledWith(
`\nbody: ${JSON.stringify(req.body, null, ' ')}\nerror`
`\nbody: ${JSON.stringify(req.body, null, ' ')}\nerror`,
{ shouldBeMultiTenantContext: true }
);

req.body = {};
middleware(error, req, res, next);
expect(errorLog.error).toHaveBeenCalledWith('\nerror');
expect(errorLog.error).toHaveBeenCalledWith('\nerror', { shouldBeMultiTenantContext: true });
});

it('should log the error query', () => {
Expand All @@ -49,7 +52,8 @@ describe('Error handling middleware', () => {
middleware(error, req, res, next);

expect(errorLog.error).toHaveBeenCalledWith(
`\nquery: ${JSON.stringify(req.query, null, ' ')}\nerror`
`\nquery: ${JSON.stringify(req.query, null, ' ')}\nerror`,
{ shouldBeMultiTenantContext: true }
);
});
});
4 changes: 2 additions & 2 deletions app/api/utils/specs/handleError.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('handleError', () => {
const error = new Error('error');
handleError(error);

expect(errorLog.error).toHaveBeenCalledWith(`\n${error.stack}`);
expect(errorLog.error).toHaveBeenCalledWith(`\n${error.stack}`, {});
});
});

Expand All @@ -38,7 +38,7 @@ describe('handleError', () => {
expect(errorLog.error).not.toHaveBeenCalled();

handleError(createError('test error'));
expect(errorLog.error).toHaveBeenCalledWith('\ntest error');
expect(errorLog.error).toHaveBeenCalledWith('\ntest error', {});
});
});

Expand Down
1 change: 0 additions & 1 deletion app/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const uncaughtError = error => {

process.on('unhandledRejection', uncaughtError);
process.on('uncaughtException', uncaughtError);
http.on('error', handleError);

const oneYear = 31557600;

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "uwazi",
"version": "1.8.0",
"version": "1.8.1",
"description": "Uwazi is a free, open-source solution for organising, analysing and publishing your documents.",
"main": "server.js",
"repository": {
Expand Down

0 comments on commit 4d14825

Please sign in to comment.