diff --git a/package-lock.json b/package-lock.json index 57d50cffb4..727599cd4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2085,6 +2085,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -3812,6 +3813,7 @@ "integrity": "sha512-b7W4snvXYi1T2puUjxamASCCNhNzVSzb/fQUuGSkdjm/AFfJ24jo8kOHQyOcaoArCG71sVQci4vkZaITzl/V1w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cucumber/ci-environment": "10.0.1", "@cucumber/cucumber-expressions": "18.0.1", @@ -4014,6 +4016,7 @@ "integrity": "sha512-659CCFsrsyvuBi/Eix1fnhSheMnojSfnBcqJ3IMPNawx7JlrNJDcXYSSdxcUw3n/nG05P+ptCjmiZY3i14p+tA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cucumber/messages": ">=19.1.4 <29" } @@ -4212,6 +4215,7 @@ "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@cucumber/messages": ">=17.1.1" } @@ -4222,6 +4226,7 @@ "integrity": "sha512-2LzZtOwYKNlCuNf31ajkrekoy2M4z0Z1QGiPH40n4gf5t8VOUFb7m1ojtR4LmGvZxBGvJZP8voOmRqDWzBzYKA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/uuid": "10.0.0", "class-transformer": "0.5.1", @@ -7129,6 +7134,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -7464,6 +7470,7 @@ "integrity": "sha512-pzGXp14KF2Q4CDZGQgPK4l8zEg7i6cNkb+10yc8ZA5K41cLe3ZbWW1YxtY2e/glHauOJwTLSVjH4tiRVtOTizg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "iterare": "1.2.1", "tslib": "2.8.1", @@ -7546,6 +7553,7 @@ "integrity": "sha512-UVSf0yaWFBC2Zn2FOWABXxCnyG8XNIXrNnvTFpbUFqaJu1YDdwJ7wQBBqxq9CtJT7ILqSmfhOU7HS0d/0EAxpw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cors": "2.8.5", "express": "5.0.1", @@ -9528,6 +9536,7 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -9716,6 +9725,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -10760,6 +10770,7 @@ "integrity": "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cluster-key-slot": "1.1.2" }, @@ -12759,6 +12770,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.1.tgz", "integrity": "sha512-y3tBaz+rjspDTylNjAX37jEC3TETEFGNJL6uQDxwF9/8GLLIjW1rvVHlynyuUKMnMr1Roq8jOv3vkopBjC4/VA==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -12840,6 +12852,7 @@ "integrity": "sha512-P9beVR/x06U9rCJzSxtENnOr4BrbJ6VrsrDTc+73TtHv9XHhryXKbjGRB+6oooB2r0G/pQkD/S4dHo/7jUfwFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "^0.16", @@ -13116,6 +13129,7 @@ "integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.41.0", "@typescript-eslint/types": "8.41.0", @@ -13966,6 +13980,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -14095,6 +14110,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14865,6 +14881,7 @@ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -15414,6 +15431,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -17539,7 +17557,8 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/di": { "version": "0.0.1", @@ -18360,6 +18379,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -24356,6 +24376,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -28355,6 +28376,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -30050,6 +30072,7 @@ "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", @@ -30964,6 +30987,7 @@ "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -31426,7 +31450,8 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -32069,6 +32094,7 @@ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -32188,6 +32214,7 @@ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -34704,6 +34731,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -35349,6 +35377,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -35878,6 +35907,7 @@ "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -36446,6 +36476,7 @@ "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -36495,6 +36526,7 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -36586,6 +36618,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -37175,6 +37208,7 @@ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -37469,6 +37503,7 @@ "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -37477,7 +37512,8 @@ "version": "0.15.1", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "packages/auto-configuration-propagators": { "name": "@opentelemetry/auto-configuration-propagators", @@ -39496,7 +39532,12 @@ "@opentelemetry/api": "^1.0.0", "@opentelemetry/contrib-test-utils": "^0.55.0", "@opentelemetry/instrumentation-http": "^0.208.0", - "@opentelemetry/sdk-trace-base": "^2.0.0" + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@types/mocha": "^10.0.10", + "mocha": "^11.7.5", + "nock": "^14.0.10", + "nyc": "^17.1.0", + "ts-node": "^10.9.2" }, "engines": { "node": "^18.19.0 || >=20.6.0" diff --git a/packages/resource-detector-azure/README.md b/packages/resource-detector-azure/README.md index 8305c8ba60..b8c29f3e88 100644 --- a/packages/resource-detector-azure/README.md +++ b/packages/resource-detector-azure/README.md @@ -71,6 +71,17 @@ This package implements Semantic Convention [Version 1.37.0](https://github.com/ | cloud.resource_id | The Azure Resource Manager URI uniquely identifying the Azure Virtual Machine. It typically follows this format: /subscriptions/{subscriptionId}/resourceGroups/{groupName}/providers/Microsoft.Compute/virtualMachines/{vmName}. Value from resourceId key on /metadata/instance/compute request. | | process.pid | The process ID collected from the running process. | +### Azure Container Apps Resource Detector + +| Resource Attribute | Description | +|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| azure.container_app.version | The revision of the Azure Container App. Value of Process Environment Variable `CONTAINER_APP_REVISION`. | +| cloud.platform | The cloud platform. Here, it's always "azure.container_apps". | +| cloud.provider | The cloud service provider. In this context, it's always "azure". | +| azure.container_app.name | The name of the Azure Container App. Value of Process Environment Variable `CONTAINER_APP_NAME`. | +| host.name | The hostname of the container. Value of Process Environment Variable `CONTAINER_APP_HOSTNAME. | +| azure.container_app.instance.id | The replica name of the Azure Container App instance. Value of Process Environment Variable `CONTAINER_APP_REPLICA_NAME`. | + ## Useful links - For more information on OpenTelemetry, visit: diff --git a/packages/resource-detector-azure/package.json b/packages/resource-detector-azure/package.json index c66a2ec921..e8e460889a 100644 --- a/packages/resource-detector-azure/package.json +++ b/packages/resource-detector-azure/package.json @@ -39,7 +39,12 @@ "@opentelemetry/api": "^1.0.0", "@opentelemetry/contrib-test-utils": "^0.55.0", "@opentelemetry/instrumentation-http": "^0.208.0", - "@opentelemetry/sdk-trace-base": "^2.0.0" + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@types/mocha": "^10.0.10", + "mocha": "^11.7.5", + "nock": "^14.0.10", + "nyc": "^17.1.0", + "ts-node": "^10.9.2" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" diff --git a/packages/resource-detector-azure/src/detectors/AzureAppServiceDetector.ts b/packages/resource-detector-azure/src/detectors/AzureAppServiceDetector.ts index ea32b99b0c..63e3e654e9 100644 --- a/packages/resource-detector-azure/src/detectors/AzureAppServiceDetector.ts +++ b/packages/resource-detector-azure/src/detectors/AzureAppServiceDetector.ts @@ -36,7 +36,7 @@ import { ATTR_CLOUD_PLATFORM, CLOUD_PLATFORM_VALUE_AZURE_APP_SERVICE, } from '../semconv'; -import { getAzureResourceUri, isAzureFunction } from '../utils'; +import { getAzureResourceUri, isAzureContainerApps, isAzureFunction } from '../utils'; const APP_SERVICE_ATTRIBUTE_ENV_VARS = { [ATTR_CLOUD_REGION]: REGION_NAME, @@ -54,7 +54,7 @@ class AzureAppServiceDetector implements ResourceDetector { detect(): DetectedResource { let attributes = {}; const websiteSiteName = process.env[WEBSITE_SITE_NAME]; - if (websiteSiteName && !isAzureFunction()) { + if (websiteSiteName && !isAzureFunction() && !isAzureContainerApps()) { attributes = { ...attributes, [ATTR_SERVICE_NAME]: websiteSiteName, diff --git a/packages/resource-detector-azure/src/detectors/AzureContainerAppsDetector.ts b/packages/resource-detector-azure/src/detectors/AzureContainerAppsDetector.ts new file mode 100644 index 0000000000..7c4ac3d808 --- /dev/null +++ b/packages/resource-detector-azure/src/detectors/AzureContainerAppsDetector.ts @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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 + * + * https://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. + */ + +import { ResourceDetector, DetectedResource } from '@opentelemetry/resources'; +import { + ATTR_CLOUD_PLATFORM, + ATTR_CLOUD_PROVIDER, + ATTR_HOST_NAME, + CLOUD_PLATFORM_VALUE_AZURE_CONTAINER_APPS, + CLOUD_PROVIDER_VALUE_AZURE, +} from '../semconv'; +import { isAzureContainerApps } from '../utils'; +import { + AZURE_CONTAINER_APP_INSTANCE_ID, + AZURE_CONTAINER_APP_NAME, + AZURE_CONTAINER_APP_VERSION, + CONTAINER_APP_HOSTNAME, + CONTAINER_APP_NAME, + CONTAINER_APP_REPLICA_NAME, + CONTAINER_APP_REVISION +} from '../types'; + +const CONTAINER_APP_ATTRIBUTE_ENV_VARS = { + [ATTR_HOST_NAME]: CONTAINER_APP_HOSTNAME, + [AZURE_CONTAINER_APP_INSTANCE_ID]: CONTAINER_APP_REPLICA_NAME, + [AZURE_CONTAINER_APP_NAME]: CONTAINER_APP_NAME, + [AZURE_CONTAINER_APP_VERSION]: CONTAINER_APP_REVISION, +} + +class AzureContainerAppsDetector implements ResourceDetector { + detect(): DetectedResource { + let attributes = {}; + + if (isAzureContainerApps()) { + attributes = { + [ATTR_CLOUD_PLATFORM]: CLOUD_PLATFORM_VALUE_AZURE_CONTAINER_APPS, + [ATTR_CLOUD_PROVIDER]: CLOUD_PROVIDER_VALUE_AZURE, + }; + + for (const [key, value] of Object.entries(CONTAINER_APP_ATTRIBUTE_ENV_VARS)) { + const envVar = process.env[value]; + if (envVar) { + attributes = { ...attributes, ...{ [key]: envVar } }; + } + } + } + return { attributes } + } +} + +export const azureContainerAppsDetector = new AzureContainerAppsDetector(); + diff --git a/packages/resource-detector-azure/src/detectors/index.ts b/packages/resource-detector-azure/src/detectors/index.ts index 0470e61782..91e1d36462 100644 --- a/packages/resource-detector-azure/src/detectors/index.ts +++ b/packages/resource-detector-azure/src/detectors/index.ts @@ -15,5 +15,6 @@ */ export { azureAppServiceDetector } from './AzureAppServiceDetector'; +export { azureContainerAppsDetector } from './AzureContainerAppsDetector'; export { azureFunctionsDetector } from './AzureFunctionsDetector'; export { azureVmDetector } from './AzureVmDetector'; diff --git a/packages/resource-detector-azure/src/index.ts b/packages/resource-detector-azure/src/index.ts index c81d93e0a2..8416205b95 100644 --- a/packages/resource-detector-azure/src/index.ts +++ b/packages/resource-detector-azure/src/index.ts @@ -16,6 +16,7 @@ export { azureAppServiceDetector, + azureContainerAppsDetector, azureFunctionsDetector, azureVmDetector, } from './detectors'; diff --git a/packages/resource-detector-azure/src/semconv.ts b/packages/resource-detector-azure/src/semconv.ts index 625a528d57..12833a0ce9 100644 --- a/packages/resource-detector-azure/src/semconv.ts +++ b/packages/resource-detector-azure/src/semconv.ts @@ -48,6 +48,15 @@ export const ATTR_CLOUD_PROVIDER = 'cloud.provider' as const; */ export const ATTR_CLOUD_REGION = 'cloud.region' as const; +/** + * Container name used by container runtime. + * + * @example opentelemetry-autoconf + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_CONTAINER_NAME = 'container.name' as const; + /** * Name of the [deployment environment](https://wikipedia.org/wiki/Deployment_environment) (aka deployment tier). * @@ -181,6 +190,15 @@ export const ATTR_SERVICE_INSTANCE_ID = 'service.instance.id' as const; export const CLOUD_PLATFORM_VALUE_AZURE_APP_SERVICE = 'azure.app_service' as const; +/** + * Enum value "azure.container_apps" for attribute {@link ATTR_CLOUD_PLATFORM}. + * + * Azure Container Apps + * + * @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const CLOUD_PLATFORM_VALUE_AZURE_CONTAINER_APPS = "azure.container_apps" as const; + /** * Enum value "azure.functions" for attribute {@link ATTR_CLOUD_PLATFORM}. * @@ -207,3 +225,4 @@ export const CLOUD_PLATFORM_VALUE_AZURE_VM = 'azure.vm' as const; * @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. */ export const CLOUD_PROVIDER_VALUE_AZURE = 'azure' as const; + diff --git a/packages/resource-detector-azure/src/types.ts b/packages/resource-detector-azure/src/types.ts index 8fbf855ebf..cf4b4705b2 100644 --- a/packages/resource-detector-azure/src/types.ts +++ b/packages/resource-detector-azure/src/types.ts @@ -36,6 +36,16 @@ export const AZURE_VM_METADATA_PATH = export const AZURE_VM_SCALE_SET_NAME_ATTRIBUTE = 'azure.vm.scaleset.name'; export const AZURE_VM_SKU_ATTRIBUTE = 'azure.vm.sku'; +export const AZURE_CONTAINER_APP_INSTANCE_ID = 'azure.container_app.instance.id'; +export const AZURE_CONTAINER_APP_NAME = 'azure.container_app.name'; +export const AZURE_CONTAINER_APP_VERSION = 'azure.container_app.version'; +export const CONTAINER_APP_NAME = 'CONTAINER_APP_NAME'; +export const CONTAINER_APP_REVISION = 'CONTAINER_APP_REVISION'; +export const CONTAINER_APP_HOSTNAME = 'CONTAINER_APP_HOSTNAME'; +export const CONTAINER_APP_ENV_DNS_SUFFIX = 'CONTAINER_APP_ENV_DNS_SUFFIX'; +export const CONTAINER_APP_REPLICA_NAME = 'CONTAINER_APP_REPLICA_NAME'; +export const CONTAINER_APP_PORT = 'CONTAINER_APP_PORT'; + export interface AzureVmMetadata { azEnvironment?: string; additionalCapabilities?: { diff --git a/packages/resource-detector-azure/src/utils.ts b/packages/resource-detector-azure/src/utils.ts index ceb548de27..f9996755b3 100644 --- a/packages/resource-detector-azure/src/utils.ts +++ b/packages/resource-detector-azure/src/utils.ts @@ -15,6 +15,12 @@ */ import { + CONTAINER_APP_ENV_DNS_SUFFIX, + CONTAINER_APP_HOSTNAME, + CONTAINER_APP_NAME, + CONTAINER_APP_PORT, + CONTAINER_APP_REPLICA_NAME, + CONTAINER_APP_REVISION, FUNCTIONS_VERSION, WEBSITE_OWNER_NAME, WEBSITE_RESOURCE_GROUP, @@ -45,3 +51,14 @@ export function isAzureFunction(): boolean { process.env[WEBSITE_SKU] === 'FlexConsumption' ); } + +export function isAzureContainerApps(): boolean { + return !!( + process.env[CONTAINER_APP_NAME] && + process.env[CONTAINER_APP_REVISION] && + process.env[CONTAINER_APP_HOSTNAME] && + process.env[CONTAINER_APP_ENV_DNS_SUFFIX] && + process.env[CONTAINER_APP_PORT] && + process.env[CONTAINER_APP_REPLICA_NAME] + ); +} diff --git a/packages/resource-detector-azure/test/detectors/AzureAppServiceDetector.test.ts b/packages/resource-detector-azure/test/detectors/AzureAppServiceDetector.test.ts index 817f3fe314..5055ebb311 100644 --- a/packages/resource-detector-azure/test/detectors/AzureAppServiceDetector.test.ts +++ b/packages/resource-detector-azure/test/detectors/AzureAppServiceDetector.test.ts @@ -15,7 +15,11 @@ */ import * as assert from 'assert'; -import { azureAppServiceDetector } from '../../src/detectors/AzureAppServiceDetector'; +import { + azureAppServiceDetector, + azureContainerAppsDetector, + azureFunctionsDetector, +} from '../../src'; import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; import { ATTR_CLOUD_PLATFORM, @@ -25,7 +29,6 @@ import { ATTR_HOST_ID, ATTR_SERVICE_INSTANCE_ID, } from '../../src/semconv'; -import { azureFunctionsDetector } from '../../src'; import { detectResources } from '@opentelemetry/resources'; describe('AzureAppServiceDetector', () => { @@ -86,7 +89,7 @@ describe('AzureAppServiceDetector', () => { process.env.WEBSITE_OWNER_NAME = 'test-owner-name'; const resource = detectResources({ - detectors: [azureFunctionsDetector, azureAppServiceDetector], + detectors: [azureFunctionsDetector, azureAppServiceDetector, azureContainerAppsDetector], }); assert.ok(resource); const attributes = resource.attributes; @@ -117,7 +120,7 @@ describe('AzureAppServiceDetector', () => { delete process.env.WEBSITE_OWNER_NAME; const resource = detectResources({ - detectors: [azureFunctionsDetector, azureAppServiceDetector], + detectors: [azureFunctionsDetector, azureAppServiceDetector, azureContainerAppsDetector], }); assert.ok(resource); const attributes = resource.attributes; diff --git a/packages/resource-detector-azure/test/detectors/AzureContainerAppsDetector.test.ts b/packages/resource-detector-azure/test/detectors/AzureContainerAppsDetector.test.ts new file mode 100644 index 0000000000..96e642c024 --- /dev/null +++ b/packages/resource-detector-azure/test/detectors/AzureContainerAppsDetector.test.ts @@ -0,0 +1,117 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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 + * + * https://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. + */ + +import * as assert from 'assert'; +import { detectResources } from '@opentelemetry/resources'; +import { + ATTR_CLOUD_PLATFORM, + ATTR_CLOUD_PROVIDER, + ATTR_HOST_NAME +} from '../../src/semconv'; +import { AZURE_CONTAINER_APP_INSTANCE_ID, AZURE_CONTAINER_APP_NAME, AZURE_CONTAINER_APP_VERSION } from '../../src/types'; +import { + azureAppServiceDetector, + azureContainerAppsDetector, + azureFunctionsDetector +} from '../../src'; + +describe('AzureContainerAppsDetector', () => { + let originalEnv: NodeJS.ProcessEnv; + beforeEach(() => { + originalEnv = process.env; + process.env = { ...originalEnv }; + // Clear Container Apps env vars before each test + delete process.env.CONTAINER_APP_NAME; + delete process.env.CONTAINER_APP_REVISION; + delete process.env.CONTAINER_APP_HOSTNAME; + delete process.env.CONTAINER_APP_ENV_DNS_SUFFIX; + delete process.env.CONTAINER_APP_PORT; + delete process.env.CONTAINER_APP_REPLICA_NAME; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it('should detect Azure Container Apps resource attributes', () => { + process.env.CONTAINER_APP_NAME = 'test-container-app'; + process.env.CONTAINER_APP_REVISION = 'test-revision'; + process.env.CONTAINER_APP_HOSTNAME = 'test-hostname'; + process.env.CONTAINER_APP_ENV_DNS_SUFFIX = 'eastus.azurecontainerapps.io'; + process.env.CONTAINER_APP_PORT = '8080'; + process.env.CONTAINER_APP_REPLICA_NAME = 'test-replica-name'; + + const resource = detectResources({ + detectors: [azureContainerAppsDetector, azureFunctionsDetector, azureAppServiceDetector], + }); + assert.ok(resource); + const attributes = resource.attributes; + assert.strictEqual(attributes[ATTR_CLOUD_PROVIDER], 'azure'); + assert.strictEqual(attributes[ATTR_CLOUD_PLATFORM], 'azure.container_apps'); + assert.strictEqual(attributes[AZURE_CONTAINER_APP_NAME], 'test-container-app'); + assert.strictEqual( + attributes[ATTR_HOST_NAME], + 'test-hostname' + ); + assert.strictEqual( + attributes[AZURE_CONTAINER_APP_INSTANCE_ID], + 'test-replica-name' + ); + assert.strictEqual( + attributes[AZURE_CONTAINER_APP_VERSION], + 'test-revision' + ); + }); + + it('should return empty attributes when not running on Azure Container Apps', () => { + delete process.env.CONTAINER_APP_NAME; + delete process.env.CONTAINER_APP_REVISION; + delete process.env.CONTAINER_APP_HOSTNAME; + delete process.env.CONTAINER_APP_ENV_DNS_SUFFIX; + delete process.env.CONTAINER_APP_PORT; + delete process.env.CONTAINER_APP_REPLICA_NAME; + + const resource = detectResources({ + detectors: [azureContainerAppsDetector], + }); + assert.ok(resource); + const attributes = resource.attributes; + assert.strictEqual(attributes[ATTR_CLOUD_PROVIDER], undefined); + assert.strictEqual(attributes[ATTR_CLOUD_PLATFORM], undefined); + assert.strictEqual(attributes[AZURE_CONTAINER_APP_NAME], undefined); + assert.strictEqual(attributes[ATTR_HOST_NAME], undefined); + assert.strictEqual(attributes[AZURE_CONTAINER_APP_INSTANCE_ID], undefined); + assert.strictEqual(attributes[AZURE_CONTAINER_APP_VERSION], undefined); + }); + + it('should return empty attributes when only some env vars are set', () => { + process.env.CONTAINER_APP_NAME = 'test-container-app'; + process.env.CONTAINER_APP_REVISION = 'test-revision'; + // Missing other required env vars + delete process.env.CONTAINER_APP_HOSTNAME; + delete process.env.CONTAINER_APP_ENV_DNS_SUFFIX; + delete process.env.CONTAINER_APP_PORT; + delete process.env.CONTAINER_APP_REPLICA_NAME; + + const resource = detectResources({ + detectors: [azureContainerAppsDetector], + }); + assert.ok(resource); + const attributes = resource.attributes; + assert.strictEqual(attributes[ATTR_CLOUD_PROVIDER], undefined); + assert.strictEqual(attributes[ATTR_CLOUD_PLATFORM], undefined); + }); +}); diff --git a/packages/resource-detector-azure/test/detectors/AzureFunctionsDetector.test.ts b/packages/resource-detector-azure/test/detectors/AzureFunctionsDetector.test.ts index 9ee20f29b0..38942e5265 100644 --- a/packages/resource-detector-azure/test/detectors/AzureFunctionsDetector.test.ts +++ b/packages/resource-detector-azure/test/detectors/AzureFunctionsDetector.test.ts @@ -15,8 +15,11 @@ */ import * as assert from 'assert'; -import { azureFunctionsDetector } from '../../src/detectors/AzureFunctionsDetector'; -import { azureAppServiceDetector } from '../../src/detectors/AzureAppServiceDetector'; +import { + azureAppServiceDetector, + azureContainerAppsDetector, + azureFunctionsDetector, +} from '../../src'; import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; import { ATTR_CLOUD_PLATFORM, @@ -50,7 +53,7 @@ describe('AzureFunctionsDetector', () => { process.env.WEBSITE_RESOURCE_GROUP = 'test-resource-group'; const resource = detectResources({ - detectors: [azureFunctionsDetector, azureAppServiceDetector], + detectors: [azureFunctionsDetector, azureAppServiceDetector, azureContainerAppsDetector], }); assert.ok(resource); const attributes = resource.attributes; @@ -86,7 +89,7 @@ describe('AzureFunctionsDetector', () => { const expectedWebsiteOwnerName = 'test-owner-name'; const resource = detectResources({ - detectors: [azureFunctionsDetector, azureAppServiceDetector], + detectors: [azureFunctionsDetector, azureAppServiceDetector, azureContainerAppsDetector], }); assert.ok(resource); const attributes = resource.attributes; @@ -108,7 +111,7 @@ it('should detect azure functions if websiteSku is defined as FlexConsumption', process.env.WEBSITE_RESOURCE_GROUP = 'test-resource-group'; const resource = detectResources({ - detectors: [azureFunctionsDetector, azureAppServiceDetector], + detectors: [azureFunctionsDetector, azureAppServiceDetector, azureContainerAppsDetector], }); assert.ok(resource); const attributes = resource.attributes;