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.
Files changed (153) hide show
  1. {glean_parser-19.1.0 → glean_parser-19.2.0}/PKG-INFO +1 -1
  2. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/go_server.py +40 -3
  3. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/go_server.jinja2 +163 -0
  4. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/translate.py +2 -1
  5. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/util.py +3 -3
  6. glean_parser-19.2.0/tests/test-go/test_publisher.go.tmpl +103 -0
  7. glean_parser-19.2.0/tests/test_go_server_pubsub.py +344 -0
  8. {glean_parser-19.1.0 → glean_parser-19.2.0}/.gitignore +0 -0
  9. {glean_parser-19.1.0 → glean_parser-19.2.0}/AUTHORS.md +0 -0
  10. {glean_parser-19.1.0 → glean_parser-19.2.0}/LICENSE +0 -0
  11. {glean_parser-19.1.0 → glean_parser-19.2.0}/README.md +0 -0
  12. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/__init__.py +0 -0
  13. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/__main__.py +0 -0
  14. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/data_review.py +0 -0
  15. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/javascript.py +0 -0
  16. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/javascript_server.py +0 -0
  17. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/kotlin.py +0 -0
  18. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/lint.py +0 -0
  19. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/markdown.py +0 -0
  20. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/metrics.py +0 -0
  21. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/parser.py +0 -0
  22. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/pings.py +0 -0
  23. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/python_server.py +0 -0
  24. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/ruby_server.py +0 -0
  25. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/rust.py +0 -0
  26. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/rust_server.py +0 -0
  27. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/rust_sym.py +0 -0
  28. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/metrics.1-0-0.schema.yaml +0 -0
  29. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/metrics.2-0-0.schema.yaml +0 -0
  30. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/pings.1-0-0.schema.yaml +0 -0
  31. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/pings.2-0-0.schema.yaml +0 -0
  32. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/schemas/tags.1-0-0.schema.yaml +0 -0
  33. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/swift.py +0 -0
  34. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/tags.py +0 -0
  35. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/data_review.jinja2 +0 -0
  36. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/javascript.buildinfo.jinja2 +0 -0
  37. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/javascript.jinja2 +0 -0
  38. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/javascript_server.jinja2 +0 -0
  39. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/kotlin.buildinfo.jinja2 +0 -0
  40. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/kotlin.jinja2 +0 -0
  41. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/markdown.jinja2 +0 -0
  42. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/python_server.jinja2 +0 -0
  43. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/qmldir.jinja2 +0 -0
  44. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/ruby_server.jinja2 +0 -0
  45. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/rust.jinja2 +0 -0
  46. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/rust_server.jinja2 +0 -0
  47. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/rust_sym.jinja2 +0 -0
  48. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/templates/swift.jinja2 +0 -0
  49. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/translation_options.py +0 -0
  50. {glean_parser-19.1.0 → glean_parser-19.2.0}/glean_parser/validate_ping.py +0 -0
  51. {glean_parser-19.1.0 → glean_parser-19.2.0}/pyproject.toml +0 -0
  52. {glean_parser-19.1.0 → glean_parser-19.2.0}/server_telemetry/sdk-metrics-compat.yaml +0 -0
  53. {glean_parser-19.1.0 → glean_parser-19.2.0}/server_telemetry/server-side-pings.yaml +0 -0
  54. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/conftest.py +0 -0
  55. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/all_metrics.yaml +0 -0
  56. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/all_pings.yaml +0 -0
  57. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/attribution.yaml +0 -0
  58. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/bad_attribution.yamlx +0 -0
  59. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/bad_ping.yamlx +0 -0
  60. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/core.yaml +0 -0
  61. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/custom_ping_no_event_metrics.yaml +0 -0
  62. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/custom_ping_no_event_pings.yaml +0 -0
  63. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/dual_labeled.yaml +0 -0
  64. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/dual_labeled_invalid.yaml +0 -0
  65. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/duplicate_labeled.yaml +0 -0
  66. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/duplicate_send_in_ping.yaml +0 -0
  67. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/empty.yaml +0 -0
  68. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/event_key_ordering.yaml +0 -0
  69. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/events_data_sensitivity.yaml +0 -0
  70. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/events_with_types.yaml +0 -0
  71. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/fxa-server-metrics.yaml +0 -0
  72. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/fxa-server-pings.yaml +0 -0
  73. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_custom_ping_only_metrics.yaml +0 -0
  74. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_custom_ping_only_pings.yaml +0 -0
  75. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_events_and_custom_ping_metrics.yaml +0 -0
  76. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_events_and_custom_ping_pings.yaml +0 -0
  77. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_events_only_metrics.yaml +0 -0
  78. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_labeled_boolean_metrics.yaml +0 -0
  79. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/go_server_metrics_unsupported.yaml +0 -0
  80. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/invalid-ping-names.yaml +0 -0
  81. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/invalid.yamlx +0 -0
  82. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/jwe.yaml +0 -0
  83. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/metric-with-tags.yaml +0 -0
  84. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/mixed-expirations.yaml +0 -0
  85. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/name_too_similar.yaml +0 -0
  86. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/object.yaml +0 -0
  87. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/old_event_api.yamlx +0 -0
  88. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/ordering.yaml +0 -0
  89. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/pings.yaml +0 -0
  90. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/python_server_metrics_unsupported.yaml +0 -0
  91. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rate.yaml +0 -0
  92. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/redefined_category.yamlx +0 -0
  93. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/redefined_metric.yamlx +0 -0
  94. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/redefined_ping.yamlx +0 -0
  95. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/reserved_categories.yamlx +0 -0
  96. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/ruby_server_metrics_unsupported.yaml +0 -0
  97. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/ruby_server_pings_unsupported.yaml +0 -0
  98. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_custom_ping_only_metrics.yaml +0 -0
  99. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_custom_ping_only_pings.yaml +0 -0
  100. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_events_and_custom_ping_metrics.yaml +0 -0
  101. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_events_and_custom_ping_pings.yaml +0 -0
  102. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_events_only_metrics.yaml +0 -0
  103. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/rust_server_metrics_unsupported.yaml +0 -0
  104. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/same_name_different_category.yaml +0 -0
  105. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/schema-violation.yaml +0 -0
  106. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/send_if_empty_with_metrics.yaml +0 -0
  107. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_custom_ping_only_compare.go +0 -0
  108. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_custom_ping_only_compare.rs +0 -0
  109. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_and_custom_ping_compare.go +0 -0
  110. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_and_custom_ping_compare.rs +0 -0
  111. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_compare.rb +0 -0
  112. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_only_compare.go +0 -0
  113. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_events_only_compare.rs +0 -0
  114. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_metrics_no_events_no_pings.yaml +0 -0
  115. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_metrics_with_event.yaml +0 -0
  116. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/server_pings.yaml +0 -0
  117. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/single_labeled.yaml +0 -0
  118. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/smaller.yaml +0 -0
  119. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/tags.yaml +0 -0
  120. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/telemetry_mirror.yaml +0 -0
  121. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/text.yaml +0 -0
  122. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/text_invalid.yaml +0 -0
  123. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/unknown_ping_used.yaml +0 -0
  124. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/wrong_key.yamlx +0 -0
  125. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/data/yaml_nits.yamlx +0 -0
  126. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/detekt.yml +0 -0
  127. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-go/test.go.tmpl +0 -0
  128. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-js/package.json +0 -0
  129. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-js/test.js.tmpl +0 -0
  130. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-py/test.py +0 -0
  131. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-rb/test.rb.tmpl +0 -0
  132. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test-rs/test.rs.tmpl +0 -0
  133. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_cli.py +0 -0
  134. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_go_server.py +0 -0
  135. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_javascript.py +0 -0
  136. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_javascript_server.py +0 -0
  137. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_kotlin.py +0 -0
  138. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_lint.py +0 -0
  139. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_markdown.py +0 -0
  140. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_metrics.py +0 -0
  141. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_parser.py +0 -0
  142. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_pings.py +0 -0
  143. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_python_server.py +0 -0
  144. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_ruby_server.py +0 -0
  145. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_rust.py +0 -0
  146. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_rust_server.py +0 -0
  147. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_rust_sym.py +0 -0
  148. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_swift.py +0 -0
  149. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_tags.py +0 -0
  150. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_translate.py +0 -0
  151. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_utils.py +0 -0
  152. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/test_validate_ping.py +0 -0
  153. {glean_parser-19.1.0 → glean_parser-19.2.0}/tests/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glean-parser
3
- Version: 19.1.0
3
+ Version: 19.2.0
4
4
  Summary: Parser tools for Mozilla's Glean telemetry
5
5
  Project-URL: Homepage, https://mozilla.github.io/glean
6
6
  Project-URL: Repository, https://github.com/mozilla/glean_parser
@@ -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 (logging) them.
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, output_dir: Path, options: Optional[Dict[str, Any]]
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.output_go, []),
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: str = urllib.request.urlopen(url).read()
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
+ }