Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.splunk.opentelemetry.appd;

import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getConfig;
import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getResource;
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
import static io.opentelemetry.semconv.incubating.DeploymentIncubatingAttributes.DEPLOYMENT_ENVIRONMENT_NAME;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.tooling.BeforeAgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.resources.Resource;
import java.util.logging.Logger;

@AutoService(BeforeAgentListener.class)
public class AppdBeforeAgentListener implements BeforeAgentListener {
private static final Logger logger = Logger.getLogger(AppdBeforeAgentListener.class.getName());

@Override
public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
ConfigProperties config = getConfig(autoConfiguredOpenTelemetrySdk);
if (!config.getBoolean("cisco.ctx.enabled", false)) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] Because we need to check "cisco.ctx.enabled" here, there was no point in getting rid of AppdBonusConfigurationCustomizerProvider class which also checks this property and adds appropriate propagator and span processor.

return;
}

Resource resource = getResource(autoConfiguredOpenTelemetrySdk);
logger.fine(() -> "Setting up AppdBonusPropagator with resource: " + resource);

AppdBonusPropagator appdBonusPropagator = AppdBonusPropagator.getInstance();
Copy link
Copy Markdown
Contributor Author

@robsunday robsunday Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] This will also run when environment variables based configuration is in use. In such a case these propagator's properties are already set by AppdBonusCustomizer. If you think this is an issue I can cleanup AppdBunusCustomizer . This may make sense only if AppdBonusPropagator and span processor are long term solution, not a temporary one.

appdBonusPropagator.setEnvironmentName(resource.getAttribute(DEPLOYMENT_ENVIRONMENT_NAME));
appdBonusPropagator.setServiceName(resource.getAttribute(SERVICE_NAME));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.splunk.opentelemetry.appd;

import static io.opentelemetry.sdk.autoconfigure.AdditionalPropertiesUtil.getAdditionalPropertyOrDefault;

import com.google.auto.service.AutoService;
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizer;
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PropagatorModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanProcessorModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProviderModel;
import java.util.Map;

@AutoService(DeclarativeConfigurationCustomizerProvider.class)
public final class AppdBonusConfigurationCustomizerProvider
implements DeclarativeConfigurationCustomizerProvider {

private static final String CONFIG_CISCO_CTX_ENABLED = "cisco.ctx.enabled";
private static final String DEFAULT_PROPAGATORS = "tracecontext,baggage";

@Override
public void customize(DeclarativeConfigurationCustomizer autoConfiguration) {
autoConfiguration.addModelCustomizer(
model -> {
if (model.getInstrumentationDevelopment() == null
|| model.getInstrumentationDevelopment().getJava() == null) {
return model;
}
Map<String, Object> properties =
model.getInstrumentationDevelopment().getJava().getAdditionalProperties();

if (isFeatureEnabled(model, properties)) {
if (maybeAddAppdBonusPropagator(model)) {
// Appd propagator has been added so add also a corresponding Appd span processor
SpanProcessorModel appdSpanProcessorModel =
new SpanProcessorModel()
.withAdditionalProperty(AppdBonusSpanProcessorComponentProvider.NAME, null);
if (model.getTracerProvider() == null) {
model.withTracerProvider(new TracerProviderModel());
}
model.getTracerProvider().getProcessors().add(appdSpanProcessorModel);
}
}

return model;
});
}

private static boolean isFeatureEnabled(
OpenTelemetryConfigurationModel model, Map<String, Object> properties) {
return getAdditionalPropertyOrDefault(properties, CONFIG_CISCO_CTX_ENABLED, false);
}

private static boolean canAddPropagator(String compositeList) {
for (String propagatorNames : compositeList.split(",")) {
if (propagatorNames.trim().equals("none")) {
return false;
}
}
return true;
}

private static boolean maybeAddAppdBonusPropagator(OpenTelemetryConfigurationModel model) {
if (model.getPropagator() == null) {
model.withPropagator(new PropagatorModel());
}

String compositeList = model.getPropagator().getCompositeList();
if (compositeList == null) {
compositeList = "";
} else if (!canAddPropagator(compositeList)) {
return false;
}
Comment on lines +89 to +94
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The else makes it unnecessarily complex. In the null case, canAddPropagator() will return false anyway.

Suggested change
if (compositeList == null) {
compositeList = "";
} else if (!canAddPropagator(compositeList)) {
return false;
}
if (compositeList == null) {
compositeList = "";
}
if (!canAddPropagator(compositeList)) {
return false;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did even more refactoring and then merged it. Let me know if you don't like it. I can always create another PR


// Possible duplicates are handled by the upstream
compositeList =
AppdBonusPropagator.NAME
+ ","
+ (compositeList.isEmpty() ? DEFAULT_PROPAGATORS : compositeList);

model.getPropagator().withCompositeList(compositeList);

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.splunk.opentelemetry.appd;

import com.google.auto.service.AutoService;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;

@SuppressWarnings("rawtypes")
@AutoService(ComponentProvider.class)
public class AppdBonusPropagatorComponentProvider implements ComponentProvider<TextMapPropagator> {
@Override
public Class<TextMapPropagator> getType() {
return TextMapPropagator.class;
}

@Override
public String getName() {
return AppdBonusPropagator.NAME;
}

@Override
public TextMapPropagator create(DeclarativeConfigProperties propagatorProperties) {
return AppdBonusPropagator.getInstance();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.splunk.opentelemetry.appd;

import com.google.auto.service.AutoService;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;
import io.opentelemetry.sdk.extension.incubator.trace.OnStartSpanProcessor;
import io.opentelemetry.sdk.trace.SpanProcessor;

@SuppressWarnings("rawtypes")
@AutoService(ComponentProvider.class)
public class AppdBonusSpanProcessorComponentProvider implements ComponentProvider<SpanProcessor> {
static final String NAME = "appd-bonus";
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] YAML naming convention prefers using underscore, but for consistency with environment variables based config I used hyphen in the name.


@Override
public Class<SpanProcessor> getType() {
return SpanProcessor.class;
}

@Override
public String getName() {
return NAME;
}

@Override
public SpanProcessor create(DeclarativeConfigProperties processorProperties) {
return OnStartSpanProcessor.create(new AppdBonusSpanProcessor());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.splunk.opentelemetry.appd;

import static com.splunk.opentelemetry.appd.AppdBonusPropagator.CTX_HEADER_ENV;
import static com.splunk.opentelemetry.appd.AppdBonusPropagator.CTX_HEADER_SERVICE;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

import com.splunk.opentelemetry.testing.declarativeconfig.DeclarativeConfigTestUtil;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;

class AppdBeforeAgentListenerTest {
@RegisterExtension final AutoCleanupExtension autoCleanup = AutoCleanupExtension.create();

@Test
void shouldSetPropagatorProperties(@TempDir Path tempDir) throws IOException {
// given
AppdBeforeAgentListener agentListener = new AppdBeforeAgentListener();
var yaml =
"""
file_format: "1.0-rc.1"
resource:
attributes:
- name: service.name
value: test-service
- name: deployment.environment.name
value: test-deployment-env
instrumentation/development:
java:
cisco:
ctx:
enabled: true
""";
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
DeclarativeConfigTestUtil.createAutoConfiguredSdk(yaml, tempDir);
autoCleanup.deferCleanup(autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk());

// when
agentListener.beforeAgent(autoConfiguredOpenTelemetrySdk);

// then
AppdBonusPropagator propagator = AppdBonusPropagator.getInstance();
Map<String, String> carrier = new HashMap<>();
Context context = Context.current();
propagator.inject(
context,
carrier,
(map, key, value) -> {
if (map != null) {
map.put(key, value);
}
});

assertThat(carrier.get(CTX_HEADER_SERVICE)).isEqualTo("test-service");
assertThat(carrier.get(CTX_HEADER_ENV)).isEqualTo("test-deployment-env");
}
}
Loading