Skip to content

Commit

Permalink
extend go_server outputter to support custom pings (#758)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmueller authored Oct 21, 2024
1 parent 303b23c commit c32ce04
Show file tree
Hide file tree
Showing 14 changed files with 1,155 additions and 187 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Bugfix: Remove unused keyword argument from exception ([#755](https://github.com/mozilla/glean_parser/pull/755))
- Add Go log outputter support for custom pings (`go_server`) ([#758](https://github.com/mozilla/glean_parser/pull/758))

## 15.0.1

Expand Down
56 changes: 31 additions & 25 deletions glean_parser/go_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
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
Generated code creates two methods for each ping (`RecordPingX` and `RecordPingXWithoutUserInfo`)
that are used for submitting (logging) them.
If pings have `event` metrics assigned, they can be passed to these methods.
"""

from collections import defaultdict
Expand All @@ -32,11 +32,19 @@

# 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", "datetime"]
SUPPORTED_METRIC_TYPES = ["string", "quantity", "event", "datetime", "boolean"]


def generate_ping_type_name(ping_name: str) -> str:
return f"{util.Camelize(ping_name)}Ping"


def generate_ping_events_type_name(ping_name: str) -> str:
return f"{util.Camelize(ping_name)}PingEvent"


def generate_event_type_name(metric: metrics.Metric) -> str:
return f"Event{util.Camelize(metric.category)}{util.Camelize(metric.name)}"
return f"{util.Camelize(metric.category)}{util.Camelize(metric.name)}Event"


def generate_metric_name(metric: metrics.Metric) -> str:
Expand Down Expand Up @@ -87,6 +95,8 @@ def output_go(
template = util.get_jinja2_template(
"go_server.jinja2",
filters=(
("ping_type_name", generate_ping_type_name),
("ping_events_type_name", generate_ping_events_type_name),
("event_type_name", generate_event_type_name),
("event_extra_name", generate_extra_name),
("metric_name", generate_metric_name),
Expand All @@ -96,14 +106,8 @@ def output_go(
),
)

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
# unique list of event metrics used in any ping
event_metrics: List[metrics.Metric] = []

# Go through all metrics in objs and build a map of
# ping->list of metric categories->list of metrics
Expand All @@ -120,29 +124,31 @@ def output_go(
+ " 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
if metric.type == "event" and metric not in event_metrics:
event_metrics.append(metric)

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")
PING_METRIC_ERROR_MSG = (
" Server-side environment is simplified and this"
+ " parser doesn't generate individual metric files. Make sure to pass all"
+ " your ping and metric definitions in a single invocation of the parser."
)
if not ping_to_metrics:
print("❌ No pings with metrics found." + PING_METRIC_ERROR_MSG)
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"]
parser_version=__version__,
pings=ping_to_metrics,
events=event_metrics
)
)
150 changes: 94 additions & 56 deletions glean_parser/templates/go_server.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (g GleanEventsLogger) createPing(documentType string, config RequestInfo, p
}
}

// method called by each event method.
// method called by each ping-specific record method.
// construct the ping, wrap it in the envelope, and print to stdout
func (g GleanEventsLogger) record(
documentType string,
Expand Down Expand Up @@ -159,71 +159,109 @@ func (g GleanEventsLogger) record(
}
fmt.Println(string(envelopeJson))
}
{# if any ping has an event metric, create methods and types for them #}
{% if events %}

func newGleanEvent(category, name string, extra map[string]string) gleanEvent {
return gleanEvent{
Category: category,
Name: name,
Timestamp: time.Now().UnixMilli(),
Extra: extra,
}
}
{# each event has a type and method to create a gleanEvent #}
{% for event in events %}

{% 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 }},
{% for extra, metadata in event.extra_keys.items() %}
{{ extra|event_extra_name }} {{ metadata.type|go_metric_type }} // {{ metadata.description|clean_string }}
{% endfor %}
}

func (e {{ event|event_type_name }}) gleanEvent() gleanEvent {
return newGleanEvent(
"{{ event.category }}",
"{{ event.name }}",
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", e.{{ extra|event_extra_name }}),
{% elif metadata.type == 'quantity' %}
"{{ extra }}": fmt.Sprintf("%d", e.{{ extra|event_extra_name }}),
{% else %}
"{{ extra }}": e.{{ extra|event_extra_name }},
{% endif %}
{% endfor %}
},
)
}
{% endfor %}
{% endif %}
{# struct & methods for submitting pings #}
{% for ping, metrics_by_type in pings.items() %}
{% if metrics_by_type['event'] %}

{# interface that eligible events for this ping will implement #}
type {{ ping|ping_events_type_name }} interface {
is{{ping|ping_events_type_name }}()
gleanEvent() gleanEvent
}

{# create functions for valid events for this ping #}
{% for metric in metrics_by_type['event'] %}
func (e {{ metric|event_type_name}}) is{{ ping|ping_events_type_name }}() {}
{% endfor %}
{% endif %}

type {{ ping|ping_type_name }} struct {
{% for metric_type, metrics in metrics_by_type.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 %}
{% if metrics_by_type['event'] %}
Event {{ ping|ping_events_type_name }} // valid event for this ping
{% endif %}
}

// Record and submit `{{ ping }}` ping
func (g GleanEventsLogger) Record{{ ping|ping_type_name }}(
requestInfo RequestInfo,
params {{ ping|ping_type_name }},
) {
var metrics = metrics{
{% for metric_type, metrics in events_ping.items() %}
{% if metric_type != 'event' %}
"{{ metric_type }}": {
{% for metric in metrics %}
{% if metric_type =='datetime' %}
var metrics = metrics{
{% for metric_type, metrics in metrics_by_type.items() %}
{% if metric_type != 'event' %}
"{{ metric_type }}": {
{% for metric in metrics %}
{% if metric_type == 'datetime' %}
"{{ metric|metric_name }}": params.{{ metric|metric_argument_name }}.Format("2006-01-02T15:04:05.000Z"),
{% else %}
{% else %}
"{{ metric|metric_name }}": params.{{ metric|metric_argument_name }},
{% endif %}
{% endfor %}
},
{% endif %}
{% endfor %}
},
{% endif %}
{% endfor %}
}

events := []gleanEvent{}
{% if metrics_by_type['event'] %}
if params.Event != nil {
events = append(events, params.Event.gleanEvent())
}
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)
{% endif %}
g.record("{{ ping }}", 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 }},
// Record and submit `{{ ping }}` ping omitting user request info
func (g GleanEventsLogger) Record{{ ping|ping_type_name}}WithoutUserInfo(
params {{ ping|ping_type_name}},
) {
g.Record{{ event|event_type_name }}(defaultRequestInfo, params)
g.Record{{ ping|ping_type_name }}(defaultRequestInfo, params)
}

{% endfor %}
95 changes: 95 additions & 0 deletions tests/data/go_server_custom_ping_only_metrics.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Any copyright is dedicated to the Public Domain.
# https://creativecommons.org/publicdomain/zero/1.0/

---
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0

metric:
name:
type: string
description: |
Test string metric
lifetime: application
send_in_pings:
- server-telemetry-scenario-one
notification_emails:
- [email protected]
bugs:
- TBD
data_reviews:
- TBD
expires: never

request_count:
type: quantity
unit: ad
description: |
Test quantity metric
lifetime: application
send_in_pings:
- server-telemetry-scenario-one
notification_emails:
- [email protected]
bugs:
- TBD
data_reviews:
- TBD
expires: never

request_datetime:
type: datetime
description: |
Test datetime metric
lifetime: application
send_in_pings:
- server-telemetry-scenario-one
notification_emails:
- [email protected]
bugs:
- TBD
data_reviews:
- TBD
expires: never

request_bool:
type: boolean
description: >
boolean
lifetime: application
send_in_pings:
- server-telemetry-scenario-one
notification_emails:
- [email protected]
bugs:
- TBD
data_reviews:
- TBD
expires: never

backend:
special_event:
type: event
description: >
special event
send_in_pings:
- server-telemetry-scenario-one
bugs:
- TBD
data_reviews:
- TBD
notification_emails:
- [email protected]
expires: never
extra_keys:
event_field_string:
description: >
A string extra field
type: string
event_field_quantity:
description: >
A quantity extra field
type: quantity
event_field_bool:
description: >
A boolean extra field
type: boolean
Loading

0 comments on commit c32ce04

Please sign in to comment.