nestjs-fox-logger 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ import {
2
+ DynamicModule,
3
+ Global,
4
+ Module,
5
+ Provider,
6
+ ValueProvider,
7
+ } from '@nestjs/common';
8
+
9
+ import { FOXLOGGER_SERVICE_OPTIONS } from './fox-logger.const';
10
+ import {
11
+ FoxLoggerServiceAsyncOptions,
12
+ FoxLoggerServiceOptions,
13
+ FoxLoggerServiceOptionsFactory,
14
+ } from './fox-logger.interfaces';
15
+ import { FoxLoggerService } from './fox-logger.service';
16
+
17
+ @Global()
18
+ @Module({})
19
+ export class FoxLoggerCoreModule {
20
+ /** */
21
+ public static forRoot(options: FoxLoggerServiceOptions): DynamicModule {
22
+ const LoopModuleOptionsProvider: ValueProvider<FoxLoggerServiceOptions> = {
23
+ provide: FOXLOGGER_SERVICE_OPTIONS,
24
+ useValue: options,
25
+ };
26
+
27
+ return {
28
+ module: FoxLoggerCoreModule,
29
+ providers: [LoopModuleOptionsProvider, FoxLoggerService],
30
+ exports: [FoxLoggerService],
31
+ };
32
+ }
33
+
34
+ public static forRootAsync(
35
+ options: FoxLoggerServiceAsyncOptions,
36
+ ): DynamicModule {
37
+ const providers: Provider[] = this.createAsyncProviders(options);
38
+
39
+ return {
40
+ module: FoxLoggerCoreModule,
41
+ providers: [
42
+ /** Providers **/
43
+ ...providers,
44
+ /** Services **/
45
+ FoxLoggerService,
46
+ /** Extra providers **/
47
+ ...(options.extraProviders || []),
48
+ ],
49
+ imports: options.imports,
50
+ exports: [
51
+ /** Services **/
52
+ FoxLoggerService,
53
+ ],
54
+ };
55
+ }
56
+
57
+ public static createAsyncProviders(
58
+ options: FoxLoggerServiceAsyncOptions,
59
+ ): Provider[] {
60
+ const providers: Provider[] = [this.createAsyncOptionsProvider(options)];
61
+
62
+ if (options.useClass) {
63
+ providers.push({
64
+ provide: options.useClass,
65
+ useClass: options.useClass,
66
+ });
67
+ }
68
+
69
+ return providers;
70
+ }
71
+
72
+ private static createAsyncOptionsProvider(
73
+ options: FoxLoggerServiceAsyncOptions,
74
+ ): Provider {
75
+ if (options.useFactory) {
76
+ return {
77
+ name: FOXLOGGER_SERVICE_OPTIONS,
78
+ provide: FOXLOGGER_SERVICE_OPTIONS,
79
+ useFactory: options.useFactory,
80
+ inject: options.inject || [],
81
+ };
82
+ }
83
+
84
+ return {
85
+ name: FOXLOGGER_SERVICE_OPTIONS,
86
+ provide: FOXLOGGER_SERVICE_OPTIONS,
87
+ useFactory: async (optionsFactory: FoxLoggerServiceOptionsFactory) => {
88
+ return optionsFactory.createLoopOptions();
89
+ },
90
+ inject: [options.useExisting! || options.useClass!],
91
+ };
92
+ }
93
+ }
@@ -0,0 +1 @@
1
+ export const FOXLOGGER_SERVICE_OPTIONS = 'FOXLOGGER_SERVICE_OPTIONS';
@@ -0,0 +1,28 @@
1
+ import { ModuleMetadata, Provider, Type } from '@nestjs/common';
2
+
3
+ export interface FoxLoggerServiceOptions {
4
+ name?: string;
5
+ timestamp?: boolean;
6
+ }
7
+
8
+ export interface FoxLoggerServiceFeatureOptions
9
+ extends FoxLoggerServiceOptions {
10
+ name: Required<string>;
11
+ }
12
+
13
+ export interface FoxLoggerServiceOptionsFactory {
14
+ createLoopOptions():
15
+ | Promise<FoxLoggerServiceOptions>
16
+ | FoxLoggerServiceOptions;
17
+ }
18
+
19
+ export interface FoxLoggerServiceAsyncOptions
20
+ extends Pick<ModuleMetadata, 'imports'> {
21
+ inject?: any[];
22
+ useClass?: Type<FoxLoggerServiceOptionsFactory>;
23
+ useExisting?: Type<FoxLoggerServiceOptionsFactory>;
24
+ useFactory?: (
25
+ ...args: any[]
26
+ ) => Promise<FoxLoggerServiceOptions> | FoxLoggerServiceOptions;
27
+ extraProviders?: Provider[];
28
+ }
@@ -0,0 +1,71 @@
1
+ import { DynamicModule, Module, Provider, ValueProvider } from '@nestjs/common';
2
+
3
+ import { FOXLOGGER_SERVICE_OPTIONS } from './fox-logger.const';
4
+ import {
5
+ FoxLoggerServiceAsyncOptions,
6
+ FoxLoggerServiceOptions,
7
+ } from './fox-logger.interfaces';
8
+ import { FoxLoggerService } from './fox-logger.service';
9
+ import { FoxLoggerCoreModule } from './fox-logger-core.module';
10
+
11
+ @Module({})
12
+ export class FoxLoggerModule {
13
+ public static forRoot(options: FoxLoggerServiceOptions): DynamicModule {
14
+ return {
15
+ module: FoxLoggerModule,
16
+ imports: [
17
+ /** Modules **/
18
+ FoxLoggerCoreModule.forRoot(options!),
19
+ ],
20
+ };
21
+ }
22
+
23
+ public static forRootAsync(
24
+ options: FoxLoggerServiceAsyncOptions,
25
+ ): DynamicModule {
26
+ return {
27
+ module: FoxLoggerModule,
28
+ imports: [
29
+ /** Modules **/
30
+ FoxLoggerCoreModule.forRootAsync(options),
31
+ ],
32
+ };
33
+ }
34
+
35
+ static forFeature(options: FoxLoggerServiceOptions): DynamicModule {
36
+ const FoxLoggerOptionsProvider: ValueProvider<FoxLoggerServiceOptions> = {
37
+ provide: FOXLOGGER_SERVICE_OPTIONS,
38
+ useValue: options,
39
+ };
40
+
41
+ return {
42
+ module: FoxLoggerModule,
43
+ imports: [],
44
+ providers: [FoxLoggerOptionsProvider, FoxLoggerService],
45
+ exports: [FoxLoggerOptionsProvider, FoxLoggerService],
46
+ };
47
+ }
48
+
49
+ static forFeatureAsync(options: FoxLoggerServiceAsyncOptions): DynamicModule {
50
+ const providers: Provider[] =
51
+ FoxLoggerCoreModule.createAsyncProviders(options);
52
+
53
+ return {
54
+ module: FoxLoggerModule,
55
+ providers: [
56
+ /** Providers **/
57
+ ...providers,
58
+ /** Services **/
59
+ FoxLoggerService,
60
+ /** Extra providers **/
61
+ ...(options.extraProviders || []),
62
+ ],
63
+ imports: options.imports,
64
+ exports: [
65
+ ...providers,
66
+ /** Services **/
67
+ FoxLoggerService,
68
+ ],
69
+ };
70
+ }
71
+ }
@@ -0,0 +1,163 @@
1
+ import { Logger } from '@nestjs/common';
2
+ import { Test, TestingModule } from '@nestjs/testing';
3
+ import { DeepMockProxy, mockDeep } from 'jest-mock-extended';
4
+ import { ClsModule, ClsService } from 'nestjs-cls';
5
+
6
+ import { FoxLoggerModule } from './fox-logger.module';
7
+ import { FoxLoggerService } from './fox-logger.service';
8
+
9
+ describe('FoxLoggerService', () => {
10
+ let cls: DeepMockProxy<ClsService>;
11
+ let service: FoxLoggerService;
12
+
13
+ beforeEach(async () => {
14
+ const module: TestingModule = await Test.createTestingModule({
15
+ imports: [
16
+ ClsModule.forRoot({
17
+ global: true,
18
+ }),
19
+ FoxLoggerModule.forFeature({
20
+ name: 'TEST',
21
+ }),
22
+ ],
23
+ providers: [FoxLoggerService, ClsService],
24
+ })
25
+ .overrideProvider(ClsService)
26
+ .useValue(mockDeep(ClsService))
27
+ .compile();
28
+
29
+ service = module.get(FoxLoggerService);
30
+ cls = module.get(ClsService);
31
+ });
32
+
33
+ it('should be defined', async () => {
34
+ expect(service).toBeDefined();
35
+ });
36
+
37
+ describe('.log', () => {
38
+ beforeEach(async () => {
39
+ jest.spyOn(Logger, 'log');
40
+ cls.getId.mockReturnValue('testLogId');
41
+ });
42
+
43
+ it('should call Logger.log', async () => {
44
+ service.log('test');
45
+ expect(Logger.log).toHaveBeenCalledTimes(1);
46
+ });
47
+
48
+ it('should call cls.getId()', async () => {
49
+ expect(cls.getId).toHaveBeenCalledTimes(2);
50
+ });
51
+
52
+ it('should call Logger.log with correct message', () => {
53
+ expect(Logger.log).toHaveBeenCalledWith(`[TEST][testLogId]: test`);
54
+ });
55
+ });
56
+
57
+ describe('.warn', () => {
58
+ beforeEach(() => {
59
+ jest.spyOn(Logger, 'warn');
60
+ cls.getId.mockReturnValue('testWarnId');
61
+ });
62
+
63
+ it('should call Logger.warn', async () => {
64
+ service.warn('test');
65
+ expect(Logger.warn).toHaveBeenCalledTimes(1);
66
+ });
67
+
68
+ it('should call cls.getId()', async () => {
69
+ expect(cls.getId).toHaveBeenCalledTimes(4);
70
+ });
71
+
72
+ it('should call Logger.warn with correct message', () => {
73
+ expect(Logger.warn).toHaveBeenCalledWith(`[TEST][testWarnId]: test`);
74
+ });
75
+ });
76
+
77
+ describe('.error', () => {
78
+ beforeEach(async () => {
79
+ jest.spyOn(Logger, 'error');
80
+ cls.getId.mockReturnValue('testErrId');
81
+ });
82
+
83
+ it('should call Logger.error', async () => {
84
+ service.error('error');
85
+ expect(Logger.error).toHaveBeenCalledTimes(1);
86
+ });
87
+
88
+ it('should call cls.getId()', async () => {
89
+ expect(cls.getId).toHaveBeenCalledWith();
90
+ });
91
+
92
+ it('should call Logger.warn with correct message', () => {
93
+ expect(Logger.error).toHaveBeenCalledWith(`[TEST][testErrId]: error`);
94
+ });
95
+ });
96
+
97
+ describe('.error (object)', () => {
98
+ beforeEach(async () => {
99
+ jest.spyOn(Logger, 'error');
100
+ });
101
+
102
+ it('should call Logger.error', async () => {
103
+ cls.getId.mockReturnValue('testErrId');
104
+
105
+ service.error({ message: `error!`, statusCode: 500 });
106
+ expect(Logger.error).toHaveBeenCalledTimes(2);
107
+ });
108
+
109
+ it('should call cls.getId()', async () => {
110
+ expect(cls.getId).toHaveBeenCalledTimes(8);
111
+ });
112
+
113
+ it('should call Logger.warn with correct message', () => {
114
+ expect(Logger.error).toHaveBeenCalledWith(
115
+ `[TEST][testErrId]: [500]: error!`,
116
+ );
117
+ });
118
+ });
119
+
120
+ describe('.verbose', () => {
121
+ beforeEach(() => {
122
+ jest.spyOn(Logger, 'verbose');
123
+ });
124
+
125
+ it('should call Logger.verbose', async () => {
126
+ cls.getId.mockReturnValue('testVerboseId');
127
+
128
+ service.verbose('verbose');
129
+ expect(Logger.verbose).toHaveBeenCalledTimes(1);
130
+ });
131
+
132
+ it('should call cls.getId()', async () => {
133
+ expect(cls.getId).toHaveBeenCalledTimes(10);
134
+ });
135
+
136
+ it('should call Logger.verbose with correct message', () => {
137
+ expect(Logger.verbose).toHaveBeenCalledWith(
138
+ `[TEST][testVerboseId]: verbose`,
139
+ );
140
+ });
141
+ });
142
+
143
+ describe('.fatal', () => {
144
+ beforeEach(() => {
145
+ jest.spyOn(Logger, 'fatal');
146
+ });
147
+
148
+ it('should call Logger.fatal', async () => {
149
+ cls.getId.mockReturnValue('testFatalId');
150
+
151
+ service.fatal('fatal');
152
+ expect(Logger.fatal).toHaveBeenCalledTimes(1);
153
+ });
154
+
155
+ it('should call cls.getId()', async () => {
156
+ expect(cls.getId).toHaveBeenCalledTimes(12);
157
+ });
158
+
159
+ it('should call Logger.verbose with correct message', () => {
160
+ expect(Logger.fatal).toHaveBeenCalledWith(`[TEST][testFatalId]: fatal`);
161
+ });
162
+ });
163
+ });
@@ -0,0 +1,88 @@
1
+ import { Inject, Injectable, Logger } from '@nestjs/common';
2
+ import { ClsService } from 'nestjs-cls';
3
+
4
+ import { FOXLOGGER_SERVICE_OPTIONS } from './fox-logger.const';
5
+ import { FoxLoggerServiceOptions } from './fox-logger.interfaces';
6
+
7
+ @Injectable()
8
+ export class FoxLoggerService extends Logger {
9
+ private readonly name?: string;
10
+
11
+ constructor(
12
+ @Inject(FOXLOGGER_SERVICE_OPTIONS)
13
+ readonly options: FoxLoggerServiceOptions,
14
+ private readonly cls?: ClsService,
15
+ ) {
16
+ super();
17
+ this.name = options.name;
18
+ }
19
+
20
+ /** Получение разных */
21
+ private formatMsg(msg: unknown) {
22
+ /** делаем сообщение из разных типов данных */
23
+ switch (typeof msg) {
24
+ case 'string':
25
+ return msg;
26
+ case 'number':
27
+ case 'bigint':
28
+ case 'boolean':
29
+ case 'symbol':
30
+ case 'undefined':
31
+ case 'function':
32
+ return String(msg);
33
+ case 'object':
34
+ /**
35
+ * Вынимаем данные из разных объектов ошибок
36
+ * и не только, со статусом и пр.
37
+ */
38
+ const prefix = [
39
+ 'statusCode' in msg && `[${msg.statusCode}]`,
40
+ 'status' in msg && `[${msg.status}]`,
41
+ 'error' in msg && `[${msg.error}]`,
42
+ ].filter(Boolean);
43
+ if (prefix.length > 0) prefix.push(': ');
44
+ const message = [
45
+ msg['response']?.['data']?.['message'] &&
46
+ `: ${msg['data']['message']}`,
47
+ msg['data']?.['message'] && `: ${msg['data']['message']}`,
48
+ msg['message'] && `${msg['message']}`,
49
+ msg['msg'] && `${msg['msg']}`,
50
+ ].filter(Boolean);
51
+ /** Если есть, что отдавать - пишем-с */
52
+ if (prefix.length + message.length > 0) {
53
+ return prefix.join(``) + message.join(' ');
54
+ }
55
+ /** В противном случае */
56
+ default:
57
+ JSON.stringify(msg);
58
+ }
59
+ }
60
+
61
+ /** Задаем префикс на базе имени, cls и возможного id */
62
+ private getPrefix(eventId?: string) {
63
+ return (
64
+ `${this.name ? `[${this.name}]` : ``}` +
65
+ `${this.cls.getId() ? `[${this.cls.getId()}]` : ''}` +
66
+ `${eventId ? `[${eventId}]` : ``}`
67
+ );
68
+ }
69
+
70
+ public log(msg: any, eventId?: string) {
71
+ return Logger.log(this.getPrefix(eventId) + `: ` + this.formatMsg(msg));
72
+ }
73
+ public warn(msg: any, eventId?: string) {
74
+ return Logger.warn(this.getPrefix(eventId) + `: ` + this.formatMsg(msg));
75
+ }
76
+ public debug(msg: any, eventId?: string) {
77
+ return Logger.debug(this.getPrefix(eventId) + `: ` + this.formatMsg(msg));
78
+ }
79
+ public error(msg: any, eventId?: string) {
80
+ return Logger.error(this.getPrefix(eventId) + `: ` + this.formatMsg(msg));
81
+ }
82
+ public verbose(msg: any, eventId?: string) {
83
+ return Logger.verbose(this.getPrefix(eventId) + `: ` + this.formatMsg(msg));
84
+ }
85
+ public fatal(msg: any, eventId?: string) {
86
+ return Logger.fatal(this.getPrefix(eventId) + `: ` + this.formatMsg(msg));
87
+ }
88
+ }