Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/happy-vans-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@neodx/log': minor
---

Extended `framework-showcase` example, added NestJS submodule configuration
5 changes: 5 additions & 0 deletions .changeset/moody-candles-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@neodx/log': minor
---

Covered NestJS submodule with basic test-cases
5 changes: 5 additions & 0 deletions .changeset/twenty-buses-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@neodx/log': minor
---

Add the first version of `@neodx/log/nest` submodule documentation
5 changes: 5 additions & 0 deletions .changeset/wicked-moles-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@neodx/log': minor
---

Introducing the first version of NestJS integration with `@neodx/log`
1 change: 1 addition & 0 deletions apps/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default defineConfig({
text: 'Frameworks',
link: '/log/frameworks/',
items: [
{ text: 'Nest.js', link: '/log/frameworks/nest.md' },
{ text: 'Express', link: '/log/frameworks/express' },
{ text: 'Koa', link: '/log/frameworks/koa' },
{ text: 'Node.js http', link: '/log/frameworks/http' }
Expand Down
257 changes: 257 additions & 0 deletions apps/docs/log/frameworks/nest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
# Add `@neodx/log` to [Nest.js](https://nestjs.com) application

## Getting started

`@neodx/log/nest` is a module for nestjs integration.
Under the hood, it uses an express adapter and http module ( [`@neodx/log/express`](./express.md) and
[`@neodx/log/http`](./http.md) )

::: info
Checkout the [configuration example](https://github.com/secundant/neodx/tree/master/examples/log-frameworks-showcase/nestjs)
in the `log-frameworks-showcase` folder.
:::

## Example

Disable `bufferLogs` and setup provided logger:

```typescript
import { Logger } from '@neodx/log/nest';

async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(Logger));
await app.listen(3000);
}

bootstrap();
```

Import the `NeodxModule` module in root module:

```typescript
import { NeodxModule } from '@neodx/log/nest';

@Module({
imports: [NeodxModule.forRoot()]
})
class AppModule {}
```

Inject a logger using the `@InjectLogger` decorator.
Use its methods wherever you want

```typescript
import { InjectLogger } from '@neodx/log/nest';
import type { Logger, DefaultLoggerLevel } from '@neodx/log';

@Controller()
export class AppController {
constructor(
@InjectLogger(AppController.name)
private readonly log: Logger<DefaultLoggerLevel>
) {}

@Get()
public getHello() {
this.log.error(new Error('Something went wrong'));

this.log.info('Hello, world!');
this.log.info({ object: 'property' }, 'Template %s', 'string');
}
}
```

It is important to note that the injection scope
`TRANSIENT` class is injected under the hood, and not the instance itself.
each provider will receive a new logger instance.

## Configuration

### Zero-config

Use `NeodxModule.forRoot` method without any parameters:

```typescript
@Module({
imports: [NeodxModule.forRoot()]
})
class AppModule {}
```

::: info
If you don't pass parameters, then logger will be created under the hood.
:::

### With provided logger

```typescript
import { createLogger, DEFAULT_LOGGER_LEVELS } from '@neodx/log';
import { pretty } from '@neodx/log/node';
import { NeodxModule } from '@neodx/log/nest';
import { Module } from '@nestjs/common';

export const logger = createLogger({
target: pretty(),
levels: {
...DEFAULT_LOGGER_LEVELS,
custom: 80
}
});

@Module({
imports: [
NeodxModule.forRoot({
logger,
http: {
simple: true,
shouldLog: true
},
// exclude or include some routes
exclude: [{ path: 'pek', method: RequestMethod.GET }],
forRoutes: [PekController, AppController]
})
]
})
export class AppModule {}
```

You can use these parameters in addition to the instance logger or its options:

```typescript
interface BaseLoggerParams {
/**
* Which routes will be ignored by logger middleware
* @example [{ path: '*', method: RequestMethod.GET }]
* @default []
*/
exclude?: ExcludedRoutes;
/**
* Which routes will be included by logger middleware
*/
forRoutes?: AppliedRoutes;
/**
* Which routes will be included by logger middleware
* @example (logger) => createExpressLogger({ logger })
*/
http?: ((logger: Logger<HttpLogLevels>) => ExpressMiddleware) | HttpLoggerParams;
/**
* Rename system logger names
* @default internalLoggerNames
* @internal
*/
overrideNames?: InternalLogNames;
}
```

The `@neodx/log` instance is not a class and has dynamic methods,
so we cannot get the class directly from the library.
So just use `typeof` here

```typescript
export type Logger = typeof logger;

@Injectable()
export class AppService {
constructor(
@InjectLogger(AppService.name)
private readonly log: Logger
) {}
}
```

### Without provided logger

In fact, you can customize the logger parameters directly in the module.

The module will create an instance logger under the hood.
To get the logger-instance type, use the `Logger`
type from `@neodx/log`

```typescript
@Module({
imports: [
NeodxModule.forRoot({
// customize levels
levels: {
info: 10,
debug: 20,
warn: 30
},
level: 'info',

target: [pretty(), file('./logs/writable.log')],
http: {
simple: true,
shouldLog: true
},
overrideNames: {
system: 'Application'
},
exclude: [{ path: 'pek', method: RequestMethod.GET }]
})
]
})
class AppModule {}
```

### Asynchronous configuration

```typescript
import { NeodxModule } from '@neodx/log/nest';

@Module({
imports: [
NeodxModule.forRootAsync({
imports: [EnvModule],
inject: [EnvService],
useFactory: async () => {
await asyncOperation();

return {
// { ... }
};
}
})
]
})
export class AppModule {}
```

### Usage with `useClass` property

Create a configuration service
that implements from `NeodxModuleOptionsFactory`
and providers module options.

```typescript
import { NeodxModuleOptionsFactory, NeodxModule } from '@neodx/log/nest';

@Injectable()
export class NeodxOptionsService implements NeodxModuleOptionsFactory {
public createNeodxOptions(): LoggerModuleParams | LoggerModuleAsyncParams {
return {
// { ... }
};
}
}

@Module({
imports: [NeodxModule.forRootAsync({ useClass: NeodxOptionsService })]
})
export class AppModule {}
```

## Utilities

### See stack trace in `err` prop with `LoggerInterceptor`

To expose actual error details you need you to use
interceptor which captures exceptions and assigns them
to the response.

```typescript
import { LoggerInterceptor } from '@neodx/log/nest';

app.useGlobalInterceptors(new LoggerInterceptor());
```
21 changes: 21 additions & 0 deletions examples/log-frameworks-showcase/nestjs/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */

import { NeodxModule } from '@neodx/log/nest';
import { Module } from '@nestjs/common';
import { AppController } from './controllers/app.controller';
import { PekController } from './controllers/pek.controller';
import { PekService } from './services/pek.service';

@Module({
imports: [
NeodxModule.forRoot()

/**
* Complex configuration
*/
// NeodxModule.forRootAsync({ useClass: NeodxOptionsService })
],
controllers: [PekController, AppController],
providers: [PekService]
})
export class AppModule {}
26 changes: 26 additions & 0 deletions examples/log-frameworks-showcase/nestjs/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Logger, LoggerInterceptor } from '@neodx/log/nest';
import { ShutdownSignal } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
// bufferLogs is important here.
const app = await NestFactory.create(AppModule, { bufferLogs: true });

// Replace the NestJS default logger
app.useLogger(app.get(Logger));

// (optional)
// Use the `LoggerInterceptor` to handle some types
// of exceptions after the response.
app.useGlobalInterceptors(new LoggerInterceptor());

app.enableShutdownHooks();

await app.listen(3000);
}

void bootstrap();

process.once(ShutdownSignal.SIGUSR2, () => process.exit(0));
process.once(ShutdownSignal.SIGINT, () => process.exit(0));
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { InjectLogger } from '@neodx/log/nest';
import { Controller, Get } from '@nestjs/common';
import type { Logger } from '../logger';

/**
* This controller will be ignored by logger
* middleware due to `NeodxModule` configuration.
* This controller is not included in `forRoutes` prop.
*/

@Controller()
export class AppController {
constructor(@InjectLogger(AppController.name) private readonly log: Logger) {}

@Get()
public getHello() {
/**
* All request logs will be ignored in this controller.
* In order to silence programmatic logs in this controller,
* you just need to fork it with the 'silence' level.
*/

this.log.info('it works');

const child = this.log.child('pek');

child.warn('child and fork methods are also included');

return null;
}
}
Loading