11import { Transform } from "node:stream" ;
2-
2+ import { npxImport } from "npx-import-light" ;
33import { prettyFactory } from "pino-pretty" ;
4- import { init , withScope , captureException } from "@sentry/node" ;
54
65const 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
151182function stringifyLogLevel ( data ) {
0 commit comments