diff --git a/CHANGELOG.md b/CHANGELOG.md index e0f43a10da0..460c439e73a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +- Adds environment variables for log exporter + ([#3037](https://github.com/open-telemetry/opentelemetry-python/pull/3037)) ## Version 1.15.0/0.36b0 (2022-12-09) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/pyproject.toml b/exporter/opentelemetry-exporter-otlp-proto-grpc/pyproject.toml index 1daa2744236..8a07a45bb60 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/pyproject.toml +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ "grpcio >= 1.0.0, < 2.0.0", "opentelemetry-api ~= 1.12", "opentelemetry-proto == 1.16.0.dev", - "opentelemetry-sdk ~= 1.12", + "opentelemetry-sdk ~= 1.16.0.dev", ] [project.optional-dependencies] diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py index 489cf35c372..f7cbadfec8b 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py @@ -11,12 +11,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +from os import environ from typing import Optional, Sequence from grpc import ChannelCredentials, Compression from opentelemetry.exporter.otlp.proto.grpc.exporter import ( OTLPExporterMixin, get_resource_data, + _get_credentials, _translate_value, + environ_to_compression, ) from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import ( ExportLogsServiceRequest, @@ -34,6 +37,15 @@ from opentelemetry.sdk._logs import LogData from opentelemetry.sdk._logs.export import LogExporter, LogExportResult +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, + OTEL_EXPORTER_OTLP_LOGS_HEADERS, + OTEL_EXPORTER_OTLP_LOGS_INSECURE, + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, +) + class OTLPLogExporter( LogExporter, @@ -52,13 +64,40 @@ def __init__( timeout: Optional[int] = None, compression: Optional[Compression] = None, ): + if insecure is None: + insecure = environ.get(OTEL_EXPORTER_OTLP_LOGS_INSECURE) + if insecure is not None: + insecure = insecure.lower() == "true" + + if ( + not insecure + and environ.get(OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE) is not None + ): + credentials = _get_credentials( + credentials, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE + ) + + environ_timeout = environ.get(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT) + environ_timeout = ( + int(environ_timeout) if environ_timeout is not None else None + ) + + compression = ( + environ_to_compression(OTEL_EXPORTER_OTLP_LOGS_COMPRESSION) + if compression is None + else compression + ) + endpoint = endpoint or environ.get(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) + + headers = headers or environ.get(OTEL_EXPORTER_OTLP_LOGS_HEADERS) + super().__init__( **{ "endpoint": endpoint, "insecure": insecure, "credentials": credentials, "headers": headers, - "timeout": timeout, + "timeout": timeout or environ_timeout, "compression": compression, } ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index 0861d06d654..c7996b9bf9e 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -14,12 +14,13 @@ import time from concurrent.futures import ThreadPoolExecutor +from os.path import dirname from unittest import TestCase from unittest.mock import patch from google.protobuf.duration_pb2 import Duration from google.rpc.error_details_pb2 import RetryInfo -from grpc import StatusCode, server +from grpc import ChannelCredentials, Compression, StatusCode, server from opentelemetry._logs import SeverityNumber from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( @@ -47,10 +48,19 @@ ) from opentelemetry.sdk._logs import LogData, LogRecord from opentelemetry.sdk._logs.export import LogExportResult +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, + OTEL_EXPORTER_OTLP_LOGS_HEADERS, + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, +) from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.trace import TraceFlags +THIS_DIR = dirname(__file__) + class LogsServiceServicerUNAVAILABLEDelay(LogsServiceServicer): # pylint: disable=invalid-name,unused-argument,no-self-use @@ -100,7 +110,6 @@ def Export(self, request, context): class TestOTLPLogExporter(TestCase): def setUp(self): - self.exporter = OTLPLogExporter() self.server = server(ThreadPoolExecutor(max_workers=10)) @@ -164,6 +173,32 @@ def test_exporting(self): # pylint: disable=protected-access self.assertEqual(self.exporter._exporting, "logs") + @patch.dict( + "os.environ", + { + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "logs:4317", + OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE: THIS_DIR + + "/../fixtures/test.cert", + OTEL_EXPORTER_OTLP_LOGS_HEADERS: " key1=value1,KEY2 = VALUE=2", + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "10", + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: "gzip", + }, + ) + @patch( + "opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__" + ) + def test_env_variables(self, mock_exporter_mixin): + OTLPLogExporter() + + self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1) + _, kwargs = mock_exporter_mixin.call_args_list[0] + self.assertEqual(kwargs["endpoint"], "logs:4317") + self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = VALUE=2") + self.assertEqual(kwargs["timeout"], 10) + self.assertEqual(kwargs["compression"], Compression.Gzip) + self.assertIsNotNone(kwargs["credentials"]) + self.assertIsInstance(kwargs["credentials"], ChannelCredentials) + @patch( "opentelemetry.exporter.otlp.proto.grpc.exporter.ssl_channel_credentials" ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/pyproject.toml b/exporter/opentelemetry-exporter-otlp-proto-http/pyproject.toml index 680fb8b76d3..aec6afe1fce 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/pyproject.toml +++ b/exporter/opentelemetry-exporter-otlp-proto-http/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "googleapis-common-protos ~= 1.52", "opentelemetry-api ~= 1.12", "opentelemetry-proto == 1.16.0.dev", - "opentelemetry-sdk ~= 1.12", + "opentelemetry-sdk ~= 1.16.0.dev", "requests ~= 2.7", ] diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py index 49daa29e8ab..dfd70180e06 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py @@ -29,6 +29,11 @@ OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, + OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_HEADERS, + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, ) from opentelemetry.sdk._logs import LogData from opentelemetry.sdk._logs.export import ( @@ -79,16 +84,26 @@ def __init__( compression: Optional[Compression] = None, session: Optional[requests.Session] = None, ): - self._endpoint = endpoint or _append_logs_path( - environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, DEFAULT_ENDPOINT) + self._endpoint = endpoint or environ.get( + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, + _append_logs_path( + environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, DEFAULT_ENDPOINT) + ), ) self._certificate_file = certificate_file or environ.get( - OTEL_EXPORTER_OTLP_CERTIFICATE, True + OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + environ.get(OTEL_EXPORTER_OTLP_CERTIFICATE, True), + ) + headers_string = environ.get( + OTEL_EXPORTER_OTLP_LOGS_HEADERS, + environ.get(OTEL_EXPORTER_OTLP_HEADERS, ""), ) - headers_string = environ.get(OTEL_EXPORTER_OTLP_HEADERS, "") self._headers = headers or parse_env_headers(headers_string) self._timeout = timeout or int( - environ.get(OTEL_EXPORTER_OTLP_TIMEOUT, DEFAULT_TIMEOUT) + environ.get( + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, + environ.get(OTEL_EXPORTER_OTLP_TIMEOUT, DEFAULT_TIMEOUT), + ) ) self._compression = compression or _compression_from_env() self._session = session or requests.Session() @@ -170,7 +185,12 @@ def shutdown(self): def _compression_from_env() -> Compression: compression = ( - environ.get(OTEL_EXPORTER_OTLP_COMPRESSION, "none").lower().strip() + environ.get( + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, + environ.get(OTEL_EXPORTER_OTLP_COMPRESSION, "none"), + ) + .lower() + .strip() ) return Compression(compression) diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py index b273a618cf9..f61a4291ed3 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py @@ -61,6 +61,11 @@ OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, + OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, + OTEL_EXPORTER_OTLP_LOGS_HEADERS, + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, OTEL_EXPORTER_OTLP_TIMEOUT, ) from opentelemetry.sdk.resources import Resource as SDKResource @@ -92,6 +97,38 @@ def test_constructor_default(self): "application/x-protobuf", ) + @patch.dict( + "os.environ", + { + OTEL_EXPORTER_OTLP_CERTIFICATE: ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, + OTEL_EXPORTER_OTLP_ENDPOINT: ENV_ENDPOINT, + OTEL_EXPORTER_OTLP_HEADERS: ENV_HEADERS, + OTEL_EXPORTER_OTLP_TIMEOUT: ENV_TIMEOUT, + OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE: "logs/certificate.env", + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: Compression.Deflate.value, + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "https://logs.endpoint.env", + OTEL_EXPORTER_OTLP_LOGS_HEADERS: "logsEnv1=val1,logsEnv2=val2,logsEnv3===val3==", + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "40", + }, + ) + def test_exporter_metrics_env_take_priority(self): + exporter = OTLPLogExporter() + + self.assertEqual(exporter._endpoint, "https://logs.endpoint.env") + self.assertEqual(exporter._certificate_file, "logs/certificate.env") + self.assertEqual(exporter._timeout, 40) + self.assertIs(exporter._compression, Compression.Deflate) + self.assertEqual( + exporter._headers, + { + "logsenv1": "val1", + "logsenv2": "val2", + "logsenv3": "==val3==", + }, + ) + self.assertIsInstance(exporter._session, requests.Session) + @patch.dict( "os.environ", { diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py index 562863156fc..376fb187dc1 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py @@ -344,6 +344,15 @@ A scheme of https indicates a secure connection and takes precedence over this configuration setting. """ +OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT" +""" +.. envvar:: OTEL_EXPORTER_OTLP_LOGS_ENDPOINT + +The :envvar:`OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` target to which the log exporter is going to send logs. +The endpoint MUST be a valid URL host, and MAY contain a scheme (http or https), port and path. +A scheme of https indicates a secure connection and takes precedence over this configuration setting. +""" + OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE = "OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE" """ .. envvar:: OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE @@ -378,6 +387,14 @@ associated with gRPC or HTTP requests. """ +OTEL_EXPORTER_OTLP_LOGS_HEADERS = "OTEL_EXPORTER_OTLP_LOGS_HEADERS" +""" +.. envvar:: OTEL_EXPORTER_OTLP_LOGS_HEADERS + +The :envvar:`OTEL_EXPORTER_OTLP_LOGS_HEADERS` contains the key-value pairs to be used as headers for logs +associated with gRPC or HTTP requests. +""" + OTEL_EXPORTER_OTLP_TRACES_COMPRESSION = "OTEL_EXPORTER_OTLP_TRACES_COMPRESSION" """ .. envvar:: OTEL_EXPORTER_OTLP_TRACES_COMPRESSION @@ -396,6 +413,14 @@ exporter. If both are present, this takes higher precedence. """ +OTEL_EXPORTER_OTLP_LOGS_COMPRESSION = "OTEL_EXPORTER_OTLP_LOGS_COMPRESSION" +""" +.. envvar:: OTEL_EXPORTER_OTLP_LOGS_COMPRESSION + +Same as :envvar:`OTEL_EXPORTER_OTLP_COMPRESSION` but only for the log +exporter. If both are present, this takes higher precedence. +""" + OTEL_EXPORTER_OTLP_TRACES_TIMEOUT = "OTEL_EXPORTER_OTLP_TRACES_TIMEOUT" """ .. envvar:: OTEL_EXPORTER_OTLP_TRACES_TIMEOUT @@ -421,6 +446,15 @@ Default: False """ +OTEL_EXPORTER_OTLP_LOGS_INSECURE = "OTEL_EXPORTER_OTLP_LOGS_INSECURE" +""" +.. envvar:: OTEL_EXPORTER_OTLP_LOGS_INSECURE + +The :envvar:`OTEL_EXPORTER_OTLP_LOGS_INSECURE` represents whether to enable client transport security +for gRPC requests for metrics. A scheme of https takes precedence over the this configuration setting. +Default: False +""" + OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT" """ .. envvar:: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT @@ -440,6 +474,14 @@ TLS credentials of gRPC client for traces. Should only be used for a secure connection for tracing. """ +OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE = "OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE" +""" +.. envvar:: OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE + +The :envvar:`OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE` stores the path to the certificate file for +TLS credentials of gRPC client for traces. Should only be used for a secure connection for tracing. +""" + OTEL_EXPORTER_OTLP_METRICS_HEADERS = "OTEL_EXPORTER_OTLP_METRICS_HEADERS" """ .. envvar:: OTEL_EXPORTER_OTLP_METRICS_HEADERS @@ -456,6 +498,14 @@ wait for each batch export for metrics. """ +OTEL_EXPORTER_OTLP_LOGS_TIMEOUT = "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT" +""" +.. envvar:: OTEL_EXPORTER_OTLP_LOGS_TIMEOUT + +The :envvar:`OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` is the maximum time the OTLP exporter will +wait for each batch export for logs. +""" + OTEL_EXPORTER_OTLP_METRICS_COMPRESSION = ( "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION" )