Reputation: 601
I am trying to log the grpc request and response in my nestjs project. It seems that the middleware would help me do that. However, the middleware in nesjs is for HTTP only. So, is there any other way that can be used to solve my problem? Maybe interceptors?
Upvotes: 1
Views: 2964
Reputation: 1836
Based on the examples on this github. I used the following code to log the request/response and redact the logs for a gRPC client:
// users.module.ts
@Module({
imports: [
ConfigModule.forRoot(),
ClientsModule.registerAsync([
{
name: USERS_PACKAGE,
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
transport: Transport.GRPC,
options: {
package: 'users',
credentials: credentials.createInsecure(),
protoPath: join(__dirname, './proto/users.proto'),
url: configService.get<string>('services.users.url'),
loader: { keepCase: true },
channelOptions: {
interceptors: [
(options, nextCall) => {
const logger: Logger = new Logger('UsersModule');
const redactedFields: string[] = configService.get<string[]>('logger.redact.fields');
const path: string = options.method_definition.path;
logger.verbose('-------------------------------------- GRPC CALL ----------------------------------');
const interceptingCall = new InterceptingCall(
nextCall(options),
GrpcLoggingInterceptor(path, redactedFields),
);
return interceptingCall;
},
],
},
},
}),
inject: [ConfigService],
},
]),
],
providers: [GetUsersClient],
exports: [GetUsersClient],
})
export class UsersModule {}
The grpc logging interceptor to intercept the grpc requests
// grpc-logging.interceptor.ts
import { Logger } from '@nestjs/common';
export const GrpcLoggingInterceptor = (path: string, redactedFields: string[]) => {
const logger: Logger = new Logger('GrpcLoggingInterceptor');
function redact(data: string): string {
return JSON.stringify(data, (k, v) => (redactedFields.includes(k) ? `[Redacted]` : v));
}
return {
start: function (metadata, listener, next) {
const newListener = {
onReceiveMetadata: function (metadata, next) {
logger.verbose(`response metadata : ${redact(metadata)}`);
next(metadata);
},
onReceiveMessage: function (message, next) {
logger.verbose(`response body : ${redact(message)}`);
next(message);
},
onReceiveStatus: function (status, next) {
logger.verbose(`response status : ${redact(status)}`);
next(status);
},
};
next(metadata, newListener);
},
sendMessage: function (message, next) {
logger.verbose(`path: ${JSON.stringify(path)}`);
logger.verbose(`request body : ${redact(message)}`);
next(message);
},
};
};
The fields to redact:
// config.yaml
logger:
redact:
fields:
- token
- password
- address
- telephone
Upvotes: 1
Reputation: 70061
You can use an Interceptor. In fact, that's exactly what my OgmaInterceptor does, and there's even a plugin for gRPC. In general, just something simple like
@Injectable()
export class LoggingInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const start = Date.now()
console.log('Request start');
return next.handle().pipe(
tap((data) => {
console.log(`Request took ${Date.now() - start}ms. Response length: ${Buffer.from(JSON.stringify(data) ?? '').length} bytes`);
})
);
}
}
would be a simple first pass at writing something for this
Upvotes: 0