Winston、Pino 或 Bunyan 可以用于登录 Loopback4 吗?如果是这样,在 Loopback4 中实现它们的基本步骤是什么?
在查看本教程时,我能够使用 Express 使 Winston 工作: https://www.digitalocean.com/community/tutorials/how-to-use-winston-to-log-node-js-applications
Winston 和 Brunyan 有 Loopback 模块。然而,我的印象是(因为上次更新已经超过 10 个月了)它们一定是针对旧版本的 Loopback(因为 v4 于 10 月 18 日发布)?
温斯顿 - https://www.npmjs.com/package/loopback-component-winston
Brunyan - https://www.npmjs.com/package/loopback-component-bunyan
让我有点困扰的是,如果我的一条路由抛出异常,输出只能记录到 stderr。因此,我执行了以下操作来修复此问题并使用 Winston 进行日志记录,同时仍然完全不知道实际使用的底层日志系统。
假设在我的一个控制器中我有以下 REST 端点:
@post('/myendpoint')
async handleEndpoint(): Promise<void> {
throw new Error('I am an error!');
}
现在添加自定义记录器,我为其创建了一个新服务并将其 Winston 变体绑定到我的应用程序。
src/services/logger.service.ts(抽象记录器服务及其使用 Winston 的具体实现)
import winston from 'winston';
export interface LoggerService {
logger: object;
}
export class WinstonLoggerService implements LoggerService {
logger: winston.Logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.printf(info => {
return `[${info.timestamp}] ${info.level}: ${info.message}`;
}),
),
}),
],
});
}
src/keys.ts
export namespace LoggerBindings {
export const LOGGER = BindingKey.create<LoggerService>('services.logger');
}
src/providers/log-error.provider.ts(一个 Loopback 4 提供程序类,其中注入了应用程序绑定的记录器类,然后可以使用它)
import {Provider} from '@loopback/context';
import {LogError, Request} from '@loopback/rest';
import {inject} from '@loopback/core';
import {LoggerBindings} from '../keys';
import {LoggerService} from '../services/logger.service';
export class LogErrorProvider implements Provider<LogError> {
constructor(@inject(LoggerBindings.LOGGER) protected logger: LoggerService) {}
value(): LogError {
return (err, statusCode, req) => this.action(err, statusCode, req);
}
action(err: Error, statusCode: number, req: Request) {
if (statusCode < 500) {
return;
}
this.logger.logger.error(
`HTTP ${statusCode} on ${req.method} ${req.url}: ${err.stack ?? err}`,
);
}
}
src/application.ts(绑定语句进入构造函数)
import {WinstonLoggerService} from './services/logger.service';
import {LogErrorProvider} from './providers/log-error.provider';
this.bind(LoggerBindings.LOGGER).toClass(WinstonLoggerService);
this.bind(RestBindings.SequenceActions.LOG_ERROR).toProvider(LogErrorProvider);
上一个代码块中的最后一行是这里的关键,因为它确保为
LOG_ERROR
绑定我们的自定义提供程序。在内部,Loopback 4 使用 RejectProvider
中定义的 @loopback/rest/src/providers/reject.provider.ts
来处理 REST 端点中抛出的错误。在此提供程序中,注入了 RestBindings.SequenceActions.LOG_ERROR
,默认情况下取自 @loopback/rest/src/providers/log-error.provider.ts
,我们在此处重新定义。这样,我们不需要重写整个拒绝提供程序,而只需重写其中处理记录 REST 错误的一小部分。
现在调用示例路由时,控制台上会显示以下内容:
[2020-01-05T23:41:28.604Z] error: HTTP 500 on POST /myendpoint: Error: I am an error!
at [...long stacktrace...]
可以在
Loopback 4
中实现自定义日志记录,这样做应该与 Express
没有太大区别。
我已经尝试过 winston
,因此会详细说明相同的内容,但这也应该可以使用 bunyan
来实现。
首先,您可以在项目的根目录下创建一个
utils
文件夹来保存自定义记录器。使用 LB4 CLI 搭建的应用程序采用典型的结构和 utils
文件夹,如下所示:
.
|
|-- public
|-- src
|-- utils
| |-- logger
| |-- index.js <-- custom logger can be defined here.
|-- node_modules
|-- index.js
|--
.
我正在使用这个例子 如 winston 的 github 存储库中所述,用于定义记录器:
// utils/logger/index.js
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
//
// - Write to all logs with level `info` and below to `combined.log`
// - Write all logs error (and below) to `error.log`.
//
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
module.exports = logger;
您现在可以通过在应用程序中“导入”记录器来开始使用记录器。对于根文件夹中的
index.js
,import
看起来像:
// index.js
const logger = require('./utils/logger');
对于之前定义的记录器,以下语句会将
I am logged.
记录到名为 combined.log
的文件中:
logger.info('I am logged.');
这应该可以帮助您开始。
附注我确信答案(和方法)可以改进,因此,非常愿意接受任何有用的建议。
我遵循了 @sigalor 的回答,但有一个变化是我使用内置的 Loopback 日志库来执行此操作。我在
logger.service.ts
中有一个 WinstonLogger 的实例,而不是 application.ts
,它是我按照 本指南 创建的:
import {LogErrorProvider} from './providers/log-error.provider';
import {format} from 'winston';
import {
LoggingBindings,
LoggingComponent,
WINSTON_TRANSPORT,
WinstonTransports,
} from '@loopback/logging';
export class VibratoRestV2Application extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
//...
// Configure logging
this.configure(LoggingBindings.COMPONENT).to({
enableFluent: false, // default to true
enableHttpAccessLog: false, // default to true
});
this.configure(LoggingBindings.WINSTON_LOGGER).to({
level: logConfig.logLevel ?? 'info',
format: format.combine(format.timestamp(), format.json()),
defaultMeta: {framework: 'Loopback'},
});
const fileTransport = new WinstonTransports.File({
level: logConfig.logLevel ?? 'info',
lazy: false,
filename: logConfig.location ?? 'file.log',
maxsize: 1000000, // 1MB
maxFiles: 5,
tailable: true,
zippedArchive: true,
});
this.bind('logger.winston.transports.file')
.to(fileTransport)
.apply(extensionFor(WINSTON_TRANSPORT));
this.component(LoggingComponent);
// Log errors through provider
this.bind(RestBindings.SequenceActions.LOG_ERROR) .toProvider(
LogErrorProvider,
);
}
然后,您所需要的就是配置他的
log-error.provider.ts
,这就是真正的魔法发生的地方:
import {Provider} from '@loopback/context';
import {inject} from '@loopback/core';
import {LoggingBindings, WinstonLogger} from '@loopback/logging';
import {LogError, Request} from '@loopback/rest';
import {createLogger, Logger} from 'winston';
export class LogErrorProvider implements Provider<LogError> {
constructor(
@inject(LoggingBindings.WINSTON_LOGGER) protected logger: WinstonLogger,
) {createLogger()}
value(): LogError {
return (err, statusCode, req) => this.action(err, statusCode, req);
}
action(err: Error, statusCode: number, req: Request) {
if (statusCode < 500) {
return;
}
this.logger.error(
`HTTP ${statusCode} on ${req.method} ${req.url}: ${err.stack ?? err}`,
);
}
}
然后您可以获得错误日志记录和自定义日志记录。您还可以使用 Loopback4 中日志库附带的
@logInvocation
装饰器,但这只会在应用程序日志级别为 DEBUG
或更高时打印日志 - 文档在这里:
import {logInvocation} from '@loopback/logging';
@logInvocation()
export class PingController {
//...
}