feat: otlp allow not translating UTF8 characters#14974
feat: otlp allow not translating UTF8 characters#14974jmichalek132 wants to merge 7 commits intoprometheus:otlp-fix-sanitizafrom
Conversation
2cda163 to
67b9038
Compare
config/config.go
Outdated
| // OTLPConfig is the configuration for writing to the OTLP endpoint. | ||
| type OTLPConfig struct { | ||
| PromoteResourceAttributes []string `yaml:"promote_resource_attributes,omitempty"` | ||
| TranslateDots bool `default:"true" yaml:"translate_dots"` |
There was a problem hiding this comment.
as said in the meeting, let's make this an Enum so that there is future-proofing for other possible translation modes. Something like "NameConversionMode" with values of "NoConversion" and "ClassicPrometheusConverstion"
|
@jmichalek132 as discussed in a few calls this week, let me take over this PR so you can focus on your mentorship project :) @aknuds1 @ywwg, I'm wondering what we want to accomplish here. There are two things on my mind:
Which of those problems are we solving here? Depending on the answer I'm thinking about two different API for the config file: otlp:
otlp_to_prometheus_translation: NoTranslation (default) | ClassicPrometheusTranslation (UTF8 to underscore + unit suffixes)otlp:
add_unit_suffixes: false (default) | true #Probably not being worked on in this PR
utf8_to_underscore: false (default) | true |
For reference linking the consensus from the Prometheus in person dev summit on this topic CONSENSUS: We recommend keeping the metric names from OTel SDKs as unchanged as possible when converting to Prometheus samples as the default. |
@ArthurSens @jmichalek132 @ywwg given the consensus in the latest dev summit ("We want to explore ways to do type and unit annotations in PromQL"), we've concluded that we need a way to ensure time series are identified through type and unit, right? So we avoid collisions between metrics that are named the same, but have different types/units. |
|
I think the PR needs to define properly which problem is being solved. Jumping into a solution isn't the best approach. |
Yes exactly! I was hoping we could come to an agreement before I continue with the PR :) |
Agree, we need to decide on a solution to the problem laid out in OpenTelemetry → Prometheus: type+unit suffixes, right? The agreed upon solution would have to be well defined, which I think we haven't got to yet. |
If that's a requirement for the work here, I'm afraid this PR will have to wait until Prometheus 4.0 :/ I don't think we'll manage to implement any of those proposals before 3.0 (scheduled for november this year as far as I could understand). So I was hoping we could find a middle ground: let OTel users have what they want, even if not the best solution. If we release 3.0 with the current behavior (doing translations), changing the default to no translation would be a breaking change. Could we release 3.0 with no translation, as far as it's well documented in the OTLP guide that name colisions are possible and people should opt-in to |
|
If the team decides that we really want to wait for proper unit/type handling, I guess I could continue the PR... just changing the default to |
Could you start a discussion with @gouthamve in CNCF Slack on what his current stance is wrt. dropping OTel metric name translation? I kind of forgot what he last communicated. |
|
My sense from the Otel folks is that the conversion to underscores is a pain point right now, so I'd prefer to find a way to allow the default to be no-translation. If my understanding is correct the issue of same-name-different-type has only been hypothetical so far. I think the benefit to the otel community of having names pass through unchanged outweighs the unproven possibility of collisions. Perhaps it would be sufficient to add a note in the documentation explaining "by default no translation is done to Otel metric names. If there are multiple metrics with the same name but different types or units, these metrics will collide when queried in Prometheus. In this situation, administrators should set the translation mode to ClassicPrometheusTranslation to avoid the collisions". As for config choices:
if we do this, does add_unit_suffixes always append with an underscore? imho I think it might be fine to use a dot if the user has otherwise not specified underscore conversion, e.g.: "http.status" → "http.status.total". Because of ambiguities like this, I think I'm more in favor of the enum approach:
|
|
@ywwg @ArthurSens in the latest dev summit, @dashpole noted the following about @gouthamve's latest thinking:
Doesn't this indicate we need a solution that avoids collisions (as our current solution does, through suffixes)? |
For the perfect scenario, yes! I was just hoping that we could release something that allows no translation before 3.0, even knowing that name collisions is a possibility, while we keep working on a long term solution that will take a few months to release. Would you be ok if we continue this PR but change the default behavior to keep translating things? |
I'm kind of agnostic, I'm just trying to figure out what is the actual consensus in this sense. I've asked @gouthamve in CNCF Slack. I primarily want to avoid adding changes we end up not wanting. @dashpole's note from the last summit gives the impression we don't want a translation mode that can lead to collisions. |
I agree with @ywwg suggestions.
|
3af6071 to
d0a868c
Compare
d0a868c to
4e03883
Compare
|
I've updated the PR to not translate UTF8 characters by default. I'm also not touching unit/type suffixes in any way! I'm happy to change the PR again to allow not adding unit/type suffixes, but I don't want to block the UTF8 translation just because we can't find an agreement on unit/type handling |
4e03883 to
4796f57
Compare
| type translationStrategyOption string | ||
|
|
||
| var ( | ||
| // NoUTF8EscapingWithSuffixes will keep UTF-8 characters as they are, units and type suffixes will still be added. |
There was a problem hiding this comment.
I thought the two options were going to be "no alteration at all" and "underscoreescapingwithsuffixes"?
There was a problem hiding this comment.
(is this because type/unit is differentiating?)
context for why I thought we were also going to have a no-translation mode with caveats |
|
Per our discussion, because units are differentiating and prometheus does not support otel unit metadata well, it makes sense for prom's otel endpoint to add suffixes in all available modes. In the future when prom does support unit metadata, we can revisit the enum and add a mode to pass names unaltered. |
| } | ||
|
|
||
| // Replace all non-alphanumeric runes with underscores | ||
| label = strings.Map(sanitizeRune, label) |
There was a problem hiding this comment.
Why do you no longer use this method (for the non-allowUTF8 case), and why do call model.EscapeName(label, model.UnderscoreEscaping) (assuming this is supposed to have the same effect?) after potentially prefixing with key?
I basically don't understand why we would change the old mode (i.e. where we convert UTF8 chars to underscores).
There was a problem hiding this comment.
I'm using model.EscapeName as a suggestion from @ywwg!
The previous function isn't working as expected, it translates characters to underscores if unicode.IsLetter(r) || unicode.IsDigit(r) is not true.
This is problematic because:
- Foreign letters like russian, japanese, german complies with
unicode.IsLetter()and they are being replaced when they shouldn't - The character
:is valid even without UTF8 and is being replaced. See otlp writer: colons (':') in metric names replaced with underscores ('_') #15199
It looks impractical to concatenate every foreign character in the sanitizeRunes conditional, so I'm just using the same function used in prometheus/common like owen suggested :)
There was a problem hiding this comment.
I'm using model.EscapeName as a suggestion from @ywwg!
If we are making changes unrelated to the PR's topic (add un-escaped UTF8 mode), it should be a separate PR. I want to see explicitly what the effect is from using model.EscapeName instead.
There was a problem hiding this comment.
This PR was now rebased on top of #15249, so we need that one merged first
| for _, allowUTF8 := range []bool{true, false} { | ||
| if allowUTF8 { | ||
| require.Equal(t, "unsupported.metric.temperature_°F", normalizeName(createGauge("unsupported.metric.temperature", "°F"), "", allowUTF8)) | ||
| require.Equal(t, "unsupported.metric.weird_+=.:,!* & #", normalizeName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), "", allowUTF8)) | ||
| require.Equal(t, "unsupported.metric.redundant___test $_per_°C", normalizeName(createGauge("unsupported.metric.redundant", "__test $/°C"), "", allowUTF8)) | ||
| } else { | ||
| require.Equal(t, "unsupported_metric_temperature_F", normalizeName(createGauge("unsupported.metric.temperature", "°F"), "", allowUTF8)) | ||
| require.Equal(t, "unsupported_metric_weird", normalizeName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), "", allowUTF8)) | ||
| require.Equal(t, "unsupported_metric_redundant_test_per_C", normalizeName(createGauge("unsupported.metric.redundant", "__test $/°C"), "", allowUTF8)) | ||
| } | ||
| } |
There was a problem hiding this comment.
This shouldn't be a loop.
| for _, allowUTF8 := range []bool{true, false} { | |
| if allowUTF8 { | |
| require.Equal(t, "unsupported.metric.temperature_°F", normalizeName(createGauge("unsupported.metric.temperature", "°F"), "", allowUTF8)) | |
| require.Equal(t, "unsupported.metric.weird_+=.:,!* & #", normalizeName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), "", allowUTF8)) | |
| require.Equal(t, "unsupported.metric.redundant___test $_per_°C", normalizeName(createGauge("unsupported.metric.redundant", "__test $/°C"), "", allowUTF8)) | |
| } else { | |
| require.Equal(t, "unsupported_metric_temperature_F", normalizeName(createGauge("unsupported.metric.temperature", "°F"), "", allowUTF8)) | |
| require.Equal(t, "unsupported_metric_weird", normalizeName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), "", allowUTF8)) | |
| require.Equal(t, "unsupported_metric_redundant_test_per_C", normalizeName(createGauge("unsupported.metric.redundant", "__test $/°C"), "", allowUTF8)) | |
| } | |
| } | |
| t.Run("allow UTF8", func(t *testing.T) { | |
| require.Equal(t, "unsupported.metric.temperature_°F", normalizeName(createGauge("unsupported.metric.temperature", "°F"), "", allowUTF8)) | |
| require.Equal(t, "unsupported.metric.weird_+=.:,!* & #", normalizeName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), "", allowUTF8)) | |
| require.Equal(t, "unsupported.metric.redundant___test $_per_°C", normalizeName(createGauge("unsupported.metric.redundant", "__test $/°C"), "", allowUTF8)) | |
| }) | |
| t.Run("disallow UTF8", func(t *testing.T) { | |
| require.Equal(t, "unsupported_metric_temperature_F", normalizeName(createGauge("unsupported.metric.temperature", "°F"), "", allowUTF8)) | |
| require.Equal(t, "unsupported_metric_weird", normalizeName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), "", allowUTF8)) | |
| require.Equal(t, "unsupported_metric_redundant_test_per_C", normalizeName(createGauge("unsupported.metric.redundant", "__test $/°C"), "", allowUTF8)) | |
| }) |
| annots, err := converter.FromMetrics(r.Context(), req.Metrics(), otlptranslator.Settings{ | ||
| AddMetricSuffixes: true, | ||
| PromoteResourceAttributes: otlpCfg.PromoteResourceAttributes, | ||
| AllowUTF8: (model.NameValidationScheme == model.UTF8Validation) && (otlpCfg.TranslationStrategy == config.NoUTF8EscapingWithSuffixes), |
There was a problem hiding this comment.
It concerns me that if model.NameValidationScheme is not model.UTF8Validation, the NoUTF8EscapingWithSuffixes translation strategy is silently ignored. How about we instead log a warning if model.NameValidationScheme != model.UTF8Validation && otlpCfg.TranslationStrategy == config.NoUTF8EscapingWithSuffixes, while also setting AllowUTF8: false?
There was a problem hiding this comment.
Good point!
I think we can warn that while loading the configuration
aknuds1
left a comment
There was a problem hiding this comment.
In retrospect, I don't think CleanUpString should change. Just don't use it when allowing UTF8.
Signed-off-by: Arthur Silva Sens <[email protected]>
Signed-off-by: Arthur Silva Sens <[email protected]>
Signed-off-by: Juraj Michalek <[email protected]>
Signed-off-by: Juraj Michalek <[email protected]>
Signed-off-by: Juraj Michalek <[email protected]>
Signed-off-by: Juraj Michalek <[email protected]>
Signed-off-by: Arthur Silva Sens <[email protected]>
edf84a8 to
e1158ca
Compare
8df09f3 to
ea06f1a
Compare
|
Folks, I've messed up my commits here 😓. Let's get this one merged first and I'll figure out what to do here later |
|
Lol ok, after merging the chained PR the branch got automatically deleted and messed up this PR. I'll open another one, sorry everyone |
#14990