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

PRMS-156 [Fix] 일부 Logging 안 되는 문제 수정 및 개선 #65

Merged
merged 9 commits into from
Apr 21, 2024
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
6 changes: 3 additions & 3 deletions deploy.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
FROM node:18-alpine AS base

ARG NOW
ENV NOW=$NOW

# TODO: PRMS-153
# ARG PRISMA_CLI_BINARY_TARGETS
# ENV PRISMA_CLI_BINARY_TARGETS=$PRISMA_CLI_BINARY_TARGETS
Expand All @@ -28,4 +25,7 @@ FROM public.ecr.aws/lambda/nodejs:18 as deploy
COPY --from=install /deps/prod/node_modules node_modules
COPY --from=build /app/dist dist

ARG NOW
ENV NOW=$NOW

CMD ["dist/main.handler"]
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"mysql2": "^3.9.2",
"remeda": "^1.57.0",
"rxjs": "^7.8.1",
"sql-highlight": "^4.4.2",
"uuid": "^9.0.1",
"winston": "^3.12.0",
"ws": "^8.16.0"
Expand Down
1 change: 1 addition & 0 deletions serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ provider:
binaryMediaTypes:
- '*/*'
websocketsApiName: ${self:provider.stage}-${self:service}-ws
websocketsApiRouteSelectionExpression: $request.body.event
environment:
SERVERLESS: true

Expand Down
3 changes: 1 addition & 2 deletions src/common/interceptors/mutation-log.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ export class MutationLogInterceptor implements NestInterceptor {
});

this.prisma.mutationLog
.create({
select: null,
.createMany({
data: {
userId,
url: request.url,
Expand Down
26 changes: 19 additions & 7 deletions src/customs/logger/logger.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { Injectable, LoggerService as NestLoggerService } from '@nestjs/common';

@Injectable()
export abstract class LoggerService implements NestLoggerService {
export abstract class LoggerService<Options extends Record<string, any> = Record<string, any>>
implements NestLoggerService
{
protected context?: string;
protected options?: Options;

constructor();
constructor(options: Options);
constructor(context: string, options: Options);
constructor(...args: any[]) {
const [context, options] = typeof args[0] === 'object' ? [undefined, args[0]] : args;
this.context = context;
this.options = options;
}

setContext(context: string) {
this.context = context;
Expand All @@ -17,12 +29,12 @@ export abstract class LoggerService implements NestLoggerService {
abstract error(message: any, stack?: string, context?: string): any;
abstract error(message: any, metadata?: Record<string, any>, stack?: string, context?: string): any;

abstract debug?(message: any, context?: string): any;
abstract debug?(message: any, metadata?: Record<string, any>, context?: string): any;
abstract debug(message: any, context?: string): any;
abstract debug(message: any, metadata?: Record<string, any>, context?: string): any;

abstract fatal?(message: any, stack?: string, context?: string): any;
abstract fatal?(message: any, metadata?: Record<string, any>, stack?: string, context?: string): any;
abstract fatal(message: any, stack?: string, context?: string): any;
abstract fatal(message: any, metadata?: Record<string, any>, stack?: string, context?: string): any;

abstract verbose?(message: any, context?: string): any;
abstract verbose?(message: any, metadata?: Record<string, any>, context?: string): any;
abstract verbose(message: any, context?: string): any;
abstract verbose(message: any, metadata?: Record<string, any>, context?: string): any;
}
40 changes: 40 additions & 0 deletions src/customs/winston-logger/color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { mapToObj } from 'remeda';

const fontStyles = ['bold', 'dim', 'italic', 'underline', 'inverse', 'hidden', 'strikethrough'] as const;
const fgColors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray', 'dim'] as const;
const bgColors = ['blackBG', 'redBG', 'greenBG', 'yellowBG', 'blueBG', 'magentaBG', 'cyanBG', 'whiteBG'] as const;

type FontStyle = (typeof fontStyles)[number];
type FgColor = (typeof fgColors)[number];
type BgColor = (typeof bgColors)[number];

type Color =
| FontStyle
| FgColor
| BgColor
| `${FontStyle} ${FgColor}`
| `${FontStyle} ${BgColor}`
| `${FgColor} ${BgColor}`
| `${FontStyle} ${FgColor} ${BgColor}`;

export function createColorMap<Extra extends string[]>(
extra?: Extra
): Record<Color | Extra[number], Color | Extra[number]> {
const combinations: (Color | Extra[number])[] = [];

combinations.push(...fontStyles);
combinations.push(...fgColors);
combinations.push(...bgColors);

for (const font of fontStyles) {
for (const fg of fgColors) {
combinations.push(`${font} ${fg}`);
for (const bg of bgColors) {
combinations.push(`${font} ${fg} ${bg}`);
}
}
}

combinations.push(...(extra ?? []));
return mapToObj(combinations, (color) => [color, color]);
}
33 changes: 18 additions & 15 deletions src/customs/winston-logger/winston-logger.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import { LoggerService } from '../logger';
import { FilterArgs, LoggerOptions } from './winston-logger.interface';

@Injectable()
export class WinstonLoggerService extends LoggerService {
export class WinstonLoggerService extends LoggerService<LoggerOptions> {
private readonly logger: Logger;

constructor(private options: LoggerOptions) {
super();
constructor(options: LoggerOptions);
constructor(context: string, options: LoggerOptions);
constructor(...args: any[]) {
const [context, options] = args.length === 1 ? [undefined, args[0]] : args;
super(context, options);
this.logger = options.winston;
}

Expand All @@ -23,31 +26,31 @@ export class WinstonLoggerService extends LoggerService {
warn(message: any, ...optionalParams: any[]) {
const metadata = this.metadata('warn', optionalParams);
if (this.isFiltered({ level: 'warn', message, metadata })) return;
this.logger.warn(message, metadata);
this.logger.info(message, metadata);
}

error(message: any, ...optionalParams: any[]) {
const metadata = this.metadata('error', optionalParams);
if (this.isFiltered({ level: 'error', message, metadata })) return;
this.logger.error(message, metadata);
this.logger.info(message, metadata);
}

debug?(message: any, ...optionalParams: any[]) {
debug(message: any, ...optionalParams: any[]) {
const metadata = this.metadata('debug', optionalParams);
if (this.isFiltered({ level: 'debug', message, metadata })) return;
this.logger.debug(message, metadata);
}

verbose?(message: any, ...optionalParams: any[]) {
const metadata = this.metadata('verbose', optionalParams);
if (this.isFiltered({ level: 'verbose', message, metadata })) return;
this.logger.verbose(message, metadata);
this.logger.info(message, metadata);
}

fatal?(message: any, ...optionalParams: any[]) {
fatal(message: any, ...optionalParams: any[]) {
const metadata = this.metadata('fatal', optionalParams);
if (this.isFiltered({ level: 'fatal', message, metadata })) return;
this.logger.error(message, metadata);
this.logger.info(message, metadata);
}

verbose(message: any, ...optionalParams: any[]) {
const metadata = this.metadata('verbose', optionalParams);
if (this.isFiltered({ level: 'verbose', message, metadata })) return;
this.logger.info(message, metadata);
}

private isFiltered(args: FilterArgs): boolean {
Expand Down
88 changes: 51 additions & 37 deletions src/customs/winston-logger/winston-logger.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,58 @@
import { highlight } from 'cli-highlight';
import { formatISO } from 'date-fns';
import { mapToObj } from 'remeda';
import { highlight } from 'sql-highlight';
import { format, createLogger, transports, Logger } from 'winston';

import { ifs } from '@/utils';
import { createColorMap } from './color';

import { ifs, memoize } from '@/utils';

export interface WinstonLoggerOptions {
colorize?: boolean;
}

export function createWinstonLogger(options: WinstonLoggerOptions = {}): Logger {
const colors = mapToObj(
[
'sql',
'dim',
'underline',
'hidden',
'black',
'red',
'green',
'yellow',
'blue',
'magenta',
'cyan',
'white',
'gray',
] as const,
(color) => [color, color]
);
const colors = createColorMap(['sql'] as const);

export const createWinstonLogger = memoize((options: WinstonLoggerOptions = {}): Logger => {
function colorize(color: keyof typeof colors, message: string) {
if (!options.colorize) {
return message;
}

if (color === 'sql') {
const blue = '\x1b[34m';
const magenta = '\x1b[35m';
const yellow = '\x1b[33m';
const green = '\x1b[32m';
const gray = '\x1b[90m';
return highlight(message, {
language: 'sql',
ignoreIllegals: true,
colors: {
keyword: blue,
function: magenta,
number: green,
string: gray,
special: yellow,
bracket: green,
comment: '\x1b[2m\x1b[90m',
clear: '\x1b[0m',
},
});
}

return format.colorize().colorize(color, message);
return format.colorize({ colors }).colorize(color, message);
}

function colorizeByLevel(level: string, message: string) {
return colorize(
ifs<keyof typeof colors>([
[level.includes('LOG'), 'green'],
[level.includes('WARN'), 'yellow'],
[level.includes('ERROR'), 'red'],
[level.includes('DEBUG'), 'blue'],
[level.includes('FATAL'), 'magenta'],
[level.includes('VERBOSE'), 'cyan'],
]) ?? 'black',
message
);
}

return createLogger({
Expand All @@ -53,7 +64,7 @@ export function createWinstonLogger(options: WinstonLoggerOptions = {}): Logger
transform(info) {
return {
...info,
level: `${info.level.toUpperCase()}`.padStart(5),
level: info.level.toUpperCase().padStart(7),
context: info.level ? `[${info.context}]` : '',
};
},
Expand All @@ -62,15 +73,18 @@ export function createWinstonLogger(options: WinstonLoggerOptions = {}): Logger
format: () => formatISO(new Date()),
}),
// format.ms(),
options.colorize ? format.colorize({ colors }) : format.uncolorize(),
format.printf((args) => {
const { timestamp, level, message, request, response, error, ms, context, query, ...meta } = args;
const { timestamp, level, request, response, error, ms, context, query, message, ...meta } = args;
const msg = ['null', 'undefined'].includes(message) ? '' : message;

function build(body: string, meta = '') {
const head = `${colorize('dim', `${timestamp}`)}`;
const label = context ? colorize('magenta', `${context} `) : '';
function build(message: string, meta = '') {
const head = `${colorize('dim', `[${timestamp}]`)}`;
const label = context ? colorize('bold cyan', `${context} `) : '';
const footer = ` ${colorize('dim', `${ms ? `+${ms}ms` : ''}`)}`;
return `${head} ${level} ${label}${body ?? ''}${footer} ${meta}`;
const lvl = colorizeByLevel(level, level);
const body = colorizeByLevel(level, message);

return `${head} ${lvl} ${label}${body ?? ''}${footer} ${meta}`;
}

if (isRequest(request) && isResponse(response)) {
Expand All @@ -89,20 +103,20 @@ export function createWinstonLogger(options: WinstonLoggerOptions = {}): Logger
if (error instanceof Error) {
const errorMessage = (error.stack ?? error.message) || `${error}`;
const stack = colorize('yellow', errorMessage);
return message ? build(message, `\n${stack}`) : build(stack);
return msg ? build(msg, `\n${stack}`) : build(stack);
} else if (typeof error === 'string') {
const stack = colorize('yellow', error);
return message ? build(`${message} ${stack}`) : build(stack);
return msg ? build(`${msg} ${stack}`) : build(stack);
}

if (query) {
const sql = colorize('sql', query);
return message ? build(message, `\n${sql}`) : build(sql);
return msg ? build(msg, `\n${sql}`) : build(sql);
}

const metaString = JSON.stringify(meta, null, 2);
const metaStringified = metaString === '{}' ? '' : `\n${metaString}`;
return build(message, metaStringified);
return build(msg, metaStringified);
})
),
}),
Expand All @@ -116,4 +130,4 @@ export function createWinstonLogger(options: WinstonLoggerOptions = {}): Logger
function isResponse(response: any): boolean {
return response && response.statusCode;
}
}
});
1 change: 0 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,5 @@ function configureWebSocketEvent(event: APIGatewayEvent) {

export const handler: Handler = async (event, context, callback) => {
configureWebSocketEvent(event);
context.callbackWaitsForEmptyEventLoop = false;
return (await bootstrap)(event, context, callback);
};
Loading
Loading