|
| 1 | +# Copyright Splunk Inc. |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +import logging |
| 16 | + |
| 17 | +from opentelemetry.sdk.resources import ( |
| 18 | + TELEMETRY_SDK_LANGUAGE, |
| 19 | + TELEMETRY_SDK_NAME, |
| 20 | + TELEMETRY_SDK_VERSION, |
| 21 | +) |
| 22 | +from opentelemetry._opamp.agent import OpAMPAgent |
| 23 | +from opentelemetry._opamp.client import OpAMPClient |
| 24 | +from opentelemetry._opamp.proto import opamp_pb2 |
| 25 | + |
| 26 | +from splunk_otel.env import ( |
| 27 | + Env, |
| 28 | + SPLUNK_ACCESS_TOKEN, |
| 29 | + SPLUNK_OPAMP_ENABLED, |
| 30 | + SPLUNK_OPAMP_ENDPOINT, |
| 31 | + SPLUNK_OPAMP_POLLING_INTERVAL, |
| 32 | + SPLUNK_OPAMP_TOKEN, |
| 33 | +) |
| 34 | +from splunk_otel.distro import _DISTRO_NAME |
| 35 | +from splunk_otel.opamp.config_registry import ConfigRegistry |
| 36 | + |
| 37 | +logger = logging.getLogger(__name__) |
| 38 | + |
| 39 | +_IDENTIFYING_RESOURCE_KEYS = ( |
| 40 | + "service.name", |
| 41 | + "service.namespace", |
| 42 | + "service.instance.id", |
| 43 | + "service.version", |
| 44 | +) |
| 45 | + |
| 46 | +_NON_IDENTIFYING_RESOURCE_KEYS = ( |
| 47 | + "os.type", |
| 48 | + "os.name", |
| 49 | + "os.version", |
| 50 | + "host.name", |
| 51 | + "host.arch", |
| 52 | + "process.pid", |
| 53 | + "process.runtime.name", |
| 54 | + "process.runtime.version", |
| 55 | + # Note: deployment.environment.name may need to move: splunk-otel-java puts this in identifying |
| 56 | + "deployment.environment.name", |
| 57 | +) |
| 58 | + |
| 59 | +_DEFAULT_POLLING_INTERVAL_MS = 30000 |
| 60 | + |
| 61 | + |
| 62 | +def _start_opamp_if_enabled(resource_attrs, registry: ConfigRegistry, env: Env) -> None: |
| 63 | + if not env.is_true(SPLUNK_OPAMP_ENABLED): |
| 64 | + logger.debug("OpAMP disabled (SPLUNK_OPAMP_ENABLED not set to true)") |
| 65 | + return |
| 66 | + |
| 67 | + endpoint = env.getval(SPLUNK_OPAMP_ENDPOINT) |
| 68 | + if not endpoint: |
| 69 | + logger.warning("SPLUNK_OPAMP_ENABLED=true but SPLUNK_OPAMP_ENDPOINT is not set; OpAMP disabled") |
| 70 | + return |
| 71 | + |
| 72 | + token = env.getval(SPLUNK_OPAMP_TOKEN) or env.getval(SPLUNK_ACCESS_TOKEN) |
| 73 | + polling_interval_ms = env.getint(SPLUNK_OPAMP_POLLING_INTERVAL, _DEFAULT_POLLING_INTERVAL_MS) |
| 74 | + |
| 75 | + logger.info("Starting OpAMP client: %s", endpoint) |
| 76 | + |
| 77 | + try: |
| 78 | + _start_opamp(endpoint, token, polling_interval_ms, registry, resource_attrs) |
| 79 | + |
| 80 | + except Exception: |
| 81 | + logger.exception("Failed to start OpAMP client") |
| 82 | + |
| 83 | + |
| 84 | +def _start_opamp(endpoint: str, token: str, polling_interval_ms: int, registry: ConfigRegistry, resource_attrs): |
| 85 | + headers = {} |
| 86 | + if token: |
| 87 | + headers["Authorization"] = f"Bearer {token}" |
| 88 | + |
| 89 | + identifying_attrs, non_identifying_attrs = _build_agent_attributes(resource_attrs) |
| 90 | + client = OpAMPClient( |
| 91 | + endpoint=endpoint, |
| 92 | + headers=headers, |
| 93 | + agent_identifying_attributes=identifying_attrs, |
| 94 | + agent_non_identifying_attributes=non_identifying_attrs, |
| 95 | + ) |
| 96 | + client.update_effective_config( |
| 97 | + {"": registry.get_all()}, |
| 98 | + content_type="application/json", |
| 99 | + ) |
| 100 | + agent = OpAMPAgent( |
| 101 | + interval=polling_interval_ms / 1000, |
| 102 | + message_handler=_handle_server_message, |
| 103 | + client=client, |
| 104 | + ) |
| 105 | + agent.start() |
| 106 | + logger.info("OpAMP client started") |
| 107 | + |
| 108 | + |
| 109 | +def _build_agent_attributes(resource_attrs) -> tuple[dict, dict]: |
| 110 | + from splunk_otel.__about__ import __version__ as distro_version |
| 111 | + |
| 112 | + identifying_attrs = {} |
| 113 | + for key in _IDENTIFYING_RESOURCE_KEYS: |
| 114 | + val = resource_attrs.get(key) |
| 115 | + if val is not None: |
| 116 | + identifying_attrs[key] = str(val) |
| 117 | + |
| 118 | + identifying_attrs.update( |
| 119 | + { |
| 120 | + TELEMETRY_SDK_LANGUAGE: "python", |
| 121 | + TELEMETRY_SDK_NAME: "opentelemetry", |
| 122 | + TELEMETRY_SDK_VERSION: str(resource_attrs.get(TELEMETRY_SDK_VERSION, "unknown")), |
| 123 | + "telemetry.distro.name": _DISTRO_NAME, |
| 124 | + "telemetry.distro.version": distro_version, |
| 125 | + } |
| 126 | + ) |
| 127 | + |
| 128 | + non_identifying_attrs = {} |
| 129 | + for key in _NON_IDENTIFYING_RESOURCE_KEYS: |
| 130 | + val = resource_attrs.get(key) |
| 131 | + if val is not None: |
| 132 | + non_identifying_attrs[key] = str(val) |
| 133 | + |
| 134 | + return identifying_attrs, non_identifying_attrs |
| 135 | + |
| 136 | + |
| 137 | +def _handle_server_message( |
| 138 | + _agent: OpAMPAgent, |
| 139 | + _client: OpAMPClient, |
| 140 | + message: opamp_pb2.ServerToAgent, |
| 141 | +) -> None: |
| 142 | + logger.debug("ServerToAgent: flags=%s", message.flags) |
0 commit comments