-
Notifications
You must be signed in to change notification settings - Fork 71
feat: OpenTelemetry standard attributes #4456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v3.x.x
Are you sure you want to change the base?
Changes from all commits
bcee5f4
0e0b568
5243473
4fbb002
1acb61c
2d03079
976bb8b
46fb08b
7d12592
1fa20d2
06206ef
d9d2541
d0e1967
9fa1332
245a277
a5e5b46
a65086e
014845a
c42575e
07054ca
f6dfe77
92f6300
c342fc4
94c9370
753c7c1
f4c4416
1015c38
1e2b2fc
a8be34c
86094cb
8dd9c79
49520e1
68680c3
a667103
ba4e739
f80fba5
c7c98ba
ed9c5c3
ca9d625
05b870d
80874a4
5c70cf5
0a4a818
1991a26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| /* | ||
| * This program and the accompanying materials are made available under the terms of the | ||
| * Eclipse Public License v2.0 which accompanies this distribution, and is available at | ||
| * https://www.eclipse.org/legal/epl-v20.html | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Copyright Contributors to the Zowe Project. | ||
| */ | ||
|
|
||
| package org.zowe.apiml.product.opentelemetry; | ||
|
|
||
| import io.opentelemetry.api.common.Attributes; | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import javax.annotation.Nonnull; | ||
|
|
||
| @ConditionalOnExpression("#{!T(org.zowe.apiml.product.zos.ZosSystemInformation).isRunningOnZos()}") | ||
| @Component | ||
| public class ApimlNonZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryResourceProvider { | ||
|
|
||
| @Override | ||
| @Nonnull | ||
| public Attributes calculateAttributes() { | ||
| return Attributes.empty(); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| /* | ||
| * This program and the accompanying materials are made available under the terms of the | ||
| * Eclipse Public License v2.0 which accompanies this distribution, and is available at | ||
| * https://www.eclipse.org/legal/epl-v20.html | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Copyright Contributors to the Zowe Project. | ||
| */ | ||
|
|
||
| package org.zowe.apiml.product.opentelemetry; | ||
|
|
||
| import io.opentelemetry.api.common.Attributes; | ||
| import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; | ||
| import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; | ||
| import io.opentelemetry.sdk.resources.Resource; | ||
|
|
||
| import javax.annotation.Nonnull; | ||
|
|
||
| public abstract class ApimlOpenTelemetryResourceProvider implements ResourceProvider { | ||
|
|
||
| public abstract @Nonnull Attributes calculateAttributes(); | ||
|
|
||
| @Override | ||
| public Resource createResource(@Nonnull ConfigProperties config) { | ||
| var attributesBuilder = Attributes.builder(); | ||
|
|
||
| attributesBuilder.putAll(calculateAttributes()); | ||
| return Resource.create(attributesBuilder.build()); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| /* | ||
| * This program and the accompanying materials are made available under the terms of the | ||
| * Eclipse Public License v2.0 which accompanies this distribution, and is available at | ||
| * https://www.eclipse.org/legal/epl-v20.html | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Copyright Contributors to the Zowe Project. | ||
| */ | ||
|
|
||
| package org.zowe.apiml.product.opentelemetry; | ||
|
|
||
| import io.opentelemetry.api.common.Attributes; | ||
| import jakarta.annotation.PostConstruct; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.apache.commons.lang3.StringUtils; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||
| import org.springframework.stereotype.Component; | ||
| import org.zowe.apiml.product.zos.ZosSystemInformation; | ||
|
|
||
| import javax.annotation.Nonnull; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.Optional; | ||
|
|
||
| import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_JOB_ID; | ||
| import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_JOB_NAME; | ||
| import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_SYSCLONE; | ||
| import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_SYSNAME; | ||
| import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_SYSPLEX; | ||
| import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_USER_ID; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| @ConditionalOnMissingBean(ApimlNonZosOpenTelemetryResourceProvider.class) | ||
| @Slf4j | ||
| public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryResourceProvider { | ||
|
|
||
| private final ZosSystemInformation zosSystemInformation; | ||
|
|
||
| @Value("${otel.resource.attributes.deployment.environment.name:#{null}}") | ||
| private String environmentName; | ||
|
|
||
| @Value("${otel.resource.attributes.service.namespace:#{null}}") | ||
| private String serviceNamespace; | ||
|
|
||
| @Value("${otel.resource.attributes.service.name:#{null}}") | ||
| private String serviceName; | ||
|
|
||
| @Value("${apiml.service.apimlId:#{null}}") | ||
| private String apimlId; | ||
|
|
||
| @Value("${apiml.service.port:10010}") | ||
pablocarle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| private int port; | ||
|
|
||
| @Value("${otel.resource.attributes.zos.sysplex.name:#{null}}") | ||
| private String sysplexName; | ||
|
|
||
| @Value("${otel.resource.attributes.mainframe.lpar.name:#{null}}") | ||
| private String lparName; | ||
|
|
||
| @Value("${otel.resource.attributes.zos.smf.id:#{null}}") | ||
| private String smfId; | ||
|
|
||
| @PostConstruct | ||
| void afterPropertiesSet() { | ||
| log.debug("Using ZOS OpenTelemetry resource provider"); | ||
| } | ||
|
|
||
| @SuppressWarnings("null") | ||
| @Override | ||
| @Nonnull | ||
| public Attributes calculateAttributes() { | ||
| var attributesBuilder = Attributes.builder(); | ||
|
|
||
| var zosAttributes = zosSystemInformation.get(); | ||
|
|
||
| if (StringUtils.isBlank(serviceNamespace)) { | ||
| var generatedDefaultNamespace = generateServiceNamespace(zosAttributes); | ||
| attributesBuilder.put("service.namespace", generatedDefaultNamespace); | ||
| log.debug("service.namespace not provided in configuration, using generated default {}", generatedDefaultNamespace); | ||
| } | ||
|
|
||
| if (StringUtils.isBlank(serviceName)) { | ||
| var generatedServiceName = generateServiceName(zosAttributes); | ||
| attributesBuilder.put("service.name", generatedServiceName); | ||
| log.debug("service.name not provided in configuration, using generated default {}", generatedServiceName); | ||
| } | ||
|
|
||
| if (StringUtils.isBlank(sysplexName)) { | ||
| var sysplexName = zosAttributes.get(ZOS_SYSPLEX); | ||
| if (sysplexName != null && StringUtils.isNotBlank(sysplexName.toString())) { | ||
| log.debug("zos.sysplex.name not provided in configuration, using system-obtained {}", sysplexName); | ||
| } else { | ||
| log.debug("zos.sysplex.name not provided in configuration. Could not determine name from system"); | ||
| } | ||
| } | ||
|
|
||
| if (StringUtils.isBlank(lparName)) { | ||
| var lparName = zosAttributes.get(ZOS_SYSNAME); | ||
| if (lparName != null && StringUtils.isNotBlank(lparName.toString())) { | ||
| log.debug("mainframe.lpar.name not provided in configuration, using system-obtained {}", lparName); | ||
| } else { | ||
| log.debug("mainframe.lpar.name not provided in configuration. Could not determine name from system"); | ||
| } | ||
| } | ||
|
|
||
| if (StringUtils.isBlank(smfId)) { | ||
| var smfId = zosAttributes.get(ZOS_SYSCLONE); | ||
| if (smfId != null && StringUtils.isNotBlank(smfId.toString())) { | ||
| log.debug("zos.smf.id not provided in configuration, using system-obtained {}", smfId); | ||
| } else { | ||
| log.debug("zos.smf.id not provided in configuration. Could not determine ID from system"); | ||
| } | ||
| } | ||
|
Comment on lines
+92
to
+117
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are checked for value but never set to the attributes.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added these only to show in the logs if such overrides are happening, if there's no value in the logs we can remove it. We don't need to explicitly set them because they are otel.* properties which are automatically picked up by OpenTelemetry implementation.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regardless it is configured or discovered, they are not set in resource attributes and are not present in the exported data. |
||
|
|
||
| Optional.ofNullable(zosAttributes.get(ZOS_JOB_ID)) | ||
| .map(String::valueOf) | ||
| .filter(StringUtils::isNotBlank) | ||
| .ifPresent(zosJobId -> attributesBuilder.put(ZosAttributes.ZOS_JOBID, zosJobId)); | ||
|
Comment on lines
+119
to
+122
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The constant naming is confusing: ZOS_JOB_ID vs ZOS_JOBID
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can rename them, but they are different values anyway, one is zos.jobid from the system information and the other one is process.zos.jobid for the target OpenTelemetry attribute
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is confusing, as you say they mean something totally different and yet named so similarly. Maybe "OTEL_ZOS_JOBNAME" or "OtelZosAttributes.ZOS_JOBNAME"?
pablocarle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Optional.ofNullable(zosAttributes.get(ZOS_JOB_NAME)) | ||
| .map(String::valueOf) | ||
| .filter(StringUtils::isNotBlank) | ||
| .ifPresent(zosJobName -> attributesBuilder.put(ZosAttributes.ZOS_JOBNAME, zosJobName)); | ||
|
|
||
| Optional.ofNullable(zosAttributes.get(ZOS_USER_ID)) | ||
| .map(String::valueOf) | ||
| .filter(StringUtils::isNotBlank) | ||
| .ifPresent(zosUserId -> attributesBuilder.put(ZosAttributes.ZOS_USERID, zosUserId)); | ||
|
|
||
| return attributesBuilder.build(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are missing the service.instance.id fallback. Or are we ok with the generated uuid?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought we were ok with the generated one. If we go the fallback route, we need to define some boolean to let the user decide.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I got the impression that you prefer the lpar+port over the uuid. |
||
| } | ||
|
|
||
| private String generateServiceName(Map<String,Object> zosAttributes) { | ||
| var systemName = StringUtils.isBlank(apimlId) ? zosAttributes.get(ZOS_SYSPLEX) : apimlId; | ||
|
|
||
| return systemName + ":" + port; | ||
| } | ||
|
|
||
| private String generateServiceNamespace(Map<String,Object> zosAttributes) { | ||
| return "apiml:" + generateServiceName(zosAttributes); | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this bring additional value over having only the service.name if no namespace is provided?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is simply my suggestion to have defaults that make sense. It's open to suggestions. I expect most users will set these two values.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it makes sense when it is only service name with a prefix. Then it has no additional value over the service name in therm of identifying the installation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The default namespace should be more of a corner case rather than the norm. We should aim at users configuring a namespace always with meaning in the configuration if they enable OpenTelemetry. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /* | ||
| * This program and the accompanying materials are made available under the terms of the | ||
| * Eclipse Public License v2.0 which accompanies this distribution, and is available at | ||
| * https://www.eclipse.org/legal/epl-v20.html | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Copyright Contributors to the Zowe Project. | ||
| */ | ||
|
|
||
| package org.zowe.apiml.product.opentelemetry; | ||
|
|
||
| /** | ||
| * Set of attributes set by API ML | ||
| */ | ||
| public class ZosAttributes { | ||
pablocarle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| public static final String ZOS_JOBNAME = "process.zos.jobname"; | ||
| public static final String ZOS_USERID = "process.zos.userid"; | ||
| public static final String ZOS_JOBID = "process.zos.jobid"; | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /* | ||
| * This program and the accompanying materials are made available under the terms of the | ||
| * Eclipse Public License v2.0 which accompanies this distribution, and is available at | ||
| * https://www.eclipse.org/legal/epl-v20.html | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Copyright Contributors to the Zowe Project. | ||
| */ | ||
|
|
||
| package org.zowe.apiml.product.opentelemetry; | ||
|
|
||
| import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.extension.ExtendWith; | ||
| import org.mockito.junit.jupiter.MockitoExtension; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| import static org.mockito.Mockito.mock; | ||
|
|
||
| @ExtendWith(MockitoExtension.class) | ||
| class ApimlNonZosOpenTelemetryResourceProviderTest { | ||
|
|
||
| private ApimlNonZosOpenTelemetryResourceProvider resourceProvider; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| this.resourceProvider = new ApimlNonZosOpenTelemetryResourceProvider(); | ||
| } | ||
|
|
||
| @Test | ||
| void testCalculateAttributes() { | ||
| var result = resourceProvider.calculateAttributes(); | ||
| assertTrue(result.isEmpty()); | ||
| } | ||
|
|
||
| @Test | ||
| void testCreateResource() { | ||
| var result = resourceProvider.createResource(mock(ConfigProperties.class)); | ||
| assertTrue(result.getAttributes().isEmpty()); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| /* | ||
| * This program and the accompanying materials are made available under the terms of the | ||
| * Eclipse Public License v2.0 which accompanies this distribution, and is available at | ||
| * https://www.eclipse.org/legal/epl-v20.html | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Copyright Contributors to the Zowe Project. | ||
| */ | ||
|
|
||
| package org.zowe.apiml.product.opentelemetry; | ||
|
|
||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.extension.ExtendWith; | ||
| import org.mockito.Mock; | ||
| import org.mockito.junit.jupiter.MockitoExtension; | ||
| import org.springframework.test.util.ReflectionTestUtils; | ||
| import org.zowe.apiml.product.zos.ZosSystemInformation; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| import static io.opentelemetry.api.common.AttributeKey.stringKey; | ||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| import static org.junit.jupiter.api.Assertions.assertNull; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| @ExtendWith(MockitoExtension.class) | ||
| class ApimlZosOpenTelemetryResourceProviderTest { | ||
|
|
||
| @Mock | ||
| private ZosSystemInformation zosSystemInformation; | ||
|
|
||
| private ApimlZosOpenTelemetryResourceProvider resourceProvider; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| resourceProvider = new ApimlZosOpenTelemetryResourceProvider(zosSystemInformation); | ||
| ReflectionTestUtils.setField(resourceProvider, "port", 10010); | ||
| } | ||
|
|
||
| @Nested | ||
| class GivenZosAttributes { | ||
|
|
||
| @Test | ||
| void testCalculateAttributes() { | ||
| when(zosSystemInformation.get()).thenReturn(Map.of( | ||
| "zos.jobid", "JOB12345", | ||
| "zos.jobname", "JOBN12", | ||
| "zos.userid", "ZWEUSR", | ||
| "zos.pid", 123456, | ||
| "zos.sysname", "SYSA", | ||
| "zos.sysclone", "16", | ||
| "zos.sysplex", "PLEX1" | ||
| )); | ||
| var attributes = resourceProvider.calculateAttributes(); | ||
|
|
||
| assertFalse(attributes.isEmpty()); | ||
| assertNull(attributes.get(stringKey("mainframe.lpar.name"))); | ||
|
|
||
| assertEquals("JOB12345", attributes.get(stringKey("process.zos.jobid"))); | ||
| assertEquals("JOBN12", attributes.get(stringKey("process.zos.jobname"))); | ||
| assertEquals("ZWEUSR", attributes.get(stringKey("process.zos.userid"))); | ||
| assertEquals("apiml:PLEX1:10010", attributes.get(stringKey("service.namespace"))); | ||
| assertEquals("PLEX1:10010", attributes.get(stringKey("service.name"))); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it's a matter of deciding if it should be part of the default namespace for instance. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should remove it form here.