diff --git a/TRACING.md b/TRACING.md index 3935a94eff160..6221305ef455b 100644 --- a/TRACING.md +++ b/TRACING.md @@ -158,6 +158,6 @@ explicitly opening a scope via the `Tracer`. [otel]: https://opentelemetry.io/ [thread-context]: ./server/src/main/java/org/elasticsearch/common/util/concurrent/ThreadContext.java [w3c]: https://www.w3.org/TR/trace-context/ -[tracing]: ./server/src/main/java/org/elasticsearch/tracing +[tracing]: ./server/src/main/java/org/elasticsearch/telemetry [agent-config]: https://www.elastic.co/guide/en/apm/agent/java/master/configuration.html [agent]: https://www.elastic.co/guide/en/apm/agent/java/current/index.html diff --git a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/APMJvmOptions.java b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/APMJvmOptions.java index 9dcfc1e373a93..898ea9757ec85 100644 --- a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/APMJvmOptions.java +++ b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/APMJvmOptions.java @@ -50,10 +50,12 @@ class APMJvmOptions { // by the agent. Don't disable writing to a log file, as the agent will then // require extra Security Manager permissions when it tries to do something // else, and it's just painful. - "log_file", "_AGENT_HOME_/../../logs/apm.log", - + "log_file", "/Users/przemyslawgomulka/workspace/pgomulka/apm.log", + "log_level", "debug", // ES does not use auto-instrumentation. - "instrument", "false" + "instrument", "false", + "experimental", "true", + "enable_experimental_instrumentations", "true" ); /** @@ -82,7 +84,7 @@ class APMJvmOptions { // is doing, leave this value alone. "log_level", "error", "application_packages", "org.elasticsearch,org.apache.lucene", - "metrics_interval", "120s", + "metrics_interval", "5s", "breakdown_metrics", "false", "central_config", "false" ); @@ -316,9 +318,7 @@ static Path findAgentJar(String installDir) throws IOException, UserException { } try (var apmStream = Files.list(apmModule)) { - final List paths = apmStream.filter( - path -> path.getFileName().toString().matches("elastic-apm-agent-\\d+\\.\\d+\\.\\d+\\.jar") - ).toList(); + final List paths = apmStream.filter(path -> path.getFileName().toString().matches("elastic-apm-agent-.*.jar")).toList(); if (paths.size() > 1) { throw new UserException( diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 6cd8d56be9530..cdb0fe45f220c 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -74,6 +74,11 @@ + + + + + diff --git a/modules/apm/build.gradle b/modules/apm/build.gradle index c8619c97d1068..667e6a8d1c0dc 100644 --- a/modules/apm/build.gradle +++ b/modules/apm/build.gradle @@ -14,11 +14,19 @@ esplugin { def otelVersion = '1.17.0' +repositories { + maven { + name "sonatype-nexus-snapshots" + url "https://oss.sonatype.org/content/repositories/snapshots" + } +} dependencies { implementation "io.opentelemetry:opentelemetry-api:${otelVersion}" implementation "io.opentelemetry:opentelemetry-context:${otelVersion}" implementation "io.opentelemetry:opentelemetry-semconv:${otelVersion}-alpha" - runtimeOnly "co.elastic.apm:elastic-apm-agent:1.36.0" + implementation "org.apache.logging.log4j:log4j-api:${versions.log4j}" + + runtimeOnly "co.elastic.apm:elastic-apm-agent:1.42.1-SNAPSHOT" } tasks.named("dependencyLicenses").configure { diff --git a/modules/apm/src/main/java/module-info.java b/modules/apm/src/main/java/module-info.java index 0bea3078f5f00..e4a1ce1768cab 100644 --- a/modules/apm/src/main/java/module-info.java +++ b/modules/apm/src/main/java/module-info.java @@ -16,4 +16,5 @@ requires io.opentelemetry.api; exports org.elasticsearch.telemetry.apm; + exports org.elasticsearch.telemetry.apm.internal.metrics; } diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/APM.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/APM.java index be59eda4a63c2..c5f58a3c98ea8 100644 --- a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/APM.java +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/APM.java @@ -27,6 +27,7 @@ import org.elasticsearch.telemetry.TelemetryProvider; import org.elasticsearch.telemetry.apm.internal.APMAgentSettings; import org.elasticsearch.telemetry.apm.internal.APMTelemetryProvider; +import org.elasticsearch.telemetry.apm.internal.metrics.APMMetric; import org.elasticsearch.telemetry.apm.internal.tracing.APMTracer; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; @@ -97,7 +98,9 @@ public Collection createComponents( apmAgentSettings.syncAgentSystemProperties(settings); apmAgentSettings.addClusterSettingsListeners(clusterService, telemetryProvider.get()); - return List.of(apmTracer); + final APMMetric apmMetric = telemetryProvider.get().getMetric(); + + return List.of(apmTracer, apmMetric); } @Override diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMAgentSettings.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMAgentSettings.java index 75ca94bb13ad6..4feeb2d9b9f1c 100644 --- a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMAgentSettings.java +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMAgentSettings.java @@ -17,6 +17,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.telemetry.apm.internal.metrics.APMMetric; import org.elasticsearch.telemetry.apm.internal.tracing.APMTracer; import java.security.AccessController; @@ -40,14 +41,21 @@ public class APMAgentSettings { * Sensible defaults that Elasticsearch configures. This cannot be done via the APM agent * config file, as then their values could not be overridden dynamically via system properties. */ - static Map APM_AGENT_DEFAULT_SETTINGS = Map.of("transaction_sample_rate", "0.2"); + static Map APM_AGENT_DEFAULT_SETTINGS = Map.of( + "transaction_sample_rate", + "0.2", + "enable_experimental_instrumentations", + "true" + ); public void addClusterSettingsListeners(ClusterService clusterService, APMTelemetryProvider apmTelemetryProvider) { final ClusterSettings clusterSettings = clusterService.getClusterSettings(); final APMTracer apmTracer = apmTelemetryProvider.getTracer(); + final APMMetric apmMetric = apmTelemetryProvider.getMetric(); clusterSettings.addSettingsUpdateConsumer(APM_ENABLED_SETTING, enabled -> { apmTracer.setEnabled(enabled); + apmMetric.setEnabled(enabled); // The agent records data other than spans, e.g. JVM metrics, so we toggle this setting in order to // minimise its impact to a running Elasticsearch. this.setAgentSetting("recording", Boolean.toString(enabled)); diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMTelemetryProvider.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMTelemetryProvider.java index 495afd43bf176..4a70b65c74761 100644 --- a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMTelemetryProvider.java +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMTelemetryProvider.java @@ -10,19 +10,27 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.telemetry.TelemetryProvider; +import org.elasticsearch.telemetry.apm.internal.metrics.APMMetric; import org.elasticsearch.telemetry.apm.internal.tracing.APMTracer; public class APMTelemetryProvider implements TelemetryProvider { private final Settings settings; private final APMTracer apmTracer; + private final APMMetric apmMetric; public APMTelemetryProvider(Settings settings) { this.settings = settings; apmTracer = new APMTracer(settings); + apmMetric = new APMMetric(settings); } @Override public APMTracer getTracer() { return apmTracer; } + + @Override + public APMMetric getMetric() { + return apmMetric; + } } diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMetric.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMetric.java new file mode 100644 index 0000000000000..43efdef640594 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMetric.java @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.component.AbstractLifecycleComponent; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.LazyInitializable; +import org.elasticsearch.telemetry.MetricName; +import org.elasticsearch.telemetry.metric.DoubleCounter; +import org.elasticsearch.telemetry.metric.DoubleGauge; +import org.elasticsearch.telemetry.metric.DoubleHistogram; +import org.elasticsearch.telemetry.metric.DoubleUpDownCounter; +import org.elasticsearch.telemetry.metric.LongCounter; +import org.elasticsearch.telemetry.metric.LongGauge; +import org.elasticsearch.telemetry.metric.LongHistogram; +import org.elasticsearch.telemetry.metric.LongUpDownCounter; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static org.elasticsearch.telemetry.apm.internal.APMAgentSettings.APM_ENABLED_SETTING; + +public class APMMetric extends AbstractLifecycleComponent implements org.elasticsearch.telemetry.metric.Metric { + + private final InstrumentRegistrar doubleCounters = new InstrumentRegistrar<>(); + private final InstrumentRegistrar doubleUpDownCounters = new InstrumentRegistrar<>(); + private final InstrumentRegistrar doubleGauges = new InstrumentRegistrar<>(); + private final InstrumentRegistrar doubleHistograms = new InstrumentRegistrar<>(); + private final InstrumentRegistrar longCounters = new InstrumentRegistrar<>(); + private final InstrumentRegistrar longUpDownCounters = new InstrumentRegistrar<>(); + private final InstrumentRegistrar longGauges = new InstrumentRegistrar<>(); + private final InstrumentRegistrar longHistograms = new InstrumentRegistrar<>(); + private volatile boolean enabled; + private AtomicReference services = new AtomicReference<>(); + + private final List allRegisteredInstruments = new ArrayList<>(); + + record APMServices(Meter meter, OpenTelemetry openTelemetry) {} + + // TODO remove duplication between APMTracer and APMMetric. enabled, create apm services etc + public APMMetric(Settings settings) { + this.enabled = APM_ENABLED_SETTING.get(settings); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (enabled) { + createApmServices(); + allRegisteredInstruments.forEach(si -> si.setEnabled(true)); + } else { + allRegisteredInstruments.forEach(si -> si.setEnabled(false)); + destroyApmServices(); + } + + } + + @Override + protected void doStart() { + if (enabled) { + createApmServices(); + } + } + + @Override + protected void doStop() { + destroyApmServices(); + } + + @Override + protected void doClose() {} + + @Override + public DoubleCounter registerDoubleCounter(MetricName name, String description, T unit) { + var counter = OtelDoubleCounter.build(()->services.get().meter, name, description, unit); + doubleCounters.register(counter); + allRegisteredInstruments.add(counter); + return counter; + } + + @Override + public DoubleUpDownCounter registerDoubleUpDownCounter(MetricName name, String description, T unit) { + var counter = OtelDoubleUpDownCounter.build(services.get().meter, name, description, unit); + doubleUpDownCounters.register(counter); + allRegisteredInstruments.add(counter); + + return counter; + } + + @Override + public DoubleGauge registerDoubleGauge(MetricName name, String description, T unit) { + var gauge = OtelDoubleGauge.build(services.get().meter, name, description, unit); + doubleGauges.register(gauge); + allRegisteredInstruments.add(gauge); + + return gauge; + } + + @Override + public DoubleHistogram registerDoubleHistogram(MetricName name, String description, T unit) { + var histogram = OtelDoubleHistogram.build(services.get().meter, name, description, unit); + doubleHistograms.register(histogram); + allRegisteredInstruments.add(histogram); + return histogram; + } + + @Override + public LongCounter registerLongCounter(MetricName name, String description, T unit) { + var lazyCounter = new LazyInitializable<>( + () -> services.get().meter.counterBuilder(name.getRawName()).setDescription(description).setUnit(unit.toString()).build() + ); + var counter = OtelLongCounter.build(lazyCounter,services.get().meter, name, description, unit); + longCounters.register(counter); + allRegisteredInstruments.add(counter); + + return counter; + } + + @Override + public LongUpDownCounter registerLongUpDownCounter(MetricName name, String description, T unit) { + + var lazyCounter = new LazyInitializable<>( + () -> services.get().meter.upDownCounterBuilder(name.getRawName()).setDescription(description).setUnit(unit.toString()).build() + ); + var counter = OtelLongUpDownCounter.build(lazyCounter,services.get().meter, name, description, unit); + longUpDownCounters.register(counter); + allRegisteredInstruments.add(counter); + + return counter; + } + + @Override + public LongGauge registerLongGauge(MetricName name, String description, T unit) { + + var lazyGauge = new LazyInitializable<>( + () -> services.get().meter.gaugeBuilder(name.getRawName()) + .ofLongs() + .setDescription(description) + .setUnit(unit.toString()) + .buildObserver() + ); + + var gauge = OtelLongGauge.build(lazyGauge, services.get().meter, name, description, unit); + longGauges.register(gauge); + allRegisteredInstruments.add(gauge); + + return gauge; + } + + @Override + public LongHistogram registerLongHistogram(MetricName name, String description, T unit) { + + var lazyHistogram = new LazyInitializable<>( + () -> services.get().meter.histogramBuilder(name.getRawName()) + .ofLongs() + .setDescription(description) + .setUnit(unit.toString()) + .build() + ); + + var histogram = OtelLongHistogram.build(lazyHistogram,services.get().meter, name, description, unit); + longHistograms.register(histogram); + allRegisteredInstruments.add(histogram); + + return histogram; + } + + void createApmServices() { + assert this.enabled; + assert this.services.get() == null; + + AccessController.doPrivileged((PrivilegedAction) () -> { + var openTelemetry = GlobalOpenTelemetry.get(); + var meter = openTelemetry.getMeter("elasticsearch"); + + this.services.set(new APMServices(meter, openTelemetry)); + return null; + }); + } + + private void destroyApmServices() { + this.services.set(null); + } + +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentRegistrar.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentRegistrar.java new file mode 100644 index 0000000000000..5812ed02200b8 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentRegistrar.java @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import org.elasticsearch.common.util.concurrent.ConcurrentCollections; +import org.elasticsearch.telemetry.MetricName; +import org.elasticsearch.telemetry.metric.Instrument; + +import java.util.Map; + +public class InstrumentRegistrar { + private final Map registered = ConcurrentCollections.newConcurrentMap(); + + void register(T instrument) { + registered.compute(instrument.getName(), (k, v) -> { + if (v != null) { + throw new IllegalStateException( + instrument.getClass().getSimpleName() + "[" + instrument.getName().getRawName() + "] already registered" + ); + } + + return instrument; + }); + } + + T get(MetricName name) { + return registered.get(name); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleCounter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleCounter.java new file mode 100644 index 0000000000000..7a6080fd4304f --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleCounter.java @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; +import org.elasticsearch.telemetry.metric.DoubleCounter; + +import java.util.Map; +import java.util.function.Function; + +public class OtelDoubleCounter extends SwitchableInstrument implements DoubleCounter { + private final MetricName name; + private final String description; + private final T unit; + + private OtelDoubleCounter( + Function producer, + Meter meter, + MetricName name, + String description, + T unit + ) { + super(producer, meter); + this.name = name; + this.description = description; + this.unit = unit; + } + + public static OtelDoubleCounter build(Meter meter, MetricName name, String description, T unit) { + return new OtelDoubleCounter<>((m) -> createInstrument(m, name, description, unit), meter, name, description, unit); + } + + private static io.opentelemetry.api.metrics.DoubleCounter createInstrument( + Meter meter, + MetricName name, + String description, + T unit + ) { + return meter.counterBuilder(name.getRawName()).ofDoubles().setDescription(description).setUnit(unit.toString()).build(); + } + + @Override + public MetricName getName() { + return name; + } + + @Override + public void increment() { + getInstrument().add(1d); + } + + @Override + public void incrementBy(double inc) { + assert inc >= 0; + getInstrument().add(inc); + } + + @Override + public void incrementBy(double inc, Map attributes) { + assert inc >= 0; + getInstrument().add(inc, OtelHelper.fromMap(attributes)); + } + + @Override + public void incrementBy(double inc, Map attributes, ThreadContext threadContext) { + throw new UnsupportedOperationException("unimplemented"); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleGauge.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleGauge.java new file mode 100644 index 0000000000000..3d05a422216ae --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleGauge.java @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; + +import org.elasticsearch.telemetry.MetricName; +import org.elasticsearch.telemetry.metric.DoubleGauge; + +import java.util.Map; +import java.util.function.Function; + +public class OtelDoubleGauge extends SwitchableInstrument implements DoubleGauge { + private final MetricName name; + private final String description; + private final T unit; + + private OtelDoubleGauge( + Function instrumentProducer, + Meter meter, + MetricName name, + String description, + T unit + ) { + super(instrumentProducer, meter); + this.name = name; + this.description = description; + this.unit = unit; + } + + public static OtelDoubleGauge build(Meter meter, MetricName name, String description, T unit) { + return new OtelDoubleGauge<>((m) -> createInstrument(meter, name, description, unit), meter, name, description, unit); + } + + private static ObservableDoubleMeasurement createInstrument(Meter meter, MetricName name, String description, T unit) { + return meter.gaugeBuilder(name.getRawName()).setDescription(description).setUnit(unit.toString()).buildObserver(); + } + + @Override + public MetricName getName() { + return name; + } + + @Override + public void record(double value) { + getInstrument().record(value); + } + + @Override + public void record(double value, Map attributes) { + getInstrument().record(value, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleHistogram.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleHistogram.java new file mode 100644 index 0000000000000..059a302657869 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleHistogram.java @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.util.LazyInitializable; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; +import org.elasticsearch.telemetry.metric.DoubleHistogram; + +import java.util.Map; +import java.util.function.Function; + +public class OtelDoubleHistogram extends SwitchableInstrument implements DoubleHistogram { + private final MetricName name; + private final String description; + private final T unit; + + public OtelDoubleHistogram( + Function instrumentProducer, + Meter meter, + MetricName name, + String description, + T unit + ) { + super(instrumentProducer, meter); + this.name = name; + this.description = description; + this.unit = unit; + } + + public static OtelDoubleHistogram build( + Meter meter, + MetricName name, + String description, + T unit + ) { + return new OtelDoubleHistogram<>( + (m) -> createInstrument(m, name, description, unit) + , meter, name, description, unit); + } + + private static io.opentelemetry.api.metrics.DoubleHistogram createInstrument(Meter meter, MetricName name, String description, T unit) { + return meter.histogramBuilder(name.getRawName()).setDescription(description).setUnit(unit.toString()).build(); + } + + @Override + public MetricName getName() { + return name; + } + + @Override + public void record(double value) { + getInstrument().record(value); + } + + @Override + public void record(double value, Map attributes) { + getInstrument().record(value, OtelHelper.fromMap(attributes)); + } + + @Override + public void record(double value, Map attributes, ThreadContext threadContext) { + throw new UnsupportedOperationException("unimplemented"); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleUpDownCounter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleUpDownCounter.java new file mode 100644 index 0000000000000..350512b5f7180 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelDoubleUpDownCounter.java @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.DoubleUpDownCounter; +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; +import java.util.function.Function; + +public class OtelDoubleUpDownCounter extends SwitchableInstrument + implements + org.elasticsearch.telemetry.metric.DoubleUpDownCounter { + private final MetricName name; + private final String description; + private final T unit; + + private OtelDoubleUpDownCounter( + Function instanceProducer, + Meter metric, + MetricName name, + String description, + T unit + ) { + super(instanceProducer, metric); + this.name = name; + this.description = description; + this.unit = unit; + } + + public static OtelDoubleUpDownCounter build(Meter meter, MetricName name, String description, T unit) { + return new OtelDoubleUpDownCounter<>((m) -> createInstrument(m, name, description, unit), meter, name, description, unit); + } + + private static DoubleUpDownCounter createInstrument(Meter meter, MetricName name, String description, T unit) { + return meter.upDownCounterBuilder(name.getRawName()).ofDoubles().setDescription(description).setUnit(unit.toString()).build(); + } + + @Override + public MetricName getName() { + return name; + } + + @Override + public void add(double inc) { + getInstrument().add(inc); + } + + @Override + public void add(double inc, Map attributes) { + getInstrument().add(inc, OtelHelper.fromMap(attributes)); + } + + @Override + public void add(double inc, Map attributes, ThreadContext threadContext) { + throw new UnsupportedOperationException("unimplemented"); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelHelper.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelHelper.java new file mode 100644 index 0000000000000..673025a1a41f4 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelHelper.java @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.common.Attributes; + +import java.util.Map; + +class OtelHelper { + static Attributes fromMap(Map attributes) { + if (attributes == null || attributes.isEmpty()) { + return Attributes.empty(); + } + var builder = Attributes.builder(); + attributes.forEach((k, v) -> { + if (v instanceof String value) { + builder.put(k, value); + } else if (v instanceof Long value) { + builder.put(k, value); + } else if (v instanceof Double value) { + builder.put(k, value); + } else if (v instanceof Boolean value) { + builder.put(k, value); + } else { + throw new IllegalArgumentException("attributes do not support value type of [" + v.getClass().getCanonicalName() + "]"); + } + }); + return builder.build(); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongCounter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongCounter.java new file mode 100644 index 0000000000000..2d47c33591652 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongCounter.java @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.LongCounter; + +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.util.LazyInitializable; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public class OtelLongCounter extends SwitchableInstrument implements org.elasticsearch.telemetry.metric.LongCounter { + private final LazyInitializable counter; + private final MetricName name; + private final String description; + private final T unit; + + private OtelLongCounter(LazyInitializable lazyCounter, MetricName name, String description, T unit, Meter meter) { + super(null, null); + this.counter = lazyCounter; + this.name = name; + this.description = description; + this.unit = unit; + } + + public static OtelLongCounter build( + LazyInitializable lazyCounter, + Meter meter, + MetricName name, + String description, + T unit + ) { + return new OtelLongCounter<>(lazyCounter, name, description, unit, meter); + } + + @Override + public MetricName getName() { + return name; + } + + @Override + public void increment() { + counter.getOrCompute().add(1L); + } + + @Override + public void incrementBy(long inc) { + assert inc >= 0; + counter.getOrCompute().add(inc); + } + + @Override + public void incrementBy(long inc, Map attributes) { + assert inc >= 0; + counter.getOrCompute().add(inc, OtelHelper.fromMap(attributes)); + } + + @Override + public void incrementBy(double inc, Map attributes, ThreadContext threadContext) { + throw new UnsupportedOperationException("unimplemented"); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongGauge.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongGauge.java new file mode 100644 index 0000000000000..628837d8e166e --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongGauge.java @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; + +import org.elasticsearch.common.util.LazyInitializable; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public class OtelLongGauge extends SwitchableInstrument implements org.elasticsearch.telemetry.metric.LongGauge { + private final LazyInitializable gauge; + private final MetricName name; + private final String description; + private final T unit; + + private OtelLongGauge( + LazyInitializable gauge, + MetricName name, + String description, + T unit + ) { + super(null, null); + + this.gauge = gauge; + this.name = name; + this.description = description; + this.unit = unit; + } + + public static OtelLongGauge build( + LazyInitializable lazyGauge, + Meter meter, + MetricName name, + String description, + T unit + ) { + return new OtelLongGauge<>(lazyGauge, name, description, unit); + } + + @Override + public MetricName getName() { + return name; + } + + @Override + public void record(long value) { + gauge.getOrCompute().record(value); + } + + @Override + public void record(long value, Map attributes) { + gauge.getOrCompute().record(value, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongHistogram.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongHistogram.java new file mode 100644 index 0000000000000..ae503ea8953a0 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongHistogram.java @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.util.LazyInitializable; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public class OtelLongHistogram extends SwitchableInstrument implements org.elasticsearch.telemetry.metric.LongHistogram { + private final LazyInitializable histogram; + private final MetricName name; + private final String description; + private final T unit; + + public OtelLongHistogram( + LazyInitializable histogram, + MetricName name, + String description, + T unit + ) { + super(null, null); + + this.histogram = histogram; + this.name = name; + this.description = description; + this.unit = unit; + } + + public static OtelLongHistogram build( + LazyInitializable lazyHistogram, + Meter meter, + MetricName name, + String description, + T unit + ) { + return new OtelLongHistogram<>(lazyHistogram, name, description, unit); + } + + @Override + public MetricName getName() { + return name; + } + + @Override + public void record(long value) { + histogram.getOrCompute().record(value); + } + + @Override + public void record(long value, Map attributes) { + histogram.getOrCompute().record(value, OtelHelper.fromMap(attributes)); + } + + @Override + public void record(long value, Map attributes, ThreadContext threadContext) { + throw new UnsupportedOperationException("unimplemented"); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongUpDownCounter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongUpDownCounter.java new file mode 100644 index 0000000000000..b8e5edee58a54 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelLongUpDownCounter.java @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.LongUpDownCounter; + +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.util.LazyInitializable; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public class OtelLongUpDownCounter extends SwitchableInstrument implements org.elasticsearch.telemetry.metric.LongUpDownCounter { + private final LazyInitializable counter; + private final MetricName name; + private final String description; + private final T unit; + + private OtelLongUpDownCounter( + LazyInitializable lazyCounter, + MetricName name, + String description, + T unit + ) { + super(null, null); + + this.counter = lazyCounter; + this.name = name; + this.description = description; + this.unit = unit; + } + + public static OtelLongUpDownCounter build( + LazyInitializable lazyCounter, + Meter meter, + MetricName name, + String description, + T unit + ) { + return new OtelLongUpDownCounter<>(lazyCounter, name, description, unit); + } + + @Override + public MetricName getName() { + return name; + } + + @Override + public void add(long inc) { + counter.getOrCompute().add(inc); + } + + @Override + public void add(long inc, Map attributes) { + counter.getOrCompute().add(inc, OtelHelper.fromMap(attributes)); + } + + @Override + public void add(long inc, Map attributes, ThreadContext threadContext) { + throw new UnsupportedOperationException("unimplemented"); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/SwitchableInstrument.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/SwitchableInstrument.java new file mode 100644 index 0000000000000..6822d5a070906 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/SwitchableInstrument.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.util.LazyInitializable; + +import java.util.function.Function; + +public abstract class SwitchableInstrument { + private static final Meter NOOP_METER = OpenTelemetry.noop().getMeter("elasticsearch"); + private final LazyInitializable instrument; + private final T noopInstrument; + private volatile boolean enabled; + + public SwitchableInstrument(Function instrumentProducer, Meter meter) { + this.instrument = new LazyInitializable<>(() -> instrumentProducer.apply(meter)); + this.noopInstrument = instrumentProducer.apply(NOOP_METER); + } + + void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public T getInstrument() { + if (enabled) { + return instrument.getOrCompute(); + } else { + return noopInstrument; + } + } + +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/tracing/APMTracer.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/tracing/APMTracer.java index daedb90047975..b8e3a113dcb68 100644 --- a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/tracing/APMTracer.java +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/tracing/APMTracer.java @@ -150,7 +150,6 @@ APMServices createApmServices() { return AccessController.doPrivileged((PrivilegedAction) () -> { var openTelemetry = GlobalOpenTelemetry.get(); var tracer = openTelemetry.getTracer("elasticsearch", Version.CURRENT.toString()); - return new APMServices(tracer, openTelemetry); }); } @@ -424,6 +423,11 @@ Map getSpans() { return spans; } + // TODO(stu): remove + APMServices getServices() { + return services; + } + private static CharacterRunAutomaton buildAutomaton(List includePatterns, List excludePatterns) { Automaton includeAutomaton = patternsToAutomaton(includePatterns); Automaton excludeAutomaton = patternsToAutomaton(excludePatterns); @@ -452,4 +456,5 @@ private static Automaton patternsToAutomaton(List patterns) { } return Operations.union(automata); } + } diff --git a/modules/apm/src/main/plugin-metadata/plugin-security.policy b/modules/apm/src/main/plugin-metadata/plugin-security.policy index b85d3ec05c277..450896fe151ec 100644 --- a/modules/apm/src/main/plugin-metadata/plugin-security.policy +++ b/modules/apm/src/main/plugin-metadata/plugin-security.policy @@ -10,10 +10,12 @@ grant { permission java.lang.RuntimePermission "accessSystemModules"; permission java.lang.RuntimePermission "createClassLoader"; permission java.lang.RuntimePermission "getClassLoader"; - permission java.util.PropertyPermission "elastic.apm.*", "write"; + permission java.util.PropertyPermission "*", "read,write"; + }; grant codeBase "${codebase.elastic-apm-agent}" { + permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.lang.RuntimePermission "setContextClassLoader"; permission java.lang.RuntimePermission "setFactory"; diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index c354d510765c2..8a9f1f0b87d73 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -384,6 +384,7 @@ org.elasticsearch.serverless.apifiltering; exports org.elasticsearch.telemetry.tracing; exports org.elasticsearch.telemetry; + exports org.elasticsearch.telemetry.metric; provides java.util.spi.CalendarDataProvider with org.elasticsearch.common.time.IsoCalendarDataProvider; provides org.elasticsearch.xcontent.ErrorOnUnknown with org.elasticsearch.common.xcontent.SuggestingErrorOnUnknown; diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index 7395d6003ec44..eb9058ba5d1bf 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -457,7 +457,7 @@ import org.elasticsearch.rest.action.synonyms.RestPutSynonymRuleAction; import org.elasticsearch.rest.action.synonyms.RestPutSynonymsAction; import org.elasticsearch.tasks.Task; -import org.elasticsearch.telemetry.tracing.Tracer; +import org.elasticsearch.telemetry.TelemetryProvider; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.usage.UsageService; @@ -522,7 +522,7 @@ public ActionModule( CircuitBreakerService circuitBreakerService, UsageService usageService, SystemIndices systemIndices, - Tracer tracer, + TelemetryProvider telemetryProvider, ClusterService clusterService, List> reservedStateHandlers, RestExtension restExtension @@ -563,12 +563,24 @@ public ActionModule( var customController = getRestServerComponent( "REST controller", actionPlugins, - restPlugin -> restPlugin.getRestController(restInterceptor, nodeClient, circuitBreakerService, usageService, tracer) + restPlugin -> restPlugin.getRestController( + restInterceptor, + nodeClient, + circuitBreakerService, + usageService, + telemetryProvider.getTracer() + ) ); if (customController != null) { restController = customController; } else { - restController = new RestController(restInterceptor, nodeClient, circuitBreakerService, usageService, tracer); + restController = new RestController( + restInterceptor, + nodeClient, + circuitBreakerService, + usageService, + telemetryProvider.getTracer() + ); } reservedClusterStateService = new ReservedClusterStateService(clusterService, reservedStateHandlers); this.restExtension = restExtension; diff --git a/server/src/main/java/org/elasticsearch/node/Node.java b/server/src/main/java/org/elasticsearch/node/Node.java index 4159da6c49575..d16968f1b3bb1 100644 --- a/server/src/main/java/org/elasticsearch/node/Node.java +++ b/server/src/main/java/org/elasticsearch/node/Node.java @@ -815,7 +815,7 @@ protected Node( circuitBreakerService, usageService, systemIndices, - tracer, + telemetryProvider, clusterService, reservedStateHandlers, pluginsService.loadSingletonServiceProvider(RestExtension.class, RestExtension::allowAll) diff --git a/server/src/main/java/org/elasticsearch/telemetry/MetricName.java b/server/src/main/java/org/elasticsearch/telemetry/MetricName.java new file mode 100644 index 0000000000000..c3eddce0cb49b --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/MetricName.java @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry; + +import java.util.Objects; + +public class MetricName { + private final String rawName; + + public MetricName(String rawName) { + this.rawName = rawName; + } + + public String getRawName() { + return rawName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MetricName metricName = (MetricName) o; + return Objects.equals(getRawName(), metricName.getRawName()); + } + + @Override + public int hashCode() { + return Objects.hash(rawName); + } + + @Override + public String toString() { + return "MetricName[" + rawName + "]"; + } +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/TelemetryProvider.java b/server/src/main/java/org/elasticsearch/telemetry/TelemetryProvider.java index 0df8aeedac7f8..fc3e4aea5e77b 100644 --- a/server/src/main/java/org/elasticsearch/telemetry/TelemetryProvider.java +++ b/server/src/main/java/org/elasticsearch/telemetry/TelemetryProvider.java @@ -8,11 +8,15 @@ package org.elasticsearch.telemetry; +import org.elasticsearch.telemetry.metric.Metric; import org.elasticsearch.telemetry.tracing.Tracer; public interface TelemetryProvider { + Tracer getTracer(); + Metric getMetric(); + TelemetryProvider NOOP = new TelemetryProvider() { @Override @@ -20,5 +24,9 @@ public Tracer getTracer() { return Tracer.NOOP; } + @Override + public Metric getMetric() { + return Metric.NOOP; + } }; } diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleCounter.java b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleCounter.java new file mode 100644 index 0000000000000..692f75c0a1e8b --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleCounter.java @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public interface DoubleCounter extends Instrument { + void increment(); + + void incrementBy(double inc); + + void incrementBy(double inc, Map attributes); + + void incrementBy(double inc, Map attributes, ThreadContext threadContext); + + DoubleCounter NOOP = new DoubleCounter() { + @Override + public MetricName getName() { + return null; + } + + @Override + public void increment() { + + } + + @Override + public void incrementBy(double inc) { + + } + + @Override + public void incrementBy(double inc, Map attributes) { + + } + + @Override + public void incrementBy(double inc, Map attributes, ThreadContext threadContext) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleGauge.java b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleGauge.java new file mode 100644 index 0000000000000..002d0a854e0e2 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleGauge.java @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +/** + * Record non-additive double values + */ +public interface DoubleGauge extends Instrument { + void record(double value); + + void record(double value, Map attributes); + + DoubleGauge NOOP = new DoubleGauge() { + @Override + public MetricName getName() { + return null; + } + + @Override + public void record(double value) { + + } + + @Override + public void record(double value, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleHistogram.java b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleHistogram.java new file mode 100644 index 0000000000000..88feacaffe42e --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleHistogram.java @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public interface DoubleHistogram extends Instrument { + void record(double value); + + void record(double value, Map attributes); + + void record(double value, Map attributes, ThreadContext threadContext); + + DoubleHistogram NOOP = new DoubleHistogram() { + @Override + public MetricName getName() { + return null; + } + + @Override + public void record(double value) { + + } + + @Override + public void record(double value, Map attributes) { + + } + + @Override + public void record(double value, Map attributes, ThreadContext threadContext) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleUpDownCounter.java b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleUpDownCounter.java new file mode 100644 index 0000000000000..4b88d3fcc9d3b --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleUpDownCounter.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public interface DoubleUpDownCounter extends Instrument { + // inc may be negative + void add(double inc); + + void add(double inc, Map attributes); + + void add(double inc, Map attributes, ThreadContext threadContext); + + DoubleUpDownCounter NOOP = new DoubleUpDownCounter() { + @Override + public MetricName getName() { + return null; + } + + @Override + public void add(double inc) { + + } + + @Override + public void add(double inc, Map attributes) { + + } + + @Override + public void add(double inc, Map attributes, ThreadContext threadContext) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/Instrument.java b/server/src/main/java/org/elasticsearch/telemetry/metric/Instrument.java new file mode 100644 index 0000000000000..0b479ca59c513 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/Instrument.java @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.telemetry.MetricName; + +public interface Instrument { + MetricName getName(); +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/LongCounter.java b/server/src/main/java/org/elasticsearch/telemetry/metric/LongCounter.java new file mode 100644 index 0000000000000..02c454869f759 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/LongCounter.java @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public interface LongCounter extends Instrument { + void increment(); + + void incrementBy(long inc); + + void incrementBy(long inc, Map attributes); + + void incrementBy(double inc, Map attributes, ThreadContext threadContext); + + LongCounter NOOP = new LongCounter() { + @Override + public MetricName getName() { + return null; + } + + @Override + public void increment() { + + } + + @Override + public void incrementBy(long inc) { + + } + + @Override + public void incrementBy(long inc, Map attributes) { + + } + + @Override + public void incrementBy(double inc, Map attributes, ThreadContext threadContext) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/LongGauge.java b/server/src/main/java/org/elasticsearch/telemetry/metric/LongGauge.java new file mode 100644 index 0000000000000..79f382a3c06ce --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/LongGauge.java @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +/** + * Record non-additive double values + */ +public interface LongGauge extends Instrument { + void record(long value); + + void record(long value, Map attributes); + + LongGauge NOOP = new LongGauge() { + @Override + public MetricName getName() { + return null; + } + + @Override + public void record(long value) { + + } + + @Override + public void record(long value, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/LongHistogram.java b/server/src/main/java/org/elasticsearch/telemetry/metric/LongHistogram.java new file mode 100644 index 0000000000000..844835564a574 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/LongHistogram.java @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public interface LongHistogram extends Instrument { + void record(long value); + + void record(long value, Map attributes); + + void record(long value, Map attributes, ThreadContext threadContext); + + LongHistogram NOOP = new LongHistogram() { + @Override + public MetricName getName() { + return null; + } + + @Override + public void record(long value) { + + } + + @Override + public void record(long value, Map attributes) { + + } + + @Override + public void record(long value, Map attributes, ThreadContext threadContext) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/LongUpDownCounter.java b/server/src/main/java/org/elasticsearch/telemetry/metric/LongUpDownCounter.java new file mode 100644 index 0000000000000..92fc17ec2074b --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/LongUpDownCounter.java @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.MetricName; + +import java.util.Map; + +public interface LongUpDownCounter extends Instrument { + void add(long inc); + + void add(long inc, Map attributes); + + void add(long inc, Map attributes, ThreadContext threadContext); + + LongUpDownCounter NOOP = new LongUpDownCounter() { + @Override + public MetricName getName() { + return null; + } + + @Override + public void add(long inc) { + + } + + @Override + public void add(long inc, Map attributes) { + + } + + @Override + public void add(long inc, Map attributes, ThreadContext threadContext) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/Metric.java b/server/src/main/java/org/elasticsearch/telemetry/metric/Metric.java new file mode 100644 index 0000000000000..40da909c24f91 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/Metric.java @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import org.elasticsearch.telemetry.MetricName; + +public interface Metric { + DoubleCounter registerDoubleCounter(MetricName name, String description, T unit); + + DoubleUpDownCounter registerDoubleUpDownCounter(MetricName name, String description, T unit); + + DoubleGauge registerDoubleGauge(MetricName name, String description, T unit); + + DoubleHistogram registerDoubleHistogram(MetricName name, String description, T unit); + + LongCounter registerLongCounter(MetricName name, String description, T unit); + + LongUpDownCounter registerLongUpDownCounter(MetricName name, String description, T unit); + + LongGauge registerLongGauge(MetricName name, String description, T unit); + + LongHistogram registerLongHistogram(MetricName name, String description, T unit); + + Metric NOOP = new Metric() { + @Override + public DoubleCounter registerDoubleCounter(MetricName name, String description, T unit) { + return DoubleCounter.NOOP; + } + + public DoubleUpDownCounter registerDoubleUpDownCounter(MetricName name, String description, T unit) { + return DoubleUpDownCounter.NOOP; + } + + @Override + public DoubleGauge registerDoubleGauge(MetricName name, String description, T unit) { + return DoubleGauge.NOOP; + } + + @Override + public DoubleHistogram registerDoubleHistogram(MetricName name, String description, T unit) { + return DoubleHistogram.NOOP; + } + + @Override + public LongCounter registerLongCounter(MetricName name, String description, T unit) { + return LongCounter.NOOP; + } + + public LongUpDownCounter registerLongUpDownCounter(MetricName name, String description, T unit) { + return LongUpDownCounter.NOOP; + } + + @Override + + public LongGauge registerLongGauge(MetricName name, String description, T unit) { + return LongGauge.NOOP; + } + + @Override + + public LongHistogram registerLongHistogram(MetricName name, String description, T unit) { + return LongHistogram.NOOP; + } + }; +} diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java index 84a7f752d56a4..1036ebc000915 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.telemetry.metric.Metric; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.xpack.security.audit.AuditLevel; @@ -80,7 +81,13 @@ public void testDynamicFilterSettings() throws Exception { settingsBuilder.put(startupFilterSettings); settingsBuilder.put(updateFilterSettings); // reference audit trail containing all filters - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); final String expected = auditTrail.eventFilterPolicyRegistry.toString(); // update settings on internal cluster updateClusterSettings(Settings.builder().put(updateFilterSettings)); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 39b62da317b9b..679032d7cdc80 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -650,7 +650,8 @@ public Collection createComponents( xContentRegistry, environment, nodeEnvironment.nodeMetadata(), - expressionResolver + expressionResolver, + telemetryProvider ); } catch (final Exception e) { throw new IllegalStateException("security initialization failed", e); @@ -667,7 +668,8 @@ Collection createComponents( NamedXContentRegistry xContentRegistry, Environment environment, NodeMetadata nodeMetadata, - IndexNameExpressionResolver expressionResolver + IndexNameExpressionResolver expressionResolver, + TelemetryProvider telemetryProvider ) throws Exception { logger.info("Security is {}", enabled ? "enabled" : "disabled"); if (enabled == false) { @@ -706,7 +708,7 @@ Collection createComponents( // audit trail service construction final AuditTrail auditTrail = XPackSettings.AUDIT_ENABLED.get(settings) - ? new LoggingAuditTrail(settings, clusterService, threadPool) + ? new LoggingAuditTrail(settings, clusterService, threadPool, telemetryProvider.getMetric()) : null; final AuditTrailService auditTrailService = new AuditTrailService(auditTrail, getLicenseState()); components.add(auditTrailService); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java index 1b4f4b891c7ea..7e3f9ac932580 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java @@ -35,6 +35,9 @@ import org.elasticsearch.node.Node; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.tasks.Task; +import org.elasticsearch.telemetry.MetricName; +import org.elasticsearch.telemetry.metric.LongCounter; +import org.elasticsearch.telemetry.metric.Metric; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportResponse; @@ -381,6 +384,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { private final ThreadContext threadContext; private final SecurityContext securityContext; final EventFilterPolicyRegistry eventFilterPolicyRegistry; + private final LongCounter authenticationFailedCounter; // package for testing volatile EnumSet events; boolean includeRequestBody; @@ -392,11 +396,16 @@ public String name() { return NAME; } - public LoggingAuditTrail(Settings settings, ClusterService clusterService, ThreadPool threadPool) { - this(settings, clusterService, LogManager.getLogger(LoggingAuditTrail.class), threadPool.getThreadContext()); + public LoggingAuditTrail(Settings settings, ClusterService clusterService, ThreadPool threadPool, Metric metric) { + this(settings, clusterService, LogManager.getLogger(LoggingAuditTrail.class), threadPool.getThreadContext(), metric); } - LoggingAuditTrail(Settings settings, ClusterService clusterService, Logger logger, ThreadContext threadContext) { + LoggingAuditTrail(Settings settings, ClusterService clusterService, Logger logger, ThreadContext threadContext, Metric metric) { + this.authenticationFailedCounter = metric.registerLongCounter( + new MetricName("authenticationFailed"), + "authenticationFailed", + new Object() + ); this.logger = logger; this.events = parse(INCLUDE_EVENT_SETTINGS.get(settings), EXCLUDE_EVENT_SETTINGS.get(settings)); this.includeRequestBody = INCLUDE_REQUEST_BODY.get(settings); @@ -532,6 +541,8 @@ public void authenticationSuccess(String requestId, Authentication authenticatio @Override public void anonymousAccessDenied(String requestId, String action, TransportRequest transportRequest) { if (events.contains(ANONYMOUS_ACCESS_DENIED)) { + authenticationFailedCounter.increment(); + final Optional indices = Optional.ofNullable(indices(transportRequest)); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices, Optional.of(action))) == false) { @@ -552,6 +563,8 @@ public void anonymousAccessDenied(String requestId, String action, TransportRequ public void anonymousAccessDenied(String requestId, HttpPreRequest request) { if (events.contains(ANONYMOUS_ACCESS_DENIED) && eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false) { + authenticationFailedCounter.increment(); + new LogEntryBuilder().with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "anonymous_access_denied") .withRestUriAndMethod(request) @@ -564,7 +577,10 @@ public void anonymousAccessDenied(String requestId, HttpPreRequest request) { @Override public void authenticationFailed(String requestId, AuthenticationToken token, String action, TransportRequest transportRequest) { + if (events.contains(AUTHENTICATION_FAILED)) { + authenticationFailedCounter.increment(); + final Optional indices = Optional.ofNullable(indices(transportRequest)); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(token), Optional.empty(), indices, Optional.of(action))) == false) { @@ -587,7 +603,10 @@ public void authenticationFailed(String requestId, AuthenticationToken token, St @Override public void authenticationFailed(String requestId, HttpPreRequest request) { + if (events.contains(AUTHENTICATION_FAILED) && eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false) { + authenticationFailedCounter.increment(); + new LogEntryBuilder().with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") .withRestUriAndMethod(request) @@ -600,7 +619,10 @@ public void authenticationFailed(String requestId, HttpPreRequest request) { @Override public void authenticationFailed(String requestId, String action, TransportRequest transportRequest) { + if (events.contains(AUTHENTICATION_FAILED)) { + authenticationFailedCounter.increment(); + final Optional indices = Optional.ofNullable(indices(transportRequest)); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices, Optional.of(action))) == false) { @@ -619,9 +641,12 @@ public void authenticationFailed(String requestId, String action, TransportReque @Override public void authenticationFailed(String requestId, AuthenticationToken token, HttpPreRequest request) { + if (events.contains(AUTHENTICATION_FAILED) && eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(token), Optional.empty(), Optional.empty(), Optional.empty())) == false) { + authenticationFailedCounter.increment(); + final LogEntryBuilder logEntryBuilder = new LogEntryBuilder().with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") .with(PRINCIPAL_FIELD_NAME, token.principal()) @@ -645,6 +670,8 @@ public void authenticationFailed( TransportRequest transportRequest ) { if (events.contains(REALM_AUTHENTICATION_FAILED)) { + authenticationFailedCounter.increment(); + final Optional indices = Optional.ofNullable(indices(transportRequest)); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(token), Optional.of(realm), indices, Optional.of(action))) == false) { @@ -669,6 +696,8 @@ public void authenticationFailed(String requestId, String realm, AuthenticationT if (events.contains(REALM_AUTHENTICATION_FAILED) && eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(token), Optional.of(realm), Optional.empty(), Optional.empty())) == false) { + authenticationFailedCounter.increment(); + new LogEntryBuilder().with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "realm_authentication_failed") .with(REALM_FIELD_NAME, realm) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 88725e015e511..f082cfa443acb 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -53,7 +53,7 @@ import org.elasticsearch.rest.RestHandler; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.script.ScriptService; -import org.elasticsearch.telemetry.tracing.Tracer; +import org.elasticsearch.telemetry.TelemetryProvider; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.IndexSettingsModule; import org.elasticsearch.test.MockLogAppender; @@ -202,6 +202,8 @@ private Collection createComponentsUtil(Settings settings) throws Except Client client = mock(Client.class); when(client.threadPool()).thenReturn(threadPool); when(client.settings()).thenReturn(settings); + TelemetryProvider telemetryProvider = mock(TelemetryProvider.class); + when(telemetryProvider.getMetric()).thenReturn(null); return security.createComponents( client, threadPool, @@ -211,7 +213,8 @@ private Collection createComponentsUtil(Settings settings) throws Except xContentRegistry(), env, nodeMetadata, - TestIndexNameExpressionResolver.newInstance(threadContext) + TestIndexNameExpressionResolver.newInstance(threadContext), + telemetryProvider ); } @@ -774,7 +777,7 @@ public void testSecurityRestHandlerInterceptorCanBeInstalled() throws IllegalAcc null, usageService, null, - Tracer.NOOP, + TelemetryProvider.NOOP, mock(ClusterService.class), List.of(), RestExtension.allowAll() diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java index 9da5de1a5dd9b..14748c47b5bf2 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java @@ -27,6 +27,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.http.HttpRequest; import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.telemetry.metric.Metric; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.test.rest.FakeRestRequest.Builder; @@ -149,7 +150,13 @@ public void testPolicyDoesNotMatchNullValuesInEvent() throws Exception { final List filteredActions = randomNonEmptyListOfFilteredActions(); settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.actionsPolicy.actions", filteredActions); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); // user field matches assertTrue( @@ -325,7 +332,13 @@ public void testSingleCompletePolicyPredicate() throws Exception { final List filteredActions = randomNonEmptyListOfFilteredActions(); settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.completeFilterPolicy.actions", filteredActions); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); // all fields match Random random = random(); @@ -521,7 +534,13 @@ public void testSingleCompleteWithEmptyFieldPolicyPredicate() throws Exception { final List filteredActions = randomNonEmptyListOfFilteredActions(); settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.completeFilterPolicy.actions", filteredActions); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); // all fields match Random random = random(); @@ -734,7 +753,13 @@ public void testTwoPolicyPredicatesWithMissingFields() throws Exception { final List filteredIndices = randomNonEmptyListOfFilteredNames(); settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.secondPolicy.indices", filteredIndices); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); final User unfilteredUser; unfilteredUser = new User(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8)); @@ -870,7 +895,13 @@ public void testUsersFilter() throws Exception { final MockToken filteredToken = new MockToken(randomFrom(allFilteredUsers)); final MockToken unfilteredToken = new MockToken(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 4)); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); // anonymous accessDenied auditTrail.anonymousAccessDenied(randomAlphaOfLength(8), "_action", request); @@ -1223,7 +1254,13 @@ public void testRealmsFilter() throws Exception { : new MockIndicesRequest(threadContext, new String[] { "idx1", "idx2" }); final MockToken authToken = new MockToken("token1"); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); // anonymous accessDenied auditTrail.anonymousAccessDenied(randomAlphaOfLength(8), "_action", request); @@ -1730,7 +1767,13 @@ public void testRolesFilter() throws Exception { : new MockIndicesRequest(threadContext, new String[] { "idx1", "idx2" }); final MockToken authToken = new MockToken("token1"); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); // anonymous accessDenied auditTrail.anonymousAccessDenied(randomAlphaOfLength(8), "_action", request); @@ -2047,7 +2090,13 @@ public void testIndicesFilter() throws Exception { final MockToken authToken = new MockToken("token1"); final TransportRequest noIndexRequest = new MockRequest(threadContext); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); // anonymous accessDenied auditTrail.anonymousAccessDenied(randomAlphaOfLength(8), "_action", noIndexRequest); @@ -2517,7 +2566,13 @@ public void testActionsFilter() throws Exception { ? new MockRequest(threadContext) : new MockIndicesRequest(threadContext, new String[] { "idx1", "idx2" }); final MockToken authToken = new MockToken("token1"); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final LoggingAuditTrail auditTrail = new LoggingAuditTrail( + settingsBuilder.build(), + clusterService, + logger, + threadContext, + Metric.NOOP + ); final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); // anonymous accessDenied @@ -2752,7 +2807,8 @@ public void testRemoveIgnoreFilter() throws IllegalAccessException, IOException Settings.builder().put(settings).build(), clusterService, logger, - threadContext + threadContext, + Metric.NOOP ); final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java index 9ec2e8be383b6..0b108217d6ec6 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java @@ -38,6 +38,7 @@ import org.elasticsearch.core.Tuple; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.tasks.Task; +import org.elasticsearch.telemetry.metric.Metric; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.test.rest.FakeRestRequest.Builder; @@ -353,7 +354,7 @@ public void init() throws Exception { ); } logger = CapturingLogger.newCapturingLogger(randomFrom(Level.OFF, Level.FATAL, Level.ERROR, Level.WARN, Level.INFO), patternLayout); - auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext, Metric.NOOP); apiKeyService = new ApiKeyService( settings, Clock.systemUTC(), @@ -2708,7 +2709,7 @@ public void testCrossClusterAccessAuthenticationSuccessTransport() throws Except public void testRequestsWithoutIndices() throws Exception { settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.include", "_all").build(); - auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext, Metric.NOOP); final AuthorizationInfo authorizationInfo = () -> Collections.singletonMap( PRINCIPAL_ROLES_FIELD_NAME, new String[] { randomAlphaOfLengthBetween(1, 6) } @@ -2796,7 +2797,7 @@ private void updateLoggerSettings(Settings settings) { this.settings = settings; // either create a new audit trail or update the settings on the existing one if (randomBoolean()) { - this.auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + this.auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext, Metric.NOOP); } else { this.clusterService.getClusterSettings().applySettings(settings); }