Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@ogma/nestjs-module

jmcdo297.7kMIT5.5.0TypeScript support: included

A NestJS module for the Ogma logger

nestjs, nest, logger, json, json-logging, request-logging, nestjs-module, module, logging

readme

@ogma/nestjs-module

A NestJS module for the Ogma logging package.

Installation

Installation is pretty simple, just npm i @ogma/nestjs-module or yarn add @ogma/nestjs-module

Usage

The OgmaService is a SINGLETON scoped service class in NestJS. That being said, if you want a new instance for each service, you can use the OgmaModule.forFeature() method and the @OgmaLogger() decorator. When working with OgmaModule.forFeature() you can pass an object as the second parameter to determine if you want the logger to be request scoped or not. This object looks like { addRequestId: true|false }.

Ogma is a lightweight logger with customization options, that prints your logs in a pretty manner with timestamping, colors, and different levels. See the GitHub repository for Ogma to learn more about configuration and options.

Configuration

In your root module, import OgmaModule.forRoot or OgmaModule.forRootAsync to apply application wide settings, such as color, json, and application. You can also set a default context as a fallback if no context is passed in the forFeature static method. You can use the standard method of async module configuration meaning useClass, useValue, and useFactory are all available methods to work with.

Synchronous configuration

import { APP_INTERCEPTOR } from '@nestjs/core';
import { OgmaInterceptor, OgmaModule } from '@ogma/nestjs-module';
import { ExpressParser } from '@ogma/platform-express';

@Module({
  imports: [
    OgmaModule.forRoot({
      service: {
        color: true,
        json: false,
        application: 'NestJS'
      },
      interceptor: {
        http: ExpressParser,
        ws: false,
        gql: false,
        rpc: false
      }
    })
  ],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: OgmaInterceptor
    }
  ]
})
export class AppModule {}

Asynchronous configuration

import { ConfigService } from '@nestjs/config';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { OgmaInterceptor, OgmaModule } from '@ogma/nestjs-module';
import { ExpressParser } from '@ogma/platform-express';
import { appendFile } from 'fs';

@Module({
  imports: [
    OgmaModule.forRootAsync({
      useFactory: (config: ConfigService) => ({
        service: {
          json: config.isProd(),
          stream: {
            write: (message) => {
              appendFile(config.getLogFile(), message, (err) => {
                if (err) {
                  throw err;
                }
                return true;
              });
            }
          },
          application: config.getAppName()
        },
        interceptor: {
          http: ExpressParser,
          ws: false,
          gql: false,
          rpc: false
        }
      }),
      inject: [ConfigService]
    })
  ],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: OgmaInterceptor
    }
  ]
})
export class AppModule {}

From here in each module you need to add the OgmaService you can add OgmaModule.forFeature(context), where context is the context you want to add to the log. This context can be either a class object (not an instance) or a string.

@Module({
  imports: [OgmaModule.forFeature(MyService)], // or OgmaModule.forFeature(MyService.name)
  providers: [MyService]
})
export class MyModule {}
@Injectable()
export class MyService {
  constructor(
    @OgmaLogger(MyService) private readonly logger: OgmaService // or @OgmaLogger(MyService.name)
  ) {}
  // ...
}

The above would set up the OgmaService to add the context of [MyService] to every log from within the MyModule module.

Request Scoping the Logger

As mentioned above, you can pass a second parameter to the forFeature method to tell Ogma that you want this logger to be request scoped and add a request Id to the logs. This request Id is generated in the interceptor which is important to note in the case of a request that fails at the Guard or Middleware level, as they will not yet have this ID. You can choose to add your own middleware to create an id if you so choose and retrieve it later. There's also a new decorator for request scoped loggers @OgmaLoggerRequestScoped(). This decorator acts exactly like the @OgmaLogger() decorator, with the same parameters and all, it just uses a different injection token with the form of OGMA_REQUEST_SCOPED_SERVICE:<Service_Name>.

Warning: Please make sure you understand the implications of using a request scoped service!

@Module({
  imports: [OgmaModule.forFeature(MyService, { addRequestId: true })],
  providers: [MyService]
})
export class MyModule {}
@Injectable()
export class MyService {
  constructor(
    @OgmaLoggerRequestScoped(MyService)
    private readonly logger: OgmaService
  ) {}
  // ...
}

forFeature/forFeatures

Normally to create a new provider with a separate context you'll use theforFeature method and pass in a string or a class. e.g. OgmaModule.forFeature(FooService). This will create the proper injection token for @OgmaLogger(FooService).

There is also the forFeatures method which takes in a parameter for multiple features to be created. This parameter can be a mixed array of classes, strings, and objects, where the objects have a shape of { contexts: string | (() => any) | Type<any>, options: OgmaProviderOptions }. This is useful for when you want to register multiple OgmaServices in the same module, such as one logger for the Service and one for the Controller, or for a Service and a Filter.

OgmaInterceptor

Ogma also comes with a built in Interceptor for logging requests made to your server. The interceptor is by default not bound to the server. To bind the interceptor, you can either use @useInterceptor on the route handlers you want to automatically log, or use the global binding with the provider {provide: APP_INTERCEPTOR, useClass: OgmaInterceptor}. You can also use app.useGlobalInterceptors(), but this is not encouraged, as the interceptor requires several dependencies.

The interceptor will need to be told what parsers it should be using for each type of request that can be intercepted. By default, all of these values are set to false, and if you try to bind the interceptor without setting a parser, you will end up with errors when running the server. If you'd like to know more about why this is the default behavior, please look at the interceptor design decisions part of the docs. Below is the general form that the interceptor logs will take:

[ISOString TimeStamp] [Application Name] PID RequestID [Context] [LogLevel]| Remote-Address - method URL protocol Status Response-Time ms - Response-Content-Length

This request ID is generated inside the OgmaInterceptor currently by using Math.random(). You may extend the logger and modify the generateRequestId method to change to it be however you like though.

Where Context is the class-method combination of the path that was called. This is especially useful for GraphQL logging where all URLs log from the /graphql route.

If you would like to skip any request url path, you can pass in a decorator either an entire class or just a route handler with the @OgmaSkip() decorator.

Note: Be aware that as this is an interceptor, any errors that happen in middleware, such as Passport's serialization/deserialization and authentication methods through the PassportStrategy will not be logged in the library. You can use an ExceptionFilter to manage that. The same goes for guards due to the request lifecycle

OgmaInterceptor Configuration Options

All configuration options are just that: options. None of them need to be provided, but it could prove useful to do in certain cases. Find the table of values below to have a better idea of what is necessary to provide the options.

name value default description
gql false or AbstractInterceptorService false The GraphQL parser for the OgmaInterceptor
http false or AbstractInterceptorService false The HTTP parser for the OgmaInterceptor
rpc false or AbstractInterceptorService false The Microservice parser for the OgmaInterceptor
ws false or AbstractInterceptorService false The Websocket parser for the OgmaInterceptor

Each of the above options, as mentioned, is false meaning that the requests for that type would not be parsed by the OgmaInterceptor and the request would flow as normal.

Note: The AbstractInterceptorService is the class that all OgmaInterceptorService parsers should extend, in order to adhere to the call scheme of the built in DelegatorService. This allows for easy extension and overwriting of the parsers.

Interceptor Design Decisions

Due to the incredible complex nature of Nest and its DI system, there needed to be some sort of way to tell users at bootstrap that if the interceptor is to be used, which should be the default behavior, then it should have one of the @ogma/platform-* packages installed, or a custom parser should be provided. Every custom parser should extend the AbstractInterceptorService to ensure that A) Typescript doesn't complain about mismatched types, and B) the DelegatorService which handles the calls to each parser, can be sure it is getting back what it expects. If you are really, really sure about what you are doing, you can always override the setting with as any to remove the Typescript warnings, but use that at your own risk.

The interceptor was designed to be adaptable, and to be able to work with any context thrown at it, but only if the parser for that context type is installed. The most common parser would be @ogma/platform-express, which will work for HTTP requests with the Express server running under the hood (Nest's default). All other parsers provided by the @ogma namespace follow a similar naming scheme, and are provided for what Nest can use out of the box (including microservices named in the microservices chapter of the Nest docs.)

Now, for the reasoning that all parsers are defaulted to false is to A) ensure that the developer does not expect the interceptor to work out of the box, B) ensure that the developer is aware of what parser is being used, and C) ensure that the parser(s) being used are installed without being blindly used (this means Typescript will complain if the class doesn't exist, whereas with JavaScript it may be okay if a linter is not installed).

Extending Pre-Built Parsers

As the pre-built parsers are built around Object Oriented Typescript, if you want to change the functionality of one of the pre-built parsers, you can always create a new class that extends the class, change the specific method(s) you want, and then provide that as your parser. All the other methods should still come from the base class and not be affected.

Putting it All Together

Okay, so now we're ready to add the OgmaModule to our Application. Let's assume we have a CatsService, CatsController and CatsModule and a ConfigService and ConfigModule in our Application. Let's also assume we want to use a class to asynchronously configure out OgmaModule. For now, assume the methods exist on the ConfigService. Let's also assume we want to log things in color to our process.stdout.

import { FastifyParser } from '@ogma/platform-fastify';
import { OgmaModuleOptions } from '@ogma/nestjs-module';

@Injectable()
export class OgmaModuleConfig {
  constructor(private readonly configService: ConfigService) {}

  createModuleConfig(): OgmaModuleOptions {
    return {
      service: {
        // returns one of Ogma's log levels, or 'ALL'.
        logLevel: this.configService.getLogLevel(),
        color: true,
        // could be something like 'MyAwesomeNestApp'
        application: this.configService.getAppName()
      },
      interceptor: {
        http: FastifyParser
      }
    };
  }
}

Next, in our AppModule we can import the OgmaModule like so

@Module({
  imports: [
    CatsModule,
    ConfigModule,
    OgmaModule.forRootAsync({
      // configuration class we created above
      useClass: OgmaModuleConfig,
      imports: [ConfigModule]
    })
  ]
})
export class AppModule {}

And now we have the interceptor bound and an OgmaService instance created for the application. If we want to add the OgmaService as the general logger for Nest we can do the following in our main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { logger: false });
  const logger = app.get<OgmaService>(OgmaService);
  app.useLogger(logger);
  await app.listen(3000);
}

bootstrap();

With this, you will lose the initial logs about instantiation of the modules, but you will still get messages about what routes are bootstrapped and when the server is ready.

If we wanted to add the logger to the CatsService we can use the module's forFeature static method to give the logger service its own context variable.

@Module({
  imports: [OgmaModule.forFeature(CatsService)],
  controllers: [CatsController],
  service: [CatsService]
})
export class CatsModule {}

And now in the CatsService the OgmaService can be injected like so:

@Injectable()
export class CatsService {
  constructor(
    @OgmaLogger(CatService) private readonly logger: OgmaService
  ) {}
}

And now this.logger is available in your CatsService class.

Demo

Below is what the OgmaInterceptor can do. These are the logs I usually see during the integration testing, and show off just what is capable in terms of the metadata captured on requests.

Ogma Interceptor Gif

Get in Touch

If there is something that needs to be addressed in regards to the module, feel free to make an issue. If you are looking to contact me, you can either email me, or find me on discord as PerfectOrphan31#6003.

Contributing

Please make sure that any and all pull requests follow the code standards enforced by the linter and prettier. Otherwise, any and all pull requests are welcomed, though an issue created to first track the PR would be appreciated. Feel free to make suggestions on how the module can improve and what can be done better.

License

NestJS-Ogma has an MIT License.

changelog

Change Log

5.5.0

Minor Changes

  • 62970a7: Interceptors can now inline meta from the getMeta method by having the inlineMeta option set at the time of module registration

5.4.0

Minor Changes

  • 9d848fd: Allow for Nest v11 dependencies

5.3.0

Minor Changes

  • 5bf006d: Create a child method to allow for creating new loggers with existing options

Patch Changes

5.2.1

Patch Changes

  • 48dbd4d: Explicitly set the styler type

5.2.0

Minor Changes

  • e940f93: The @ogma/styler instance is now exposed at the .style property of ogma

Patch Changes

5.1.4

Patch Changes

  • 02cd763: Upgrade reflect-metadata to v0.2.1 and update peer dependency range

5.1.3

Patch Changes

  • 99b3be9: OgmaService properly returns the OgmaService instance when changing the log level, insterad of the underkying Ogma class instance
  • Updated dependencies [6cd729d]

5.1.2

Patch Changes

  • 1ec5683: Properly handle observable and promise errors for the @Log() decorator

5.1.1

Patch Changes

  • 18c0d5f: Update the service options provider to correctly pass on the options to the ogma creation process
  • Updated dependencies [743cf48]

5.1.0

Minor Changes

  • 15b6f6f: Allow the log level to be changed during runtime

Patch Changes

5.0.1

Patch Changes

  • c970428: Warn if there are multiple parsers installed of the same type

5.0.0

Major Changes

  • 36ff6d0: New parser format and module options

    With the new version, there is no longer a need toseparate the service and interceptor options.As such, the options passed to the module are now the same as those passed to Ogma directly plus a traceMethod property for the @Log() decorator.

    As for the parsers that were originally passed to interceptor.[type], they should now be registered directly as providers so that the discovery service can find them on application start.

    FOR ANY CUSTOM EXISTING PARSERS

    Add the @Parser() decorator to your parser and pass in the context in which it should be called. This should match what context.getType() or host.getType() returns

Minor Changes

  • 84c799f: Update @ogma/styler to 2.0.0

Patch Changes

4.2.3

Patch Changes

  • 7ce391c: chore(deps): fix peer dependency typo in nestjs-module
  • dd5215e: Security updates of dependencies. Should be nothing major here.
  • Updated dependencies [dd5215e]

4.2.2

Patch Changes

  • 33c279b: Fix dependencies for better install experience

    @ogma/logger

    @ogma/common and @ogma/styler were set as peerDependencies instead of dependencies meaning package managers wouldn't install them by default. They are now properly set as dependencies

    @ogma/nestjs-module

    @ogma/logger was set as a peerDependency instead of a dependency. Now has been set to a dependency.

  • Updated dependencies [33c279b]

4.2.1

Patch Changes

  • a4a01af: Fixed the published peer dependencies of each package

4.2.0

Minor Changes

  • 4327443: Allow for logging out the response body as well

    While technically this is a breaking change in the getContextSuccessString method, passing the data object instead of the buffer length, to my knowledge there are no custom parsers out there that make use of this method and all @ogma/* parsers have been checked that no changes are necessary for them. If this does end up breaking something for someone, I'm sorry.

4.1.1

Patch Changes

4.1.0

Minor Changes

  • 2f5ccce: Allow for printing of each array value

4.0.1

Patch Changes

  • 6f7b8cb: fix: add application property in OgmaServiceMeta

4.0.0

Major Changes

  • e82c80b: NestJS v9 Support

    Features

    • Use the new ConfigurableModuleBuilder from @nestjs/common@9
    • Support Fastify v4
      • As a side effect, @ogma/platform-graphql-fastify can only be used with @nestjs/mercurius until apollo-server-fastify supports v4

    How to Upgrade

    Run your preferred pacakge manager's method of ugrading. There's no code chagnes necessary to the ogma imports, but implications of underlying packages that should be taken into consideration

3.3.1

Patch Changes

  • a674a96: fix defect in @Log decorator that produces an error when the original function was called

3.3.0

Minor Changes

  • 828ad94: made determineStatusCodeFromError protected (instead of private) to be able to override it in child classes

3.2.2

Patch Changes

  • 50f7664: Add peer dependencie on @ogma/logger for @ogma/nestjs-module and set up peerDependenciesMeta for better package manager integration

3.2.1

Patch Changes

  • 43e2854: Updated the doc string for the OgmaService log methods

3.2.0

Minor Changes

3.1.1

Patch Changes

  • d0217ad: patch: remove extra blank line print from printError.

3.1.0

Minor Changes

  • 384fc2b: Extra metadata can now be logged in interceptors by using a custom parser and the getMeta method

3.0.1

Patch Changes

  • 329f92a: Set the main file in the package.json correctly based on the proper publish method

3.0.0

Major Changes

  • 5e51fdc: Update package versions to work with Nest v8

    Breaking Changes

    For @ogma/nestjs-module and all of the @ogma/platform-* packages, Nest v8 is the supported package version. Nest v7 may still continue to work, but has no guarantees. Also, RxJS is upgraded to v7 to stay inline with Nest's expectations.

    Why the change was made

    To stay current with Nest.

    How to upgrade

    Upgrade with Nest v8. There shouldn't be any breaking underlying changes, but you can never be too careful with coded upgrades.

2.0.2

Patch Changes

2.0.1

Patch Changes

2.0.0

Major Changes

  • 8bea02f: Release of @ogma/common and @ogma/styler. Upgrade @ogma/nestjs-module to be 100% compatible with Nest's logger

    Breaking Changes

    • @ogma/logger now depends on @ogma/common and @ogma/styler for types and string styling, instead of managing it on its own
    • @ogma/nestjs-module now accepts trace as the second parameter to error instead of context. meta can still be passed as a second parameter too or it can be a third parameter.
    • @ogma/logger now sets an ool property when logging in JSON mode to accommodate when using a custom log map
    • @ogma/logger no longer needs the stream property to have a hasColor function
    • @ogma/cli now reads from the ool property instead level to allow writing back to Ogma's standard format

    Features

    • @ogma/logger now correctly logs Error objects the same way process.stdout does instead of logging {}
    • @ogma/logger can accept a levelMap property for custom level mapping
    • @ogma/logger's stream option can now have a getColorDepth property method, but it is not necessary

    Why

    I wanted to be able to have full control over string styles and this gave me a great chance to learn about SGRs and how they work. Along with that, this gave me the perfect opportunity to make some changes to the logger to be more compliant with Nest's logger and have better compatibility with it.

    How to Upgrade

    I tried to make this is painless as possible in terms of breaking changes. For the most part, you should just be able to upgrade with no problems. If you have a stream with hasColor you will need to remove that method. You may want to add in the getColorDepth method, but can also just use FORCE_COLOR if necessary.

Patch Changes

1.0.0

Major Changes

  • bbf66f6: Major release of @ogma

    The ogma command has been moved from @ogma/logger to @ogma/cli. There are no other breaking changes. This change was made to keep the package size as small as possible and to keep the code clean and maintainable.

Patch Changes

All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines.

0.4.3 (2020-10-30)

Features

  • interceptor: getRequestId now optionally accepts ExecutionContext (130b9e6), closes #326

0.4.2 (2020-10-26)

Note: Version bump only for package @ogma/nestjs-module

0.4.1 (2020-10-25)

Bug Fixes

  • service: fixes logger methods to follow LoggerService interface (f1fd191)

0.4.0 (2020-10-25)

Features

BREAKING CHANGES

  • all: log methods now take an object as the second parameter instead of having 3 extra optional parameters

0.3.1 (2020-09-12)

Features

  • module: adds a forFeatures method to the module (e7f5df1)

0.3.0 (2020-09-05)

Bug Fixes

  • module: add requestId check to printMessage (97bcda7)

Features

  • all: add request id generation and log (00fd8c7)
  • module: remove registration of global interceptor (fdf5ef7)

0.2.2 (2020-08-10)

Note: Version bump only for package @ogma/nestjs-module

0.2.1 (2020-07-25)

Bug Fixes

  • all: fixes OgmaCoreModule not configured (c7cd6d7), closes #106

Features

  • module: adds export for createProviderToken (9b0b43f)

0.2.0 (2020-07-20)

0.2.0 (2020-07-20)

Note: Version bump only for package @ogma/nestjs-module

0.1.2 (2020-07-18)

Bug Fixes

  • module: updates decorators type to accept a class (1bc3df7)

0.1.1 (2020-07-18)

Bug Fixes

  • gql: updates gql types to work with apollo > 2.11 (a097842)

0.1.0 (2020-04-20)

Bug Fixes

  • interceptor: adds case to skip over graphql subscriptions (1e35310)

Features

  • gql: implements gql parser for express (9290504), closes #14
  • module: implements plugin system for interceptor context parser (d116da3), closes #7 #8 #9 #10 #11
  • module: let base module work for http express (1bb52a7)