Skip to content

Commit 638209f

Browse files
authored
use npx-import-light, make getTransformStream async (#363)
feat: make getTranformStream async, load sentry dynamically BREAKING CHANGE: getTransformStream becomes async BREAKING CHANGE: @sentry/node becomes dev dependency and needs to be installed explicitly
1 parent 9ce2c69 commit 638209f

9 files changed

Lines changed: 275 additions & 102 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ node my-script.js | LOG_FORMAT=json pino-probot
2020

2121
## Programmatic usage
2222

23-
`@probot/pino` exports a `getTransformStream()` method which can be passed as 2nd argument to `pino()`
23+
`@probot/pino` exports a `getTransformStream()` async function which can be passed as 2nd argument to `pino()`
2424

2525
```js
2626
import pino from "pino";
@@ -30,7 +30,7 @@ const log = pino(
3030
{
3131
name: "probot",
3232
},
33-
getTransformStream(),
33+
await getTransformStream(),
3434
);
3535
```
3636

@@ -40,7 +40,7 @@ This won't log anything to stdout though. In order to pass the formatted logs ba
4040
import pino from "pino";
4141
import { getTransformStream } from "@probot/pino";
4242

43-
const transform = getTransformStream();
43+
const transform = await getTransformStream();
4444
transform.pipe(pino.destination(1));
4545
const log = pino(
4646
{
@@ -53,7 +53,7 @@ const log = pino(
5353
With custom options:
5454

5555
```js
56-
const transform = getTransformStream({
56+
const transform = await getTransformStream({
5757
logFormat: "json",
5858
logLevelInString: true,
5959
sentryDsn: "http://username@example.com/1234",

bin/cli.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"use strict";
2-
31
import pump from "pump";
42
import split from "split2";
53

@@ -11,4 +9,4 @@ const options = {
119
sentryDsn: process.env.SENTRY_DSN,
1210
};
1311

14-
pump(process.stdin, split(), getTransformStream(options), process.stdout);
12+
pump(process.stdin, split(), await getTransformStream(options), process.stdout);

index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Transform } from "node:stream";
22

3-
declare function getTransformStream(options?: Options): Transform;
3+
declare function getTransformStream(options?: Options): Promise<Transform>;
44

55
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
66

index.js

Lines changed: 115 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Transform } from "node:stream";
2-
2+
import { npxImport } from "npx-import-light";
33
import { prettyFactory } from "pino-pretty";
4-
import { init, withScope, captureException } from "@sentry/node";
54

65
const LEVEL_MAP = {
76
10: "trace",
@@ -31,45 +30,39 @@ const pinoErrorProps = [
3130
"sentryEventId",
3231
].join(",");
3332

33+
/** @type {import('@sentry/node').init} */
34+
let init;
35+
/** @type {import('@sentry/node').withScope} */
36+
let withScope;
37+
/** @type {import('@sentry/node').captureException} */
38+
let captureException;
39+
40+
/** @type {import('@sentry/node')} */
41+
let sentry;
42+
3443
/**
3544
* Implements Probot's default logging formatting and error captioning using Sentry.
3645
*
3746
* @param {import("./").Options} options
38-
* @returns Transform
47+
* @returns {Promise<Transform>}
3948
* @see https://getpino.io/#/docs/transports
4049
*/
41-
export function getTransformStream(options = {}) {
50+
export async function getTransformStream(options = {}) {
4251
const formattingEnabled = options.logFormat !== "json";
4352

4453
const levelAsString = options.logLevelInString;
45-
const sentryEnabled = !!options.sentryDsn;
46-
47-
if (sentryEnabled) {
48-
init({
49-
dsn: options.sentryDsn,
50-
// See https://github.com/getsentry/sentry-javascript/issues/1964#issuecomment-688482615
51-
// 6 is enough to serialize the deepest property across all GitHub Event payloads
52-
normalizeDepth: 6,
53-
});
54-
}
5554

5655
const pretty = prettyFactory({
5756
ignore: pinoIgnore,
5857
errorProps: pinoErrorProps,
5958
});
6059

61-
return new Transform({
62-
objectMode: true,
63-
transform(chunk, enc, cb) {
64-
const line = chunk.toString().trim();
65-
66-
/* c8 ignore start */
67-
if (line === undefined) return cb();
68-
/* c8 ignore stop */
69-
70-
const data = sentryEnabled ? JSON.parse(line) : null;
60+
if (!options.sentryDsn) {
61+
return new Transform({
62+
objectMode: true,
63+
transform(chunk, enc, cb) {
64+
const line = chunk.toString().trim();
7165

72-
if (!sentryEnabled || data.level < 50) {
7366
if (formattingEnabled) {
7467
return cb(null, pretty(line));
7568
}
@@ -80,72 +73,110 @@ export function getTransformStream(options = {}) {
8073

8174
cb(null, line + "\n");
8275
return;
83-
}
84-
85-
withScope((scope) => {
86-
const sentryLevelName = data.level === 50 ? "error" : "fatal";
87-
scope.setLevel(sentryLevelName);
88-
89-
if (data.event) {
90-
scope.setExtra("event", data.event);
91-
}
92-
if (data.headers) {
93-
scope.setExtra("headers", data.headers);
94-
}
95-
if (data.request) {
96-
scope.setExtra("request", data.request);
97-
}
98-
if (data.status) {
99-
scope.setExtra("status", data.status);
100-
}
76+
},
77+
});
78+
} else {
79+
if (!sentry) {
80+
// Import Sentry dynamically to avoid loading it when not needed
81+
sentry = await npxImport("@sentry/node@9.27.0", {
82+
onlyPackageRunner: true,
83+
});
84+
init = sentry.init;
85+
withScope = sentry.withScope;
86+
captureException = sentry.captureException;
87+
}
10188

102-
// set user id and username to installation ID and account login
103-
const payload = data.event?.payload || data.err?.event?.payload;
104-
if (payload) {
105-
const {
106-
// When GitHub App is installed organization wide
107-
installation: { id, account: { login: account } = {} } = {},
108-
109-
// When the repository belongs to an organization
110-
organization: { login: organization } = {},
111-
// When the repository belongs to a user
112-
repository: { owner: { login: owner } = {} } = {},
113-
} = payload;
114-
115-
scope.setUser({
116-
id,
117-
username: account || organization || owner,
118-
});
119-
}
89+
init({
90+
dsn: options.sentryDsn,
91+
// See https://github.com/getsentry/sentry-javascript/issues/1964#issuecomment-688482615
92+
// 6 is enough to serialize the deepest property across all GitHub Event payloads
93+
normalizeDepth: 6,
94+
});
95+
return new Transform({
96+
objectMode: true,
97+
transform(chunk, enc, cb) {
98+
const line = chunk.toString().trim();
12099

121-
const sentryEventId = captureException(toSentryError(data));
100+
const data = JSON.parse(line);
122101

123-
// reduce logging data and add reference to sentry event instead
124-
if (data.event) {
125-
data.event = { id: data.event.id };
126-
}
127-
if (data.request) {
128-
data.request = {
129-
method: data.request.method,
130-
url: data.request.url,
131-
};
132-
}
133-
data.sentryEventId = sentryEventId;
102+
if (data.level < 50) {
103+
if (formattingEnabled) {
104+
return cb(null, pretty(line));
105+
}
134106

135-
if (formattingEnabled) {
136-
return cb(null, pretty(data));
137-
}
107+
if (levelAsString) {
108+
return cb(null, stringifyLogLevel(data));
109+
}
138110

139-
/* c8 ignore start */
140-
if (levelAsString) {
141-
return cb(null, stringifyLogLevel(data));
111+
cb(null, line + "\n");
112+
return;
142113
}
143-
/* c8 ignore stop */
144114

145-
cb(null, JSON.stringify(data) + "\n");
146-
});
147-
},
148-
});
115+
withScope((scope) => {
116+
const sentryLevelName = data.level === 50 ? "error" : "fatal";
117+
scope.setLevel(sentryLevelName);
118+
119+
if (data.event) {
120+
scope.setExtra("event", data.event);
121+
}
122+
if (data.headers) {
123+
scope.setExtra("headers", data.headers);
124+
}
125+
if (data.request) {
126+
scope.setExtra("request", data.request);
127+
}
128+
if (data.status) {
129+
scope.setExtra("status", data.status);
130+
}
131+
132+
// set user id and username to installation ID and account login
133+
const payload = data.event?.payload || data.err?.event?.payload;
134+
if (payload) {
135+
const {
136+
// When GitHub App is installed organization wide
137+
installation: { id, account: { login: account } = {} } = {},
138+
139+
// When the repository belongs to an organization
140+
organization: { login: organization } = {},
141+
// When the repository belongs to a user
142+
repository: { owner: { login: owner } = {} } = {},
143+
} = payload;
144+
145+
scope.setUser({
146+
id,
147+
username: account || organization || owner,
148+
});
149+
}
150+
151+
const sentryEventId = captureException(toSentryError(data));
152+
153+
// reduce logging data and add reference to sentry event instead
154+
if (data.event) {
155+
data.event = { id: data.event.id };
156+
}
157+
if (data.request) {
158+
data.request = {
159+
method: data.request.method,
160+
url: data.request.url,
161+
};
162+
}
163+
data.sentryEventId = sentryEventId;
164+
165+
if (formattingEnabled) {
166+
return cb(null, pretty(data));
167+
}
168+
169+
/* c8 ignore start */
170+
if (levelAsString) {
171+
return cb(null, stringifyLogLevel(data));
172+
}
173+
/* c8 ignore stop */
174+
175+
cb(null, JSON.stringify(data) + "\n");
176+
});
177+
},
178+
});
179+
}
149180
}
150181

151182
function stringifyLogLevel(data) {

0 commit comments

Comments
 (0)