Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore(feature)/add request and session logs #532 #727

Merged
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
context: .
dockerfile: Dockerfile
environment:
DATABASE_URL: postgres://postgres:postgres@host.docker.internal:35000/bloom
DATABASE_URL: postgres://postgres:postgres@db:5432/bloom
ports:
- 35001:35001
depends_on:
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,25 @@
"firebase": "^10.10.0",
"firebase-admin": "^12.2.0",
"lodash": "^4.17.21",
"nestjs-cls": "^4.4.1",
"newrelic": "^12.5.1",
"pg": "^8.11.5",
"pg-connection-string": "^2.7.0",
"reflect-metadata": "^0.2.1",
"rimraf": "^6.0.1",
"rollbar": "^2.26.4",
"rxjs": "^7.8.1",
"typeorm": "^0.3.20",
"uuid": "^11.0.3",
"storyblok-js-client": "^6.10.6",
"typeorm": "^0.3.20"
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@golevelup/ts-jest": "^0.5.0",
"@nestjs/cli": "^10.3.2",
"@nestjs/testing": "^10.4.6",
"@nestjs/schematics": "^10.2.3",
"@nestjs/testing": "^10.4.6",
"@types/date-fns": "^2.6.0",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.13",
Expand Down
11 changes: 11 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,22 @@ import { SessionModule } from './session/session.module';
import { SubscriptionUserModule } from './subscription-user/subscription-user.module';
import { UserModule } from './user/user.module';
import { WebhooksModule } from './webhooks/webhooks.module';
import { ClsModule } from 'nestjs-cls';
import { v4 as uuidv4 } from 'uuid';

@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRoot(dataSourceOptions as TypeOrmModuleOptions),
ClsModule.forRoot({
global: true,
middleware: {
mount: true,
generateId: true,
idGenerator: (req: Request) =>
req.headers['X-Request-Id'] ?? uuidv4(),
}
}),
LoggerModule,
PartnerAccessModule,
AuthModule,
Expand Down
2 changes: 1 addition & 1 deletion src/crisp/crisp.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class CrispService {

async deleteCrispProfile(email: string) {
try {
CrispClient.website.removePeopleProfile(crispWebsiteId, email);
await CrispClient.website.removePeopleProfile(crispWebsiteId, email);
} catch (error) {
throw new Error(`Delete crisp profile API call failed: ${error}`);
}
Expand Down
14 changes: 13 additions & 1 deletion src/firebase/firebase-auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';

import { AUTH_GUARD_ERRORS, FIREBASE_ERRORS } from 'src/utils/errors';
import { AuthService } from '../auth/auth.service';
import { UserService } from '../user/user.service';
import { IFirebaseUser } from './firebase-user.interface';
import { ClsService } from 'nestjs-cls';
import { DecodedIdToken } from 'firebase-admin/lib/auth/token-verifier';
import * as crypto from 'crypto';

@Injectable()
export class FirebaseAuthGuard implements CanActivate {
Expand All @@ -21,6 +23,7 @@ export class FirebaseAuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private userService: UserService,
private cls : ClsService,
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
Expand Down Expand Up @@ -84,6 +87,15 @@ export class FirebaseAuthGuard implements CanActivate {
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
this.setSessionId(user);
return true;
}


private setSessionId(user : DecodedIdToken) {
const uid = user.uid;
const authTime = user.auth_time;
const sessionId: string = crypto.createHash('sha256').update(`${uid}-${authTime}`).digest('hex');
this.cls.set('sessionId', sessionId);
}
}
55 changes: 53 additions & 2 deletions src/logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,74 @@ import Rollbar from 'rollbar';
import { FIREBASE_ERRORS } from 'src/utils/errors';
import { isProduction, rollbarEnv, rollbarToken } from '../utils/constants';
import { ErrorLog } from './utils';
import { ClsService, ClsServiceManager } from 'nestjs-cls';

interface LogMessage {
event: string;
userId: string;
fields?: string[];
}

interface RequestContext {
requestId: string;
sessionId: string;
}

export class Logger extends ConsoleLogger {
private rollbar?: Rollbar;
private cls: ClsService;

constructor(context?: string, isTimestampEnabled?) {
super(context, isTimestampEnabled);

this.cls = ClsServiceManager.getClsService();
this.initialiseRollbar();
}

getRequestContext() : RequestContext
{
try{
return {
requestId: this.cls.getId(),
sessionId: this.cls.get('sessionId')
}
}catch{
console.log("Error getting request context");
return {
requestId: "not set",
sessionId: "not set"
}
}

}

log(message: string | LogMessage): void {
const formattedMessage = typeof message === 'string' ? message : JSON.stringify(message);
const requestContext = this.getRequestContext();
const decoratedMessage = `[Request ID: ${requestContext.requestId}, Session ID: ${requestContext.sessionId}] ${formattedMessage}`;
super.log(decoratedMessage);
}

warn(message: string | ErrorLog, trace?:string): void {
try {
const formattedMessage = typeof message === 'string' ? message : JSON.stringify(message);
const requestContext = this.getRequestContext();
const decoratedMessage = `[Request ID: ${requestContext.requestId}, Session ID: ${requestContext.sessionId}] ${formattedMessage}`;
const taggedMessage = `[warn] ${decoratedMessage}`;
super.warn(taggedMessage, trace);
}catch{
console.error("Error logging warning");
}
}

error(message: string | ErrorLog, trace?: string): void {
if (this.rollbar) {
this.rollbar.error(message);
}
const formattedMessage = typeof message === 'string' ? message : JSON.stringify(message);
const requestContext = this.getRequestContext();
const decoratedMessage = `[Request ID: ${requestContext.requestId}, Session ID: ${requestContext.sessionId}] ${formattedMessage}`;

const taggedMessage = `[error] ${formattedMessage}`;
const taggedMessage = `[error] ${decoratedMessage}`;
super.error(taggedMessage, trace);
}

Expand Down
8 changes: 7 additions & 1 deletion src/partner-access/partner-access.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable */
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
Expand All @@ -10,6 +11,9 @@ import { UserService } from '../user/user.service';
import { CreatePartnerAccessDto } from './dtos/create-partner-access.dto';
import { PartnerAccessController } from './partner-access.controller';
import { PartnerAccessService } from './partner-access.service';
import { Logger } from '../logger/logger';
import {mockClsService} from 'test/utils/mockedServices';
import { ClsService } from 'nestjs-cls';

const mockUserRepository = () => ({});

Expand Down Expand Up @@ -48,7 +52,8 @@ describe('PartnerAccessController', () => {
} as unknown as PartnerAccessEntity);
},
};

const logger = (authGuard as any).logger as Logger;
(logger as any).cls = mockClsService;
const module: TestingModule = await Test.createTestingModule({
controllers: [PartnerAccessController],
providers: [
Expand All @@ -59,6 +64,7 @@ describe('PartnerAccessController', () => {
provide: getRepositoryToken(UserEntity),
useFactory: mockUserRepository,
},
{provide: ClsService, useValue: mockClsService},
],
})
.overrideGuard(PartnerAdminAuthGuard)
Expand Down
7 changes: 6 additions & 1 deletion src/partner-access/partner-access.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable */
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
Expand All @@ -17,15 +18,17 @@ import {
import {
mockPartnerAccessRepositoryMethods,
mockPartnerRepositoryMethods,
mockClsService,
} from 'test/utils/mockedServices';
import { Repository } from 'typeorm';
import { Repository } from 'typeorm';
import { createQueryBuilderMock } from '../../test/utils/mockUtils';
import { PartnerAccessEntity } from '../entities/partner-access.entity';
import { PartnerAccessCodeStatusEnum } from '../utils/constants';
import { CreatePartnerAccessDto } from './dtos/create-partner-access.dto';
import { GetPartnerAccessesDto } from './dtos/get-partner-access.dto';
import { UpdatePartnerAccessDto } from './dtos/update-partner-access.dto';
import { PartnerAccessService } from './partner-access.service';
import {Logger} from '../logger/logger';

const partnerId = 'partnerId1';
const partnerAdminId = 'partnerAdminId1';
Expand Down Expand Up @@ -96,6 +99,8 @@ describe('PartnerAccessService', () => {
}).compile();

service = module.get<PartnerAccessService>(PartnerAccessService);
const logger = (service as any).logger as Logger;
(logger as any).cls = mockClsService;
repo = module.get<Repository<PartnerAccessEntity>>(getRepositoryToken(PartnerAccessEntity));
});

Expand Down
5 changes: 5 additions & 0 deletions src/subscription-user/subscription-user.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable */
import { DeepMocked, createMock } from '@golevelup/ts-jest/lib/mocks';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
Expand All @@ -9,11 +10,13 @@ import { EventLoggerService } from 'src/event-logger/event-logger.service';
import { SubscriptionService } from 'src/subscription/subscription.service';
import { mockSubscriptionUserEntity, mockUserEntity } from 'test/utils/mockData';
import {
mockClsService,
mockSubscriptionUserRepositoryMethods,
mockZapierWebhookClientMethods,
} from 'test/utils/mockedServices';
import { Repository } from 'typeorm/repository/Repository';
import { SubscriptionUserService } from './subscription-user.service';
import { Logger } from '@nestjs/common';

describe('SubscriptionUserService', () => {
let service: SubscriptionUserService;
Expand Down Expand Up @@ -58,6 +61,8 @@ describe('SubscriptionUserService', () => {
}).compile();

service = module.get<SubscriptionUserService>(SubscriptionUserService);
const logger = (service as any).logger as Logger;
(logger as any).cls = mockClsService;
});

it('should be defined', () => {
Expand Down
5 changes: 5 additions & 0 deletions src/user/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
mockPartnerAccessRepositoryMethods,
mockPartnerRepositoryMethods,
mockUserRepositoryMethodsFactory,
mockClsService,
mockUserRepositoryMethods,
} from 'test/utils/mockedServices';
import { Repository } from 'typeorm';
import { createQueryBuilderMock } from '../../test/utils/mockUtils';
Expand All @@ -36,6 +38,7 @@ import { AdminUpdateUserDto } from './dtos/admin-update-user.dto';
import { CreateUserDto } from './dtos/create-user.dto';
import { UpdateUserDto } from './dtos/update-user.dto';
import { UserService } from './user.service';
import {Logger} from '../logger/logger';

const createUserDto: CreateUserDto = {
email: '[email protected]',
Expand Down Expand Up @@ -120,6 +123,8 @@ describe('UserService', () => {
}).compile();

service = module.get<UserService>(UserService);
const logger = (service as any).logger as Logger;
(logger as any).cls = mockClsService;
repo = module.get<Repository<UserEntity>>(getRepositoryToken(UserEntity));
});

Expand Down
11 changes: 4 additions & 7 deletions src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ export class UserService {

if (
Object.keys(updateUserDto).length === 1 &&
!!updateUserDto.lastActiveAt &&
!!updateUserDto.lastActiveAt && !!user.lastActiveAt &&
updateUserDto.lastActiveAt.getDate() === user.lastActiveAt.getDate()
) {
// Do nothing, prevent unnecessay updates to service profiles when last active date is same date
Expand Down Expand Up @@ -302,16 +302,14 @@ export class UserService {

while (startIndex < users.length) {
const batch = users.slice(startIndex, startIndex + BATCH_SIZE);

await Promise.all(
batch.map(async (user) => {
try {
await this.crispService.deleteCrispProfile(user.email);
} catch (error) {
this.logger.warn(
`deleteCypressTestUsers - unable to delete crisp profile for user ${user.id}`,
error,
);
error);
}
try {
await deleteMailchimpProfile(user.email);
Expand All @@ -338,9 +336,8 @@ export class UserService {
error,
);
}
}),
);

})
)
startIndex += BATCH_SIZE;
await new Promise((resolve) => setTimeout(resolve, INTERVAL)); // Wait before processing next batch
}
Expand Down
7 changes: 7 additions & 0 deletions test/utils/mockedServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
mockUserRecord,
} from './mockData';
import { createQueryBuilderMock } from './mockUtils';
import { ClsService } from 'nestjs-cls';

export const mockSlackMessageClientMethods: PartialFuncReturn<SlackMessageClient> = {
sendMessageToTherapySlackChannel: async () => {
Expand All @@ -52,6 +53,11 @@ export const mockWebhooksServiceMethods: PartialFuncReturn<WebhooksService> = {
},
};

export const mockClsService = {
getId: jest.fn().mockReturnValue('mockRequestId'),
get: jest.fn().mockReturnValue('mockSessionId'),
} as Partial<jest.Mocked<ClsService>> as jest.Mocked<ClsService>;

export const mockPartnerServiceMethods = {
getPartnerById: async (arg): Promise<PartnerEntity> => {
return { ...mockPartnerEntity, id: arg };
Expand Down Expand Up @@ -361,3 +367,4 @@ export const mockSubscriptionUserRepositoryMethods: PartialFuncReturn<
};

export const mockZapierWebhookClientMethods = {} as ZapierWebhookClient;

10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5524,6 +5524,11 @@ neo-async@^2.6.2:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==

nestjs-cls@^4.4.1:
version "4.5.0"
resolved "https://registry.yarnpkg.com/nestjs-cls/-/nestjs-cls-4.5.0.tgz#c18914b35b808429b6974a197385aa28e11d8176"
integrity sha512-oi3GNCc5pnsnVI5WJKMDwmg4NP+JyEw+edlwgepyUba5+RGGtJzpbVaaxXGW1iPbDuQde3/fA8Jdjq9j88BVcQ==

newrelic@^12.5.1:
version "12.5.1"
resolved "https://registry.yarnpkg.com/newrelic/-/newrelic-12.5.1.tgz#b1c391b15bc6c2b94e9dd02b34c45d79559bd21b"
Expand Down Expand Up @@ -7213,6 +7218,11 @@ uuid@^10.0.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==

uuid@^11.0.3:
version "11.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.3.tgz#248451cac9d1a4a4128033e765d137e2b2c49a3d"
integrity sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==

uuid@^8.0.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
Expand Down
Loading