__init__.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. # Copyright The OpenTelemetry Authors
  2. # SPDX-License-Identifier: Apache-2.0
  3. # pylint: disable=too-many-ancestors
  4. """
  5. The OpenTelemetry metrics API describes the classes used to generate
  6. metrics.
  7. The :class:`.MeterProvider` provides users access to the :class:`.Meter` which in
  8. turn is used to create :class:`.Instrument` objects. The :class:`.Instrument` objects are
  9. used to record measurements.
  10. This module provides abstract (i.e. unimplemented) classes required for
  11. metrics, and a concrete no-op implementation :class:`.NoOpMeter` that allows applications
  12. to use the API package alone without a supporting implementation.
  13. To get a meter, you need to provide the package name from which you are
  14. calling the meter APIs to OpenTelemetry by calling `MeterProvider.get_meter`
  15. with the calling instrumentation name and the version of your package.
  16. The following code shows how to obtain a meter using the global :class:`.MeterProvider`::
  17. from opentelemetry.metrics import get_meter
  18. meter = get_meter("example-meter")
  19. counter = meter.create_counter("example-counter")
  20. .. versionadded:: 1.10.0
  21. """
  22. import warnings
  23. from abc import ABC, abstractmethod
  24. from collections.abc import Sequence
  25. from dataclasses import dataclass
  26. from logging import getLogger
  27. from os import environ
  28. from threading import Lock
  29. from typing import cast
  30. from opentelemetry.environment_variables import OTEL_PYTHON_METER_PROVIDER
  31. from opentelemetry.metrics._internal.instrument import (
  32. CallbackT,
  33. Counter,
  34. Gauge,
  35. Histogram,
  36. NoOpCounter,
  37. NoOpGauge,
  38. NoOpHistogram,
  39. NoOpObservableCounter,
  40. NoOpObservableGauge,
  41. NoOpObservableUpDownCounter,
  42. NoOpUpDownCounter,
  43. ObservableCounter,
  44. ObservableGauge,
  45. ObservableUpDownCounter,
  46. UpDownCounter,
  47. _MetricsHistogramAdvisory,
  48. _ProxyCounter,
  49. _ProxyGauge,
  50. _ProxyHistogram,
  51. _ProxyObservableCounter,
  52. _ProxyObservableGauge,
  53. _ProxyObservableUpDownCounter,
  54. _ProxyUpDownCounter,
  55. )
  56. from opentelemetry.util._once import Once
  57. from opentelemetry.util._providers import _load_provider
  58. from opentelemetry.util.types import (
  59. Attributes,
  60. )
  61. _logger = getLogger(__name__)
  62. # pylint: disable=invalid-name
  63. _ProxyInstrumentT = (
  64. _ProxyCounter
  65. | _ProxyHistogram
  66. | _ProxyGauge
  67. | _ProxyObservableCounter
  68. | _ProxyObservableGauge
  69. | _ProxyObservableUpDownCounter
  70. | _ProxyUpDownCounter
  71. )
  72. class MeterProvider(ABC):
  73. """
  74. MeterProvider is the entry point of the API. It provides access to `Meter` instances.
  75. """
  76. @abstractmethod
  77. def get_meter(
  78. self,
  79. name: str,
  80. version: str | None = None,
  81. schema_url: str | None = None,
  82. attributes: Attributes | None = None,
  83. ) -> "Meter":
  84. """Returns a `Meter` for use by the given instrumentation library.
  85. For any two calls it is undefined whether the same or different
  86. `Meter` instances are returned, even for different library names.
  87. This function may return different `Meter` types (e.g. a no-op meter
  88. vs. a functional meter).
  89. Args:
  90. name: The name of the instrumenting module.
  91. ``__name__`` should be avoided as this can result in
  92. different meter names if the meters are in different files.
  93. It is better to use a fixed string that can be imported where
  94. needed and used consistently as the name of the meter.
  95. This should *not* be the name of the module that is
  96. instrumented but the name of the module doing the instrumentation.
  97. E.g., instead of ``"requests"``, use
  98. ``"opentelemetry.instrumentation.requests"``.
  99. version: Optional. The version string of the
  100. instrumenting library. Usually this should be the same as
  101. ``importlib.metadata.version(instrumenting_library_name)``.
  102. schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
  103. attributes: Optional. Attributes that are associated with the emitted telemetry.
  104. """
  105. class NoOpMeterProvider(MeterProvider):
  106. """The default MeterProvider used when no MeterProvider implementation is available."""
  107. def get_meter(
  108. self,
  109. name: str,
  110. version: str | None = None,
  111. schema_url: str | None = None,
  112. attributes: Attributes | None = None,
  113. ) -> "Meter":
  114. """Returns a NoOpMeter."""
  115. return NoOpMeter(name, version=version, schema_url=schema_url)
  116. class _ProxyMeterProvider(MeterProvider):
  117. def __init__(self) -> None:
  118. self._lock = Lock()
  119. self._meters: list[_ProxyMeter] = []
  120. self._real_meter_provider: MeterProvider | None = None
  121. def get_meter(
  122. self,
  123. name: str,
  124. version: str | None = None,
  125. schema_url: str | None = None,
  126. attributes: Attributes | None = None,
  127. ) -> "Meter":
  128. with self._lock:
  129. if self._real_meter_provider is not None:
  130. return self._real_meter_provider.get_meter(
  131. name, version, schema_url
  132. )
  133. meter = _ProxyMeter(name, version=version, schema_url=schema_url)
  134. self._meters.append(meter)
  135. return meter
  136. def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
  137. with self._lock:
  138. self._real_meter_provider = meter_provider
  139. for meter in self._meters:
  140. meter.on_set_meter_provider(meter_provider)
  141. @dataclass
  142. class _InstrumentRegistrationStatus:
  143. instrument_id: str
  144. already_registered: bool
  145. conflict: bool
  146. current_advisory: _MetricsHistogramAdvisory | None
  147. class Meter(ABC):
  148. """Handles instrument creation.
  149. This class provides methods for creating instruments which are then
  150. used to produce measurements.
  151. """
  152. def __init__(
  153. self,
  154. name: str,
  155. version: str | None = None,
  156. schema_url: str | None = None,
  157. ) -> None:
  158. super().__init__()
  159. self._name = name
  160. self._version = version
  161. self._schema_url = schema_url
  162. self._instrument_ids: dict[str, _MetricsHistogramAdvisory | None] = {}
  163. self._instrument_ids_lock = Lock()
  164. @property
  165. def name(self) -> str:
  166. """
  167. The name of the instrumenting module.
  168. """
  169. return self._name
  170. @property
  171. def version(self) -> str | None:
  172. """
  173. The version string of the instrumenting library.
  174. """
  175. return self._version
  176. @property
  177. def schema_url(self) -> str | None:
  178. """
  179. Specifies the Schema URL of the emitted telemetry
  180. """
  181. return self._schema_url
  182. def _register_instrument(
  183. self,
  184. name: str,
  185. type_: type,
  186. unit: str,
  187. description: str,
  188. advisory: _MetricsHistogramAdvisory | None = None,
  189. ) -> _InstrumentRegistrationStatus:
  190. """
  191. Register an instrument with the name, type, unit and description as
  192. identifying keys and the advisory as value.
  193. Returns a tuple. The first value is the instrument id.
  194. The second value is an `_InstrumentRegistrationStatus` where
  195. `already_registered` is `True` if the instrument has been registered
  196. already.
  197. If `conflict` is set to True the `current_advisory` attribute contains
  198. the registered instrument advisory.
  199. """
  200. instrument_id = ",".join(
  201. [name.strip().lower(), type_.__name__, unit, description]
  202. )
  203. already_registered = False
  204. conflict = False
  205. current_advisory = None
  206. with self._instrument_ids_lock:
  207. # we are not using get because None is a valid value
  208. already_registered = instrument_id in self._instrument_ids
  209. if already_registered:
  210. current_advisory = self._instrument_ids[instrument_id]
  211. conflict = current_advisory != advisory
  212. else:
  213. self._instrument_ids[instrument_id] = advisory
  214. return _InstrumentRegistrationStatus(
  215. instrument_id=instrument_id,
  216. already_registered=already_registered,
  217. conflict=conflict,
  218. current_advisory=current_advisory,
  219. )
  220. @staticmethod
  221. def _log_instrument_registration_conflict(
  222. name: str,
  223. instrumentation_type: str,
  224. unit: str,
  225. description: str,
  226. status: _InstrumentRegistrationStatus,
  227. ) -> None:
  228. _logger.warning(
  229. "An instrument with name %s, type %s, unit %s and "
  230. "description %s has been created already with a "
  231. "different advisory value %s and will be used instead.",
  232. name,
  233. instrumentation_type,
  234. unit,
  235. description,
  236. status.current_advisory,
  237. )
  238. @abstractmethod
  239. def create_counter(
  240. self,
  241. name: str,
  242. unit: str = "",
  243. description: str = "",
  244. ) -> Counter:
  245. """Creates a `Counter` instrument
  246. Args:
  247. name: The name of the instrument to be created
  248. unit: The unit for observations this instrument reports. For
  249. example, ``By`` for bytes. UCUM units are recommended.
  250. description: A description for this instrument and what it measures.
  251. """
  252. @abstractmethod
  253. def create_up_down_counter(
  254. self,
  255. name: str,
  256. unit: str = "",
  257. description: str = "",
  258. ) -> UpDownCounter:
  259. """Creates an `UpDownCounter` instrument
  260. Args:
  261. name: The name of the instrument to be created
  262. unit: The unit for observations this instrument reports. For
  263. example, ``By`` for bytes. UCUM units are recommended.
  264. description: A description for this instrument and what it measures.
  265. """
  266. @abstractmethod
  267. def create_observable_counter(
  268. self,
  269. name: str,
  270. callbacks: Sequence[CallbackT] | None = None,
  271. unit: str = "",
  272. description: str = "",
  273. ) -> ObservableCounter:
  274. """Creates an `ObservableCounter` instrument
  275. An observable counter observes a monotonically increasing count by calling provided
  276. callbacks which accept a :class:`~opentelemetry.metrics.CallbackOptions` and return
  277. multiple :class:`~opentelemetry.metrics.Observation`.
  278. For example, an observable counter could be used to report system CPU
  279. time periodically. Here is a basic implementation::
  280. def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
  281. observations = []
  282. with open("/proc/stat") as procstat:
  283. procstat.readline() # skip the first line
  284. for line in procstat:
  285. if not line.startswith("cpu"): break
  286. cpu, *states = line.split()
  287. observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
  288. observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
  289. observations.append(Observation(int(states[2]) // 100, {"cpu": cpu, "state": "system"}))
  290. # ... other states
  291. return observations
  292. meter.create_observable_counter(
  293. "system.cpu.time",
  294. callbacks=[cpu_time_callback],
  295. unit="s",
  296. description="CPU time"
  297. )
  298. To reduce memory usage, you can use generator callbacks instead of
  299. building the full list::
  300. def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
  301. with open("/proc/stat") as procstat:
  302. procstat.readline() # skip the first line
  303. for line in procstat:
  304. if not line.startswith("cpu"): break
  305. cpu, *states = line.split()
  306. yield Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"})
  307. yield Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"})
  308. # ... other states
  309. Alternatively, you can pass a sequence of generators directly instead of a sequence of
  310. callbacks, which each should return iterables of :class:`~opentelemetry.metrics.Observation`::
  311. def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Observation]]:
  312. # accept options sent in from OpenTelemetry
  313. options = yield
  314. while True:
  315. observations = []
  316. with open("/proc/stat") as procstat:
  317. procstat.readline() # skip the first line
  318. for line in procstat:
  319. if not line.startswith("cpu"): break
  320. cpu, *states = line.split()
  321. if "user" in states_to_include:
  322. observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
  323. if "nice" in states_to_include:
  324. observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
  325. # ... other states
  326. # yield the observations and receive the options for next iteration
  327. options = yield observations
  328. meter.create_observable_counter(
  329. "system.cpu.time",
  330. callbacks=[cpu_time_callback({"user", "system"})],
  331. unit="s",
  332. description="CPU time"
  333. )
  334. The :class:`~opentelemetry.metrics.CallbackOptions` contain a timeout which the
  335. callback should respect. For example if the callback does asynchronous work, like
  336. making HTTP requests, it should respect the timeout::
  337. def scrape_http_callback(options: CallbackOptions) -> Iterable[Observation]:
  338. r = requests.get('http://scrapethis.com', timeout=options.timeout_millis / 10**3)
  339. for value in r.json():
  340. yield Observation(value)
  341. Args:
  342. name: The name of the instrument to be created
  343. callbacks: A sequence of callbacks that return an iterable of
  344. :class:`~opentelemetry.metrics.Observation`. Alternatively, can be a sequence of generators that each
  345. yields iterables of :class:`~opentelemetry.metrics.Observation`.
  346. unit: The unit for observations this instrument reports. For
  347. example, ``By`` for bytes. UCUM units are recommended.
  348. description: A description for this instrument and what it measures.
  349. """
  350. @abstractmethod
  351. def create_histogram(
  352. self,
  353. name: str,
  354. unit: str = "",
  355. description: str = "",
  356. *,
  357. explicit_bucket_boundaries_advisory: Sequence[float] | None = None,
  358. ) -> Histogram:
  359. """Creates a :class:`~opentelemetry.metrics.Histogram` instrument
  360. Args:
  361. name: The name of the instrument to be created
  362. unit: The unit for observations this instrument reports. For
  363. example, ``By`` for bytes. UCUM units are recommended.
  364. description: A description for this instrument and what it measures.
  365. """
  366. def create_gauge( # type: ignore # pylint: disable=no-self-use
  367. self,
  368. name: str,
  369. unit: str = "",
  370. description: str = "",
  371. ) -> Gauge: # pyright: ignore[reportReturnType]
  372. """Creates a ``Gauge`` instrument
  373. Args:
  374. name: The name of the instrument to be created
  375. unit: The unit for observations this instrument reports. For
  376. example, ``By`` for bytes. UCUM units are recommended.
  377. description: A description for this instrument and what it measures.
  378. """
  379. warnings.warn("create_gauge() is not implemented and will be a no-op")
  380. @abstractmethod
  381. def create_observable_gauge(
  382. self,
  383. name: str,
  384. callbacks: Sequence[CallbackT] | None = None,
  385. unit: str = "",
  386. description: str = "",
  387. ) -> ObservableGauge:
  388. """Creates an `ObservableGauge` instrument
  389. Args:
  390. name: The name of the instrument to be created
  391. callbacks: A sequence of callbacks that return an iterable of
  392. :class:`~opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
  393. of :class:`~opentelemetry.metrics.Observation`.
  394. unit: The unit for observations this instrument reports. For
  395. example, ``By`` for bytes. UCUM units are recommended.
  396. description: A description for this instrument and what it measures.
  397. """
  398. @abstractmethod
  399. def create_observable_up_down_counter(
  400. self,
  401. name: str,
  402. callbacks: Sequence[CallbackT] | None = None,
  403. unit: str = "",
  404. description: str = "",
  405. ) -> ObservableUpDownCounter:
  406. """Creates an `ObservableUpDownCounter` instrument
  407. Args:
  408. name: The name of the instrument to be created
  409. callbacks: A sequence of callbacks that return an iterable of
  410. :class:`~opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
  411. of :class:`~opentelemetry.metrics.Observation`.
  412. unit: The unit for observations this instrument reports. For
  413. example, ``By`` for bytes. UCUM units are recommended.
  414. description: A description for this instrument and what it measures.
  415. """
  416. class _ProxyMeter(Meter):
  417. def __init__(
  418. self,
  419. name: str,
  420. version: str | None = None,
  421. schema_url: str | None = None,
  422. ) -> None:
  423. super().__init__(name, version=version, schema_url=schema_url)
  424. self._lock = Lock()
  425. self._instruments: list[_ProxyInstrumentT] = []
  426. self._real_meter: Meter | None = None
  427. def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
  428. """Called when a real meter provider is set on the creating _ProxyMeterProvider
  429. Creates a real backing meter for this instance and notifies all created
  430. instruments so they can create real backing instruments.
  431. """
  432. real_meter = meter_provider.get_meter(
  433. self._name, self._version, self._schema_url
  434. )
  435. with self._lock:
  436. self._real_meter = real_meter
  437. # notify all proxy instruments of the new meter so they can create
  438. # real instruments to back themselves
  439. for instrument in self._instruments:
  440. instrument.on_meter_set(real_meter)
  441. def create_counter(
  442. self,
  443. name: str,
  444. unit: str = "",
  445. description: str = "",
  446. ) -> Counter:
  447. with self._lock:
  448. if self._real_meter:
  449. return self._real_meter.create_counter(name, unit, description)
  450. proxy = _ProxyCounter(name, unit, description)
  451. self._instruments.append(proxy)
  452. return proxy
  453. def create_up_down_counter(
  454. self,
  455. name: str,
  456. unit: str = "",
  457. description: str = "",
  458. ) -> UpDownCounter:
  459. with self._lock:
  460. if self._real_meter:
  461. return self._real_meter.create_up_down_counter(
  462. name, unit, description
  463. )
  464. proxy = _ProxyUpDownCounter(name, unit, description)
  465. self._instruments.append(proxy)
  466. return proxy
  467. def create_observable_counter(
  468. self,
  469. name: str,
  470. callbacks: Sequence[CallbackT] | None = None,
  471. unit: str = "",
  472. description: str = "",
  473. ) -> ObservableCounter:
  474. with self._lock:
  475. if self._real_meter:
  476. return self._real_meter.create_observable_counter(
  477. name, callbacks, unit, description
  478. )
  479. proxy = _ProxyObservableCounter(
  480. name, callbacks, unit=unit, description=description
  481. )
  482. self._instruments.append(proxy)
  483. return proxy
  484. def create_histogram(
  485. self,
  486. name: str,
  487. unit: str = "",
  488. description: str = "",
  489. *,
  490. explicit_bucket_boundaries_advisory: Sequence[float] | None = None,
  491. ) -> Histogram:
  492. with self._lock:
  493. if self._real_meter:
  494. return self._real_meter.create_histogram(
  495. name,
  496. unit,
  497. description,
  498. explicit_bucket_boundaries_advisory=explicit_bucket_boundaries_advisory,
  499. )
  500. proxy = _ProxyHistogram(
  501. name, unit, description, explicit_bucket_boundaries_advisory
  502. )
  503. self._instruments.append(proxy)
  504. return proxy
  505. def create_gauge(
  506. self,
  507. name: str,
  508. unit: str = "",
  509. description: str = "",
  510. ) -> Gauge:
  511. with self._lock:
  512. if self._real_meter:
  513. return self._real_meter.create_gauge(name, unit, description)
  514. proxy = _ProxyGauge(name, unit, description)
  515. self._instruments.append(proxy)
  516. return proxy
  517. def create_observable_gauge(
  518. self,
  519. name: str,
  520. callbacks: Sequence[CallbackT] | None = None,
  521. unit: str = "",
  522. description: str = "",
  523. ) -> ObservableGauge:
  524. with self._lock:
  525. if self._real_meter:
  526. return self._real_meter.create_observable_gauge(
  527. name, callbacks, unit, description
  528. )
  529. proxy = _ProxyObservableGauge(
  530. name, callbacks, unit=unit, description=description
  531. )
  532. self._instruments.append(proxy)
  533. return proxy
  534. def create_observable_up_down_counter(
  535. self,
  536. name: str,
  537. callbacks: Sequence[CallbackT] | None = None,
  538. unit: str = "",
  539. description: str = "",
  540. ) -> ObservableUpDownCounter:
  541. with self._lock:
  542. if self._real_meter:
  543. return self._real_meter.create_observable_up_down_counter(
  544. name,
  545. callbacks,
  546. unit,
  547. description,
  548. )
  549. proxy = _ProxyObservableUpDownCounter(
  550. name, callbacks, unit=unit, description=description
  551. )
  552. self._instruments.append(proxy)
  553. return proxy
  554. class NoOpMeter(Meter):
  555. """The default Meter used when no Meter implementation is available.
  556. All operations are no-op.
  557. """
  558. def create_counter(
  559. self,
  560. name: str,
  561. unit: str = "",
  562. description: str = "",
  563. ) -> Counter:
  564. """Returns a no-op Counter."""
  565. status = self._register_instrument(
  566. name, NoOpCounter, unit, description
  567. )
  568. if status.conflict:
  569. self._log_instrument_registration_conflict(
  570. name,
  571. Counter.__name__,
  572. unit,
  573. description,
  574. status,
  575. )
  576. return NoOpCounter(name, unit=unit, description=description)
  577. def create_gauge(
  578. self,
  579. name: str,
  580. unit: str = "",
  581. description: str = "",
  582. ) -> Gauge:
  583. """Returns a no-op Gauge."""
  584. status = self._register_instrument(name, NoOpGauge, unit, description)
  585. if status.conflict:
  586. self._log_instrument_registration_conflict(
  587. name,
  588. Gauge.__name__,
  589. unit,
  590. description,
  591. status,
  592. )
  593. return NoOpGauge(name, unit=unit, description=description)
  594. def create_up_down_counter(
  595. self,
  596. name: str,
  597. unit: str = "",
  598. description: str = "",
  599. ) -> UpDownCounter:
  600. """Returns a no-op UpDownCounter."""
  601. status = self._register_instrument(
  602. name, NoOpUpDownCounter, unit, description
  603. )
  604. if status.conflict:
  605. self._log_instrument_registration_conflict(
  606. name,
  607. UpDownCounter.__name__,
  608. unit,
  609. description,
  610. status,
  611. )
  612. return NoOpUpDownCounter(name, unit=unit, description=description)
  613. def create_observable_counter(
  614. self,
  615. name: str,
  616. callbacks: Sequence[CallbackT] | None = None,
  617. unit: str = "",
  618. description: str = "",
  619. ) -> ObservableCounter:
  620. """Returns a no-op ObservableCounter."""
  621. status = self._register_instrument(
  622. name, NoOpObservableCounter, unit, description
  623. )
  624. if status.conflict:
  625. self._log_instrument_registration_conflict(
  626. name,
  627. ObservableCounter.__name__,
  628. unit,
  629. description,
  630. status,
  631. )
  632. return NoOpObservableCounter(
  633. name,
  634. callbacks,
  635. unit=unit,
  636. description=description,
  637. )
  638. def create_histogram(
  639. self,
  640. name: str,
  641. unit: str = "",
  642. description: str = "",
  643. *,
  644. explicit_bucket_boundaries_advisory: Sequence[float] | None = None,
  645. ) -> Histogram:
  646. """Returns a no-op Histogram."""
  647. status = self._register_instrument(
  648. name,
  649. NoOpHistogram,
  650. unit,
  651. description,
  652. _MetricsHistogramAdvisory(
  653. explicit_bucket_boundaries=explicit_bucket_boundaries_advisory
  654. ),
  655. )
  656. if status.conflict:
  657. self._log_instrument_registration_conflict(
  658. name,
  659. Histogram.__name__,
  660. unit,
  661. description,
  662. status,
  663. )
  664. return NoOpHistogram(
  665. name,
  666. unit=unit,
  667. description=description,
  668. explicit_bucket_boundaries_advisory=explicit_bucket_boundaries_advisory,
  669. )
  670. def create_observable_gauge(
  671. self,
  672. name: str,
  673. callbacks: Sequence[CallbackT] | None = None,
  674. unit: str = "",
  675. description: str = "",
  676. ) -> ObservableGauge:
  677. """Returns a no-op ObservableGauge."""
  678. status = self._register_instrument(
  679. name, NoOpObservableGauge, unit, description
  680. )
  681. if status.conflict:
  682. self._log_instrument_registration_conflict(
  683. name,
  684. ObservableGauge.__name__,
  685. unit,
  686. description,
  687. status,
  688. )
  689. return NoOpObservableGauge(
  690. name,
  691. callbacks,
  692. unit=unit,
  693. description=description,
  694. )
  695. def create_observable_up_down_counter(
  696. self,
  697. name: str,
  698. callbacks: Sequence[CallbackT] | None = None,
  699. unit: str = "",
  700. description: str = "",
  701. ) -> ObservableUpDownCounter:
  702. """Returns a no-op ObservableUpDownCounter."""
  703. status = self._register_instrument(
  704. name, NoOpObservableUpDownCounter, unit, description
  705. )
  706. if status.conflict:
  707. self._log_instrument_registration_conflict(
  708. name,
  709. ObservableUpDownCounter.__name__,
  710. unit,
  711. description,
  712. status,
  713. )
  714. return NoOpObservableUpDownCounter(
  715. name,
  716. callbacks,
  717. unit=unit,
  718. description=description,
  719. )
  720. _METER_PROVIDER_SET_ONCE = Once()
  721. _METER_PROVIDER: MeterProvider | None = None
  722. _PROXY_METER_PROVIDER = _ProxyMeterProvider()
  723. def get_meter(
  724. name: str,
  725. version: str = "",
  726. meter_provider: MeterProvider | None = None,
  727. schema_url: str | None = None,
  728. attributes: Attributes | None = None,
  729. ) -> "Meter":
  730. """Returns a `Meter` for use by the given instrumentation library.
  731. This function is a convenience wrapper for
  732. `opentelemetry.metrics.MeterProvider.get_meter`.
  733. If meter_provider is omitted the current configured one is used.
  734. """
  735. if meter_provider is None:
  736. meter_provider = get_meter_provider()
  737. return meter_provider.get_meter(name, version, schema_url, attributes)
  738. def _set_meter_provider(meter_provider: MeterProvider, log: bool) -> None:
  739. def set_mp() -> None:
  740. global _METER_PROVIDER # pylint: disable=global-statement
  741. _METER_PROVIDER = meter_provider
  742. # gives all proxies real instruments off the newly set meter provider
  743. _PROXY_METER_PROVIDER.on_set_meter_provider(meter_provider)
  744. did_set = _METER_PROVIDER_SET_ONCE.do_once(set_mp)
  745. if log and not did_set:
  746. _logger.warning("Overriding of current MeterProvider is not allowed")
  747. def set_meter_provider(meter_provider: MeterProvider) -> None:
  748. """Sets the current global :class:`~.MeterProvider` object.
  749. This can only be done once, a warning will be logged if any further attempt
  750. is made.
  751. """
  752. _set_meter_provider(meter_provider, log=True)
  753. def get_meter_provider() -> MeterProvider:
  754. """Gets the current global :class:`~.MeterProvider` object."""
  755. if _METER_PROVIDER is None:
  756. if OTEL_PYTHON_METER_PROVIDER not in environ:
  757. return _PROXY_METER_PROVIDER
  758. meter_provider: MeterProvider = _load_provider( # type: ignore
  759. OTEL_PYTHON_METER_PROVIDER, "meter_provider"
  760. )
  761. _set_meter_provider(meter_provider, log=False)
  762. # _METER_PROVIDER will have been set by one thread
  763. return cast("MeterProvider", _METER_PROVIDER)