pino-sentry
Version:
@sentry/node transport for pino logger
239 lines • 9.32 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWriteStreamAsync = exports.createWriteStream = exports.PinoSentryTransport = exports.Severity = exports.SentryInstance = void 0;
const async_hooks_1 = require("async_hooks");
const split2_1 = __importDefault(require("split2"));
const pumpify_1 = __importDefault(require("pumpify"));
const through2_1 = __importDefault(require("through2"));
const Sentry = __importStar(require("@sentry/node"));
exports.SentryInstance = Sentry;
class ExtendedError extends Error {
constructor(info) {
super(info.message);
this.name = "Error";
this.stack = info.stack || null;
}
}
// Local enum declaration, as @sentry/node deprecated using enums over strings for bundle size
var Severity;
(function (Severity) {
Severity["Fatal"] = "fatal";
Severity["Error"] = "error";
Severity["Warning"] = "warning";
Severity["Log"] = "log";
Severity["Info"] = "info";
Severity["Debug"] = "debug";
// @deprecated: "critical" is not present in sentry 7.x sdk
// https://github.com/getsentry/sentry-javascript/issues/3067
Severity["Critical"] = "critical";
})(Severity = exports.Severity || (exports.Severity = {}));
const SEVERITIES_MAP = {
10: Severity.Debug,
20: Severity.Debug,
30: Severity.Info,
40: Severity.Warning,
50: Severity.Error,
60: Severity.Fatal,
// Support for useLevelLabels
// https://github.com/pinojs/pino/blob/master/docs/api.md#uselevellabels-boolean
trace: Severity.Debug,
debug: Severity.Debug,
info: Severity.Info,
warning: Severity.Warning,
error: Severity.Error,
fatal: Severity.Fatal,
};
// How severe the Severity is
const SeverityIota = {
[Severity.Debug]: 1,
[Severity.Log]: 2,
[Severity.Info]: 3,
[Severity.Warning]: 4,
[Severity.Error]: 5,
[Severity.Fatal]: 6,
[Severity.Critical]: 7,
};
function get(data, path) {
return path.split('.').reduce((acc, part) => acc && acc[part], data);
}
class PinoSentryTransport {
constructor(options) {
// Default minimum log level to `debug`
this.minimumLogLevel = SeverityIota[Severity.Debug];
this.messageAttributeKey = 'msg';
this.extraAttributeKeys = ['extra'];
this.stackAttributeKey = 'stack';
this.maxValueLength = 250;
this.sentryExceptionLevels = [Severity.Fatal, Severity.Error];
this.decorateScope = (_data, _scope) => { };
Sentry.init(this.validateOptions(options || {}));
}
getLogSeverity(level) {
return SEVERITIES_MAP[level] || Severity.Info;
}
get sentry() {
return Sentry;
}
transformer() {
return through2_1.default.obj((chunk, _enc, cb) => {
this.prepareAndGo(chunk, cb);
});
}
prepareAndGo(chunkInfo, cb) {
chunkInfo.run((chunk) => {
this.chunkInfoCallback(chunk, cb);
});
}
chunkInfoCallback(chunk, cb) {
const severity = this.getLogSeverity(chunk.level);
// Check if we send this Severity to Sentry
if (!this.shouldLog(severity)) {
setImmediate(cb);
return;
}
const tags = chunk.tags || {};
const breadcrumbs = chunk.breadcrumbs || {};
if (chunk.reqId) {
tags.uuid = chunk.reqId;
}
if (chunk.responseTime) {
tags.responseTime = chunk.responseTime;
}
if (chunk.hostname) {
tags.hostname = chunk.hostname;
}
const extra = {};
this.extraAttributeKeys.forEach((key) => {
const value = get(chunk, key);
if (value !== undefined) {
extra[key] = value;
}
});
const message = get(chunk, this.messageAttributeKey);
const stack = get(chunk, this.stackAttributeKey) || '';
const scope = new Sentry.Scope();
this.decorateScope(chunk, scope);
scope.setLevel(severity);
if (this.isObject(tags)) {
Object.keys(tags).forEach(tag => scope.setTag(tag, tags[tag]));
}
if (this.isObject(extra)) {
Object.keys(extra).forEach(ext => scope.setExtra(ext, extra[ext]));
}
if (this.isObject(breadcrumbs)) {
Object.values(breadcrumbs).forEach(breadcrumb => scope.addBreadcrumb(breadcrumb));
}
// Capturing Errors / Exceptions
if (this.isSentryException(severity)) {
const error = message instanceof Error ? message : new ExtendedError({ message, stack });
Sentry.captureException(error, scope);
setImmediate(cb);
}
else {
// Capturing Messages
Sentry.captureMessage(message, scope);
setImmediate(cb);
}
}
validateOptions(options) {
var _a, _b, _c, _d, _e, _f;
const dsn = options.dsn || process.env.SENTRY_DSN;
if (!dsn) {
console.log('Warning: [pino-sentry] Sentry DSN must be supplied, otherwise logs will not be reported. Pass via options or `SENTRY_DSN` environment variable.');
}
if (options.level) {
const allowedLevels = Object.keys(SeverityIota);
if (!allowedLevels.includes(options.level)) {
throw new Error(`[pino-sentry] Option \`level\` must be one of: ${allowedLevels.join(', ')}. Received: ${options.level}`);
}
// Set minimum log level
this.minimumLogLevel = SeverityIota[options.level];
}
this.stackAttributeKey = (_a = options.stackAttributeKey) !== null && _a !== void 0 ? _a : this.stackAttributeKey;
this.extraAttributeKeys = (_b = options.extraAttributeKeys) !== null && _b !== void 0 ? _b : this.extraAttributeKeys;
this.messageAttributeKey = (_c = options.messageAttributeKey) !== null && _c !== void 0 ? _c : this.messageAttributeKey;
this.maxValueLength = (_d = options.maxValueLength) !== null && _d !== void 0 ? _d : this.maxValueLength;
this.sentryExceptionLevels = (_e = options.sentryExceptionLevels) !== null && _e !== void 0 ? _e : this.sentryExceptionLevels;
this.decorateScope = (_f = options.decorateScope) !== null && _f !== void 0 ? _f : this.decorateScope;
return {
dsn,
// npm_package_name will be available if ran with
// from a "script" field in package.json.
serverName: process.env.npm_package_name || 'pino-sentry',
environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV || 'production',
debug: !!process.env.SENTRY_DEBUG || false,
sampleRate: 1.0,
maxBreadcrumbs: 100,
...options,
};
}
isObject(obj) {
const type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
}
isSentryException(level) {
return this.sentryExceptionLevels.includes(level);
}
shouldLog(severity) {
const logLevel = SeverityIota[severity];
return logLevel >= this.minimumLogLevel;
}
}
exports.PinoSentryTransport = PinoSentryTransport;
class ChunkInfo extends async_hooks_1.AsyncResource {
constructor(chunk) {
super("ChunkInfo");
this.chunk = chunk;
}
run(callback, ...args) {
try {
return this.runInAsyncScope(callback, undefined, this.chunk, ...args);
}
finally {
this.emitDestroy();
}
}
}
function createWriteStream(options) {
const transport = new PinoSentryTransport(options);
const sentryTransformer = transport.transformer();
return new pumpify_1.default((0, split2_1.default)((line) => {
try {
return new ChunkInfo(JSON.parse(line));
}
catch (e) {
// Returning undefined will not run the sentryTransformer
return;
}
}), sentryTransformer);
}
exports.createWriteStream = createWriteStream;
// Duplicate to not break API
exports.createWriteStreamAsync = createWriteStream;
//# sourceMappingURL=transport.js.map