diff --git a/.changesets/add-pino-transport.md b/.changesets/add-pino-transport.md new file mode 100644 index 00000000..3fcf0c0d --- /dev/null +++ b/.changesets/add-pino-transport.md @@ -0,0 +1,18 @@ +--- +bump: patch +type: add +--- + +A Pino transport is now available. If Pino is your main logger, you can now use the AppSignal pino transport to send those logs to AppSignal. + +```js +import pino from "pino" +import { Appsignal, AppsignalPinoTransport } from "@appsignal/nodejs" + +const logger = pino( + AppsignalPinoTransport({ + client: Appsignal.client, + group: "application", + }) +) +``` diff --git a/package-lock.json b/package-lock.json index 4a2c68bb..ba2d14e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "@prisma/instrumentation": ">= 5.11.0 < 5.14.0", "node-addon-api": "^3.1.0", "node-gyp": "^10.0.0", + "pino-abstract-transport": "^2.0.0", "tslib": "^2.0.3", "winston": "^3.6.0" }, @@ -10700,6 +10701,14 @@ "node": ">=4" } }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "dependencies": { + "split2": "^4.0.0" + } + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -11612,6 +11621,14 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", diff --git a/package.json b/package.json index a3bd2ebb..b8165be6 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@prisma/instrumentation": ">= 5.11.0 < 5.14.0", "node-addon-api": "^3.1.0", "node-gyp": "^10.0.0", + "pino-abstract-transport": "^2.0.0", "tslib": "^2.0.3", "winston": "^3.6.0" }, diff --git a/src/index.ts b/src/index.ts index 8fb58daf..a1049a52 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,3 +13,6 @@ export { WinstonTransport } from "./winston_transport" export * from "./helpers" export * as checkIn from "./check_in" export { Heartbeat, heartbeat } from "./heartbeat" + +import AppsignalPinoTransport from "./pino_transport" +export { AppsignalPinoTransport } diff --git a/src/pino_transport.ts b/src/pino_transport.ts new file mode 100644 index 00000000..3d60ce1d --- /dev/null +++ b/src/pino_transport.ts @@ -0,0 +1,78 @@ +import { LOGGER_LEVEL_SEVERITY } from "./logger" +import build from "pino-abstract-transport" +import { Client } from "./client" + +type PinoTransportOptions = { + client: Client + group: string +} + +const appsignalPinoTransport = ({ + client, + group = "app" +}: PinoTransportOptions) => { + return build(async (source: any) => { + for await (const obj of source) { + sendLogs(parseInfo(obj, group), client) + } + }) + + async function sendLogs(data: Record, client: Client) { + client.extension.log( + data.group || "app", + data.severity, + 0, + data.msg, + data.attributes + ) + } +} + +function parseInfo( + obj: Record, + group: string +): Record { + const { hostname, level, msg, ...attributes } = obj + + return { + severity: getSeverity(level), + hostname, + group, + msg, + attributes: flattenAttributes(attributes) + } +} + +function flattenAttributes( + attributes: Record, + prefix = "" +): Record { + let result: Record = {} + + for (const key in attributes) { + const newKey = prefix ? `${prefix}.${key}` : key + + if ( + typeof attributes[key] === "object" && + attributes[key] !== null && + !Array.isArray(attributes[key]) + ) { + const flattened = flattenAttributes(attributes[key], newKey) + result = { ...result, ...flattened } + } else { + result[newKey] = attributes[key] + } + } + + return result +} + +function getSeverity(level: number): number { + if (level >= 50) return LOGGER_LEVEL_SEVERITY.error + if (level >= 40) return LOGGER_LEVEL_SEVERITY.warn + if (level >= 30) return LOGGER_LEVEL_SEVERITY.info + if (level >= 20) return LOGGER_LEVEL_SEVERITY.debug + return LOGGER_LEVEL_SEVERITY.trace +} + +export = appsignalPinoTransport