• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / 8bd4307c-ef7a-4052-8295-6e0c6dfe01fa

11 Apr 2025 09:00PM CUT coverage: 85.214% (+0.01%) from 85.201%
8bd4307c-ef7a-4052-8295-6e0c6dfe01fa

Pull #5500

circleci

groovecoder
MPP-4012 - feat(glean): log API access as Glean server event

Introduce a new `api.accessed` Glean event to capture accesses to Relay API
endpoints. This includes the HTTP method and endpoint path, and logs events
for all `/api/` prefixed routes via a new middleware component.

- Added `record_api_accessed()` to `EventsServerEventLogger`
- Extended `RelayGleanLogger` with `log_api_accessed()` for easier integration
- Registered `GleanApiAccessMiddleware` to log access for all API routes
- Added corresponding unit test for API access logging
- Updated `relay-server-metrics.yaml` to define the `api.accessed` metric
- Updated notification email for several existing metrics to use relay-team@mozilla.com
Pull Request #5500: WIP: MPP-4012 - feat(glean): log API access as Glean server event

2460 of 3595 branches covered (68.43%)

Branch coverage included in aggregate %.

59 of 60 new or added lines in 7 files covered. (98.33%)

1 existing line in 1 file now uncovered.

17250 of 19535 relevant lines covered (88.3%)

9.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.7
/privaterelay/glean/server_events.py
1
"""
2
This Source Code Form is subject to the terms of the Mozilla Public
3
License, v. 2.0. If a copy of the MPL was not distributed with this
4
file, You can obtain one at http://mozilla.org/MPL/2.0/.
5

6
AUTOGENERATED BY glean_parser v17.1.0. DO NOT EDIT. To recreate, run:
7

8
bash .circleci/python_job.bash run build_glean
9
"""
10

11
from __future__ import annotations
1✔
12

13
import json
1✔
14
from datetime import UTC, datetime
1✔
15
from typing import Any
1✔
16
from uuid import uuid4
1✔
17

18
GLEAN_EVENT_MOZLOG_TYPE = "glean-server-event"
1✔
19

20

21
class EventsServerEventLogger:
1✔
22
    def __init__(
1✔
23
        self, application_id: str, app_display_version: str, channel: str
24
    ) -> None:
25
        """
26
        Create EventsServerEventLogger instance.
27

28
        :param str application_id: The application ID.
29
        :param str app_display_version: The application display version.
30
        :param str channel: The channel.
31
        """
32
        self._application_id = application_id
1✔
33
        self._app_display_version = app_display_version
1✔
34
        self._channel = channel
1✔
35

36
    def _record(self, user_agent: str, ip_address: str, event: dict[str, Any]) -> None:
1✔
37
        now = datetime.now(UTC)
1✔
38
        timestamp = now.isoformat()
1✔
39
        event["timestamp"] = int(1000.0 * now.timestamp())  # Milliseconds since epoch
1✔
40
        event_payload = {
1✔
41
            "metrics": {},
42
            "events": [event],
43
            "ping_info": {
44
                # seq is required in the Glean schema, however is not useful in server context
45
                "seq": 0,
46
                "start_time": timestamp,
47
                "end_time": timestamp,
48
            },
49
            # `Unknown` fields below are required in the Glean schema, however they are
50
            # not useful in server context
51
            "client_info": {
52
                "telemetry_sdk_build": "glean_parser v17.1.0",
53
                "first_run_date": "Unknown",
54
                "os": "Unknown",
55
                "os_version": "Unknown",
56
                "architecture": "Unknown",
57
                "app_build": "Unknown",
58
                "app_display_version": self._app_display_version,
59
                "app_channel": self._channel,
60
            },
61
        }
62
        event_payload_serialized = json.dumps(event_payload)
1✔
63

64
        # This is the message structure that Decoder expects:
65
        # https://github.com/mozilla/gcp-ingestion/pull/2400
66
        ping = {
1✔
67
            "document_namespace": self._application_id,
68
            "document_type": "events",
69
            "document_version": "1",
70
            "document_id": str(uuid4()),
71
            "user_agent": user_agent,
72
            "ip_address": ip_address,
73
            "payload": event_payload_serialized,
74
        }
75

76
        self.emit_record(now, ping)
1✔
77

78
    def emit_record(self, now: datetime, ping: dict[str, Any]) -> None:
1✔
79
        """Log the ping to STDOUT.
80
        Applications might want to override this method to use their own logging.
81
        If doing so, make sure to log the ping as JSON, and to include the
82
        `Type: GLEAN_EVENT_MOZLOG_TYPE`."""
83
        ping_envelope = {
×
84
            "Timestamp": now.isoformat(),
85
            "Logger": "glean",
86
            "Type": GLEAN_EVENT_MOZLOG_TYPE,
87
            "Fields": ping,
88
        }
89
        ping_envelope_serialized = json.dumps(ping_envelope)
×
90

91
        print(ping_envelope_serialized)
×
92

93
    def record_api_accessed(
1✔
94
        self,
95
        user_agent: str,
96
        ip_address: str,
97
        endpoint: str,
98
        method: str,
99
    ) -> None:
100
        """
101
        Record and submit a api_accessed event:
102
        An API endpoint was accessed.
103
        Event is logged to STDOUT via `print`.
104

105
        :param str user_agent: The user agent.
106
        :param str ip_address: The IP address. Will be used to decode Geo information
107
            and scrubbed at ingestion.
108
        :param str endpoint: The name of the endpoint accessed
109
        :param str method: HTTP method used
110
        """
111
        event = {
1✔
112
            "category": "api",
113
            "name": "accessed",
114
            "extra": {
115
                "endpoint": str(endpoint),
116
                "method": str(method),
117
            },
118
        }
119
        self._record(user_agent, ip_address, event)
1✔
120

121
    def record_email_blocked(
1✔
122
        self,
123
        user_agent: str,
124
        ip_address: str,
125
        fxa_id: str,
126
        platform: str,
127
        n_random_masks: int,
128
        n_domain_masks: int,
129
        n_deleted_random_masks: int,
130
        n_deleted_domain_masks: int,
131
        date_joined_relay: int,
132
        premium_status: str,
133
        date_joined_premium: int,
134
        has_extension: bool,
135
        date_got_extension: int,
136
        is_random_mask: bool,
137
        is_reply: bool,
138
        reason: str,
139
    ) -> None:
140
        """
141
        Record and submit a email_blocked event:
142
        Relay receives but does not forward an email for a Relay user.
143
        Event is logged to STDOUT via `print`.
144

145
        :param str user_agent: The user agent.
146
        :param str ip_address: The IP address. Will be used to decode Geo information
147
            and scrubbed at ingestion.
148
        :param str fxa_id: Mozilla accounts user ID
149
        :param str platform: Relay client platform
150
        :param int n_random_masks: Number of random masks
151
        :param int n_domain_masks: Number of premium subdomain masks
152
        :param int n_deleted_random_masks: Number of deleted random masks
153
        :param int n_deleted_domain_masks: Number of deleted domain masks
154
        :param int date_joined_relay: Timestamp for joining Relay, seconds since epoch
155
        :param str premium_status: Subscription type and term
156
        :param int date_joined_premium: Timestamp for starting premium_status subscription, seconds since epoch, -1 if not subscribed
157
        :param bool has_extension: The user has the Relay Add-on
158
        :param int date_got_extension: Timestamp for adding Relay Add-on, seconds since epoch, -1 if not used
159
        :param bool is_random_mask: The mask is a random mask, instead of a domain mask
160
        :param bool is_reply: The email is a reply from the Relay user
161
        :param str reason: Code describing why the email was blocked
162
        """
163
        event = {
1✔
164
            "category": "email",
165
            "name": "blocked",
166
            "extra": {
167
                "fxa_id": str(fxa_id),
168
                "platform": str(platform),
169
                "n_random_masks": str(n_random_masks),
170
                "n_domain_masks": str(n_domain_masks),
171
                "n_deleted_random_masks": str(n_deleted_random_masks),
172
                "n_deleted_domain_masks": str(n_deleted_domain_masks),
173
                "date_joined_relay": str(date_joined_relay),
174
                "premium_status": str(premium_status),
175
                "date_joined_premium": str(date_joined_premium),
176
                "has_extension": str(has_extension).lower(),
177
                "date_got_extension": str(date_got_extension),
178
                "is_random_mask": str(is_random_mask).lower(),
179
                "is_reply": str(is_reply).lower(),
180
                "reason": str(reason),
181
            },
182
        }
183
        self._record(user_agent, ip_address, event)
1✔
184

185
    def record_email_forwarded(
1✔
186
        self,
187
        user_agent: str,
188
        ip_address: str,
189
        fxa_id: str,
190
        platform: str,
191
        n_random_masks: int,
192
        n_domain_masks: int,
193
        n_deleted_random_masks: int,
194
        n_deleted_domain_masks: int,
195
        date_joined_relay: int,
196
        premium_status: str,
197
        date_joined_premium: int,
198
        has_extension: bool,
199
        date_got_extension: int,
200
        is_random_mask: bool,
201
        is_reply: bool,
202
    ) -> None:
203
        """
204
        Record and submit a email_forwarded event:
205
        Relay receives and forwards an email for a Relay user.
206
        Event is logged to STDOUT via `print`.
207

208
        :param str user_agent: The user agent.
209
        :param str ip_address: The IP address. Will be used to decode Geo information
210
            and scrubbed at ingestion.
211
        :param str fxa_id: Mozilla accounts user ID
212
        :param str platform: Relay client platform
213
        :param int n_random_masks: Number of random masks
214
        :param int n_domain_masks: Number of premium subdomain masks
215
        :param int n_deleted_random_masks: Number of deleted random masks
216
        :param int n_deleted_domain_masks: Number of deleted domain masks
217
        :param int date_joined_relay: Timestamp for joining Relay, seconds since epoch
218
        :param str premium_status: Subscription type and term
219
        :param int date_joined_premium: Timestamp for starting premium_status subscription, seconds since epoch, -1 if not subscribed
220
        :param bool has_extension: The user has the Relay Add-on
221
        :param int date_got_extension: Timestamp for adding Relay Add-on, seconds since epoch, -1 if not used
222
        :param bool is_random_mask: The mask is a random mask, instead of a domain mask
223
        :param bool is_reply: The email is a reply from the Relay user
224
        """
225
        event = {
1✔
226
            "category": "email",
227
            "name": "forwarded",
228
            "extra": {
229
                "fxa_id": str(fxa_id),
230
                "platform": str(platform),
231
                "n_random_masks": str(n_random_masks),
232
                "n_domain_masks": str(n_domain_masks),
233
                "n_deleted_random_masks": str(n_deleted_random_masks),
234
                "n_deleted_domain_masks": str(n_deleted_domain_masks),
235
                "date_joined_relay": str(date_joined_relay),
236
                "premium_status": str(premium_status),
237
                "date_joined_premium": str(date_joined_premium),
238
                "has_extension": str(has_extension).lower(),
239
                "date_got_extension": str(date_got_extension),
240
                "is_random_mask": str(is_random_mask).lower(),
241
                "is_reply": str(is_reply).lower(),
242
            },
243
        }
244
        self._record(user_agent, ip_address, event)
1✔
245

246
    def record_email_mask_created(
1✔
247
        self,
248
        user_agent: str,
249
        ip_address: str,
250
        fxa_id: str,
251
        platform: str,
252
        n_random_masks: int,
253
        n_domain_masks: int,
254
        n_deleted_random_masks: int,
255
        n_deleted_domain_masks: int,
256
        date_joined_relay: int,
257
        premium_status: str,
258
        date_joined_premium: int,
259
        has_extension: bool,
260
        date_got_extension: int,
261
        is_random_mask: bool,
262
        created_by_api: bool,
263
        has_website: bool,
264
    ) -> None:
265
        """
266
        Record and submit a email_mask_created event:
267
        A Relay user creates an email mask.
268
        Event is logged to STDOUT via `print`.
269

270
        :param str user_agent: The user agent.
271
        :param str ip_address: The IP address. Will be used to decode Geo information
272
            and scrubbed at ingestion.
273
        :param str fxa_id: Mozilla accounts user ID
274
        :param str platform: Relay client platform
275
        :param int n_random_masks: Number of random masks
276
        :param int n_domain_masks: Number of premium subdomain masks
277
        :param int n_deleted_random_masks: Number of deleted random masks
278
        :param int n_deleted_domain_masks: Number of deleted domain masks
279
        :param int date_joined_relay: Timestamp for joining Relay, seconds since epoch
280
        :param str premium_status: Subscription type and term
281
        :param int date_joined_premium: Timestamp for starting premium_status subscription, seconds since epoch, -1 if not subscribed
282
        :param bool has_extension: The user has the Relay Add-on
283
        :param int date_got_extension: Timestamp for adding Relay Add-on, seconds since epoch, -1 if not used
284
        :param bool is_random_mask: The mask is a random mask, instead of a domain mask
285
        :param bool created_by_api: The mask was created via the API, rather than an incoming email
286
        :param bool has_website: The mask was created by the Add-on or integration on a website
287
        """
288
        event = {
1✔
289
            "category": "email_mask",
290
            "name": "created",
291
            "extra": {
292
                "fxa_id": str(fxa_id),
293
                "platform": str(platform),
294
                "n_random_masks": str(n_random_masks),
295
                "n_domain_masks": str(n_domain_masks),
296
                "n_deleted_random_masks": str(n_deleted_random_masks),
297
                "n_deleted_domain_masks": str(n_deleted_domain_masks),
298
                "date_joined_relay": str(date_joined_relay),
299
                "premium_status": str(premium_status),
300
                "date_joined_premium": str(date_joined_premium),
301
                "has_extension": str(has_extension).lower(),
302
                "date_got_extension": str(date_got_extension),
303
                "is_random_mask": str(is_random_mask).lower(),
304
                "created_by_api": str(created_by_api).lower(),
305
                "has_website": str(has_website).lower(),
306
            },
307
        }
308
        self._record(user_agent, ip_address, event)
1✔
309

310
    def record_email_mask_deleted(
1✔
311
        self,
312
        user_agent: str,
313
        ip_address: str,
314
        fxa_id: str,
315
        platform: str,
316
        n_random_masks: int,
317
        n_domain_masks: int,
318
        n_deleted_random_masks: int,
319
        n_deleted_domain_masks: int,
320
        date_joined_relay: int,
321
        premium_status: str,
322
        date_joined_premium: int,
323
        has_extension: bool,
324
        date_got_extension: int,
325
        is_random_mask: bool,
326
    ) -> None:
327
        """
328
        Record and submit a email_mask_deleted event:
329
        A Relay user deletes an email mask.
330
        Event is logged to STDOUT via `print`.
331

332
        :param str user_agent: The user agent.
333
        :param str ip_address: The IP address. Will be used to decode Geo information
334
            and scrubbed at ingestion.
335
        :param str fxa_id: Mozilla accounts user ID
336
        :param str platform: Relay client platform
337
        :param int n_random_masks: Number of random masks
338
        :param int n_domain_masks: Number of premium subdomain masks
339
        :param int n_deleted_random_masks: Number of deleted random masks
340
        :param int n_deleted_domain_masks: Number of deleted domain masks
341
        :param int date_joined_relay: Timestamp for joining Relay, seconds since epoch
342
        :param str premium_status: Subscription type and term
343
        :param int date_joined_premium: Timestamp for starting premium_status subscription, seconds since epoch, -1 if not subscribed
344
        :param bool has_extension: The user has the Relay Add-on
345
        :param int date_got_extension: Timestamp for adding Relay Add-on, seconds since epoch, -1 if not used
346
        :param bool is_random_mask: The mask is a random mask, instead of a domain mask
347
        """
348
        event = {
1✔
349
            "category": "email_mask",
350
            "name": "deleted",
351
            "extra": {
352
                "fxa_id": str(fxa_id),
353
                "platform": str(platform),
354
                "n_random_masks": str(n_random_masks),
355
                "n_domain_masks": str(n_domain_masks),
356
                "n_deleted_random_masks": str(n_deleted_random_masks),
357
                "n_deleted_domain_masks": str(n_deleted_domain_masks),
358
                "date_joined_relay": str(date_joined_relay),
359
                "premium_status": str(premium_status),
360
                "date_joined_premium": str(date_joined_premium),
361
                "has_extension": str(has_extension).lower(),
362
                "date_got_extension": str(date_got_extension),
363
                "is_random_mask": str(is_random_mask).lower(),
364
            },
365
        }
366
        self._record(user_agent, ip_address, event)
1✔
367

368
    def record_email_mask_label_updated(
1✔
369
        self,
370
        user_agent: str,
371
        ip_address: str,
372
        fxa_id: str,
373
        platform: str,
374
        n_random_masks: int,
375
        n_domain_masks: int,
376
        n_deleted_random_masks: int,
377
        n_deleted_domain_masks: int,
378
        date_joined_relay: int,
379
        premium_status: str,
380
        date_joined_premium: int,
381
        has_extension: bool,
382
        date_got_extension: int,
383
        is_random_mask: bool,
384
    ) -> None:
385
        """
386
        Record and submit a email_mask_label_updated event:
387
        A Relay user updates an email mask's label.
388
        Event is logged to STDOUT via `print`.
389

390
        :param str user_agent: The user agent.
391
        :param str ip_address: The IP address. Will be used to decode Geo information
392
            and scrubbed at ingestion.
393
        :param str fxa_id: Mozilla accounts user ID
394
        :param str platform: Relay client platform
395
        :param int n_random_masks: Number of random masks
396
        :param int n_domain_masks: Number of premium subdomain masks
397
        :param int n_deleted_random_masks: Number of deleted random masks
398
        :param int n_deleted_domain_masks: Number of deleted domain masks
399
        :param int date_joined_relay: Timestamp for joining Relay, seconds since epoch
400
        :param str premium_status: Subscription type and term
401
        :param int date_joined_premium: Timestamp for starting premium_status subscription, seconds since epoch, -1 if not subscribed
402
        :param bool has_extension: The user has the Relay Add-on
403
        :param int date_got_extension: Timestamp for adding Relay Add-on, seconds since epoch, -1 if not used
404
        :param bool is_random_mask: The mask is a random mask, instead of a domain mask
405
        """
406
        event = {
1✔
407
            "category": "email_mask",
408
            "name": "label_updated",
409
            "extra": {
410
                "fxa_id": str(fxa_id),
411
                "platform": str(platform),
412
                "n_random_masks": str(n_random_masks),
413
                "n_domain_masks": str(n_domain_masks),
414
                "n_deleted_random_masks": str(n_deleted_random_masks),
415
                "n_deleted_domain_masks": str(n_deleted_domain_masks),
416
                "date_joined_relay": str(date_joined_relay),
417
                "premium_status": str(premium_status),
418
                "date_joined_premium": str(date_joined_premium),
419
                "has_extension": str(has_extension).lower(),
420
                "date_got_extension": str(date_got_extension),
421
                "is_random_mask": str(is_random_mask).lower(),
422
            },
423
        }
424
        self._record(user_agent, ip_address, event)
1✔
425

426

427
def create_events_server_event_logger(
1✔
428
    application_id: str,
429
    app_display_version: str,
430
    channel: str,
431
) -> EventsServerEventLogger:
432
    """
433
    Factory function that creates an instance of Glean Server Event Logger to record
434
    `events` ping events.
435
    :param str application_id: The application ID.
436
    :param str app_display_version: The application display version.
437
    :param str channel: The channel.
438
    :return: An instance of EventsServerEventLogger.
439
    :rtype: EventsServerEventLogger
440
    """
NEW
441
    return EventsServerEventLogger(application_id, app_display_version, channel)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc