diff --git a/docs/user/reference/metrics/text.md b/docs/user/reference/metrics/text.md
index 9f279130f..6493f49bd 100644
--- a/docs/user/reference/metrics/text.md
+++ b/docs/user/reference/metrics/text.md
@@ -18,13 +18,54 @@ Sets a text metric to a specific value.
{{#include ../../../shared/tab_header.md}}
-
-
-
{{#include ../../../shared/tab_footer.md}}
@@ -58,17 +97,60 @@ article.content.set(extractedText);
### `testGetValue`
-Gets the recorded value for a given text metric.
+Gets the recorded value for a given text metric.
+Returns the string if data is stored.
+Returns `null` if no data is stored.
{{#include ../../../shared/tab_header.md}}
-
+
-
-
{{#include ../../../shared/tab_footer.md}}
@@ -92,13 +172,74 @@ Gets the number of errors recorded for a given text metric.
{{#include ../../../shared/tab_header.md}}
-
+
-
+```Kotlin
+import org.mozilla.yourApplication.GleanMetrics.Article
-
+// Was the string truncated, and an error reported?
+assertEquals(
+ 0,
+ Article.content.testGetNumRecordedErrors(ErrorType.INVALID_OVERFLOW)
+)
+```
-
+
+
+
+
+```Kotlin
+import org.mozilla.yourApplication.GleanMetrics.Article;
+
+// Was the string truncated, and an error reported?
+assertEquals(
+ 0,
+ Article.content.testGetNumRecordedErrors(ErrorType.INVALID_OVERFLOW)
+);
+```
+
+
+
+
+
+```Swift
+// Was the string truncated, and an error reported?
+XCTAssertEqual(0, Article.content.testGetNumRecordedErrors(.invalidOverflow))
+```
+
+
+
+
+
+```Python
+from glean import load_metrics
+metrics = load_metrics("metrics.yaml")
+
+# Was the string truncated, and an error reported?
+assert 0 == metrics.article.content.test_get_num_recorded_errors(
+ ErrorType.INVALID_OVERFLOW
+)
+```
+
+
+
+
+
+```Rust
+use glean::ErrorType;
+use glean_metrics::article;
+
+// Was the string truncated, and an error reported?
+assert_eq!(
+ 0,
+ article::content.test_get_num_recorded_errors(
+ ErrorType::InvalidOverflow,
+ None
+ )
+);
+```
+
+
@@ -114,8 +255,6 @@ assert.strictEqual(
-
-
{{#include ../../../shared/tab_footer.md}}
diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/private/TextMetricType.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/TextMetricType.kt
new file mode 100644
index 000000000..03f61911d
--- /dev/null
+++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/TextMetricType.kt
@@ -0,0 +1,16 @@
+/* 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/. */
+
+package mozilla.telemetry.glean.private
+
+/**
+ * This implements the developer facing API for recording text metrics.
+ *
+ * Instances of this class type are automatically generated by the parsers at build time,
+ * allowing developers to record values that were previously registered in the metrics.yaml file.
+ *
+ * The text API only exposes the [set] method, which takes care of validating the input
+ * data and making sure that limits are enforced.
+ */
+typealias TextMetricType = mozilla.telemetry.glean.internal.TextMetric
diff --git a/glean-core/ios/Glean.xcodeproj/project.pbxproj b/glean-core/ios/Glean.xcodeproj/project.pbxproj
index 0030c95f2..4ebb6b5e4 100644
--- a/glean-core/ios/Glean.xcodeproj/project.pbxproj
+++ b/glean-core/ios/Glean.xcodeproj/project.pbxproj
@@ -56,6 +56,7 @@
BFFE18382350A5F50068D97B /* TimingDistributionMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFE18372350A5F50068D97B /* TimingDistributionMetric.swift */; };
BFFE183A2350A61F0068D97B /* TimingDistributionMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFE18392350A61F0068D97B /* TimingDistributionMetricTests.swift */; };
BFFE33AB232927C3005348FE /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFE33AA232927C3005348FE /* Utils.swift */; };
+ CD062129284110970006370D /* TextMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD062128284110970006370D /* TextMetric.swift */; };
CD0CADA427E216810015A997 /* glean.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD08C8527E21104007C8400 /* glean.swift */; };
CD0F7CC026F0F27900EDA6A4 /* UrlMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0F7CBF26F0F27900EDA6A4 /* UrlMetric.swift */; };
CD0F7CC226F0F28900EDA6A4 /* UrlMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0F7CC126F0F28900EDA6A4 /* UrlMetricTests.swift */; };
@@ -132,6 +133,7 @@
BFFE18372350A5F50068D97B /* TimingDistributionMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimingDistributionMetric.swift; sourceTree = ""; };
BFFE18392350A61F0068D97B /* TimingDistributionMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimingDistributionMetricTests.swift; sourceTree = ""; };
BFFE33AA232927C3005348FE /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; };
+ CD062128284110970006370D /* TextMetric.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextMetric.swift; sourceTree = ""; };
CD0F7CBF26F0F27900EDA6A4 /* UrlMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlMetric.swift; sourceTree = ""; };
CD0F7CC126F0F28900EDA6A4 /* UrlMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlMetricTests.swift; sourceTree = ""; };
CD387868271D9CD100C097D8 /* glean.udl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = glean.udl; path = ../../src/glean.udl; sourceTree = ""; };
@@ -314,6 +316,7 @@
BF30FDC3233260B500840607 /* TimespanMetric.swift */,
BFAED5072369751100DF293D /* StringListMetric.swift */,
BF89055E232BC213003CA2BA /* StringMetric.swift */,
+ CD062128284110970006370D /* TextMetric.swift */,
1FD4527423395B4500F4C7E8 /* UuidMetric.swift */,
CD0F7CBF26F0F27900EDA6A4 /* UrlMetric.swift */,
BF2E57042334B77D00364D92 /* EventMetric.swift */,
@@ -576,6 +579,7 @@
1F58920D23C7D615007D2D80 /* MetricsPingScheduler.swift in Sources */,
BF7CC0A62473F61C003B166D /* Metrics.swift in Sources */,
1FB70AEF23301C1D00C7CF09 /* Logger.swift in Sources */,
+ CD062129284110970006370D /* TextMetric.swift in Sources */,
1F6A8FF0233C049D007837D5 /* BooleanMetric.swift in Sources */,
1F6A8FF4233C0A91007837D5 /* DatetimeMetric.swift in Sources */,
AC06529C26E032E300D92D5E /* QuantityMetric.swift in Sources */,
diff --git a/glean-core/ios/Glean/Metrics/TextMetric.swift b/glean-core/ios/Glean/Metrics/TextMetric.swift
new file mode 100644
index 000000000..799f51daa
--- /dev/null
+++ b/glean-core/ios/Glean/Metrics/TextMetric.swift
@@ -0,0 +1,12 @@
+/* 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/. */
+
+/// This implements the developer facing API for recording text metrics.
+///
+/// Instances of this class type are automatically generated by the parsers at build time,
+/// allowing developers to record values that were previously registered in the metrics.yaml file.
+///
+/// The text API only exposes the `TextMetricType.set(_:)` method, which takes care of validating the input
+/// data and making sure that limits are enforced.
+public typealias TextMetricType = TextMetric
diff --git a/glean-core/src/glean.udl b/glean-core/src/glean.udl
index 03984c158..7a62b1784 100644
--- a/glean-core/src/glean.udl
+++ b/glean-core/src/glean.udl
@@ -537,3 +537,13 @@ interface NumeratorMetric {
i32 test_get_num_recorded_errors(ErrorType error, optional string? ping_name = null);
};
+
+interface TextMetric {
+ constructor(CommonMetricData meta);
+
+ void set(string value);
+
+ string? test_get_value(optional string? ping_name = null);
+
+ i32 test_get_num_recorded_errors(ErrorType error, optional string? ping_name = null);
+};
diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs
index fd7dbd1ef..10f7832fa 100644
--- a/glean-core/src/lib.rs
+++ b/glean-core/src/lib.rs
@@ -59,8 +59,8 @@ pub use crate::metrics::{
BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
DenominatorMetric, DistributionData, EventMetric, MemoryDistributionMetric, MemoryUnit,
NumeratorMetric, PingType, QuantityMetric, Rate, RateMetric, RecordedEvent, RecordedExperiment,
- StringListMetric, StringMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric,
- UrlMetric, UuidMetric,
+ StringListMetric, StringMetric, TextMetric, TimeUnit, TimerId, TimespanMetric,
+ TimingDistributionMetric, UrlMetric, UuidMetric,
};
pub use crate::upload::{PingRequest, PingUploadTask, UploadResult};
diff --git a/glean-core/src/lib_unit_tests.rs b/glean-core/src/lib_unit_tests.rs
index 6f0f8127c..0765a9b1f 100644
--- a/glean-core/src/lib_unit_tests.rs
+++ b/glean-core/src/lib_unit_tests.rs
@@ -402,6 +402,7 @@ fn correct_order() {
}
// One of every metric type. The values are arbitrary and don't matter.
+ let long_string = "0123456789".repeat(200);
let all_metrics = vec![
Boolean(false),
Counter(0),
@@ -418,6 +419,7 @@ fn correct_order() {
MemoryDistribution(Histogram::functional(2.0, 8.0)),
Jwe("eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ".into()),
Rate(0, 0),
+ Text(long_string),
];
for metric in all_metrics {
@@ -445,6 +447,7 @@ fn correct_order() {
Jwe(..) => assert_eq!(13, disc),
Rate(..) => assert_eq!(14, disc),
Url(..) => assert_eq!(15, disc),
+ Text(..) => assert_eq!(16, disc),
}
}
}
diff --git a/glean-core/src/metrics/mod.rs b/glean-core/src/metrics/mod.rs
index 511610794..fb3534492 100644
--- a/glean-core/src/metrics/mod.rs
+++ b/glean-core/src/metrics/mod.rs
@@ -27,6 +27,7 @@ mod rate;
mod recorded_experiment;
mod string;
mod string_list;
+mod text;
mod time_unit;
mod timespan;
mod timing_distribution;
@@ -56,6 +57,7 @@ pub use self::quantity::QuantityMetric;
pub use self::rate::{Rate, RateMetric};
pub use self::string::StringMetric;
pub use self::string_list::StringListMetric;
+pub use self::text::TextMetric;
pub use self::time_unit::TimeUnit;
pub use self::timespan::TimespanMetric;
pub use self::timing_distribution::TimerId;
@@ -125,6 +127,8 @@ pub enum Metric {
Rate(i32, i32),
/// A URL metric. See [`UrlMetric`] for more information.
Url(String),
+ /// A Text metric. See [`TextMetric`] for more information.
+ Text(String),
}
/// A [`MetricType`] describes common behavior across all metrics.
@@ -181,6 +185,7 @@ impl Metric {
Metric::Uuid(_) => "uuid",
Metric::MemoryDistribution(_) => "memory_distribution",
Metric::Jwe(_) => "jwe",
+ Metric::Text(_) => "text",
}
}
@@ -209,6 +214,7 @@ impl Metric {
Metric::Uuid(s) => json!(s),
Metric::MemoryDistribution(hist) => json!(memory_distribution::snapshot(hist)),
Metric::Jwe(s) => json!(s),
+ Metric::Text(s) => json!(s),
}
}
}
diff --git a/glean-core/src/metrics/text.rs b/glean-core/src/metrics/text.rs
new file mode 100644
index 000000000..ec8c464b6
--- /dev/null
+++ b/glean-core/src/metrics/text.rs
@@ -0,0 +1,180 @@
+// 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 https://mozilla.org/MPL/2.0/.
+
+use std::sync::Arc;
+
+use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
+use crate::metrics::Metric;
+use crate::metrics::MetricType;
+use crate::storage::StorageManager;
+use crate::util::truncate_string_at_boundary_with_error;
+use crate::CommonMetricData;
+use crate::Glean;
+
+// The maximum number of characters for text.
+const MAX_LENGTH_VALUE: usize = 200 * 1024;
+
+/// A text metric.
+///
+/// Records a single long Unicode text,
+/// used when the limits on `String` are too low.
+/// Text is length-limited to `MAX_LENGTH_VALUE` bytes.
+#[derive(Clone, Debug)]
+pub struct TextMetric {
+ meta: Arc,
+}
+
+impl MetricType for TextMetric {
+ fn meta(&self) -> &CommonMetricData {
+ &self.meta
+ }
+
+ fn with_name(&self, name: String) -> Self {
+ let mut meta = (*self.meta).clone();
+ meta.name = name;
+ Self {
+ meta: Arc::new(meta),
+ }
+ }
+
+ fn with_dynamic_label(&self, label: String) -> Self {
+ let mut meta = (*self.meta).clone();
+ meta.dynamic_label = Some(label);
+ Self {
+ meta: Arc::new(meta),
+ }
+ }
+}
+
+// IMPORTANT:
+//
+// When changing this implementation, make sure all the operations are
+// also declared in the related trait in `../traits/`.
+impl TextMetric {
+ /// Creates a new text metric.
+ pub fn new(meta: CommonMetricData) -> Self {
+ Self {
+ meta: Arc::new(meta),
+ }
+ }
+
+ /// Sets to the specified value.
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The text to set the metric to.
+ ///
+ /// ## Notes
+ ///
+ /// Truncates the value (at codepoint boundaries) if it is longer than `MAX_LENGTH_VALUE` bytes
+ /// and logs an error.
+ pub fn set(&self, value: String) {
+ let metric = self.clone();
+ crate::launch_with_glean(move |glean| metric.set_sync(glean, &value))
+ }
+
+ /// Sets to the specified value synchronously,
+ /// truncating and recording an error if longer than `MAX_LENGTH_VALUE`.
+ #[doc(hidden)]
+ pub fn set_sync>(&self, glean: &Glean, value: S) {
+ if !self.should_record(glean) {
+ return;
+ }
+
+ let s = truncate_string_at_boundary_with_error(glean, &self.meta, value, MAX_LENGTH_VALUE);
+
+ let value = Metric::Text(s);
+ glean.storage().record(glean, &self.meta, &value)
+ }
+
+ /// Gets the currently-stored value as a string, or None if there is no value.
+ #[doc(hidden)]
+ pub fn get_value<'a, S: Into