How to use nestjs Logging service - javascript

I tried to use the internal Logger of nestjs (described on https://docs.nestjs.com/techniques/logger -> but with no description of how to use it)
But I had problems (tried to inject LoggerService and so on)
Can anybody explain how to do this?

Best practice
Better than accessing the Logger statically is to create an instance for your class:
#Controller()
export class AppController {
private readonly logger = new Logger(AppController.name);
#Get()
async get() {
this.logger.log('Getting stuff');
}
}
Why is this better?
You can provide a context in the constructor like new Logger(AppController.name) so that the class name (or anything else) will be part of all log messages in this class.
If you at some point want to extend or replace the default LoggerService, you do not need to change any of your application code besides setting the new logger. Your new logger will automatically be used. If you access it statically it will continue to take the default implementation.
const app = await NestFactory.create(AppModule, {logger: new MyLogger()});
You can mock the Logger in your tests:
module.useLogger(new NoOpLogger());

You need to import first into your class:
import { Logger } from '#nestjs/common';
and then you can begin with logging:
Logger.log('info')
Logger.warn('warning')
Logger.error('something went wrong! ', error)

This answer might be useful for others who are trying with CustomLogger Implementation.
I am trying to show a sample custom logger implementation and how it can be injected to the Nestjs framework.
I understand that Nestjs inherently uses pino logger. This is just a custom implementation of logger service (which you can replace with bunyan, winston, etc..)
This is the folder structure I use:
> src /
> modules /
> database /
> ...
> database.module.ts
> api /
> services /
> controllers /
> interceptors /
> middlewares /
> models /
> schemas /
> shared /
> services /
> app.util.service.ts
> pino.logger.service.ts
> utils /
> interceptors /
> filters /
> main.ts
> app.controller.ts
> app.service.ts
> server.util.service.ts
This is the main gist of it. So the logger service is implemented as follows
import {Injectable, LoggerService, Scope} from "#nestjs/common";
import * as pino from 'pino';
import {AppUtilService} from "./app.util.service";
import * as os from "os";
import {APP_LOG_REDACT, APP_MESSAGE_KEY} from "../utils/app.constants";
#Injectable({
scope: Scope.DEFAULT
})
export class PinoLoggerService implements LoggerService{
constructor(private appUtilService: AppUtilService) {
}
logService = (fileNameString): pino.Logger => {
return pino({
useLevelLabels: true,
prettyPrint: this.appUtilService.isDevEnv(),
// tslint:disable-next-line: object-literal-sort-keys
messageKey: APP_MESSAGE_KEY,
level: this.appUtilService.getLogLevel(),
redact: {
paths: APP_LOG_REDACT,
censor: '**SECRET-INFO**'
},
base: {
hostName: os.hostname(),
platform: os.platform(),
processId: process.pid,
timestamp: this.appUtilService.getCurrentLocaleTimeZone(),
// tslint:disable-next-line: object-literal-sort-keys
fileName: this.appUtilService.getFileName(fileNameString),
},
});
}
debug(message: any, context?: string): any {
}
error(message: any, trace?: string, context?: string): any {
}
log(message: any, context?: string): any {
}
warn(message: any, context?: string): any {
}
}
The custom implementation is implemented with the my specific options in pinojs github
I am using fastifyjs instead of express (again to match my prject needs). So I've added the logger in fastify js server options. If you are using express, its better to specify the new custom implementation in the Nest application Adapter as stated above.
My util service that takes care of implementing the fastify server
import * as fastify from "fastify";
import {Http2Server, Http2ServerRequest, Http2ServerResponse} from "http2";
import {DocumentBuilder, SwaggerModule} from "#nestjs/swagger";
import * as fs from "fs";
import * as path from "path";
import * as uuid from "uuid";
import * as qs from "query-string";
import {PinoLoggerService} from "./modules/shared/services/pino.logger.service";
import {AppUtilService} from "./modules/shared/services/app.util.service";
import {AppConstantsService} from "./modules/shared/services/app.constants.service";
import {AppModel} from "./modules/shared/model/app.model";
import {Reflector} from "#nestjs/core";
export class ServerUtilService {
private logService;
private appConstantsService;
private appUtilServiceInstance: AppUtilService;
private fastifyInstance: fastify.FastifyInstance<Http2Server, Http2ServerRequest, Http2ServerResponse>;
constructor() {
this.appUtilServiceInstance = new AppUtilService();
this.logService = new PinoLoggerService(this.appUtilServiceInstance);
this.appConstantsService = new AppConstantsService(this.appUtilServiceInstance);
}
retrieveAppConstants(): AppModel {
return this.appConstantsService.getServerConstants();
}
retrieveAppUtilService(): AppUtilService {
return this.appConstantsService;
}
createFastifyServerInstance = (): fastify.FastifyInstance<Http2Server, Http2ServerRequest, Http2ServerResponse> => {
const serverConstants = this.appConstantsService.getServerConstants();
const httpsOptions = {
cert: fs.readFileSync(path.join(process.cwd() + '/https-keys/cert.pem')),
key: fs.readFileSync(path.join(process.cwd() + '/https-keys/key.pem')),
allowHTTP1: true,
rejectUnauthorized: true,
};
this.fastifyInstance = fastify({
http2: true,
https: httpsOptions,
bodyLimit: 26214400,
pluginTimeout: 20000,
genReqId: () => {
return uuid.v4().toString();
},
requestIdHeader: serverConstants.requestIdHeader,
modifyCoreObjects: true,
trustProxy: serverConstants.trustProxy,
ignoreTrailingSlash: true,
logger: this.logService,
querystringParser: (str) => {
return qs.parse(str);
},
});
this.addContentTypeParser();
return this.fastifyInstance;
};
private addContentTypeParser() {
this.fastifyInstance.addContentTypeParser('*', (req, done) => {
let data = '';
req.on('data', chunk => {
console.log('inside data listener event');
return data += chunk; });
req.on('end', () => {
done(null,data);
})
});
}
}
export const ServerUtilServiceInstance = new ServerUtilService();
And in my main.ts
async function bootstrap() {
const fastifyServerInstance =
ServerUtilServiceInstance.createFastifyServerInstance();
const serverConstants = ServerUtilServiceInstance.retrieveAppConstants();
const app: NestFastifyApplication = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(fastifyServerInstance)
);
....
... // global filters, interceptors, pipes
....
await app.listen(serverConstants.port, '0.0.0.0');
}

Best practice is to inject the existing logger.
app.module.ts
import { Logger, Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
#Module({
imports: [],
controllers: [AppController],
providers: [AppService, Logger],
})
export class AppModule {}
And in the app.service.ts
import { Injectable, Logger } from '#nestjs/common';
#Injectable()
export class AppService {
constructor(private readonly logger: Logger) {}
sayHello() {
this.logger.log('Hello world!')
}
}

The answer is simple. There are static methods on the Logger class.
e.g.
static log(message: string, context = '', isTimeDiffEnabled = true)
Usage:
Logger.log('Only a test');

Simply you can use logger for your requirement(for error, for warn).This is the sample code for it.
import {Logger, Injectable} from '#nestjs/common';
#Injectable()
export class EmployersService {
private readonly logger = new Logger(EmployersService.name);
findAll() {
this.logger.log('info message'); //for info
this.logger.warn('warn message'); //for warn
this.logger.error('error message'); //for error
}
}
then output:

Related

Nest js custom logger doubled console.logs

have a problem with implementing custom logger by extending default logger in NestJS. I followed documentation https://docs.nestjs.com/techniques/logger#using-the-logger-for-application-logging.
import { Injectable, Logger, Scope } from '#nestjs/common';
#Injectable({ scope: Scope.TRANSIENT })
export class LoggerService extends Logger {
votes = 0;
debug(message: string, trace?: string) {
super.debug(message, trace);
}
error(message: string, trace?: string) {
super.error(message, trace);
}
log(message: string, trace?: string) {
this.votes = this.votes + 1;
console.log(this.votes, 'log', message);
super.log(message, trace);
}
verbose(message: string, trace?: string) {
super.verbose(message, trace);
}
warn(message: string, trace?: string) {
super.warn(message, trace);
}
}
and in main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { AllExceptionsFilter } from './shared/exception-filters/all-exception-filters';
import { LoggerService } from './shared/modules/logger/logger.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: false,
});
app.useLogger(new LoggerService());
app.useGlobalFilters(new AllExceptionsFilter());
await app.listen(3000);
}
bootstrap();
And in my controller
Controller('user')
export class UserController {
constructor(private readonly userService: UserService, private logger: LoggerService) {}
#Get('/logger-example')
getLoggerExample() {
this.logger.log('it works ?');
return 'loggerExample';
}
And of course user module imports logger module.
I have two problems with that logger:
First one:
When i hit user/logger-example, i got two times log from Logger. It looks like that logger from main.ts exactly app.useLogger(new LoggerService()); is executed along with injected instance, in future i want to put some async logger like pino or something and i'm afraid that log will be send two times. I assume something is wrong with my configuration, because it doesn't look like some common problem. What is more from default Logger i got properly one message.
The second one:
If i also inject that service to different module, for example payments. And made the same endpoint as for user, votes field on Logger class is always 0. Is it proper behaviour for transient scope of injection ? Because as i understood it should be injected per module. So in one module that counter shouldn't be reseted.
Is it properly configured ? Or it is normal behaviour ? I'm really afraid about two loggers executed in one this.logger.log.
I found, that all elements passed into super.log are forced to be another log line. So duplicated logs are from two arguments at super.log. Please forward only message.
log(message: string) {
this.votes = this.votes + 1;
console.log(this.votes, 'log', message);
super.log(message);
}

how to subscribe the Id of the created object after POST operation

I have an component where i am adding a new object called customer by calling the api like this:
public onAdd(): void {
this.myCustomer = this.customerForm.value;
this.myService.addCustomer(this.myCustome).subscribe(
() => { // If POST is success
this.callSuccessMethod();
},
(error) => { // If POST is failed
this.callFailureMethod();
},
);
}
Service file:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
import {ICustomer } from 'src/app/models/app.models';
#Injectable({
providedIn: 'root',
})
export class MyService {
private baseUrl : string = '....URL....';
constructor(private http: HttpClient) {}
public addCustomer(customer: ICustomer): Observable<object> {
const apiUrl: string = `${this.baseUrl}/customers`;
return this.http.post(apiUrl, customer);
}
}
As shown in component code, i have already subscribed the api call like this:
this.myService.addCustomer(this.myCustome).subscribe(
() => { // If POST is success
.....
},
(error) => { // If POST is failed
...
},
);
But,I want to subscribe the results in another component, I have tried like this:
public getAddedCustomer() {
this.myService.addCustomer().subscribe(
(data:ICustomer) => {
this.addedCustomer.id = data.id; <======
}
);
}
I am getting this lint error: Expected 1 arguments, but got 0 since i am not passing any parameter.
What is the right approach to subscribe the api call in other components? after POST operation.
Because i want to get added object id for other functionality.
Well it totally depends on the design of your application and the relation between components. You can use Subjects for multicasting the data to multiple subscribers.
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
import { ICustomer } from 'src/app/models/app.models';
#Injectable({
providedIn: 'root',
})
export class MyService {
private baseUrl : string = '....URL....';
private latestAddedCustomer = new Subject();
public latestAddedCustomer$ = this.latestAddedCustomer.asObservable()
constructor(private http: HttpClient) {}
public addCustomer(customer: ICustomer): Observable<object> {
const apiUrl: string = `${this.baseUrl}/customers`;
return this.http.post(apiUrl, customer).pipe(map((data) => this.latestAddedCustomer.next(data)));
}
}
and subscribing to the subject as follows
this.latestAddedCustomer$.subscribe()
should get you the latest added customer details. Even though i would not do this the way its written. I would basically write a seperate service to share the data between the components or would write a cache service if its used across the application. But the idea here is to use the concept of Subjects. You can read more about it Here

Load different classes and execute similar function name

I have a problem that I can't resolve.
Let's say we have some classes in a directory named services.
Each of theses classes contain a contructor() and send() method.
We can have differents classes such as Discord, Slack, SMS, etc.
Their goal is just to sent notification through external service.
I think I have to use an interface or an abstract class which contain contructor() and send() method but how can I instanciate every class and call send() in an elegantly way ?
My project structure :
services/
-> discord.js
-> slack.js
-> [...]
index.js
Regards.
I think what you are looking for is a kind of manager where you have a single send() function that chooses a specific service based on a parameter. Something like this:
services/index.js
import SlackService from 'slack.js';
import DiscordService from 'discord.js';
export const TYPES = {
SLACK: 'slack',
DISCORD: 'discord',
};
export class ServiceManager {
services;
constructor() {
this.services = {
[TYPES.DISCORD]: new DiscordService(/* discordConfig */),
[TYPES.SLACK]: new SlackService(/* slackConfig */),
};
}
send(type, data) {
return this.services[type].send(data);
}
}
index.js
import ServiceManager from 'services/index.js';
const serviceManager = new ServiceManager();
serviceManager.send(ServiceManager.TYPES.SLACK, { message: 'Sent to Slack' });
serviceManager.send(ServiceManager.TYPES.DISCORD, { message: 'Sent to Discord' });
Dynamically loading services from files
You can use require-dir to import all files from a directory and then map over those to create each service. The individual service files have to be written in a defined syntax for the manager to use them. Something like this:
services/slack.js (as example for all service files):
export const name = 'slack';
export class Service {
constructor() {
// Set up connection to slack
}
send() {
// Send something to slack
}
}
services/index.js
const requireDir = require('require-dir');
export class ServiceManager {
services;
constructor() {
const serviceObjects = requireDir('.');
this.services = Object.values(serviceObjects).reduce(
(services, { name, Service }) => {
services[name] = new Service();
return services;
}
)
}
getRegisteredServices() {
return Object.keys(this.services);
}
send(name, data) {
return this.services[name].send(data);
}
sendAll(data) {
Object.values(this.services).each(service => service.send(data));
}
}
index.js (stays pretty much the same)
import ServiceManager from 'ServiceManager.js';
const serviceManager = new ServiceManager();
console.log('Registered services are: ', serviceManager.getRegisteredServices());
serviceManager.send('slack', { message: 'Sent to Slack' });
serviceManager.send('discord', { message: 'Sent to Discord' });

how to access object properties from TypeScript?

I'm new to Angular and TypeScript and just started working on a project using MEAN stack (MongoDB, Express, Angular, Node.js).
I created this mongoose module :
import * as mongoose from 'mongoose';
var Schema = mongoose.Schema;
const entrepriseSchema = new mongoose.Schema({
name: {type: String, unique: true, required : true},
telephone: Number,
logo: String,
web_site: String,
sites: [
{site_id: {type: Schema.Types.ObjectId, ref: 'Site'}}
]
});
const Entreprise = mongoose.model('Entreprise', entrepriseSchema);
export default Entreprise;
and this is my entreprise.component.ts :
import { Component, OnInit } from '#angular/core';
import { Http } from '#angular/http';
import { FormGroup, FormControl, Validators, FormBuilder } from '#angular/forms';
import { ActivatedRoute } from '#angular/router';
import { EntrepriseService } from '../services/entreprise.service';
import { SiteService } from '../services/site.service';
#Component({
selector: 'app-entreprise',
templateUrl: './entreprise.component.html',
styleUrls: ['./entreprise.component.scss'],
providers: [EntrepriseService, SiteService]
})
export class EntrepriseComponent implements OnInit {
entreprise = {};
sites = [];
id: String;
constructor(private entrepriseService: EntrepriseService,
private siteService: SiteService,
private http: Http,
private route: ActivatedRoute) {
this.id = route.snapshot.params['id'];
}
ngOnInit() {
this.getEntrepriseById(this.id);
//not working
//console.log(this.entreprise.name);
//console.log(this.entreprise.sites);
//this.getSitesIn(this.entreprise.sites);
}
getEntrepriseById(id) {
this.entrepriseService.getEntreprise(id).subscribe(
data => this.entreprise = data,
error => console.log(error)
);
}
getSitesIn(ids) {
this.siteService.getSitesIn(ids).subscribe(
data => this.sites = data,
error => console.log(error)
);
}
}
when I try to display the properties of the returned from entreprise.component.html it works fine and displays all the properties :
<h3>{{entreprise.name}}</h3>
<div *ngFor="let site of entreprise.sites">
{{site.site_id}}
</div>
{{entreprise.logo}}
{{entreprise.web_site}}
but how can I access the same properties on the TypeScript side ?
The commented code in the EntrepriseComponent is what I'm trying to accomplish but it's not working since this.entreprise is type {} .
The Enterprise model/schema that you created in Mongoose in Node.js resides on the server side. If you want the TypeScript code on the UI to recognize the properties in Enterprise, you will have to create a class in your angular codebase.
Create a folder named, say, models at the same level as your services folder. (Optional)
Create two files named site.ts and enterprise.ts in the models folder created in the previous step (You can put these file at a different location if you want) with the following contents:
site.ts
export interface Site {
site_id?: string;
}
enterprise.ts
import { Site } from './site';
export interface Enterprise {
name?: string;
telephone?: string;
logo?: string;
web_site?: string;
sites?: Site[];
}
Now, inside the EntrepriseComponent file, add the following imports
import { Enterprise} from '../models/entreprise';
import { Site } from '../models/site';
And change the first lines inside the EntrepriseComponent file to
export class EntrepriseComponent implements OnInit {
entreprise: Enterprise = {};
sites: Site[] = [];
Now, the enterprise attribute will be of type Enterprise and you will be able to access the properties that we declared in the enterprise.ts file.
Update:
Also, you cannot console.log(this.enterprise.name) immediately after this.getEntrepriseById(this.id); in your ngOnInit() function. This is because the web service you are making to get the enterprise object would not have resolved when you are trying to log it to the console.
If you want to see the enterprise object in the console or you want to run some code that needs to run after the service call has resolved and the this.enterprise object has a value, the best place to do this would be your getEntrepriseById function. Change the getEntrepriseById function to
getEntrepriseById(id) {
this.entrepriseService.getEntreprise(id).subscribe(
data => {
this.enterprise = data;
console.log(this.enterprise.name);
// Any code to run after this.enterprise resolves can go here.
},
error => console.log(error)
);
}

Class lacks methods after passed in Observable chain

I encounter a strange problem with my custom class in angular2 after passing it through an Observable chain.
I always receive the error:
EXCEPTION: f.mapToParams is not a function
ORIGINAL STACKTRACE:
TypeError: f.mapToParams is not a function
at SafeSubscriber._next (filter.component.ts)
...
Uncaught TypeError: f.mapToParams is not a function
at Safesubscriber._next (filter.component.ts)
Here is my coding:
filter.ts:
import { Params } from '#angular/router';
export class Filter {
public text:String = '';
public mapToParams():Params {
let params:Params = {};
// Do some mapping here...
return params;
}
}
filter.component.ts
import { Component, OnInit Output, EventEmitter } from '#angular/core';
import { Router, Params } from '#angular/router';
import { Filter } from './filter';
import { Observable, Subject } from 'rxjs/Rx';
export class FilterComponent implements OnInit {
private _filter:Filter;
private _filterStream = new Subject<Filter>();
ngOnInit() {
this._filter = new Filter();
this._filterStream
.debounceTime(300)
.switchMap((f:Filter) => Observable.of(f))
.subscribe((f:Filter) => {
let params:Params = {};
console.log(f.text); // <-- No problem here
// params = this._map(f); // <-- This would work
params = f.mapToParams(); // <-- Here occurs the error
});
}
private _map(f:Filter):Params {
// Do some mapping here
}
public onInputChanged(searchText:String):void {
this._mergeFilter( {
map(f:Filter) {
f.text = searchText;
}
})
}
private _mergeFilter(callback:FilterMergeCallback):void {
let f:Filter = JSON.parse(JSON.stringify(this._filter));
callback.map(f);
this._filterStream.next(f);
}
}
I have tried to comment out the debounceTime and switchMap statement but with no success.
At a different point in my coding the filter.mapToParams method can be called without any problems. It seems to me like the Observable chain strips all methods from my object.
Here is my angular config:
#angular/cli: 1.0.0.-beta.32.3
#angular/common: ^2.4.0
#angular/compiler ^2.4.0
#angular/core ^2.4.0
rxjs: ^5.1.0
Can anyone help me on this?
I think I got it:
I copied the current _filter Object to a new filter variable by
JSON.parse(JSON.stringify())
By this, all methods get stripped from the new object.
Means, I have to find a new method to clone the object...
Thanks all for you replies!

Categories