glean-parser 19.1.0__tar.gz → 19.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {glean_parser-19.1.0 → glean_parser-19.2.0}/PKG-INFO +1 -1
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/go_server.py +40 -3
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/go_server.jinja2 +163 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/translate.py +2 -1
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/util.py +3 -3
- glean_parser-19.2.0/tests/test-go/test_publisher.go.tmpl +103 -0
- glean_parser-19.2.0/tests/test_go_server_pubsub.py +344 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/.gitignore +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/AUTHORS.md +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/LICENSE +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/README.md +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/__init__.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/__main__.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/data_review.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/javascript.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/javascript_server.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/kotlin.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/lint.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/markdown.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/metrics.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/parser.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/pings.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/python_server.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/ruby_server.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/rust.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/rust_server.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/rust_sym.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/metrics.1-0-0.schema.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/metrics.2-0-0.schema.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/pings.1-0-0.schema.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/pings.2-0-0.schema.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/tags.1-0-0.schema.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/swift.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/tags.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/data_review.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/javascript.buildinfo.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/javascript.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/javascript_server.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/kotlin.buildinfo.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/kotlin.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/markdown.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/python_server.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/qmldir.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/ruby_server.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/rust.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/rust_server.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/rust_sym.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/swift.jinja2 +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/translation_options.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/validate_ping.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/pyproject.toml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/server_telemetry/sdk-metrics-compat.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/server_telemetry/server-side-pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/conftest.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/all_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/all_pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/attribution.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/bad_attribution.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/bad_ping.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/core.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/custom_ping_no_event_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/custom_ping_no_event_pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/dual_labeled.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/dual_labeled_invalid.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/duplicate_labeled.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/duplicate_send_in_ping.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/empty.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/event_key_ordering.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/events_data_sensitivity.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/events_with_types.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/fxa-server-metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/fxa-server-pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_custom_ping_only_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_custom_ping_only_pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_events_and_custom_ping_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_events_and_custom_ping_pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_events_only_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_labeled_boolean_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_metrics_unsupported.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/invalid-ping-names.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/invalid.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/jwe.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/metric-with-tags.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/mixed-expirations.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/name_too_similar.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/object.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/old_event_api.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/ordering.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/python_server_metrics_unsupported.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rate.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/redefined_category.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/redefined_metric.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/redefined_ping.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/reserved_categories.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/ruby_server_metrics_unsupported.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/ruby_server_pings_unsupported.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_custom_ping_only_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_custom_ping_only_pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_events_and_custom_ping_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_events_and_custom_ping_pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_events_only_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_metrics_unsupported.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/same_name_different_category.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/schema-violation.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/send_if_empty_with_metrics.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_custom_ping_only_compare.go +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_custom_ping_only_compare.rs +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_and_custom_ping_compare.go +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_and_custom_ping_compare.rs +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_compare.rb +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_only_compare.go +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_only_compare.rs +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_metrics_no_events_no_pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_metrics_with_event.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_pings.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/single_labeled.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/smaller.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/tags.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/telemetry_mirror.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/text.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/text_invalid.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/unknown_ping_used.yaml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/wrong_key.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/yaml_nits.yamlx +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/detekt.yml +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-go/test.go.tmpl +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-js/package.json +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-js/test.js.tmpl +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-py/test.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-rb/test.rb.tmpl +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-rs/test.rs.tmpl +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_cli.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_go_server.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_javascript.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_javascript_server.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_kotlin.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_lint.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_markdown.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_metrics.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_parser.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_pings.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_python_server.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_ruby_server.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_rust.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_rust_server.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_rust_sym.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_swift.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_tags.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_translate.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_utils.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_validate_ping.py +0 -0
- {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/util.py +0 -0
|
@@ -11,14 +11,17 @@ This outputter is different from the rest of the outputters in that the code it
|
|
|
11
11
|
generates does not use the Glean SDK. It is meant to be used to collect events
|
|
12
12
|
in server-side environments. In these environments SDK assumptions to measurement
|
|
13
13
|
window and connectivity don't hold.
|
|
14
|
+
|
|
14
15
|
Generated code takes care of assembling pings with metrics, and serializing to messages
|
|
15
|
-
conforming to Glean schema.
|
|
16
|
+
conforming to Glean schema. Two transport modes are supported:
|
|
17
|
+
- Cloud Logging (go_server): Logs to stdout in MozLog format for ingestion via GCP log routing
|
|
18
|
+
- Pub/Sub (go_server_pubsub): Publishes directly to GCP Pub/Sub topics
|
|
16
19
|
|
|
17
20
|
Warning: this outputter supports limited set of metrics,
|
|
18
21
|
see `SUPPORTED_METRIC_TYPES` below.
|
|
19
22
|
|
|
20
23
|
Generated code creates two methods for each ping (`RecordPingX` and `RecordPingXWithoutUserInfo`)
|
|
21
|
-
that are used for submitting
|
|
24
|
+
that are used for submitting events.
|
|
22
25
|
If pings have `event` metrics assigned, they can be passed to these methods.
|
|
23
26
|
"""
|
|
24
27
|
|
|
@@ -111,7 +114,10 @@ def validate_labeled_boolean(metric: metrics.Metric) -> bool:
|
|
|
111
114
|
|
|
112
115
|
|
|
113
116
|
def output_go(
|
|
114
|
-
objs: metrics.ObjectTree,
|
|
117
|
+
objs: metrics.ObjectTree,
|
|
118
|
+
output_dir: Path,
|
|
119
|
+
options: Optional[Dict[str, Any]],
|
|
120
|
+
transport: str = "logging",
|
|
115
121
|
) -> None:
|
|
116
122
|
"""
|
|
117
123
|
Given a tree of objects, output Go code to `output_dir`.
|
|
@@ -122,6 +128,8 @@ def output_go(
|
|
|
122
128
|
:param objects: A tree of objects (metrics and pings) as returned from
|
|
123
129
|
`parser.parse_objects`.
|
|
124
130
|
:param output_dir: Path to an output directory to write to.
|
|
131
|
+
:param transport: Transport mode - either "logging" (Cloud Logging) or
|
|
132
|
+
"pubsub" (Pub/Sub direct publishing). Default is "logging".
|
|
125
133
|
"""
|
|
126
134
|
|
|
127
135
|
template = util.get_jinja2_template(
|
|
@@ -198,5 +206,34 @@ def output_go(
|
|
|
198
206
|
pings=ping_to_metrics,
|
|
199
207
|
events=event_metrics,
|
|
200
208
|
labeled_booleans=labeled_boolean_metrics,
|
|
209
|
+
transport=transport,
|
|
201
210
|
)
|
|
202
211
|
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def output_go_logger(
|
|
215
|
+
objs: metrics.ObjectTree, output_dir: Path, options: Optional[Dict[str, Any]] = None
|
|
216
|
+
) -> None:
|
|
217
|
+
"""
|
|
218
|
+
Given a tree of objects, output Go code using Cloud Logging transport.
|
|
219
|
+
|
|
220
|
+
:param objects: A tree of objects (metrics and pings) as returned from
|
|
221
|
+
`parser.parse_objects`.
|
|
222
|
+
:param output_dir: Path to an output directory to write to.
|
|
223
|
+
:param options: options dictionary (currently unused for Go).
|
|
224
|
+
"""
|
|
225
|
+
output_go(objs, output_dir, options, transport="logging")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def output_go_pubsub(
|
|
229
|
+
objs: metrics.ObjectTree, output_dir: Path, options: Optional[Dict[str, Any]] = None
|
|
230
|
+
) -> None:
|
|
231
|
+
"""
|
|
232
|
+
Given a tree of objects, output Go code using Pub/Sub transport.
|
|
233
|
+
|
|
234
|
+
:param objects: A tree of objects (metrics and pings) as returned from
|
|
235
|
+
`parser.parse_objects`.
|
|
236
|
+
:param output_dir: Path to an output directory to write to.
|
|
237
|
+
:param options: options dictionary (currently unused for Go).
|
|
238
|
+
"""
|
|
239
|
+
output_go(objs, output_dir, options, transport="pubsub")
|
|
@@ -9,6 +9,18 @@ package glean
|
|
|
9
9
|
|
|
10
10
|
// required imports
|
|
11
11
|
import (
|
|
12
|
+
{% if transport == "pubsub" %}
|
|
13
|
+
"bytes"
|
|
14
|
+
"compress/gzip"
|
|
15
|
+
"encoding/json"
|
|
16
|
+
"fmt"
|
|
17
|
+
"io"
|
|
18
|
+
"sync"
|
|
19
|
+
"time"
|
|
20
|
+
|
|
21
|
+
pubsub "cloud.google.com/go/pubsub/v2"
|
|
22
|
+
"github.com/google/uuid"
|
|
23
|
+
{% else %}
|
|
12
24
|
"encoding/json"
|
|
13
25
|
"errors"
|
|
14
26
|
"fmt"
|
|
@@ -17,8 +29,52 @@ import (
|
|
|
17
29
|
"time"
|
|
18
30
|
|
|
19
31
|
"github.com/google/uuid"
|
|
32
|
+
{% endif %}
|
|
20
33
|
)
|
|
21
34
|
|
|
35
|
+
{% if transport == "pubsub" %}
|
|
36
|
+
// GleanEventsBuilder constructs Pub/Sub messages carrying Glean pings.
|
|
37
|
+
// Builder is stateless beyond its app-identity fields; callers own the
|
|
38
|
+
// pubsub.Client, the pubsub.Publisher, batching settings, retries, shutdown sequencing, and any
|
|
39
|
+
// publish-result metrics
|
|
40
|
+
type GleanEventsBuilder struct {
|
|
41
|
+
AppID string // Application ID to identify application per Glean standards
|
|
42
|
+
AppDisplayVersion string // Version of application emitting the event
|
|
43
|
+
AppChannel string // Channel to differentiate logs from prod/beta/staging/devel
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// gzipPool reuses gzip.Writer instances across calls. gzip.NewWriter()
|
|
47
|
+
// allocates ~800 KB internally; pooling + Reset() drops per-message
|
|
48
|
+
// allocations to near zero at high publish volumes.
|
|
49
|
+
var gzipPool = sync.Pool{
|
|
50
|
+
New: func() interface{} {
|
|
51
|
+
return gzip.NewWriter(io.Discard)
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// compressPayload gzips data using the package-level writer pool. The
|
|
56
|
+
// returned slice is caller-owned; only the *gzip.Writer is returned to the
|
|
57
|
+
// pool.
|
|
58
|
+
func compressPayload(data []byte) ([]byte, error) {
|
|
59
|
+
var buf bytes.Buffer
|
|
60
|
+
// Optimistic preallocation: gzip on small JSON typically achieves ~2x ratio.
|
|
61
|
+
buf.Grow(len(data) / 2)
|
|
62
|
+
|
|
63
|
+
gz := gzipPool.Get().(*gzip.Writer)
|
|
64
|
+
gz.Reset(&buf)
|
|
65
|
+
|
|
66
|
+
if _, err := gz.Write(data); err != nil {
|
|
67
|
+
gzipPool.Put(gz)
|
|
68
|
+
return nil, fmt.Errorf("gzip write failed: %w", err)
|
|
69
|
+
}
|
|
70
|
+
if err := gz.Close(); err != nil {
|
|
71
|
+
gzipPool.Put(gz)
|
|
72
|
+
return nil, fmt.Errorf("gzip close failed: %w", err)
|
|
73
|
+
}
|
|
74
|
+
gzipPool.Put(gz)
|
|
75
|
+
return buf.Bytes(), nil
|
|
76
|
+
}
|
|
77
|
+
{% else %}
|
|
22
78
|
// log type string used to identify logs to process in the Moz Data Pipeline
|
|
23
79
|
var gleanEventMozlogType string = "glean-server-event"
|
|
24
80
|
|
|
@@ -32,6 +88,7 @@ type GleanEventsLogger struct {
|
|
|
32
88
|
AppChannel string // Channel to differentiate logs from prod/beta/staging/devel
|
|
33
89
|
Writer io.Writer // Writer to output to. Normal operation expects os.Stdout
|
|
34
90
|
}
|
|
91
|
+
{% endif %}
|
|
35
92
|
|
|
36
93
|
// exported type for public method parameters
|
|
37
94
|
type RequestInfo struct {
|
|
@@ -63,6 +120,7 @@ type pingInfo struct {
|
|
|
63
120
|
EndTime string `json:"end_time"`
|
|
64
121
|
}
|
|
65
122
|
|
|
123
|
+
{% if transport == "logging" %}
|
|
66
124
|
type ping struct {
|
|
67
125
|
DocumentNamespace string `json:"document_namespace"`
|
|
68
126
|
DocumentType string `json:"document_type"`
|
|
@@ -72,6 +130,7 @@ type ping struct {
|
|
|
72
130
|
IpAddress string `json:"ip_address,omitempty"`
|
|
73
131
|
Payload string `json:"payload"`
|
|
74
132
|
}
|
|
133
|
+
{% endif %}
|
|
75
134
|
|
|
76
135
|
type metrics map[string]map[string]any
|
|
77
136
|
|
|
@@ -89,14 +148,20 @@ type gleanEvent struct {
|
|
|
89
148
|
Extra map[string]string `json:"extra"`
|
|
90
149
|
}
|
|
91
150
|
|
|
151
|
+
{% if transport == "logging" %}
|
|
92
152
|
type logEnvelope struct {
|
|
93
153
|
Timestamp string
|
|
94
154
|
Logger string
|
|
95
155
|
Type string
|
|
96
156
|
Fields ping
|
|
97
157
|
}
|
|
158
|
+
{% endif %}
|
|
98
159
|
|
|
160
|
+
{% if transport == "pubsub" %}
|
|
161
|
+
func (g GleanEventsBuilder) createClientInfo() clientInfo {
|
|
162
|
+
{% else %}
|
|
99
163
|
func (g GleanEventsLogger) createClientInfo() clientInfo {
|
|
164
|
+
{% endif %}
|
|
100
165
|
// Fields with default values are required in the Glean schema, but not used in server context
|
|
101
166
|
return clientInfo{
|
|
102
167
|
TelemetrySDKBuild: "glean_parser v{{ parser_version }}",
|
|
@@ -120,6 +185,7 @@ func createPingInfo() pingInfo {
|
|
|
120
185
|
}
|
|
121
186
|
}
|
|
122
187
|
|
|
188
|
+
{% if transport == "logging" %}
|
|
123
189
|
func (g GleanEventsLogger) createPing(documentType string, config RequestInfo, payload pingPayload) (ping, error) {
|
|
124
190
|
payloadJson, err := json.Marshal(payload)
|
|
125
191
|
if err != nil {
|
|
@@ -180,6 +246,7 @@ func (g GleanEventsLogger) record(
|
|
|
180
246
|
fmt.Fprintln(g.Writer, string(envelopeJson))
|
|
181
247
|
return nil
|
|
182
248
|
}
|
|
249
|
+
{% endif %}
|
|
183
250
|
{# if any ping has an event metric, create methods and types for them #}
|
|
184
251
|
{% if events %}
|
|
185
252
|
|
|
@@ -265,6 +332,101 @@ type {{ ping|ping_type_name }} struct {
|
|
|
265
332
|
{% endif %}
|
|
266
333
|
}
|
|
267
334
|
|
|
335
|
+
{% if transport == "pubsub" %}
|
|
336
|
+
// Build{{ ping|ping_type_name }}Message constructs a Pub/Sub message carrying
|
|
337
|
+
// the given `{{ ping }}` ping. The caller publishes the returned message
|
|
338
|
+
// (e.g., topic.Publish(ctx, msg)) and owns batching, retries, shutdown
|
|
339
|
+
// sequencing, and any publish-result metrics.
|
|
340
|
+
//
|
|
341
|
+
// Wire-format contract: see
|
|
342
|
+
// docs/architecture/decoder_service_specification.md in mozilla/gcp-ingestion.
|
|
343
|
+
func (g GleanEventsBuilder) Build{{ ping|ping_type_name }}Message(
|
|
344
|
+
requestInfo RequestInfo,
|
|
345
|
+
params {{ ping|ping_type_name }},
|
|
346
|
+
) (*pubsub.Message, error) {
|
|
347
|
+
{% if metrics_by_type['string_list'] %}
|
|
348
|
+
{% for metric in metrics_by_type['string_list'] %}
|
|
349
|
+
// Ensure nil string_list metrics serialize as empty arrays, not null
|
|
350
|
+
if params.{{ metric|metric_argument_name }} == nil {
|
|
351
|
+
params.{{ metric|metric_argument_name }} = []string{}
|
|
352
|
+
}
|
|
353
|
+
{% endfor %}
|
|
354
|
+
{% endif %}
|
|
355
|
+
metrics := metrics{
|
|
356
|
+
{% for metric_type, metrics in metrics_by_type.items() %}
|
|
357
|
+
{% if metric_type != 'event' %}
|
|
358
|
+
"{{ metric_type }}": {
|
|
359
|
+
{% for metric in metrics %}
|
|
360
|
+
{% if metric_type == 'datetime' %}
|
|
361
|
+
"{{ metric|metric_name }}": params.{{ metric|metric_argument_name }}.Format("2006-01-02T15:04:05.000Z"),
|
|
362
|
+
{% else %}
|
|
363
|
+
"{{ metric|metric_name }}": params.{{ metric|metric_argument_name }},
|
|
364
|
+
{% endif %}
|
|
365
|
+
{% endfor %}
|
|
366
|
+
},
|
|
367
|
+
{% endif %}
|
|
368
|
+
{% endfor %}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
events := []gleanEvent{}
|
|
372
|
+
{% if metrics_by_type['event'] %}
|
|
373
|
+
if params.Event != nil {
|
|
374
|
+
events = append(events, params.Event.gleanEvent())
|
|
375
|
+
}
|
|
376
|
+
{% endif %}
|
|
377
|
+
|
|
378
|
+
payload := pingPayload{
|
|
379
|
+
ClientInfo: g.createClientInfo(),
|
|
380
|
+
PingInfo: createPingInfo(),
|
|
381
|
+
Metrics: metrics,
|
|
382
|
+
Events: events,
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
payloadJSON, err := json.Marshal(payload)
|
|
386
|
+
if err != nil {
|
|
387
|
+
return nil, fmt.Errorf("marshal ping payload: %w", err)
|
|
388
|
+
}
|
|
389
|
+
compressed, err := compressPayload(payloadJSON)
|
|
390
|
+
if err != nil {
|
|
391
|
+
return nil, fmt.Errorf("compress payload: %w", err)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
documentID, err := uuid.NewRandom()
|
|
395
|
+
if err != nil {
|
|
396
|
+
return nil, fmt.Errorf("generate document_id: %w", err)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
attributes := map[string]string{
|
|
400
|
+
"document_namespace": g.AppID,
|
|
401
|
+
"document_type": "{{ ping }}",
|
|
402
|
+
"document_version": "1",
|
|
403
|
+
"document_id": documentID.String(),
|
|
404
|
+
}
|
|
405
|
+
// Skip empty optional attributes; the decoder treats missing and empty
|
|
406
|
+
// the same, and Pub/Sub charges for attribute bytes. The publisher does
|
|
407
|
+
// not set submission_timestamp; the decoder stamps it from publishTime.
|
|
408
|
+
if requestInfo.UserAgent != "" {
|
|
409
|
+
attributes["user_agent"] = requestInfo.UserAgent
|
|
410
|
+
}
|
|
411
|
+
if requestInfo.IpAddress != "" {
|
|
412
|
+
attributes["x_forwarded_for"] = requestInfo.IpAddress
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return &pubsub.Message{
|
|
416
|
+
Data: compressed,
|
|
417
|
+
Attributes: attributes,
|
|
418
|
+
}, nil
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Build{{ ping|ping_type_name }}MessageWithoutUserInfo constructs a Pub/Sub
|
|
422
|
+
// message carrying the given `{{ ping }}` ping with no request-derived
|
|
423
|
+
// attributes.
|
|
424
|
+
func (g GleanEventsBuilder) Build{{ ping|ping_type_name }}MessageWithoutUserInfo(
|
|
425
|
+
params {{ ping|ping_type_name }},
|
|
426
|
+
) (*pubsub.Message, error) {
|
|
427
|
+
return g.Build{{ ping|ping_type_name }}Message(defaultRequestInfo, params)
|
|
428
|
+
}
|
|
429
|
+
{% else %}
|
|
268
430
|
// Record and submit `{{ ping }}` ping
|
|
269
431
|
func (g GleanEventsLogger) Record{{ ping|ping_type_name }}(
|
|
270
432
|
requestInfo RequestInfo,
|
|
@@ -309,4 +471,5 @@ func (g GleanEventsLogger) Record{{ ping|ping_type_name}}WithoutUserInfo(
|
|
|
309
471
|
) error {
|
|
310
472
|
return g.Record{{ ping|ping_type_name }}(defaultRequestInfo, params)
|
|
311
473
|
}
|
|
474
|
+
{% endif %}
|
|
312
475
|
{% endfor %}
|
|
@@ -57,7 +57,8 @@ class Outputter:
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
OUTPUTTERS = {
|
|
60
|
-
"go_server": Outputter(go_server.
|
|
60
|
+
"go_server": Outputter(go_server.output_go_logger, []),
|
|
61
|
+
"go_server_pubsub": Outputter(go_server.output_go_pubsub, []),
|
|
61
62
|
"javascript": Outputter(javascript.output_javascript, []),
|
|
62
63
|
"typescript": Outputter(javascript.output_typescript, []),
|
|
63
64
|
"javascript_server": Outputter(javascript_server.output_javascript, []),
|
|
@@ -298,14 +298,14 @@ def fetch_remote_url(url: str, cache: bool = True):
|
|
|
298
298
|
|
|
299
299
|
if cache:
|
|
300
300
|
cache_dir = platformdirs.user_cache_dir("glean_parser", "mozilla")
|
|
301
|
-
with diskcache.Cache(cache_dir) as dc:
|
|
301
|
+
with diskcache.Cache(cache_dir, disk=diskcache.JSONDisk) as dc:
|
|
302
302
|
if key in dc:
|
|
303
303
|
return dc[key]
|
|
304
304
|
|
|
305
|
-
contents
|
|
305
|
+
contents = urllib.request.urlopen(url).read().decode("utf-8")
|
|
306
306
|
|
|
307
307
|
if cache:
|
|
308
|
-
with diskcache.Cache(cache_dir) as dc:
|
|
308
|
+
with diskcache.Cache(cache_dir, disk=diskcache.JSONDisk) as dc:
|
|
309
309
|
dc[key] = contents
|
|
310
310
|
|
|
311
311
|
return contents
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/base64"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"fmt"
|
|
8
|
+
"glean/glean"
|
|
9
|
+
"os"
|
|
10
|
+
"time"
|
|
11
|
+
/* IMPORTS */
|
|
12
|
+
|
|
13
|
+
pubsub "cloud.google.com/go/pubsub/v2"
|
|
14
|
+
"cloud.google.com/go/pubsub/v2/apiv1/pubsubpb"
|
|
15
|
+
"cloud.google.com/go/pubsub/v2/pstest"
|
|
16
|
+
"google.golang.org/api/option"
|
|
17
|
+
"google.golang.org/grpc"
|
|
18
|
+
"google.golang.org/grpc/credentials/insecure"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
func main() {
|
|
22
|
+
ctx := context.Background()
|
|
23
|
+
|
|
24
|
+
// In-process Pub/Sub fake. The Go pubsub client picks up PUBSUB_EMULATOR_HOST
|
|
25
|
+
// automatically; pstest exposes its bound address via srv.Addr.
|
|
26
|
+
srv := pstest.NewServer()
|
|
27
|
+
defer srv.Close()
|
|
28
|
+
os.Setenv("PUBSUB_EMULATOR_HOST", srv.Addr)
|
|
29
|
+
|
|
30
|
+
// Pre-create the topic on the fake server. Use a transient client; the
|
|
31
|
+
// test code below opens its own client to mirror normal app usage.
|
|
32
|
+
setupConn, err := grpc.Dial(srv.Addr,
|
|
33
|
+
grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
34
|
+
if err != nil {
|
|
35
|
+
fmt.Fprintln(os.Stderr, "dial:", err)
|
|
36
|
+
os.Exit(1)
|
|
37
|
+
}
|
|
38
|
+
setupClient, err := pubsub.NewClient(ctx, "test-project", option.WithGRPCConn(setupConn))
|
|
39
|
+
if err != nil {
|
|
40
|
+
fmt.Fprintln(os.Stderr, "setup-client:", err)
|
|
41
|
+
os.Exit(1)
|
|
42
|
+
}
|
|
43
|
+
if _, err := setupClient.TopicAdminClient.CreateTopic(ctx, &pubsubpb.Topic{
|
|
44
|
+
Name: "projects/test-project/topics/test-topic",
|
|
45
|
+
}); err != nil {
|
|
46
|
+
fmt.Fprintln(os.Stderr, "create-topic:", err)
|
|
47
|
+
os.Exit(1)
|
|
48
|
+
}
|
|
49
|
+
setupClient.Close()
|
|
50
|
+
setupConn.Close()
|
|
51
|
+
|
|
52
|
+
// Client + publisher used by the test snippet to publish.
|
|
53
|
+
client, err := pubsub.NewClient(ctx, "test-project")
|
|
54
|
+
if err != nil {
|
|
55
|
+
fmt.Fprintln(os.Stderr, "client:", err)
|
|
56
|
+
os.Exit(1)
|
|
57
|
+
}
|
|
58
|
+
topic := client.Publisher("test-topic")
|
|
59
|
+
|
|
60
|
+
builder := glean.GleanEventsBuilder{
|
|
61
|
+
AppID: "glean.test",
|
|
62
|
+
AppDisplayVersion: "0.0.1",
|
|
63
|
+
AppChannel: "nightly",
|
|
64
|
+
}
|
|
65
|
+
_ = builder // suppress unused warning if the snippet does not reference it
|
|
66
|
+
|
|
67
|
+
// The injected snippet must assign `msg, err` by calling a builder
|
|
68
|
+
// Build<Ping>Message method; the harness owns publish + result handling.
|
|
69
|
+
/* CODE */
|
|
70
|
+
|
|
71
|
+
if err != nil {
|
|
72
|
+
fmt.Fprintln(os.Stderr, "build:", err)
|
|
73
|
+
os.Exit(1)
|
|
74
|
+
}
|
|
75
|
+
result := topic.Publish(ctx, msg)
|
|
76
|
+
if _, err := result.Get(ctx); err != nil {
|
|
77
|
+
fmt.Fprintln(os.Stderr, "publish:", err)
|
|
78
|
+
os.Exit(1)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Drain whatever was queued, then release the client. topic.Stop
|
|
82
|
+
// blocks until the batcher flushes; client.Close releases the gRPC conn.
|
|
83
|
+
// result.Get above already confirmed the server acked the message, so it
|
|
84
|
+
// is durably in pstest by the time we read srv.Messages below.
|
|
85
|
+
topic.Stop()
|
|
86
|
+
if err := client.Close(); err != nil {
|
|
87
|
+
fmt.Fprintln(os.Stderr, "client close:", err)
|
|
88
|
+
os.Exit(1)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
msgs := srv.Messages()
|
|
92
|
+
out := make([]map[string]interface{}, 0, len(msgs))
|
|
93
|
+
for _, m := range msgs {
|
|
94
|
+
out = append(out, map[string]interface{}{
|
|
95
|
+
"data": base64.StdEncoding.EncodeToString(m.Data),
|
|
96
|
+
"attributes": m.Attributes,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
if err := json.NewEncoder(os.Stdout).Encode(out); err != nil {
|
|
100
|
+
fmt.Fprintln(os.Stderr, "encode:", err)
|
|
101
|
+
os.Exit(1)
|
|
102
|
+
}
|
|
103
|
+
}
|