Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions redisinsight/api/config/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export default {
: true,
pipelineSummaryLimit:
parseInt(process.env.RI_LOGGER_PIPELINE_SUMMARY_LIMIT, 10) || 5,
logDepthLevel: parseInt(process.env.RI_LOGGER_DEPTH_LEVEL, 10) || 5,
},
plugins: {
stateMaxSize:
Expand Down
15 changes: 9 additions & 6 deletions redisinsight/api/config/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
} from 'nest-winston';
import { join } from 'path';
import config from 'src/utils/config';
import { prettyFormat, sensitiveDataFormatter } from 'src/utils/logsFormatter';
import {
prepareLogsData,
prettyFileFormat,
} from 'src/utils/logsFormatter';

const PATH_CONFIG = config.get('dir_path');
const LOGGER_CONFIG = config.get('logger');
Expand All @@ -17,7 +20,7 @@ if (LOGGER_CONFIG.stdout) {
transportsConfig.push(
new transports.Console({
format: format.combine(
sensitiveDataFormatter({
prepareLogsData({
omitSensitiveData: LOGGER_CONFIG.omitSensitiveData,
}),
format.timestamp(),
Expand All @@ -42,10 +45,10 @@ if (LOGGER_CONFIG.files) {
filename: 'redisinsight-errors-%DATE%.log',
level: 'error',
format: format.combine(
sensitiveDataFormatter({
prepareLogsData({
omitSensitiveData: LOGGER_CONFIG.omitSensitiveData,
}),
prettyFormat,
prettyFileFormat,
),
}),
);
Expand All @@ -57,10 +60,10 @@ if (LOGGER_CONFIG.files) {
maxFiles: '7d',
filename: 'redisinsight-%DATE%.log',
format: format.combine(
sensitiveDataFormatter({
prepareLogsData({
omitSensitiveData: LOGGER_CONFIG.omitSensitiveData,
}),
prettyFormat,
prettyFileFormat,
),
}),
);
Expand Down
21 changes: 3 additions & 18 deletions redisinsight/api/src/common/logger/app-logger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,7 @@ describe('AppLogger', () => {
message: 'Test message',
context: null,
data: [error2],
error: {
message: error1.message,
response: undefined,
stack: error1.stack,
},
error: error1,
});
},
);
Expand All @@ -95,14 +91,7 @@ describe('AppLogger', () => {
message: 'Test message',
context: null,
data: undefined,
error: {
message: error1.message,
response: {
status: 500,
data: 'Internal server error',
},
stack: error1.stack,
},
error: error1,
});
},
);
Expand Down Expand Up @@ -181,11 +170,7 @@ describe('AppLogger', () => {
},
sessionMetadata: clientMetadata.sessionMetadata,
data: [{ foo: 'bar' }],
error: {
message: error.message,
stack: error.stack,
response: undefined,
},
error,
});
expect(optionalParams).toEqual(optionalParamsOriginal);
},
Expand Down
12 changes: 2 additions & 10 deletions redisinsight/api/src/common/logger/app-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,14 @@ export class AppLogger implements LoggerService {
* Note: args array might be mutated
* @param args
*/
static getError(args: ErrorOrMeta[] = []): void | {} {
static getError(args: ErrorOrMeta[] = []): Error {
let error = null;
const index = args.findIndex((arg) => arg instanceof Error);
if (index > -1) {
[error] = args.splice(index, 1);
}

if (error) {
return {
message: error.message,
stack: error.stack,
response: error.response,
};
}

return undefined;
return error || undefined;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,13 @@ export class CloudAuthService {

return CloudAuthStrategy.generateAuthUrl(authRequest).toString();
} catch (e) {
this.logger.error('Unable to generate authorization url', e);

if (e instanceof CloudOauthSsoUnsupportedEmailException) {
throw e;
}

throw new CloudOauthMisconfigurationException();
throw new CloudOauthMisconfigurationException(undefined, { cause: e });
}
}

Expand Down
177 changes: 177 additions & 0 deletions redisinsight/api/src/utils/logsFormatter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { CloudOauthMisconfigurationException } from 'src/modules/cloud/auth/exceptions';
import { AxiosError, AxiosHeaders } from 'axios';
import { mockSessionMetadata } from 'src/__mocks__';
import { getOriginalErrorCause, sanitizeError, sanitizeErrors } from './logsFormatter';

const simpleError = new Error('Original error');
simpleError['some'] = 'field';
const errorWithCause = new NotFoundException('Not found', { cause: simpleError });
const errorWithCauseDepth2 = new BadRequestException('Bad req', { cause: errorWithCause });
const errorWithCauseDepth3 = new CloudOauthMisconfigurationException('Misconfigured', { cause: errorWithCauseDepth2 });
const axiosError = new AxiosError(
'Request failed with status code 404',
'NOT_FOUND',
{
method: 'get',
url: '/test-endpoint',
headers: AxiosHeaders.prototype,
data: null,
},
null,
{
status: 404,
statusText: 'Not Found',
headers: {},
config: {
headers: AxiosHeaders.prototype,
},
data: {
message: 'Resource not found',
},
},
);

const mockLogData: any = {
sessionMetadata: mockSessionMetadata,
error: errorWithCauseDepth3,
data: [
errorWithCauseDepth2,
{
any: [
'other',
{
possible: 'data',
with: [
'nested',
'structure',
errorWithCause,
{
error: simpleError,
},
],
},
],
},
],
};
mockLogData.data.push({ circular: mockLogData.data });

describe('logsFormatter', () => {
describe('getOriginalErrorCause', () => {
it('should return last cause in the chain', () => {
expect(getOriginalErrorCause(errorWithCauseDepth3)).toEqual(simpleError);
});

it('should return undefined if input is not an Error instance', () => {
expect(getOriginalErrorCause({ cause: simpleError })).toEqual(undefined);
});

it('should not fail if input is not specified', () => {
expect(getOriginalErrorCause(undefined)).toEqual(undefined);
});
});

describe('sanitizeError', () => {
it('should sanitize simple error and return only message', () => {
expect(sanitizeError(simpleError, { omitSensitiveData: true })).toEqual({
type: 'Error',
message: simpleError.message,
});
});

it('should sanitize simple error and return message (with stack)', () => {
expect(sanitizeError(simpleError)).toEqual({
type: 'Error',
message: simpleError.message,
stack: simpleError.stack,
});
});

it('should return sanitized object with a single original cause for nested errors', () => {
expect(sanitizeError(errorWithCauseDepth3, { omitSensitiveData: true })).toEqual({
type: 'CloudOauthMisconfigurationException',
message: errorWithCauseDepth3.message,
cause: {
type: 'Error',
message: simpleError.message,
},
});
});

it('should return sanitized object with a single original cause for nested errors (with stack)', () => {
expect(sanitizeError(errorWithCauseDepth3)).toEqual({
type: 'CloudOauthMisconfigurationException',
message: errorWithCauseDepth3.message,
stack: errorWithCauseDepth3.stack,
cause: {
type: 'Error',
message: simpleError.message,
stack: simpleError.stack,
},
});
});

it('should sanitize axios error and return only message when sensitive data is omitted', () => {
expect(sanitizeError(axiosError, { omitSensitiveData: true })).toEqual({
type: 'AxiosError',
message: axiosError.message,
});
});
});

describe('sanitizeErrors', () => {
it('should sanitize all errors and replace circular dependencies', () => {
expect(sanitizeErrors(mockLogData, { omitSensitiveData: true })).toEqual({
sessionMetadata: mockSessionMetadata,
error: {
type: 'CloudOauthMisconfigurationException',
message: 'Misconfigured',
cause: {
message: 'Original error',
type: 'Error',
},
},
data: [
{
type: 'BadRequestException',
message: 'Bad req',
cause: {
type: 'Error',
message: 'Original error',
},
},
{
any: [
'other',
{
possible: 'data',
with: [
'nested',
'structure',
{
cause: {
message: 'Original error',
type: 'Error',
},
message: 'Not found',
type: 'NotFoundException',
},
{
error: {
message: 'Original error',
type: 'Error',
},
},
],
},
],
},
{
circular: '[Circular]',
},
],
});
});
});
});
Loading
Loading