summaryrefslogtreecommitdiffstats
path: root/third_party/python/glean_parser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:13:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:13:27 +0000
commit40a355a42d4a9444dc753c04c6608dade2f06a23 (patch)
tree871fc667d2de662f171103ce5ec067014ef85e61 /third_party/python/glean_parser
parentAdding upstream version 124.0.1. (diff)
downloadfirefox-upstream/125.0.1.tar.xz
firefox-upstream/125.0.1.zip
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/python/glean_parser')
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.0.dist-info/AUTHORS.md (renamed from third_party/python/glean_parser/glean_parser-11.0.1.dist-info/AUTHORS.md)0
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.0.dist-info/LICENSE (renamed from third_party/python/glean_parser/glean_parser-11.0.1.dist-info/LICENSE)0
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.0.dist-info/METADATA (renamed from third_party/python/glean_parser/glean_parser-11.0.1.dist-info/METADATA)19
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.0.dist-info/RECORD (renamed from third_party/python/glean_parser/glean_parser-11.0.1.dist-info/RECORD)36
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.0.dist-info/WHEEL (renamed from third_party/python/glean_parser/glean_parser-11.0.1.dist-info/WHEEL)0
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.0.dist-info/entry_points.txt (renamed from third_party/python/glean_parser/glean_parser-11.0.1.dist-info/entry_points.txt)0
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.0.dist-info/top_level.txt (renamed from third_party/python/glean_parser/glean_parser-11.0.1.dist-info/top_level.txt)0
-rw-r--r--third_party/python/glean_parser/glean_parser/go_server.py145
-rw-r--r--third_party/python/glean_parser/glean_parser/metrics.py60
-rw-r--r--third_party/python/glean_parser/glean_parser/pings.py4
-rw-r--r--third_party/python/glean_parser/glean_parser/python_server.py130
-rw-r--r--third_party/python/glean_parser/glean_parser/rust.py23
-rw-r--r--third_party/python/glean_parser/glean_parser/schemas/metrics.2-0-0.schema.yaml13
-rw-r--r--third_party/python/glean_parser/glean_parser/schemas/pings.2-0-0.schema.yaml15
-rw-r--r--third_party/python/glean_parser/glean_parser/templates/go_server.jinja2225
-rw-r--r--third_party/python/glean_parser/glean_parser/templates/python_server.jinja2194
-rw-r--r--third_party/python/glean_parser/glean_parser/templates/rust.jinja249
-rw-r--r--third_party/python/glean_parser/glean_parser/templates/swift.jinja21
-rw-r--r--third_party/python/glean_parser/glean_parser/translate.py4
-rw-r--r--third_party/python/glean_parser/glean_parser/util.py1
20 files changed, 900 insertions, 19 deletions
diff --git a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/AUTHORS.md b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/AUTHORS.md
index 525116ee7e..525116ee7e 100644
--- a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/AUTHORS.md
+++ b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/AUTHORS.md
diff --git a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/LICENSE b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/LICENSE
index a612ad9813..a612ad9813 100644
--- a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/LICENSE
+++ b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/LICENSE
diff --git a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/METADATA b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/METADATA
index 201d8bb48b..1e31df3dd4 100644
--- a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/METADATA
+++ b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: glean-parser
-Version: 11.0.1
+Version: 13.0.0
Summary: Parser tools for Mozilla's Glean telemetry
Home-page: https://github.com/mozilla/glean_parser
Author: The Glean Team
@@ -79,6 +79,23 @@ $ glean_parser check < ping.json
## Unreleased
+## 13.0.0
+
+- BREAKING CHANGE: Support metadata field `include_info_sections` ([bug 1866559](https://bugzilla.mozilla.org/show_bug.cgi?id=1866559))
+
+## 12.0.1
+
+- Fix Rust codegen for object metric type ([#662](https://github.com/mozilla/glean_parser/pull/662))
+
+## 12.0.0
+
+- Add new metric type object (only Rust codegen support right now) ([#587](https://github.com/mozilla/glean_parser/pull/587))
+
+## 11.1.0
+
+- Add Go log outputter (`go_server`) ([#645](https://github.com/mozilla/glean_parser/pull/645))
+- Add Python log outputter (`python_server`) ([MPP-3642](https://mozilla-hub.atlassian.net/browse/MPP-3642))
+
## 11.0.1
- Fix javascript_server template to include non-event metric parameters in #record call for event metrics ([#643](https://github.com/mozilla/glean_parser/pull/643))
diff --git a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/RECORD b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/RECORD
index 417484d30b..62e4bb6fbb 100644
--- a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/RECORD
+++ b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/RECORD
@@ -2,28 +2,31 @@ glean_parser/__init__.py,sha256=bJljD052_0y-efcBhYpllICVCXOMHLcXRLNyrvfgt5A,533
glean_parser/__main__.py,sha256=Rw0PpuQtAvdHJMK1YLozeZkc6x1yjeNZwidu4faovdk,8633
glean_parser/coverage.py,sha256=2IwC4XMDtDamMkBFoYilmqJzW4gyypq65YVCur8SNas,4405
glean_parser/data_review.py,sha256=BweeeTkNNS6HrIDkztawhbDByrk_-Avxpg7YeST3VAs,2152
+glean_parser/go_server.py,sha256=SCcGrjRktlPyl79LbjIvtBeCNYVOXOW4Q8xkuks0bzE,5345
glean_parser/javascript.py,sha256=w4ZhNBHBKWYk0h3t7G0Ud2tR__hRqzn9dlEXNKLdQrA,11230
glean_parser/javascript_server.py,sha256=SDV9tPL1uZMyS1VSyo5lOFuNPFHZu-PZxr1vhND-GzM,7971
glean_parser/kotlin.py,sha256=5z8_74xlqvHDsedwZhGf1_qb7swPEgIZumkJIuj3ef8,12598
glean_parser/lint.py,sha256=STqdgyOhR4Q3fHivSizgn9bOOyqrNHhzjaqyJxz6qzI,19948
glean_parser/markdown.py,sha256=GkCr1CrV6mnRQseT6FO1-JJ7Eup8X3lxUfRMBTxXpe4,9066
-glean_parser/metrics.py,sha256=uWOJdZRB9udMni2EWXcL3P1T4pRIlJ5kiE5fJsHkmdU,12450
+glean_parser/metrics.py,sha256=YAO8wPuRHTLkdT9M4zh9ZwoFI1_VS8O9oQqwZNYyDp0,14612
glean_parser/parser.py,sha256=cUOnvSXKfEBg8YTpRcWiPcMwpFpK1TTqsVO_zjUtpR4,15309
-glean_parser/pings.py,sha256=la9HdJTjtSqC7vc5-JuANW0otbozTnFARlIMgKoypGU,2982
+glean_parser/pings.py,sha256=AQ-fBmIx2GKQv6J2NyTFfHHZzSnApZZoC770LlstkoI,3180
+glean_parser/python_server.py,sha256=3ZsqeNJknKO9yvtBJWxe67JthzSMqNMuo9DfhgF2kvg,4790
glean_parser/ruby_server.py,sha256=-bNXjfXWwHWUHmLJVvfi6jCyw8q0MBwx9VXVWQ3bU-A,5189
-glean_parser/rust.py,sha256=PJzTfYWzAumJYCP5IYPc6fhS_Qa30Q8NTK9plg3sDnk,6744
+glean_parser/rust.py,sha256=UEHeIZlToxCBelfec5sl_l_uLZfk8f_OUXqa_ZoEvnk,7330
glean_parser/swift.py,sha256=T1BSGahd9wUd6VDeNC89SdN6M34jKXDlydMpSI0QLOs,8379
glean_parser/tags.py,sha256=bemKYvcbMO4JrghiNSe-A4BNNDtx_FlUPkgrPPJy84Y,1391
-glean_parser/translate.py,sha256=C7FY7AAbnVsPZOu2bKELW1CfTwnvLGpmgzY7uMDqOec,8233
+glean_parser/translate.py,sha256=luKQoraARZ2tjenHs0SVtCxflnYaMkzPYFfKEdKdSqQ,8403
glean_parser/translation_options.py,sha256=Lxzr6G7MP0tC_ZYlZXftS4j0SLiqO-5mGVTEc7ggXis,2037
-glean_parser/util.py,sha256=X5YFAU4kWdDJjMsJzXH-QJVSjUJc_qvXktiM-dJSfzo,16004
+glean_parser/util.py,sha256=KgvmjETOV1IIGD4hF_o5zcUDE-wp3SHxrNHM1niU0CM,16033
glean_parser/validate_ping.py,sha256=0TNvILH6dtzJDys3W8Kqorw6kk03me73OCUDtpoHcXU,2118
glean_parser/schemas/metrics.1-0-0.schema.yaml,sha256=cND3cvi6iBfPUVmtfIBQfGJV9AALpbvN7nu8E33_J-o,19566
-glean_parser/schemas/metrics.2-0-0.schema.yaml,sha256=sfrARxefWy1WN5HxUKjwjN8lGobbPds5l7Y46VHfP1g,25849
+glean_parser/schemas/metrics.2-0-0.schema.yaml,sha256=wx1q0L4C0-Vcwk1SPU6t8OfjDEQvgrwwEG6xfSHO1MI,26365
glean_parser/schemas/pings.1-0-0.schema.yaml,sha256=hwCnsKpEysmrmVp-QHGBArEkVY3vaU1rVsxlTwhAzws,4315
-glean_parser/schemas/pings.2-0-0.schema.yaml,sha256=l-nIuyXJ9-D0X_U6hzGVbhIBhtZDg-rGau-RDrhgpng,4705
+glean_parser/schemas/pings.2-0-0.schema.yaml,sha256=vDyvFT8KwAwaqyWHG4y6pFNrsc3NO7OyDDagA2eTeqM,5415
glean_parser/schemas/tags.1-0-0.schema.yaml,sha256=OGXIJlvvVW1vaqB_NVZnwKeZ-sLlfH57vjBSHbj6DNI,1231
glean_parser/templates/data_review.jinja2,sha256=jeYU29T1zLSyu9fKBBFu5BFPfIw8_hmOUXw8RXhRXK8,3287
+glean_parser/templates/go_server.jinja2,sha256=Jy1e0uQqr_WZNoj-AWnygRmygX2jyj_GQMMV8mSah2k,6825
glean_parser/templates/javascript.buildinfo.jinja2,sha256=4mXiZCQIk9if4lxlA05kpSIL4a95IdwGwqle2OqqNAs,474
glean_parser/templates/javascript.jinja2,sha256=cT_bG-jC6m4afECXmcsqHwiiHjRuVtJnfv90OD2Mwxw,2669
glean_parser/templates/javascript_server.jinja2,sha256=H991yQOKJMwSgM0bLEA-Q5Z15LWsfEPh6bTYz_owSCU,9423
@@ -31,14 +34,15 @@ glean_parser/templates/kotlin.buildinfo.jinja2,sha256=X0lk2SNu5OIIj2i6mUyF9CWFQI
glean_parser/templates/kotlin.geckoview.jinja2,sha256=MJOgtoDXmBjE9pwk-G6T89y36RZuMbDWM_-DBN_gFJo,5099
glean_parser/templates/kotlin.jinja2,sha256=3DqUMXJRkmTvSp_5IRyvGmw5iXYWdox7coMFe3YDxcc,5247
glean_parser/templates/markdown.jinja2,sha256=vAHHGGm28HRDPd3zO_wQMAUZIuxE9uQ7hl3NpXxcKV4,3425
+glean_parser/templates/python_server.jinja2,sha256=gu2C1rkn760IqBCG2SWaK7o32T1ify94wDEsudLPUg8,7260
glean_parser/templates/qmldir.jinja2,sha256=m6IGsp-tgTiOfQ7VN8XW6GqX0gJqJkt3B6Pkaul6FVo,156
glean_parser/templates/ruby_server.jinja2,sha256=vm4BEenOqzomQNTLFfMOzlWHARnsWUjTBbnR-v2cadI,6247
-glean_parser/templates/rust.jinja2,sha256=pdbjq_JGm8XWHsVXk0m2xZ5Pd-Y9T_zxJfZKBoT0ERU,3635
-glean_parser/templates/swift.jinja2,sha256=NfZdvrG8LGT4H2AWk-vB_GDTMcpW1XZJcApO4OF5AYE,4874
-glean_parser-11.0.1.dist-info/AUTHORS.md,sha256=yxgj8MioO4wUnrh0gmfb8l3DJJrf-l4HmmEDbQsbbNI,455
-glean_parser-11.0.1.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
-glean_parser-11.0.1.dist-info/METADATA,sha256=z5yLEYgY4EV1e_cHNQhenhkwK5ryURgljfTfaYK-NYs,30877
-glean_parser-11.0.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
-glean_parser-11.0.1.dist-info/entry_points.txt,sha256=mf9d3sv8BwSjjR58x9KDnpVkONCnv3fPQC2NjJl15Xg,68
-glean_parser-11.0.1.dist-info/top_level.txt,sha256=q7T3duD-9tYZFyDry6Wv2LcdMsK2jGnzdDFhxWcT2Z8,13
-glean_parser-11.0.1.dist-info/RECORD,,
+glean_parser/templates/rust.jinja2,sha256=wlV0OZvV3Mk2ulrqFkN1vGjdsahsupEy2TQvWxQKzww,5439
+glean_parser/templates/swift.jinja2,sha256=xkvVsTpfK0QK3tI32wGqzxm2hqFNaBQ6Y71rKIsCmAI,4944
+glean_parser-13.0.0.dist-info/AUTHORS.md,sha256=yxgj8MioO4wUnrh0gmfb8l3DJJrf-l4HmmEDbQsbbNI,455
+glean_parser-13.0.0.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
+glean_parser-13.0.0.dist-info/METADATA,sha256=BzYfW5GF-wZLrokfvUTiZg7JT5BTfB1E3xIDKW6h_BY,31493
+glean_parser-13.0.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
+glean_parser-13.0.0.dist-info/entry_points.txt,sha256=mf9d3sv8BwSjjR58x9KDnpVkONCnv3fPQC2NjJl15Xg,68
+glean_parser-13.0.0.dist-info/top_level.txt,sha256=q7T3duD-9tYZFyDry6Wv2LcdMsK2jGnzdDFhxWcT2Z8,13
+glean_parser-13.0.0.dist-info/RECORD,,
diff --git a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/WHEEL b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/WHEEL
index 98c0d20b7a..98c0d20b7a 100644
--- a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/WHEEL
+++ b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/WHEEL
diff --git a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/entry_points.txt b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/entry_points.txt
index 08fde9d655..08fde9d655 100644
--- a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/entry_points.txt
+++ b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/entry_points.txt
diff --git a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/top_level.txt b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/top_level.txt
index a7f3a37918..a7f3a37918 100644
--- a/third_party/python/glean_parser/glean_parser-11.0.1.dist-info/top_level.txt
+++ b/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/top_level.txt
diff --git a/third_party/python/glean_parser/glean_parser/go_server.py b/third_party/python/glean_parser/glean_parser/go_server.py
new file mode 100644
index 0000000000..403a0d71f4
--- /dev/null
+++ b/third_party/python/glean_parser/glean_parser/go_server.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Outputter to generate server Go code for collecting events.
+
+This outputter is different from the rest of the outputters in that the code it
+generates does not use the Glean SDK. It is meant to be used to collect events
+in server-side environments. In these environments SDK assumptions to measurement
+window and connectivity don't hold.
+Generated code takes care of assembling pings with metrics, and serializing to messages
+conforming to Glean schema.
+
+Warning: this outputter supports limited set of metrics,
+see `SUPPORTED_METRIC_TYPES` below.
+
+The generated code creates the following:
+* Two methods for logging an Event metric
+ one with and one without user request info specified
+"""
+from collections import defaultdict
+from pathlib import Path
+from typing import Any, Dict, Optional, List
+
+from . import __version__
+from . import metrics
+from . import util
+
+# Adding a metric here will require updating the `generate_metric_type` function
+# and require adjustments to `metrics` variables the the template.
+SUPPORTED_METRIC_TYPES = ["string", "quantity", "event"]
+
+
+def generate_event_type_name(metric: metrics.Metric) -> str:
+ return f"Event{util.Camelize(metric.category)}{util.Camelize(metric.name)}"
+
+
+def generate_metric_name(metric: metrics.Metric) -> str:
+ return f"{metric.category}.{metric.name}"
+
+
+def generate_extra_name(extra: str) -> str:
+ return util.Camelize(extra)
+
+
+def generate_metric_argument_name(metric: metrics.Metric) -> str:
+ return f"{util.Camelize(metric.category)}{util.Camelize(metric.name)}"
+
+
+def generate_metric_type(metric_type: str) -> str:
+ if metric_type == "quantity":
+ return "int64"
+ elif metric_type == "string":
+ return "string"
+ elif metric_type == "boolean":
+ return "bool"
+ else:
+ print("❌ Unable to generate Go type from metric type: " + metric_type)
+ exit
+ return "NONE"
+
+
+def clean_string(s: str) -> str:
+ return s.replace("\n", " ").rstrip()
+
+
+def output_go(
+ objs: metrics.ObjectTree, output_dir: Path, options: Optional[Dict[str, Any]]
+) -> None:
+ """
+ Given a tree of objects, output Go code to `output_dir`.
+
+ The output is a single file containing all the code for assembling pings with
+ metrics, serializing, and submitting.
+
+ :param objects: A tree of objects (metrics and pings) as returned from
+ `parser.parse_objects`.
+ :param output_dir: Path to an output directory to write to.
+ """
+
+ template = util.get_jinja2_template(
+ "go_server.jinja2",
+ filters=(
+ ("event_type_name", generate_event_type_name),
+ ("event_extra_name", generate_extra_name),
+ ("metric_name", generate_metric_name),
+ ("metric_argument_name", generate_metric_argument_name),
+ ("go_metric_type", generate_metric_type),
+ ("clean_string", clean_string),
+ ),
+ )
+
+ PING_METRIC_ERROR_MSG = (
+ " Server-side environment is simplified and only supports the events ping type."
+ + " You should not be including pings.yaml with your parser call"
+ + " or referencing any other pings in your metric configuration."
+ )
+ if "pings" in objs:
+ print("❌ Ping definition found." + PING_METRIC_ERROR_MSG)
+ return
+
+ # Go through all metrics in objs and build a map of
+ # ping->list of metric categories->list of metrics
+ # for easier processing in the template.
+ ping_to_metrics: Dict[str, Dict[str, List[metrics.Metric]]] = defaultdict(dict)
+ for _category_key, category_val in objs.items():
+ for _metric_name, metric in category_val.items():
+ if isinstance(metric, metrics.Metric):
+ if metric.type not in SUPPORTED_METRIC_TYPES:
+ print(
+ "❌ Ignoring unsupported metric type: "
+ + f"{metric.type}:{metric.name}."
+ + " Reach out to Glean team to add support for this"
+ + " metric type."
+ )
+ continue
+ for ping in metric.send_in_pings:
+ if ping != "events":
+ (
+ print(
+ "❌ Non-events ping reference found."
+ + PING_METRIC_ERROR_MSG
+ + f"Ignoring the {ping} ping type."
+ )
+ )
+ continue
+ metrics_by_type = ping_to_metrics[ping]
+ metrics_list = metrics_by_type.setdefault(metric.type, [])
+ metrics_list.append(metric)
+
+ if "event" not in ping_to_metrics["events"]:
+ print("❌ No event metrics found...at least one event metric is required")
+ return
+
+ extension = ".go"
+ filepath = output_dir / ("server_events" + extension)
+ with filepath.open("w", encoding="utf-8") as fd:
+ fd.write(
+ template.render(
+ parser_version=__version__, events_ping=ping_to_metrics["events"]
+ )
+ )
diff --git a/third_party/python/glean_parser/glean_parser/metrics.py b/third_party/python/glean_parser/glean_parser/metrics.py
index 5738239f97..accfbd763d 100644
--- a/third_party/python/glean_parser/glean_parser/metrics.py
+++ b/third_party/python/glean_parser/glean_parser/metrics.py
@@ -181,6 +181,7 @@ class Metric:
d.pop("unit")
d.pop("_config", None)
d.pop("_generate_enums", None)
+ d.pop("_generate_structure", None)
return d
def _serialize_input(self) -> Dict[str, util.JSONType]:
@@ -434,4 +435,63 @@ class Text(Metric):
typename = "text"
+class Object(Metric):
+ typename = "object"
+
+ def __init__(self, *args, **kwargs):
+ structure = kwargs.pop("structure", None)
+ if not structure:
+ raise ValueError("`object` is missing required parameter `structure`")
+
+ self._generate_structure = self.validate_structure(structure)
+ super().__init__(*args, **kwargs)
+
+ ALLOWED_TOPLEVEL = {"type", "properties", "items"}
+ ALLOWED_TYPES = ["object", "array", "number", "string", "boolean"]
+
+ @staticmethod
+ def _validate_substructure(structure):
+ extra = set(structure.keys()) - Object.ALLOWED_TOPLEVEL
+ if extra:
+ extra = ", ".join(extra)
+ allowed = ", ".join(Object.ALLOWED_TOPLEVEL)
+ raise ValueError(
+ f"Found additional fields: {extra}. Only allowed: {allowed}"
+ )
+
+ if "type" not in structure or structure["type"] not in Object.ALLOWED_TYPES:
+ raise ValueError("invalid or missing `type` in object structure")
+
+ if structure["type"] == "object":
+ if "items" in structure:
+ raise ValueError("`items` not allowed in object structure")
+
+ if "properties" not in structure:
+ raise ValueError("`properties` missing for type `object`")
+
+ for key in structure["properties"]:
+ value = structure["properties"][key]
+ structure["properties"][key] = Object._validate_substructure(value)
+
+ if structure["type"] == "array":
+ if "properties" in structure:
+ raise ValueError("`properties` not allowed in array structure")
+
+ if "items" not in structure:
+ raise ValueError("`items` missing for type `array`")
+
+ value = structure["items"]
+ structure["items"] = Object._validate_substructure(value)
+
+ return structure
+
+ @staticmethod
+ def validate_structure(structure):
+ if None:
+ raise ValueError("`structure` needed for object metric.")
+
+ structure = Object._validate_substructure(structure)
+ return structure
+
+
ObjectTree = Dict[str, Dict[str, Union[Metric, pings.Ping, tags.Tag]]]
diff --git a/third_party/python/glean_parser/glean_parser/pings.py b/third_party/python/glean_parser/glean_parser/pings.py
index 3099fa1d16..b4145ea68d 100644
--- a/third_party/python/glean_parser/glean_parser/pings.py
+++ b/third_party/python/glean_parser/glean_parser/pings.py
@@ -45,6 +45,7 @@ class Ping:
metadata = {}
self.metadata = metadata
self.precise_timestamps = self.metadata.get("precise_timestamps", True)
+ self.include_info_sections = self.metadata.get("include_info_sections", True)
if data_reviews is None:
data_reviews = []
self.data_reviews = data_reviews
@@ -90,6 +91,9 @@ class Ping:
d = self.serialize()
modified_dict = util.remove_output_params(d, "defined_in")
modified_dict = util.remove_output_params(modified_dict, "precise_timestamps")
+ modified_dict = util.remove_output_params(
+ modified_dict, "include_info_sections"
+ )
return modified_dict
def identifier(self) -> str:
diff --git a/third_party/python/glean_parser/glean_parser/python_server.py b/third_party/python/glean_parser/glean_parser/python_server.py
new file mode 100644
index 0000000000..8ead0eb315
--- /dev/null
+++ b/third_party/python/glean_parser/glean_parser/python_server.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Outputter to generate server Python code for collecting events.
+
+This outputter is different from the rest of the outputters in that the code it
+generates does not use the Glean SDK. It is meant to be used to collect events
+in server-side environments. In these environments SDK assumptions to measurement
+window and connectivity don't hold.
+Generated code takes care of assembling pings with metrics, and serializing to messages
+conforming to Glean schema.
+
+Warning: this outputter supports limited set of metrics,
+see `SUPPORTED_METRIC_TYPES` below.
+
+The generated code creates a `ServerEventLogger` class for each ping that has
+at least one event metric. The class has a `record` method for each event metric.
+"""
+from collections import defaultdict
+from pathlib import Path
+from typing import Any, Dict, Optional, List
+
+from . import __version__
+from . import metrics
+from . import util
+
+# Adding a metric here will require updating the `generate_metric_type` function
+# and require adjustments to `metrics` variables the the template.
+SUPPORTED_METRIC_TYPES = ["string", "quantity", "event"]
+
+
+def camelize(s: str) -> str:
+ return util.Camelize(s)
+
+
+def generate_metric_type(metric_type: str) -> str:
+ if metric_type == "quantity":
+ return "int"
+ elif metric_type == "string":
+ return "str"
+ elif metric_type == "boolean":
+ return "bool"
+ else:
+ print("❌ Unable to generate Python type from metric type: " + metric_type)
+ exit
+ return "NONE"
+
+
+def clean_string(s: str) -> str:
+ return s.replace("\n", " ").rstrip()
+
+
+def generate_ping_factory_method(ping: str) -> str:
+ return f"create_{util.snake_case(ping)}_server_event_logger"
+
+
+def generate_event_record_function_name(event_metric: metrics.Metric) -> str:
+ return (
+ f"record_{util.snake_case(event_metric.category)}_"
+ + f"{util.snake_case(event_metric.name)}"
+ )
+
+
+def output_python(
+ objs: metrics.ObjectTree, output_dir: Path, options: Optional[Dict[str, Any]]
+) -> None:
+ """
+ Given a tree of objects, output Python code to `output_dir`.
+
+ The output is a file containing all the code for assembling pings with
+ metrics, serializing, and submitting, and an empty `__init__.py` file to
+ make the directory a package.
+
+ :param objects: A tree of objects (metrics and pings) as returned from
+ `parser.parse_objects`.
+ :param output_dir: Path to an output directory to write to.
+ """
+
+ template = util.get_jinja2_template(
+ "python_server.jinja2",
+ filters=(
+ ("camelize", camelize),
+ ("py_metric_type", generate_metric_type),
+ ("clean_string", clean_string),
+ ("factory_method", generate_ping_factory_method),
+ ("record_event_function_name", generate_event_record_function_name),
+ ),
+ )
+
+ # Go through all metrics in objs and build a map of
+ # ping->list of metric categories->list of metrics
+ # for easier processing in the template.
+ ping_to_metrics: Dict[str, Dict[str, List[metrics.Metric]]] = defaultdict(dict)
+ for _category_key, category_val in objs.items():
+ for _metric_name, metric in category_val.items():
+ if isinstance(metric, metrics.Metric):
+ if metric.type not in SUPPORTED_METRIC_TYPES:
+ print(
+ "❌ Ignoring unsupported metric type: "
+ + f"{metric.type}:{metric.name}."
+ + " Reach out to Glean team to add support for this"
+ + " metric type."
+ )
+ continue
+ for ping in metric.send_in_pings:
+ metrics_by_type = ping_to_metrics[ping]
+ metrics_list = metrics_by_type.setdefault(metric.type, [])
+ metrics_list.append(metric)
+
+ for ping, metrics_by_type in ping_to_metrics.items():
+ if "event" not in metrics_by_type:
+ print(
+ f"❌ No event metrics found for ping: {ping}."
+ + " At least one event metric is required."
+ )
+ return
+
+ extension = ".py"
+ filepath = output_dir / ("server_events" + extension)
+ with filepath.open("w", encoding="utf-8") as fd:
+ fd.write(template.render(parser_version=__version__, pings=ping_to_metrics))
+
+ # create an empty `__init__.py` file to make the directory a package
+ init_file = output_dir / "__init__.py"
+ with init_file.open("w", encoding="utf-8") as fd:
+ fd.write("")
diff --git a/third_party/python/glean_parser/glean_parser/rust.py b/third_party/python/glean_parser/glean_parser/rust.py
index eb3355e382..6dd4426d84 100644
--- a/third_party/python/glean_parser/glean_parser/rust.py
+++ b/third_party/python/glean_parser/glean_parser/rust.py
@@ -65,7 +65,7 @@ def rust_datatypes_filter(value):
elif isinstance(value, metrics.CowString):
yield f'::std::borrow::Cow::from("{value.inner}")'
elif isinstance(value, str):
- yield f'"{value}".into()'
+ yield f"{json.dumps(value)}.into()"
elif isinstance(value, metrics.Rate):
yield "CommonMetricData("
first = True
@@ -115,6 +115,11 @@ def type_name(obj):
return "{}<{}>".format(class_name(obj.type), generic)
+ generate_structure = getattr(obj, "_generate_structure", [])
+ if len(generate_structure):
+ generic = util.Camelize(obj.name) + "Object"
+ return "{}<{}>".format(class_name(obj.type), generic)
+
return class_name(obj.type)
@@ -133,6 +138,21 @@ def extra_type_name(typ: str) -> str:
return "UNSUPPORTED"
+def structure_type_name(typ: str) -> str:
+ """
+ Returns the corresponding Rust type for structure items.
+ """
+
+ if typ == "boolean":
+ return "bool"
+ elif typ == "string":
+ return "String"
+ elif typ == "number":
+ return "i64"
+ else:
+ return "UNSUPPORTED"
+
+
def class_name(obj_type):
"""
Returns the Rust class name for a given metric or ping type.
@@ -190,6 +210,7 @@ def output_rust(
("camelize", util.camelize),
("type_name", type_name),
("extra_type_name", extra_type_name),
+ ("structure_type_name", structure_type_name),
("ctor", ctor),
("extra_keys", extra_keys),
),
diff --git a/third_party/python/glean_parser/glean_parser/schemas/metrics.2-0-0.schema.yaml b/third_party/python/glean_parser/glean_parser/schemas/metrics.2-0-0.schema.yaml
index 0bc8d500c6..0e785c5303 100644
--- a/third_party/python/glean_parser/glean_parser/schemas/metrics.2-0-0.schema.yaml
+++ b/third_party/python/glean_parser/glean_parser/schemas/metrics.2-0-0.schema.yaml
@@ -119,6 +119,9 @@ definitions:
- `text`: Record long text data.
+ - `object`: Record structured data based on a pre-defined schema
+ Additional properties: `structure`.
+
type: string
enum:
- event
@@ -140,6 +143,7 @@ definitions:
- labeled_counter
- rate
- text
+ - object
description:
title: Description
@@ -567,6 +571,15 @@ definitions:
so glean_parser can find it.
type: string
+ structure:
+ title: A subset of a JSON schema definition
+ description: |
+ The expected structure of data, defined in a strict subset of
+ YAML-dialect JSON Schema (Draft 7) supporting keys "type"
+ (only values "object", "array", "number", "string", and "boolean"),
+ "properties", and "items".
+ type: object
+
required:
- type
- bugs
diff --git a/third_party/python/glean_parser/glean_parser/schemas/pings.2-0-0.schema.yaml b/third_party/python/glean_parser/glean_parser/schemas/pings.2-0-0.schema.yaml
index 2f25405d45..6679a8066b 100644
--- a/third_party/python/glean_parser/glean_parser/schemas/pings.2-0-0.schema.yaml
+++ b/third_party/python/glean_parser/glean_parser/schemas/pings.2-0-0.schema.yaml
@@ -84,6 +84,18 @@ additionalProperties:
When `false` Glean uses minute-precise timestamps for
the ping's start/end time.
type: boolean
+ include_info_sections:
+ title: Include Info Sections
+ description: |
+ When `true`, assemble and include the `client_info` and `ping_info`
+ sections in the ping on submission.
+ When `false`, omit the `client_info` and `ping_info` sections when
+ assembling the ping on submission.
+ Defaults to `true`.
+
+ Interaction with `include_client_id`: `include_client_id` only takes
+ effect when `metadata.include_info_sections` is `true`.
+ type: boolean
default: {}
@@ -93,6 +105,9 @@ additionalProperties:
**Required.**
When `true`, include the `client_id` value in the ping.
+
+ Interaction with `metadata.include_info_sections`: `include_client_id`
+ only takes effect when `metadata.include_info_sections` is `true`.
type: boolean
send_if_empty:
diff --git a/third_party/python/glean_parser/glean_parser/templates/go_server.jinja2 b/third_party/python/glean_parser/glean_parser/templates/go_server.jinja2
new file mode 100644
index 0000000000..0a26831b0f
--- /dev/null
+++ b/third_party/python/glean_parser/glean_parser/templates/go_server.jinja2
@@ -0,0 +1,225 @@
+{# The final Go code is autogenerated, but this template is not. Please file bugs! #}
+package glean
+
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// AUTOGENERATED BY glean_parser v{{ parser_version }}. DO NOT EDIT.
+
+// required imports
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/google/uuid"
+)
+
+// log type string used to identify logs to process in the Moz Data Pipeline
+var gleanEventMozlogType string = "glean-server-event"
+
+type GleanEventsLogger struct {
+ AppID string // Application Id to identify application per Glean standards
+ AppDisplayVersion string // Version of application emitting the event
+ AppChannel string // Channel to differentiate logs from prod/beta/staging/devel
+}
+
+// exported type for public method parameters
+type RequestInfo struct {
+ UserAgent string
+ IpAddress string
+}
+
+// default empty values will be omitted in json from ping struct definition
+var defaultRequestInfo = RequestInfo{
+ UserAgent: "",
+ IpAddress: "",
+}
+
+// structs to construct the glean ping
+type clientInfo struct {
+ TelemetrySDKBuild string `json:"telemetry_sdk_build"`
+ FirstRunDate string `json:"first_run_date"`
+ OS string `json:"os"`
+ OSVersion string `json:"os_version"`
+ Architecture string `json:"architecture"`
+ AppBuild string `json:"app_build"`
+ AppDisplayVersion string `json:"app_display_version"`
+ AppChannel string `json:"app_channel"`
+}
+
+type pingInfo struct {
+ Seq int `json:"seq"`
+ StartTime string `json:"start_time"`
+ EndTime string `json:"end_time"`
+}
+
+type ping struct {
+ DocumentNamespace string `json:"document_namespace"`
+ DocumentType string `json:"document_type"`
+ DocumentVersion string `json:"document_version"`
+ DocumentID string `json:"document_id"`
+ UserAgent string `json:"user_agent,omitempty"`
+ IpAddress string `json:"ip_address,omitempty"`
+ Payload string `json:"payload"`
+}
+
+type metrics map[string]map[string]interface{}
+
+type pingPayload struct {
+ ClientInfo clientInfo `json:"client_info"`
+ PingInfo pingInfo `json:"ping_info"`
+ Metrics metrics `json:"metrics"`
+ Events []gleanEvent `json:"events"`
+}
+
+type gleanEvent struct {
+ Category string `json:"category"`
+ Name string `json:"name"`
+ Timestamp int64 `json:"timestamp"`
+ Extra map[string]string `json:"extra"`
+}
+
+type logEnvelope struct {
+ Timestamp string
+ Logger string
+ Type string
+ Fields ping
+}
+
+func (g GleanEventsLogger) createClientInfo() clientInfo {
+ // Fields with default values are required in the Glean schema, but not used in server context
+ return clientInfo{
+ TelemetrySDKBuild: "glean_parser v{{ parser_version }}",
+ FirstRunDate: "Unknown",
+ OS: "Unknown",
+ OSVersion: "Unknown",
+ Architecture: "Unknown",
+ AppBuild: "Unknown",
+ AppDisplayVersion: g.AppDisplayVersion,
+ AppChannel: g.AppChannel,
+ }
+}
+
+func createPingInfo() pingInfo {
+ {# times are ISO-8601 strings, e.g. "2023-12-19T22:09:17.440Z" #}
+ var now = time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
+ return pingInfo{
+ Seq: 0,
+ StartTime: now,
+ EndTime: now,
+ }
+}
+
+func (g GleanEventsLogger) createPing(documentType string, config RequestInfo, payload pingPayload) ping {
+ var payloadJson, payloadErr = json.Marshal(payload)
+ if payloadErr != nil {
+ panic("Unable to marshal payload to json")
+ }
+ var documentId = uuid.New()
+ return ping{
+ DocumentNamespace: g.AppID,
+ DocumentType: documentType,
+ DocumentVersion: "1",
+ DocumentID: documentId.String(),
+ UserAgent: config.UserAgent,
+ IpAddress: config.IpAddress,
+ Payload: string(payloadJson),
+ }
+}
+
+// method called by each event method.
+// construct the ping, wrap it in the envelope, and print to stdout
+func (g GleanEventsLogger) record(
+ documentType string,
+ requestInfo RequestInfo,
+ metrics metrics,
+ events []gleanEvent,
+) {
+ var telemetryPayload = pingPayload{
+ ClientInfo: g.createClientInfo(),
+ PingInfo: createPingInfo(),
+ Metrics: metrics,
+ Events: events,
+ }
+
+ var ping = g.createPing(documentType, requestInfo, telemetryPayload)
+
+ var envelope = logEnvelope{
+ Timestamp: strconv.FormatInt(time.Now().UnixNano(), 10),
+ Logger: "glean",
+ Type: gleanEventMozlogType,
+ Fields: ping,
+ }
+ var envelopeJson, envelopeErr = json.Marshal(envelope)
+ if envelopeErr != nil {
+ panic("Unable to marshal log envelope to json")
+ }
+ fmt.Println(string(envelopeJson))
+}
+
+{% for event in events_ping["event"] %}
+type {{ event|event_type_name }} struct {
+ {% for metric_type, metrics in events_ping.items() %}
+ {% if metric_type != 'event' %}
+ {% for metric in metrics %}
+ {{ metric|metric_argument_name }} {{ metric.type|go_metric_type }} // {{ metric.description|clean_string }}
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
+ {% for extra, metadata in event.extra_keys.items() %}
+ {{ extra|event_extra_name }} {{ metadata.type|go_metric_type }} // {{ metadata.description|clean_string }}
+ {% endfor %}
+}
+
+// Record and submit an {{ event|event_type_name }} event.
+// {{ event.description|clean_string }}
+func (g GleanEventsLogger) Record{{ event|event_type_name }}(
+ requestInfo RequestInfo,
+ params {{ event|event_type_name }},
+) {
+ var metrics = metrics{
+ {% for metric_type, metrics in events_ping.items() %}
+ {% if metric_type != 'event' %}
+ "{{ metric_type }}": {
+ {% for metric in metrics %}
+ "{{ metric|metric_name }}": params.{{ metric|metric_argument_name }},
+ {% endfor %}
+ },
+ {% endif %}
+ {% endfor %}
+ }
+ var extraKeys = map[string]string{
+ {% for extra, metadata in event.extra_keys.items() %}
+ {# convert all extra fields to string for submission #}
+ {% if metadata.type == 'boolean' %}
+ "{{ extra }}": fmt.Sprintf("%t", params.{{ extra|event_extra_name }}),
+ {% elif metadata.type == 'quantity' %}
+ "{{ extra }}": fmt.Sprintf("%d", params.{{ extra|event_extra_name }}),
+ {% else %}
+ "{{ extra }}": params.{{ extra|event_extra_name }},
+ {% endif %}
+ {% endfor %}
+ }
+ var events = []gleanEvent{
+ gleanEvent{
+ Category: "{{ event.category }}",
+ Name: "{{ event.name }}",
+ Timestamp: time.Now().UnixMilli(),
+ Extra: extraKeys,
+ },
+ }
+ g.record("events", requestInfo, metrics, events)
+}
+
+// Record and submit an {{ event|event_type_name }} event omitting user request info
+// {{ event.description|clean_string }}
+func (g GleanEventsLogger) Record{{ event|event_type_name }}WithoutUserInfo(
+ params {{ event|event_type_name }},
+) {
+ g.Record{{ event|event_type_name }}(defaultRequestInfo, params)
+}
+
+{% endfor %}
diff --git a/third_party/python/glean_parser/glean_parser/templates/python_server.jinja2 b/third_party/python/glean_parser/glean_parser/templates/python_server.jinja2
new file mode 100644
index 0000000000..689fab2109
--- /dev/null
+++ b/third_party/python/glean_parser/glean_parser/templates/python_server.jinja2
@@ -0,0 +1,194 @@
+{# The final Go code is autogenerated, but this template is not. Please file bugs! #}
+"""
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+AUTOGENERATED BY glean_parser v{{ parser_version }}. DO NOT EDIT. DO NOT COMMIT.
+"""
+
+from __future__ import annotations
+from datetime import datetime, timezone
+from typing import Any
+from uuid import uuid4
+import json
+
+GLEAN_EVENT_MOZLOG_TYPE = "glean-server-event"
+
+
+{% for ping, metrics_by_type in pings.items() %}
+class {{ ping|camelize }}ServerEventLogger:
+ def __init__(
+ self, application_id: str, app_display_version: str, channel: str
+ ) -> None:
+ """
+ Create {{ ping|camelize }}ServerEventLogger instance.
+
+ :param str application_id: The application ID.
+ :param str app_display_version: The application display version.
+ :param str channel: The channel.
+ """
+ self._application_id = application_id
+ self._app_display_version = app_display_version
+ self._channel = channel
+
+ def _record(
+ self,
+ user_agent: str,
+ ip_address: str,
+ {% for metric_type, metrics in metrics_by_type.items() %}
+ {% if metric_type != 'event' %}
+ {% for metric in metrics %}
+ {{ metric.category }}_{{ metric.name }}: {{ metric.type|py_metric_type }},
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
+ event: dict[str, Any]
+ ) -> None:
+ now = datetime.now(timezone.utc)
+ timestamp = now.isoformat()
+ event["timestamp"] = int(1000.0 * now.timestamp()) # Milliseconds since epoch
+ event_payload = {
+ "metrics": {
+ {% for metric_type, metrics in metrics_by_type.items() %}
+ {% if metric_type != 'event' %}
+ "{{ metric_type }}": {
+ {% for metric in metrics %}
+ "{{ metric.category }}.{{ metric.name }}": {{ metric.category }}_{{ metric.name }},
+ {% endfor %}
+ },
+ {% endif %}
+ {% endfor %}
+ },
+ "events": [event],
+ "ping_info": {
+ # seq is required in the Glean schema, however is not useful in server context
+ "seq": 0,
+ "start_time": timestamp,
+ "end_time": timestamp,
+ },
+ # `Unknown` fields below are required in the Glean schema, however they are
+ # not useful in server context
+ "client_info": {
+ "telemetry_sdk_build": "glean_parser v{{ parser_version }}",
+ "first_run_date": "Unknown",
+ "os": "Unknown",
+ "os_version": "Unknown",
+ "architecture": "Unknown",
+ "app_build": "Unknown",
+ "app_display_version": self._app_display_version,
+ "app_channel": self._channel,
+ },
+ }
+ event_payload_serialized = json.dumps(event_payload)
+
+ # This is the message structure that Decoder expects:
+ # https://github.com/mozilla/gcp-ingestion/pull/2400
+ ping = {
+ "document_namespace": self._application_id,
+ "document_type": "{{ ping }}",
+ "document_version": "1",
+ "document_id": str(uuid4()),
+ "user_agent": user_agent,
+ "ip_address": ip_address,
+ "payload": event_payload_serialized,
+ }
+
+
+ self.emit_record(now, ping)
+
+ def emit_record(self, now: datetime, ping:dict[str, Any]) -> None:
+ """Log the ping to STDOUT.
+ Applications might want to override this method to use their own logging.
+ If doing so, make sure to log the ping as JSON, and to include the
+ `Type: GLEAN_EVENT_MOZLOG_TYPE`."""
+ ping_envelope = {
+ "Timestamp": now.isoformat(),
+ "Logger": "glean",
+ "Type": GLEAN_EVENT_MOZLOG_TYPE,
+ "Fields": ping,
+ }
+ ping_envelope_serialized = json.dumps(ping_envelope)
+
+ print(ping_envelope_serialized)
+
+ {% for event in metrics_by_type["event"] %}
+ def {{ event|record_event_function_name }}(
+ self,
+ user_agent: str,
+ ip_address: str,
+ {% for metric_type, metrics in metrics_by_type.items() %}
+ {% if metric_type != 'event' %}
+ {% for metric in metrics %}
+ {{ metric.category }}_{{ metric.name }}: {{ metric.type|py_metric_type }},
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
+ {% for extra, metadata in event.extra_keys.items() %}
+ {{ extra }}: {{ metadata.type|py_metric_type }},
+ {% endfor %}
+ ) -> None:
+ """
+ Record and submit a {{ event.category }}_{{ event.name }} event:
+ {{ event.description|clean_string }}
+ Event is logged to STDOUT via `print`.
+
+ :param str user_agent: The user agent.
+ :param str ip_address: The IP address. Will be used to decode Geo information
+ and scrubbed at ingestion.
+ {% for metric_type, metrics in metrics_by_type.items() %}
+ {% if metric_type != 'event' %}
+ {% for metric in metrics %}
+ :param {{ metric.type|py_metric_type }} {{ metric.category }}_{{ metric.name }}: {{ metric.description|clean_string }}
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
+ {% if event.extra_keys %}
+ {% for extra, metadata in event.extra_keys.items() %}
+ :param {{ metadata.type|py_metric_type }} {{ extra }}: {{ metadata.description|clean_string }}
+ {% endfor %}
+ {% endif %}
+ """
+ event = {
+ "category": "{{ event.category }}",
+ "name": "{{ event.name }}",
+ {% if event.extra_keys %}
+ "extra": {
+ {% for extra, metadata in event.extra_keys.items() %}
+ "{{ extra }}": str({{ extra }}){% if 'bool' == metadata.type|py_metric_type %}.lower(){% endif %},
+ {% endfor %}
+ },
+ {% endif %}
+ }
+ self._record(
+ user_agent,
+ ip_address,
+ {% for metric_type, metrics in metrics_by_type.items() %}
+ {% if metric_type != 'event' %}
+ {% for metric in metrics %}
+ {{ metric.category }}_{{ metric.name }},
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
+ event
+ )
+ {% endfor %}
+{% endfor %}
+
+{% for ping in pings %}
+def {{ ping|factory_method }}(
+ application_id: str,
+ app_display_version: str,
+ channel: str,
+) -> {{ ping|camelize }}ServerEventLogger:
+ """
+ Factory function that creates an instance of Glean Server Event Logger to record
+ `{{ ping }}` ping events.
+ :param str application_id: The application ID.
+ :param str app_display_version: The application display version.
+ :param str channel: The channel.
+ :return: An instance of {{ ping|camelize }}ServerEventLogger.
+ :rtype: {{ ping|camelize }}ServerEventLogger
+ """
+ return {{ ping|camelize }}ServerEventLogger(application_id, app_display_version, channel)
+{% endfor %}
diff --git a/third_party/python/glean_parser/glean_parser/templates/rust.jinja2 b/third_party/python/glean_parser/glean_parser/templates/rust.jinja2
index 51e458cddf..4c54dd2b2c 100644
--- a/third_party/python/glean_parser/glean_parser/templates/rust.jinja2
+++ b/third_party/python/glean_parser/glean_parser/templates/rust.jinja2
@@ -8,6 +8,49 @@ Jinja2 template is not. Please file bugs! #}
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+{%- macro generate_structure(name, struct) %}
+{% if struct.type == "array" %}
+ pub type {{ name }} = Vec<{{ name }}Item>;
+
+ {{ generate_structure(name ~ "Item", struct["items"]) }}
+
+{% elif struct.type == "object" %}
+ #[derive(Debug, Hash, Eq, PartialEq, ::glean::traits::__serde::Serialize, ::glean::traits::__serde::Deserialize)]
+ #[serde(crate = "::glean::traits::__serde")]
+ #[serde(deny_unknown_fields)]
+ pub struct {{ name }} {
+ {% for itemname, val in struct.properties.items() %}
+ {% if val.type == "object" %}
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub {{itemname|snake_case}}: Option<{{ name ~ "Item" ~ itemname|Camelize ~ "Object" }}>,
+ {% elif val.type == "array" %}
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub {{itemname|snake_case}}: {{ name ~ "Item" ~ itemname|Camelize }},
+ {% else %}
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub {{itemname|snake_case}}: Option<{{val.type|structure_type_name}}>,
+ {% endif %}
+ {% endfor %}
+ }
+
+ {% for itemname, val in struct.properties.items() %}
+ {% if val.type == "array" %}
+ {% set nested_name = name ~ "Item" ~ itemname|Camelize %}
+ {{ generate_structure(nested_name, val) }}
+ {% elif val.type == "object" %}
+ {% set nested_name = name ~ "Item" ~ itemname|Camelize ~ "Object" %}
+ {{ generate_structure(nested_name, val) }}
+ {% endif %}
+ {% endfor %}
+
+{% else %}
+
+pub type {{ name }} = {{ struct.type|structure_type_name }};
+
+{% endif %}
+
+{% endmacro %}
+
{% macro generate_extra_keys(obj) %}
{% for name, _ in obj["_generate_enums"] %}
{# we always use the `extra` suffix, because we only expose the new event API #}
@@ -44,7 +87,7 @@ impl ExtraKeys for {{ obj.name|Camelize }}{{ suffix }} {
/// {{ obj.description|wordwrap() | replace('\n', '\n/// ') }}
#[rustfmt::skip]
pub static {{ obj.name|snake_case }}: ::glean::private::__export::Lazy<::glean::private::PingType> =
- ::glean::private::__export::Lazy::new(|| ::glean::private::PingType::new("{{ obj.name }}", {{ obj.include_client_id|rust }}, {{ obj.send_if_empty|rust }}, {{ obj.precise_timestamps|rust }}, {{ obj.reason_codes|rust }}));
+ ::glean::private::__export::Lazy::new(|| ::glean::private::PingType::new("{{ obj.name }}", {{ obj.include_client_id|rust }}, {{ obj.send_if_empty|rust }}, {{ obj.precise_timestamps|rust }}, {{ obj.include_info_sections|rust }}, {{ obj.reason_codes|rust }}));
{% endfor %}
{% else %}
pub mod {{ category.name|snake_case }} {
@@ -52,6 +95,10 @@ pub mod {{ category.name|snake_case }} {
use glean::{private::*, traits::ExtraKeys, traits::NoExtraKeys, CommonMetricData, HistogramType, Lifetime, TimeUnit, MemoryUnit};
{% for obj in category.objs.values() %}
+ {% if obj|attr("_generate_structure") %}
+{{ generate_structure(obj.name|Camelize ~ "Object", obj._generate_structure) }}
+ {%- endif %}
+
{% if obj|attr("_generate_enums") %}
{{ generate_extra_keys(obj) }}
{%- endif %}
diff --git a/third_party/python/glean_parser/glean_parser/templates/swift.jinja2 b/third_party/python/glean_parser/glean_parser/templates/swift.jinja2
index 82ad37bf20..714bf20ec2 100644
--- a/third_party/python/glean_parser/glean_parser/templates/swift.jinja2
+++ b/third_party/python/glean_parser/glean_parser/templates/swift.jinja2
@@ -96,6 +96,7 @@ extension {{ namespace }} {
includeClientId: {{obj.include_client_id|swift}},
sendIfEmpty: {{obj.send_if_empty|swift}},
preciseTimestamps: {{obj.precise_timestamps|swift}},
+ includeInfoSections: {{obj.include_info_sections|swift}},
reasonCodes: {{obj.reason_codes|swift}}
)
diff --git a/third_party/python/glean_parser/glean_parser/translate.py b/third_party/python/glean_parser/glean_parser/translate.py
index 021fce47fb..6293a99491 100644
--- a/third_party/python/glean_parser/glean_parser/translate.py
+++ b/third_party/python/glean_parser/glean_parser/translate.py
@@ -17,8 +17,10 @@ from typing import Any, Callable, Dict, Iterable, List, Optional
from . import lint
from . import parser
+from . import go_server
from . import javascript
from . import javascript_server
+from . import python_server
from . import kotlin
from . import markdown
from . import metrics
@@ -54,10 +56,12 @@ class Outputter:
OUTPUTTERS = {
+ "go_server": Outputter(go_server.output_go, []),
"javascript": Outputter(javascript.output_javascript, []),
"typescript": Outputter(javascript.output_typescript, []),
"javascript_server": Outputter(javascript_server.output_javascript, []),
"typescript_server": Outputter(javascript_server.output_typescript, []),
+ "python_server": Outputter(python_server.output_python, []),
"ruby_server": Outputter(ruby_server.output_ruby, []),
"kotlin": Outputter(kotlin.output_kotlin, ["*.kt"]),
"markdown": Outputter(markdown.output_markdown, []),
diff --git a/third_party/python/glean_parser/glean_parser/util.py b/third_party/python/glean_parser/glean_parser/util.py
index edaeed9578..41cda8833d 100644
--- a/third_party/python/glean_parser/glean_parser/util.py
+++ b/third_party/python/glean_parser/glean_parser/util.py
@@ -525,6 +525,7 @@ ping_args = [
"include_client_id",
"send_if_empty",
"precise_timestamps",
+ "include_info_sections",
"reason_codes",
]