Skip to content

Commit 4750b4c

Browse files
committed
use serverLogger as Glean stream
1 parent e59c44a commit 4750b4c

File tree

2 files changed

+153
-115
lines changed

2 files changed

+153
-115
lines changed

servers/curated-corpus-api/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
"description": "",
55
"main": "dist/main.js",
66
"scripts": {
7-
"build": "rm -rf dist && tsc && npm run build-schema-admin && npm run build-schema-public && npm run build-glean",
7+
"build": "rm -rf dist && tsc && npm run build-schema-admin && npm run build-schema-public",
88
"build-schema-admin": "node dist/admin/buildSchema.js",
99
"build-schema-public": "node dist/public/buildSchema.js",
10-
"build-glean": "glean translate src/events/glean/backend-metrics.yaml -f typescript_server --option module_spec=es -o ./src/events/glean/generated",
1110
"watch": "tsc -w & nodemon",
1211
"start": "npm run migrate:prisma-deploy && node dist/main.js",
1312
"dev": "npm run migrate:prisma-deploy && npm run build && npm run watch",
@@ -22,8 +21,9 @@
2221
"migrate:prisma-dev": "prisma migrate dev",
2322
"migrate:prisma-deploy": "prisma migrate deploy",
2423
"migrate:prisma-reset": "prisma migrate reset --skip-seed --force",
25-
"prebuild": "dotenv -e .env.ci -- prisma generate",
26-
"pretest": "dotenv -e .env.ci -- prisma generate",
24+
"generate-glean": "glean translate src/events/glean/backend-metrics.yaml -f typescript_server --option module_spec=es -o ./src/events/glean/generated",
25+
"prebuild": "dotenv -e .env.ci -- prisma generate && npm run generate-glean",
26+
"pretest": "dotenv -e .env.ci -- prisma generate && npm run generate-glean",
2727
"pretest-integrations": "dotenv -e .env.ci -- prisma migrate reset --skip-seed --force"
2828
},
2929
"prisma": {
Lines changed: 149 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,149 @@
1-
import { CuratedCorpusEventEmitter } from '../curatedCorpusEventEmitter';
2-
import { serverLogger } from '@pocket-tools/ts-logger';
3-
import * as Sentry from '@sentry/node';
4-
import { createEventsServerEventLogger } from './generated/server_events';
5-
6-
import {
7-
ApprovedCorpusItemPayload,
8-
BaseEventData,
9-
RejectedCorpusItemPayload,
10-
ReviewedCorpusItemPayload,
11-
} from '../types';
12-
import { CuratedStatus } from '.prisma/client';
13-
import { getUnixTimestamp } from '../../shared/utils';
14-
import { CorpusItemSource } from 'content-common';
15-
import { CorpusReviewStatus } from '../snowplow/schema';
16-
17-
const gleanLogger = createEventsServerEventLogger({
18-
applicationId: 'curated-corpus-api',
19-
appDisplayVersion: '1.0.0',
20-
channel: 'production',
21-
logger_options: { app: 'glean-logger' },
22-
});
23-
24-
export class ReviewedItemGleanHandler {
25-
constructor(
26-
protected emitter: CuratedCorpusEventEmitter,
27-
events: string[]
28-
) {
29-
events.forEach((eventName) => {
30-
this.emitter.on(eventName, (data) => this.process(data));
31-
});
32-
}
33-
34-
async process(data: ReviewedCorpusItemPayload & BaseEventData) {
35-
try {
36-
const gleanExtras = ReviewedItemGleanHandler.buildGleanExtras(data);
37-
38-
gleanLogger.recordCuratedCorpusReviewedCorpusItem({
39-
user_agent: 'unknown',
40-
ip_address: 'unknown',
41-
...gleanExtras,
42-
experimental_json: gleanExtras.experimental_json ?? '',
43-
});
44-
} catch (ex) {
45-
serverLogger.error('ReviewedItemGleanHandler: failed to record Glean event', {
46-
error: ex,
47-
eventType: data.eventType,
48-
});
49-
Sentry.captureException(ex);
50-
}
51-
}
52-
53-
private static buildGleanExtras(data: ReviewedCorpusItemPayload & BaseEventData) {
54-
const item = data.reviewedCorpusItem;
55-
56-
let isApproved = false;
57-
let corpusReviewStatus: string;
58-
let approvedExternalId = '';
59-
let rejectedExternalId = '';
60-
let rejectionReasons = '';
61-
62-
if ((item as ApprovedCorpusItemPayload).status !== undefined) {
63-
const status = (item as ApprovedCorpusItemPayload).status;
64-
corpusReviewStatus =
65-
status === CuratedStatus.RECOMMENDATION
66-
? CorpusReviewStatus.RECOMMENDATION
67-
: CorpusReviewStatus.CORPUS;
68-
isApproved = true;
69-
approvedExternalId = (item as ApprovedCorpusItemPayload).externalId;
70-
} else {
71-
corpusReviewStatus = CorpusReviewStatus.REJECTED;
72-
rejectedExternalId = (item as RejectedCorpusItemPayload).externalId;
73-
if ((item as RejectedCorpusItemPayload).reason) {
74-
rejectionReasons = JSON.stringify(
75-
(item as RejectedCorpusItemPayload).reason.split(',')
76-
);
77-
}
78-
}
79-
80-
return {
81-
object_version: 'new',
82-
approved_corpus_item_external_id: isApproved ? approvedExternalId : '',
83-
rejected_corpus_item_external_id: isApproved ? '' : rejectedExternalId,
84-
prospect_id: item.prospectId ?? '',
85-
url: item.url ?? '',
86-
loaded_from: '', // TODO: Property 'source' does not exist on type 'ApprovedCorpusItemPayload | RejectedCorpusItemPayload'
87-
corpus_review_status: corpusReviewStatus,
88-
rejection_reasons_json: rejectionReasons,
89-
action_screen: (item as any).action_screen ?? '',
90-
title: item.title ?? '',
91-
excerpt: isApproved ? (item as ApprovedCorpusItemPayload).excerpt ?? '' : '',
92-
image_url: isApproved ? (item as ApprovedCorpusItemPayload).imageUrl ?? '' : '',
93-
language: item.language ?? '',
94-
topic: item.topic ?? '',
95-
is_collection: isApproved ? !!(item as ApprovedCorpusItemPayload).isCollection : false,
96-
is_syndicated: isApproved ? !!(item as ApprovedCorpusItemPayload).isSyndicated : false,
97-
is_time_sensitive: isApproved ? !!(item as ApprovedCorpusItemPayload).isTimeSensitive : false,
98-
created_at: item.createdAt ? getUnixTimestamp(item.createdAt).toString() : '',
99-
created_by: item.createdBy ?? '',
100-
updated_at: isApproved && (item as ApprovedCorpusItemPayload).updatedAt
101-
? getUnixTimestamp((item as ApprovedCorpusItemPayload).updatedAt).toString()
102-
: '',
103-
updated_by: isApproved ? (item as ApprovedCorpusItemPayload).updatedBy ?? '' : '',
104-
authors_json: isApproved && (item as ApprovedCorpusItemPayload).authors
105-
? JSON.stringify((item as ApprovedCorpusItemPayload).authors.map((a) => a.name))
106-
: '',
107-
publisher: isApproved ? (item as ApprovedCorpusItemPayload).publisher ?? '' : '',
108-
experimental_json: '',
109-
};
110-
}
111-
}
1+
import { CuratedCorpusEventEmitter } from '../curatedCorpusEventEmitter';
2+
import { serverLogger } from '@pocket-tools/ts-logger';
3+
import * as Sentry from '@sentry/node';
4+
import { createEventsServerEventLogger } from './generated/server_events';
5+
6+
import {
7+
ApprovedCorpusItemPayload,
8+
BaseEventData,
9+
RejectedCorpusItemPayload,
10+
ReviewedCorpusItemPayload,
11+
} from '../types';
12+
import { CuratedStatus } from '.prisma/client';
13+
import { getUnixTimestamp } from '../../shared/utils';
14+
import { CorpusReviewStatus } from '../snowplow/schema';
15+
16+
// Create a custom stream that forwards messages to serverLogger.
17+
const customStream = {
18+
write: (message: string) => {
19+
// Forward the log message to the existing serverLogger.
20+
serverLogger.info(message);
21+
},
22+
};
23+
24+
// We know that mozlog supports the `stream` property, so we assert our object to `any` to suppress TS error.
25+
const gleanLogger = createEventsServerEventLogger({
26+
applicationId: 'curated-corpus-api',
27+
appDisplayVersion: '1.0.0',
28+
channel: 'production',
29+
logger_options: {
30+
app: 'curated-corpus-api',
31+
stream: customStream, // Use our custom stream for log output.
32+
} as any, // generated Glean code does not define stream logger option that mozlog supports
33+
});
34+
35+
export class ReviewedItemGleanHandler {
36+
constructor(
37+
protected emitter: CuratedCorpusEventEmitter,
38+
events: string[],
39+
) {
40+
events.forEach((eventName) => {
41+
this.emitter.on(eventName, (data) => this.process(data));
42+
});
43+
}
44+
45+
async process(data: ReviewedCorpusItemPayload & BaseEventData) {
46+
try {
47+
const gleanExtras = ReviewedItemGleanHandler.buildGleanExtras(data);
48+
49+
gleanLogger.recordCuratedCorpusReviewedCorpusItem({
50+
user_agent: 'unknown',
51+
ip_address: 'unknown',
52+
...gleanExtras,
53+
experimental_json: gleanExtras.experimental_json ?? '',
54+
});
55+
} catch (ex) {
56+
serverLogger.error(
57+
'ReviewedItemGleanHandler: failed to record Glean event',
58+
{
59+
error: ex,
60+
eventType: data.eventType,
61+
},
62+
);
63+
Sentry.captureException(ex);
64+
}
65+
}
66+
67+
private static buildGleanExtras(
68+
data: ReviewedCorpusItemPayload & BaseEventData,
69+
) {
70+
const item = data.reviewedCorpusItem;
71+
72+
let isApproved = false;
73+
let corpusReviewStatus: string;
74+
let approvedExternalId = '';
75+
let rejectedExternalId = '';
76+
let rejectionReasons = '';
77+
78+
if ((item as ApprovedCorpusItemPayload).status !== undefined) {
79+
const status = (item as ApprovedCorpusItemPayload).status;
80+
corpusReviewStatus =
81+
status === CuratedStatus.RECOMMENDATION
82+
? CorpusReviewStatus.RECOMMENDATION
83+
: CorpusReviewStatus.CORPUS;
84+
isApproved = true;
85+
approvedExternalId = (item as ApprovedCorpusItemPayload).externalId;
86+
} else {
87+
corpusReviewStatus = CorpusReviewStatus.REJECTED;
88+
rejectedExternalId = (item as RejectedCorpusItemPayload).externalId;
89+
if ((item as RejectedCorpusItemPayload).reason) {
90+
rejectionReasons = JSON.stringify(
91+
(item as RejectedCorpusItemPayload).reason.split(','),
92+
);
93+
}
94+
}
95+
96+
return {
97+
object_version: 'new',
98+
approved_corpus_item_external_id: isApproved ? approvedExternalId : '',
99+
rejected_corpus_item_external_id: isApproved ? '' : rejectedExternalId,
100+
prospect_id: item.prospectId ?? '',
101+
url: item.url ?? '',
102+
loaded_from: '', // TODO: Property 'source' does not exist on type 'ApprovedCorpusItemPayload | RejectedCorpusItemPayload'
103+
corpus_review_status: corpusReviewStatus,
104+
rejection_reasons_json: rejectionReasons,
105+
action_screen: (item as any).action_screen ?? '',
106+
title: item.title ?? '',
107+
excerpt: isApproved
108+
? (item as ApprovedCorpusItemPayload).excerpt ?? ''
109+
: '',
110+
image_url: isApproved
111+
? (item as ApprovedCorpusItemPayload).imageUrl ?? ''
112+
: '',
113+
language: item.language ?? '',
114+
topic: item.topic ?? '',
115+
is_collection: isApproved
116+
? !!(item as ApprovedCorpusItemPayload).isCollection
117+
: false,
118+
is_syndicated: isApproved
119+
? !!(item as ApprovedCorpusItemPayload).isSyndicated
120+
: false,
121+
is_time_sensitive: isApproved
122+
? !!(item as ApprovedCorpusItemPayload).isTimeSensitive
123+
: false,
124+
created_at: item.createdAt
125+
? getUnixTimestamp(item.createdAt).toString()
126+
: '',
127+
created_by: item.createdBy ?? '',
128+
updated_at:
129+
isApproved && (item as ApprovedCorpusItemPayload).updatedAt
130+
? getUnixTimestamp(
131+
(item as ApprovedCorpusItemPayload).updatedAt,
132+
).toString()
133+
: '',
134+
updated_by: isApproved
135+
? (item as ApprovedCorpusItemPayload).updatedBy ?? ''
136+
: '',
137+
authors_json:
138+
isApproved && (item as ApprovedCorpusItemPayload).authors
139+
? JSON.stringify(
140+
(item as ApprovedCorpusItemPayload).authors.map((a) => a.name),
141+
)
142+
: '',
143+
publisher: isApproved
144+
? (item as ApprovedCorpusItemPayload).publisher ?? ''
145+
: '',
146+
experimental_json: '',
147+
};
148+
}
149+
}

0 commit comments

Comments
 (0)