diff options
Diffstat (limited to 'third_party/python/glean_parser')
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", ] |