bdd-trace 0.3.1__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bdd-trace
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: BDD项目日志和追踪SDK
5
5
  Author: 蔡涛
6
6
  Author-email: 蔡涛 <caitao@zhejianglab.cn>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bdd-trace"
3
- version = "0.3.1"
3
+ version = "0.4.0"
4
4
  description = "BDD项目日志和追踪SDK"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "蔡涛", email = "caitao@zhejianglab.cn" }]
@@ -0,0 +1,4 @@
1
+ from .log import setup_logging, stop_logging
2
+ from .trace import Profile, init_trace
3
+
4
+ __all__ = ["init_trace", "Profile", "setup_logging", "stop_logging"]
@@ -0,0 +1,80 @@
1
+ import sys
2
+ from logging import INFO, Formatter, Handler, LogRecord, StreamHandler, getLogger
3
+ from logging.handlers import QueueHandler, QueueListener
4
+ from queue import Queue
5
+
6
+ from opentelemetry.sdk._logs import LoggingHandler
7
+
8
+ from bdd_trace.trace import Profile
9
+
10
+ _log_queue: Queue | None = None
11
+ _log_listener: QueueListener | None = None
12
+
13
+
14
+ def setup_logging(
15
+ profile: Profile,
16
+ modules: list[str],
17
+ level: str | int = INFO,
18
+ trace_id_format: str = "%(asctime)s|%(levelname)s|%(name)s|%(otelTraceID)s|%(message)s",
19
+ no_trace_id_format: str = "%(asctime)s|%(levelname)s|%(name)s|%(message)s",
20
+ ) -> None:
21
+ global _log_queue, _log_listener
22
+ if _log_queue is not None or _log_listener is not None:
23
+ raise RuntimeError("logging is already setup")
24
+
25
+ root_logger = getLogger()
26
+ root_logger.handlers = []
27
+
28
+ _log_queue = Queue(1000)
29
+ for module in modules:
30
+ _setup_module_logger(level, module)
31
+
32
+ format = trace_id_format if profile == Profile.NO_TRACE else no_trace_id_format
33
+ console_handler = _create_console_handler(modules, format)
34
+ log_process_handlers = [console_handler]
35
+ if profile != Profile.NO_TRACE:
36
+ otel_handler = LoggingHandler()
37
+ log_process_handlers.append(otel_handler)
38
+
39
+ _log_listener = QueueListener(_log_queue, *log_process_handlers)
40
+ _log_listener.start()
41
+
42
+
43
+ def stop_logging() -> None:
44
+ global _log_queue, _log_listener
45
+ if _log_listener is not None:
46
+ _log_listener.stop()
47
+ _log_listener = None
48
+ _log_queue = None
49
+
50
+
51
+ def _create_console_handler(modules: list[str], format: str) -> Handler:
52
+ class ModulePrefixFilter:
53
+ def __init__(self, module_prefixes: list[str]):
54
+ self.module_prefixes: set[str] = set(module_prefixes)
55
+ self.module_prefixes_with_dot: tuple[str, ...] = tuple(f"{prefix}." for prefix in module_prefixes)
56
+
57
+ def filter(self, record: LogRecord) -> bool:
58
+ return record.name in self.module_prefixes or record.name.startswith(self.module_prefixes_with_dot)
59
+
60
+ handler = StreamHandler(sys.stderr)
61
+ handler.setFormatter(Formatter(format))
62
+ handler.addFilter(ModulePrefixFilter(modules))
63
+ return handler
64
+
65
+
66
+ def _setup_module_logger(level: str | int, module: str) -> None:
67
+ class SingleLineFormatter(Formatter):
68
+ def formatMessage(self, record: LogRecord) -> str:
69
+ return super().formatMessage(record).replace("\n", " ").replace("\r", "")
70
+
71
+ logger = getLogger(module)
72
+ logger.setLevel(level)
73
+ logger.propagate = False
74
+
75
+ queue = _log_queue
76
+ if queue is None:
77
+ raise RuntimeError("log queue is not initialized")
78
+ handler = QueueHandler(queue)
79
+ handler.setFormatter(SingleLineFormatter())
80
+ logger.handlers = [handler]
@@ -0,0 +1,108 @@
1
+ import logging
2
+ import os
3
+ from enum import Enum
4
+
5
+ from opentelemetry.instrumentation import auto_instrumentation
6
+
7
+
8
+ class Profile(str, Enum):
9
+ NO_TRACE = "no_trace"
10
+ DEV = "dev"
11
+ TEST = "test"
12
+ PROD = "prod"
13
+
14
+
15
+ _TRACES_EXPORTER_KEY = "traces_exporter"
16
+ _METRICS_EXPORTER_KEY = "metrics_exporter"
17
+ _LOGS_EXPORTER_KEY = "logs_exporter"
18
+ _EXPORTER_OTLP_ENDPOINT_KEY = "exporter_otlp_endpoint"
19
+ _EXPORTER_OTLP_INSECURE_KEY = "exporter_otlp_insecure"
20
+ _SERVICE_NAME_KEY = "service_name"
21
+ _PYTHON_FASTAPI_EXCLUDE_URLS_KEY = "python_fastapi_excluded_urls"
22
+ _INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST_KEY = "instrumentation_http_capture_headers_server_request"
23
+
24
+ _default_envs = {
25
+ _TRACES_EXPORTER_KEY: "otlp",
26
+ _METRICS_EXPORTER_KEY: "otlp",
27
+ _LOGS_EXPORTER_KEY: "otlp",
28
+ _PYTHON_FASTAPI_EXCLUDE_URLS_KEY: "/healthCheck",
29
+ _INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST_KEY: "X-User-Id,X-Conversation-From",
30
+ _EXPORTER_OTLP_INSECURE_KEY: "true",
31
+ }
32
+
33
+ _profile_config = {
34
+ Profile.DEV: {
35
+ _TRACES_EXPORTER_KEY: "console",
36
+ _METRICS_EXPORTER_KEY: "console",
37
+ _LOGS_EXPORTER_KEY: "console",
38
+ _EXPORTER_OTLP_ENDPOINT_KEY: None,
39
+ },
40
+ Profile.TEST: {
41
+ _TRACES_EXPORTER_KEY: "otlp",
42
+ _METRICS_EXPORTER_KEY: "otlp",
43
+ _LOGS_EXPORTER_KEY: "otlp",
44
+ _EXPORTER_OTLP_ENDPOINT_KEY: "http://otel-sls-collector:4317",
45
+ },
46
+ Profile.PROD: {
47
+ _TRACES_EXPORTER_KEY: "otlp",
48
+ _METRICS_EXPORTER_KEY: "otlp",
49
+ _LOGS_EXPORTER_KEY: "otlp",
50
+ _EXPORTER_OTLP_ENDPOINT_KEY: "http://otel-sls-collector:4317",
51
+ },
52
+ }
53
+
54
+ logger = logging.getLogger(__name__)
55
+
56
+
57
+ def init_trace(
58
+ *,
59
+ service_name: str | None = None,
60
+ profile: Profile | None = None,
61
+ exporter_otlp_endpoint: str | None = None,
62
+ **kwargs,
63
+ ) -> None:
64
+ # 设置环境变量,优先级:环境变量 > 参数 > profile 配置 > 默认配置
65
+ for key, value in kwargs.items():
66
+ _set_env(key, value)
67
+
68
+ if profile:
69
+ if profile == Profile.NO_TRACE:
70
+ logger.debug("NO_TRACE profile is set, skip auto instrumentation")
71
+ return
72
+ for key, value in _profile_config[profile].items():
73
+ _set_env(key, value)
74
+ elif exporter_otlp_endpoint:
75
+ _set_env(_EXPORTER_OTLP_ENDPOINT_KEY, exporter_otlp_endpoint)
76
+ else:
77
+ raise ValueError("either profile or exporter_otlp_endpoint is required")
78
+
79
+ if service_name:
80
+ _set_env(_SERVICE_NAME_KEY, service_name)
81
+ elif _get_env(_SERVICE_NAME_KEY) is None:
82
+ raise ValueError("service_name is required")
83
+
84
+ for key, value in _default_envs.items():
85
+ _set_env(key, value)
86
+
87
+ auto_instrumentation.initialize()
88
+
89
+
90
+ def _set_env(key: str, value: str | None) -> None:
91
+ if value is None:
92
+ return
93
+
94
+ env_key = _convert_to_env_key(key)
95
+ if existing_value := os.getenv(env_key):
96
+ logger.info(f"existing env {env_key}={existing_value}")
97
+ else:
98
+ logger.debug(f"set env {env_key}={value}")
99
+ os.environ[env_key] = value
100
+
101
+
102
+ def _get_env(key: str) -> str | None:
103
+ env_key = _convert_to_env_key(key)
104
+ return os.getenv(env_key)
105
+
106
+
107
+ def _convert_to_env_key(key: str) -> str:
108
+ return f"OTEL_{key.upper()}"
@@ -1,3 +0,0 @@
1
- from .trace import Profile, init_trace
2
-
3
- __all__ = ["init_trace", "Profile"]
@@ -1,116 +0,0 @@
1
- import logging
2
- import os
3
- from enum import Enum
4
-
5
- from opentelemetry.instrumentation import auto_instrumentation
6
- from opentelemetry.sdk._logs import LoggingHandler
7
-
8
-
9
- class Profile(str, Enum):
10
- NO_TRACE = "no_trace"
11
- DEV = "dev"
12
- TEST = "test"
13
- PROD = "prod"
14
-
15
-
16
- TRACES_EXPORTER_KEY = "traces_exporter"
17
- METRICS_EXPORTER_KEY = "metrics_exporter"
18
- LOGS_EXPORTER_KEY = "logs_exporter"
19
- EXPORTER_OTLP_ENDPOINT_KEY = "exporter_otlp_endpoint"
20
- EXPORTER_OTLP_INSECURE_KEY = "exporter_otlp_insecure"
21
- SERVICE_NAME_KEY = "service_name"
22
- PYTHON_FASTAPI_EXCLUDE_URLS_KEY = "python_fastapi_excluded_urls"
23
- INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST_KEY = "instrumentation_http_capture_headers_server_request"
24
-
25
- default_envs = {
26
- TRACES_EXPORTER_KEY: "otlp",
27
- METRICS_EXPORTER_KEY: "otlp",
28
- LOGS_EXPORTER_KEY: "otlp",
29
- PYTHON_FASTAPI_EXCLUDE_URLS_KEY: "/healthCheck",
30
- INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST_KEY: "X-User-Id,X-Conversation-From",
31
- EXPORTER_OTLP_INSECURE_KEY: "true",
32
- }
33
-
34
- profile_config = {
35
- Profile.DEV: {
36
- TRACES_EXPORTER_KEY: "console",
37
- METRICS_EXPORTER_KEY: "console",
38
- LOGS_EXPORTER_KEY: "console",
39
- EXPORTER_OTLP_ENDPOINT_KEY: None,
40
- },
41
- Profile.TEST: {
42
- TRACES_EXPORTER_KEY: "otlp",
43
- METRICS_EXPORTER_KEY: "otlp",
44
- LOGS_EXPORTER_KEY: "otlp",
45
- EXPORTER_OTLP_ENDPOINT_KEY: "http://otel-sls-collector:4317",
46
- },
47
- Profile.PROD: {
48
- TRACES_EXPORTER_KEY: "otlp",
49
- METRICS_EXPORTER_KEY: "otlp",
50
- LOGS_EXPORTER_KEY: "otlp",
51
- EXPORTER_OTLP_ENDPOINT_KEY: "http://otel-sls-collector:4317",
52
- },
53
- }
54
-
55
- logger = logging.getLogger(__name__)
56
-
57
-
58
- def init_trace(
59
- *,
60
- service_name: str | None = None,
61
- profile: Profile | None = None,
62
- exporter_otlp_endpoint: str | None = None,
63
- **kwargs,
64
- ) -> None:
65
- # 设置环境变量,优先级:环境变量 > 参数 > profile 配置 > 默认配置
66
- for key, value in kwargs.items():
67
- _set_env(key, value)
68
-
69
- if profile:
70
- if profile == Profile.NO_TRACE:
71
- logger.debug("NO_TRACE profile is set, skip auto instrumentation")
72
- return
73
- for key, value in profile_config[profile].items():
74
- _set_env(key, value)
75
- elif exporter_otlp_endpoint:
76
- _set_env(EXPORTER_OTLP_ENDPOINT_KEY, exporter_otlp_endpoint)
77
- else:
78
- raise ValueError("either profile or exporter_otlp_endpoint is required")
79
-
80
- if service_name:
81
- _set_env(SERVICE_NAME_KEY, service_name)
82
- elif _get_env(SERVICE_NAME_KEY) is None:
83
- raise ValueError("service_name is required")
84
-
85
- for key, value in default_envs.items():
86
- _set_env(key, value)
87
-
88
- auto_instrumentation.initialize()
89
-
90
- _setup_logger_handler()
91
-
92
-
93
- def _set_env(key: str, value: str | None) -> None:
94
- if value is None:
95
- return
96
-
97
- env_key = _convert_to_env_key(key)
98
- if existing_value := os.getenv(env_key):
99
- logger.info(f"existing env {env_key}={existing_value}")
100
- else:
101
- logger.debug(f"set env {env_key}={value}")
102
- os.environ[env_key] = value
103
-
104
-
105
- def _get_env(key: str) -> str | None:
106
- env_key = _convert_to_env_key(key)
107
- return os.getenv(env_key)
108
-
109
-
110
- def _convert_to_env_key(key: str) -> str:
111
- return f"OTEL_{key.upper()}"
112
-
113
-
114
- def _setup_logger_handler() -> None:
115
- otel_handler = LoggingHandler()
116
- logging.getLogger().addHandler(otel_handler)
File without changes