Skip to content

Commit 9896c30

Browse files
feat(core): Metrics exporter factory to customize exporter extensively (#2080)
Otel Metrics Exporter Factory to allow custom implementations how metrics are exported
1 parent 128ba84 commit 9896c30

File tree

2 files changed

+77
-29
lines changed

2 files changed

+77
-29
lines changed

core/src/main/resources/filodb-defaults.conf

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ filodb {
1919
// "deployment.environment" = "development"
2020
}
2121

22-
# Exporter type: "otlp" for OTLP export, "log" for log-based export
23-
exporter-type = "log"
22+
# Exporter type: one of
23+
# filodb.core.metrics.LogMetricExporterFactory or
24+
# filodb.core.metrics.OtlpGrpcMetricExporterFactory
25+
# or any other implementation of MetricExporterFactory
26+
exporter-factory-class-name = "filodb.core.metrics.LogMetricExporterFactory"
2427

2528
# Use exponential histograms
2629
exponential-histogram = false

core/src/main/scala/filodb.core/metrics/FilodbMetrics.scala

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import filodb.core.GlobalConfig
3737
case class OTelMetricsConfig(exportIntervalSeconds: Int = 60,
3838
resourceAttributes: Map[String, String],
3939
otlpHeaders: Map[String, String],
40-
exporterType: String, // "otlp" or "log"
40+
exporterFactoryClassName: String, // Fully qualified class name of MetricExporterFactory
4141
exponentialHistogram: Boolean,
4242
customHistogramBucketsTime: List[Double], // used only if exponentialHistogram=false
4343
customHistogramBuckets: List[Double], // used only if exponentialHistogram=false
@@ -46,14 +46,79 @@ case class OTelMetricsConfig(exportIntervalSeconds: Int = 60,
4646
otlpClientCertPath: Option[String],
4747
otlpClientKeyPath: Option[String])
4848

49+
/**
50+
* Factory interface for creating MetricExporter instances
51+
*/
52+
trait MetricExporterFactory {
53+
/**
54+
* Creates a MetricExporter instance
55+
* @param config The OTelMetricsConfig to use for configuration
56+
* @return A configured MetricExporter
57+
*/
58+
def create(config: OTelMetricsConfig): MetricExporter
59+
}
60+
61+
/**
62+
* Factory for creating OtlpGrpcMetricExporter instances
63+
*/
64+
class OtlpGrpcMetricExporterFactory extends MetricExporterFactory {
65+
override def create(config: OTelMetricsConfig): MetricExporter = {
66+
val b = OtlpGrpcMetricExporter.builder()
67+
.setAggregationTemporalitySelector(AggregationTemporalitySelector.deltaPreferred())
68+
.setEndpoint(config.otlpEndpoint.getOrElse(
69+
throw new IllegalArgumentException("otlp-endpoint must be configured when using OTLP exporter")))
70+
71+
if (config.otlpTrustedCertsPath.isDefined) {
72+
b.setTrustedCertificates(Files.readAllBytes(Paths.get(config.otlpTrustedCertsPath.get)))
73+
}
74+
if (config.otlpClientKeyPath.isDefined && config.otlpClientCertPath.isDefined) {
75+
b.setClientTls(Files.readAllBytes(Paths.get(config.otlpClientKeyPath.get)),
76+
Files.readAllBytes(Paths.get(config.otlpClientCertPath.get)))
77+
}
78+
config.otlpHeaders.foreach { case (key, value) =>
79+
b.addHeader(key, value)
80+
}
81+
b.build()
82+
}
83+
}
84+
85+
/**
86+
* Factory for creating OtlpJsonLoggingMetricExporter instances
87+
*/
88+
class LogMetricExporterFactory extends MetricExporterFactory {
89+
override def create(config: OTelMetricsConfig): MetricExporter = {
90+
OtlpJsonLoggingMetricExporter.create(AggregationTemporality.DELTA)
91+
}
92+
}
93+
4994
object OTelMetricsConfig {
50-
def fromConfig(metricsConfig: Config): OTelMetricsConfig = {
5195

96+
/**
97+
* Creates a MetricExporterFactory instance using reflection
98+
* @param className Fully qualified class name of the factory
99+
* @return Instance of MetricExporterFactory
100+
*/
101+
def instantiateExporterFactory(className: String): MetricExporterFactory = {
102+
try {
103+
val clazz = Class.forName(className)
104+
clazz.getDeclaredConstructor().newInstance().asInstanceOf[MetricExporterFactory]
105+
} catch {
106+
case e: ClassNotFoundException =>
107+
throw new IllegalArgumentException(s"Factory class not found: $className", e)
108+
case e: ClassCastException =>
109+
throw new IllegalArgumentException(
110+
s"Class $className does not implement MetricExporterFactory trait", e)
111+
case e: Exception =>
112+
throw new IllegalArgumentException(s"Failed to instantiate factory: $className", e)
113+
}
114+
}
115+
116+
def fromConfig(metricsConfig: Config): OTelMetricsConfig = {
52117
OTelMetricsConfig(
53118
otlpEndpoint = metricsConfig.as[Option[String]]("otlp-endpoint"),
54119
exportIntervalSeconds = metricsConfig.as[Int]("export-interval-seconds"),
55120
resourceAttributes = metricsConfig.as[Map[String, String]]("resource-attributes"),
56-
exporterType = metricsConfig.as[String]("exporter-type"),
121+
exporterFactoryClassName = metricsConfig.as[String]("exporter-factory-class-name"),
57122
exponentialHistogram = metricsConfig.as[Boolean]("exponential-histogram"),
58123
customHistogramBucketsTime = metricsConfig.as[List[Double]]("custom-histogram-buckets-time").sorted,
59124
customHistogramBuckets = metricsConfig.as[List[Double]]("custom-histogram-buckets").sorted,
@@ -173,30 +238,10 @@ private class FilodbMetrics(filodbMetricsConfig: Config) extends StrictLogging {
173238
}
174239
val resource = resourceBuilder.build()
175240

176-
// Create exporter based on configuration
177-
val metricExporter: MetricExporter = otelConfig.exporterType.toLowerCase match {
178-
case "log" =>
179-
logger.info("Using log-based metrics exporter")
180-
OtlpJsonLoggingMetricExporter.create(AggregationTemporality.DELTA)
181-
case "otlp" =>
182-
logger.info(s"Using OTLP metrics exporter with endpoint: ${otelConfig.otlpEndpoint}")
183-
val b = OtlpGrpcMetricExporter.builder()
184-
.setAggregationTemporalitySelector(AggregationTemporalitySelector.deltaPreferred())
185-
.setEndpoint(otelConfig.otlpEndpoint.get)
186-
187-
if (otelConfig.otlpTrustedCertsPath.isDefined) {
188-
b.setTrustedCertificates(Files.readAllBytes(Paths.get(otelConfig.otlpTrustedCertsPath.get)))
189-
}
190-
if (otelConfig.otlpClientKeyPath.isDefined && otelConfig.otlpClientCertPath.isDefined) {
191-
b.setClientTls(Files.readAllBytes(Paths.get(otelConfig.otlpClientKeyPath.get)),
192-
Files.readAllBytes(Paths.get(otelConfig.otlpClientCertPath.get)))
193-
}
194-
otelConfig.otlpHeaders.foreach { case (key, value) =>
195-
b.addHeader(key, value)
196-
}
197-
b.build()
198-
case _ => throw new IllegalArgumentException(s"Unknown exporter type: ${otelConfig.exporterType}")
199-
}
241+
// Create exporter using the configured factory
242+
logger.info(s"Using ${otelConfig.exporterFactoryClassName} metrics exporter")
243+
val metricExporter: MetricExporter =
244+
OTelMetricsConfig.instantiateExporterFactory(otelConfig.exporterFactoryClassName).create(otelConfig)
200245

201246
// Create periodic metric reader with delta aggregation
202247
val metricReader = PeriodicMetricReader.builder(metricExporter)

0 commit comments

Comments
 (0)