huaweicloud-encryption-sdk-python 1.2.1__py3-none-any.whl

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.
Files changed (174) hide show
  1. examples/file_encryption_example.py +57 -0
  2. examples/key_capsule_encryption_example.py +240 -0
  3. examples/kms_discovery_encryption_example.py +56 -0
  4. examples/local_aes_encryption_example.py +59 -0
  5. examples/test_rsa.py +42 -0
  6. examples/test_sm2.py +38 -0
  7. examples/test_sm4_cbc.py +37 -0
  8. examples/test_sm4_gcm.py +37 -0
  9. huaweicloud_encryption_sdk_python-1.2.1.dist-info/METADATA +18 -0
  10. huaweicloud_encryption_sdk_python-1.2.1.dist-info/RECORD +174 -0
  11. huaweicloud_encryption_sdk_python-1.2.1.dist-info/WHEEL +5 -0
  12. huaweicloud_encryption_sdk_python-1.2.1.dist-info/top_level.txt +3 -0
  13. huaweicloud_encryptionsdk/__init__.py +5 -0
  14. huaweicloud_encryptionsdk/_apig_sdk/__init__.py +1 -0
  15. huaweicloud_encryptionsdk/_apig_sdk/signer.py +278 -0
  16. huaweicloud_encryptionsdk/_apig_sdk/signer_v11.py +70 -0
  17. huaweicloud_encryptionsdk/_apig_sdk/sm3_hash.py +141 -0
  18. huaweicloud_encryptionsdk/_lts_sdk/auth/__init__.py +0 -0
  19. huaweicloud_encryptionsdk/_lts_sdk/auth/signer.py +174 -0
  20. huaweicloud_encryptionsdk/_lts_sdk/consumer/__init__.py +0 -0
  21. huaweicloud_encryptionsdk/_lts_sdk/consumer/client/__init__.py +0 -0
  22. huaweicloud_encryptionsdk/_lts_sdk/consumer/client/consumer_client_adapter.py +59 -0
  23. huaweicloud_encryptionsdk/_lts_sdk/consumer/client/http_client.py +211 -0
  24. huaweicloud_encryptionsdk/_lts_sdk/consumer/core/__init__.py +0 -0
  25. huaweicloud_encryptionsdk/_lts_sdk/consumer/core/client_consumer_worker.py +106 -0
  26. huaweicloud_encryptionsdk/_lts_sdk/consumer/core/consumer_heartbeat.py +73 -0
  27. huaweicloud_encryptionsdk/_lts_sdk/consumer/core/log_shard_consumer.py +179 -0
  28. huaweicloud_encryptionsdk/_lts_sdk/consumer/interface/__init__.py +0 -0
  29. huaweicloud_encryptionsdk/_lts_sdk/consumer/interface/consumer_check_point_tracker.py +120 -0
  30. huaweicloud_encryptionsdk/_lts_sdk/consumer/interface/consumer_processor.py +18 -0
  31. huaweicloud_encryptionsdk/_lts_sdk/consumer/interface/consumer_processor_factory.py +3 -0
  32. huaweicloud_encryptionsdk/_lts_sdk/consumer/model/__init__.py +0 -0
  33. huaweicloud_encryptionsdk/_lts_sdk/consumer/model/check_point.py +20 -0
  34. huaweicloud_encryptionsdk/_lts_sdk/consumer/model/consumer_config.py +27 -0
  35. huaweicloud_encryptionsdk/_lts_sdk/consumer/model/log_data.py +18 -0
  36. huaweicloud_encryptionsdk/_lts_sdk/consumer/model/task_result.py +40 -0
  37. huaweicloud_encryptionsdk/_lts_sdk/consumer/task/__init__.py +0 -0
  38. huaweicloud_encryptionsdk/_lts_sdk/consumer/task/consumer_fetch_task.py +53 -0
  39. huaweicloud_encryptionsdk/_lts_sdk/consumer/task/initial_task.py +55 -0
  40. huaweicloud_encryptionsdk/_lts_sdk/consumer/task/process_task.py +25 -0
  41. huaweicloud_encryptionsdk/_lts_sdk/consumer/task/shutdown_task.py +24 -0
  42. huaweicloud_encryptionsdk/_lts_sdk/consumer_main.py +88 -0
  43. huaweicloud_encryptionsdk/_lts_sdk/parallel_main.py +64 -0
  44. huaweicloud_encryptionsdk/_lts_sdk/producer/__init__.py +0 -0
  45. huaweicloud_encryptionsdk/_lts_sdk/producer/callback/__init__.py +0 -0
  46. huaweicloud_encryptionsdk/_lts_sdk/producer/callback/call_back.py +7 -0
  47. huaweicloud_encryptionsdk/_lts_sdk/producer/core/__init__.py +0 -0
  48. huaweicloud_encryptionsdk/_lts_sdk/producer/core/accumulator.py +135 -0
  49. huaweicloud_encryptionsdk/_lts_sdk/producer/core/io_thread_pool.py +107 -0
  50. huaweicloud_encryptionsdk/_lts_sdk/producer/core/io_worker.py +124 -0
  51. huaweicloud_encryptionsdk/_lts_sdk/producer/core/mover.py +69 -0
  52. huaweicloud_encryptionsdk/_lts_sdk/producer/core/producer.py +98 -0
  53. huaweicloud_encryptionsdk/_lts_sdk/producer/lts_http/__init__.py +0 -0
  54. huaweicloud_encryptionsdk/_lts_sdk/producer/lts_http/http_client.py +63 -0
  55. huaweicloud_encryptionsdk/_lts_sdk/producer/lts_http/simple_request.py +14 -0
  56. huaweicloud_encryptionsdk/_lts_sdk/producer/model/__init__.py +0 -0
  57. huaweicloud_encryptionsdk/_lts_sdk/producer/model/config.py +50 -0
  58. huaweicloud_encryptionsdk/_lts_sdk/producer/model/error.py +18 -0
  59. huaweicloud_encryptionsdk/_lts_sdk/producer/model/log.py +87 -0
  60. huaweicloud_encryptionsdk/_lts_sdk/producer/model/log_batch.py +154 -0
  61. huaweicloud_encryptionsdk/_lts_sdk/producer/model/lts_store.py +21 -0
  62. huaweicloud_encryptionsdk/_lts_sdk/producer/model/result.py +17 -0
  63. huaweicloud_encryptionsdk/_lts_sdk/producer/model/retry_queue.py +39 -0
  64. huaweicloud_encryptionsdk/_lts_sdk/serial_main.py +77 -0
  65. huaweicloud_encryptionsdk/_lts_sdk/utils/__init__.py +0 -0
  66. huaweicloud_encryptionsdk/_lts_sdk/utils/common.py +57 -0
  67. huaweicloud_encryptionsdk/_lts_sdk/utils/lts_threading.py +32 -0
  68. huaweicloud_encryptionsdk/cache/__init__.py +0 -0
  69. huaweicloud_encryptionsdk/cache/data_key_cache.py +79 -0
  70. huaweicloud_encryptionsdk/cache/local_data_key_cache.py +161 -0
  71. huaweicloud_encryptionsdk/common/__init__.py +0 -0
  72. huaweicloud_encryptionsdk/common/constants.py +15 -0
  73. huaweicloud_encryptionsdk/common/counting_output_stream.py +18 -0
  74. huaweicloud_encryptionsdk/common/crypto_factory.py +48 -0
  75. huaweicloud_encryptionsdk/common/ec_private_key_loader.py +43 -0
  76. huaweicloud_encryptionsdk/common/ecs_metadata_service.py +42 -0
  77. huaweicloud_encryptionsdk/common/file_path_for_example_constants.py +4 -0
  78. huaweicloud_encryptionsdk/common/utils.py +162 -0
  79. huaweicloud_encryptionsdk/exception/__init__.py +0 -0
  80. huaweicloud_encryptionsdk/exception/errors.py +109 -0
  81. huaweicloud_encryptionsdk/handler/__init__.py +0 -0
  82. huaweicloud_encryptionsdk/handler/cipher_handler.py +294 -0
  83. huaweicloud_encryptionsdk/handler/crypto_handler.py +24 -0
  84. huaweicloud_encryptionsdk/handler/default_crypto_handler.py +148 -0
  85. huaweicloud_encryptionsdk/handler/default_serialize_handler.py +45 -0
  86. huaweicloud_encryptionsdk/handler/file_decrypt_handler.py +101 -0
  87. huaweicloud_encryptionsdk/handler/file_encrypt_handler.py +30 -0
  88. huaweicloud_encryptionsdk/handler/serialize_handler.py +14 -0
  89. huaweicloud_encryptionsdk/huawei_config.py +49 -0
  90. huaweicloud_encryptionsdk/huawei_crypto.py +311 -0
  91. huaweicloud_encryptionsdk/keyrings/__init__.py +0 -0
  92. huaweicloud_encryptionsdk/keyrings/keyring.py +14 -0
  93. huaweicloud_encryptionsdk/keyrings/keyring_factory.py +10 -0
  94. huaweicloud_encryptionsdk/keyrings/kms_keyring_factory.py +21 -0
  95. huaweicloud_encryptionsdk/keyrings/kmskeyring/__init__.py +0 -0
  96. huaweicloud_encryptionsdk/keyrings/kmskeyring/kms_discovery_keyring.py +51 -0
  97. huaweicloud_encryptionsdk/keyrings/kmskeyring/kms_key_capsule_keyring.py +100 -0
  98. huaweicloud_encryptionsdk/keyrings/kmskeyring/kms_keyring.py +155 -0
  99. huaweicloud_encryptionsdk/keyrings/kmskeyring/kms_multi_region_keyring.py +8 -0
  100. huaweicloud_encryptionsdk/keyrings/raw_keyring_factory.py +27 -0
  101. huaweicloud_encryptionsdk/keyrings/rawkeyring/__init__.py +0 -0
  102. huaweicloud_encryptionsdk/keyrings/rawkeyring/raw_aes_keyring.py +63 -0
  103. huaweicloud_encryptionsdk/keyrings/rawkeyring/raw_keyring.py +112 -0
  104. huaweicloud_encryptionsdk/keyrings/rawkeyring/raw_rsa_keyring.py +55 -0
  105. huaweicloud_encryptionsdk/keyrings/rawkeyring/raw_sm2_keyring.py +75 -0
  106. huaweicloud_encryptionsdk/keyrings/rawkeyring/raw_sm4_cbc_keyring.py +66 -0
  107. huaweicloud_encryptionsdk/keyrings/rawkeyring/raw_sm4_keyring.py +70 -0
  108. huaweicloud_encryptionsdk/kms/__init__.py +0 -0
  109. huaweicloud_encryptionsdk/kms/create_key_capsule_request.py +35 -0
  110. huaweicloud_encryptionsdk/kms/create_key_capsule_response.py +19 -0
  111. huaweicloud_encryptionsdk/kms/decrypt_key_capsule_request.py +32 -0
  112. huaweicloud_encryptionsdk/kms/decrypt_key_capsule_response.py +19 -0
  113. huaweicloud_encryptionsdk/kms/kms_http_client.py +116 -0
  114. huaweicloud_encryptionsdk/log/__init__.py +0 -0
  115. huaweicloud_encryptionsdk/log/log_config.py +30 -0
  116. huaweicloud_encryptionsdk/log/log_file_backup.py +144 -0
  117. huaweicloud_encryptionsdk/log/log_metrics.py +132 -0
  118. huaweicloud_encryptionsdk/log/log_reporter_manager.py +163 -0
  119. huaweicloud_encryptionsdk/log/lts_sdk_producer.py +386 -0
  120. huaweicloud_encryptionsdk/log/lts_struct_log_builder.py +106 -0
  121. huaweicloud_encryptionsdk/meterialmanager/__init__.py +0 -0
  122. huaweicloud_encryptionsdk/meterialmanager/cache_crypto_meterial_manager.py +221 -0
  123. huaweicloud_encryptionsdk/meterialmanager/crypto_meterial_manager.py +19 -0
  124. huaweicloud_encryptionsdk/meterialmanager/data_key_generate.py +10 -0
  125. huaweicloud_encryptionsdk/meterialmanager/data_key_generate_factory.py +20 -0
  126. huaweicloud_encryptionsdk/meterialmanager/default_crypto_meterials_manager.py +71 -0
  127. huaweicloud_encryptionsdk/meterialmanager/key_capsule_data_key_generate.py +63 -0
  128. huaweicloud_encryptionsdk/meterialmanager/kms_data_key_generate.py +65 -0
  129. huaweicloud_encryptionsdk/meterialmanager/local_data_key_generate.py +13 -0
  130. huaweicloud_encryptionsdk/model/__init__.py +0 -0
  131. huaweicloud_encryptionsdk/model/access_point_config.py +9 -0
  132. huaweicloud_encryptionsdk/model/cipher_body.py +81 -0
  133. huaweicloud_encryptionsdk/model/cipher_header.py +405 -0
  134. huaweicloud_encryptionsdk/model/ciphertext_data_key.py +166 -0
  135. huaweicloud_encryptionsdk/model/crypto_result.py +11 -0
  136. huaweicloud_encryptionsdk/model/data_key_materials.py +17 -0
  137. huaweicloud_encryptionsdk/model/data_materials.py +11 -0
  138. huaweicloud_encryptionsdk/model/enums/__init__.py +0 -0
  139. huaweicloud_encryptionsdk/model/enums/access_point_type.py +28 -0
  140. huaweicloud_encryptionsdk/model/enums/crypto_algorithm.py +75 -0
  141. huaweicloud_encryptionsdk/model/enums/data_key_generate_type.py +20 -0
  142. huaweicloud_encryptionsdk/model/enums/key_provider_enum.py +19 -0
  143. huaweicloud_encryptionsdk/model/enums/keyring_type_enum.py +27 -0
  144. huaweicloud_encryptionsdk/model/key_capsule.py +10 -0
  145. huaweicloud_encryptionsdk/model/key_capsule_config.py +12 -0
  146. huaweicloud_encryptionsdk/model/kms_config.py +10 -0
  147. huaweicloud_encryptionsdk/model/request/__init__.py +0 -0
  148. huaweicloud_encryptionsdk/model/request/encrypt_request.py +10 -0
  149. huaweicloud_encryptionsdk/model/request/sign_request.py +2 -0
  150. huaweicloud_encryptionsdk/model/request/verify_request.py +2 -0
  151. huaweicloud_encryptionsdk/proof/__init__.py +0 -0
  152. huaweicloud_encryptionsdk/proof/attestation_document.py +36 -0
  153. huaweicloud_encryptionsdk/proof/collector/__init__.py +0 -0
  154. huaweicloud_encryptionsdk/proof/collector/cce_proof_collector.py +24 -0
  155. huaweicloud_encryptionsdk/proof/collector/ecs_proof_collector.py +11 -0
  156. huaweicloud_encryptionsdk/proof/collector/key_pair_proof_collector.py +55 -0
  157. huaweicloud_encryptionsdk/proof/collector/ma_proof_collector.py +51 -0
  158. huaweicloud_encryptionsdk/proof/private_key_decrypt_callback.py +7 -0
  159. huaweicloud_encryptionsdk/proof/proof_collector.py +11 -0
  160. huaweicloud_encryptionsdk/proof/proof_collector_factory.py +53 -0
  161. huaweicloud_encryptionsdk/proof/proof_info.py +10 -0
  162. huaweicloud_encryptionsdk/util/__init__.py +0 -0
  163. huaweicloud_encryptionsdk/util/common_utils.py +12 -0
  164. tests/__init__.py +0 -0
  165. tests/test_enums.py +84 -0
  166. tests/test_exceptions.py +38 -0
  167. tests/test_factories.py +71 -0
  168. tests/test_huawei_crypto.py +67 -0
  169. tests/test_imports.py +46 -0
  170. tests/test_kms_models.py +95 -0
  171. tests/test_log_file_backup.py +45 -0
  172. tests/test_lts_struct_log_builder.py +97 -0
  173. tests/test_models.py +142 -0
  174. tests/test_utils.py +111 -0
@@ -0,0 +1,386 @@
1
+ import json
2
+ import re
3
+ import time
4
+ import logging
5
+ from typing import List, Optional
6
+ from urllib.parse import urlencode
7
+
8
+ from huaweicloud_encryptionsdk.log.log_config import LogConfig
9
+ from huaweicloud_encryptionsdk.log.log_metrics import LogMetrics
10
+ from huaweicloud_encryptionsdk.log.log_file_backup import LogFileBackup
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ _CONTROL_CHARS_PATTERN = re.compile(r'[\r\n\t\x00-\x1F\x7F-\x9F]')
15
+
16
+
17
+ def sanitize(input_str: Optional[str]) -> str:
18
+ if input_str is None:
19
+ return ""
20
+ return _CONTROL_CHARS_PATTERN.sub("", input_str)
21
+
22
+
23
+ class _LtsLogCallBack:
24
+
25
+ def __init__(self, producer, logs):
26
+ self._producer = producer
27
+ self._logs = logs
28
+
29
+ def invoke_on_success(self, result):
30
+ try:
31
+ self._producer._handle_success(self._logs)
32
+ except Exception as e:
33
+ logger.error("Error in invoke_on_success callback: %s", str(e))
34
+
35
+ def invoke_on_failed(self, result):
36
+ last_attempt = result.attempt_list[-1] if result.attempt_list else None
37
+ if last_attempt:
38
+ error_message = "ErrorCode: {}, ErrorMessage: {}".format(
39
+ last_attempt.error_code, last_attempt.error_msg)
40
+ logger.error("LTS log report failed, httpCode=%s, %s",
41
+ last_attempt.http_code, error_message)
42
+ else:
43
+ error_message = "LTS log report failed, no attempt info available"
44
+ logger.error(error_message)
45
+ try:
46
+ self._producer._handle_failure(self._logs, error_message)
47
+ except Exception as e:
48
+ logger.error("Error in invoke_on_failed callback: %s", str(e))
49
+
50
+
51
+ class LTSSDKProducer:
52
+
53
+ def __init__(self, ak: str, sk: str, project_id: str, region: str, log_config: LogConfig, security_token: str = ""):
54
+ self._ak = ak
55
+ self._sk = sk
56
+ self._project_id = project_id
57
+ self._region = region
58
+ self._log_config = log_config
59
+ self._security_token = security_token
60
+ self._log_file_backup = LogFileBackup(log_config.backup_dir, log_config.fail_on_backup_dir_missing)
61
+ self._resolve_log_group_stream_ids()
62
+ self._producer = self._create_producer(ak, sk, project_id, region, log_config)
63
+ self._initialized = True
64
+ self._metrics = LogMetrics()
65
+ self._recover_failed_logs_on_startup()
66
+
67
+ def _resolve_log_group_stream_ids(self) -> None:
68
+ if self._log_config.log_group_id and self._log_config.log_stream_id:
69
+ logger.info("Using logGroupId and logStreamId from config: groupId=%s, streamId=%s",
70
+ self._log_config.log_group_id, self._log_config.log_stream_id)
71
+ return
72
+
73
+ log_group_name = self._log_config.log_group_name
74
+ log_stream_name = self._log_config.log_stream_name
75
+ if not log_group_name or not log_stream_name:
76
+ logger.warning("Both logGroupId/logStreamId and logGroupName/logStreamName are not configured")
77
+ return
78
+
79
+ logger.info("logGroupId or logStreamId is empty, resolving from names: groupName=%s, streamName=%s",
80
+ log_group_name, log_stream_name)
81
+ try:
82
+ group_id, stream_id = self._query_log_group_stream_info_by_name(
83
+ self._project_id, log_group_name, log_stream_name
84
+ )
85
+ if group_id and stream_id:
86
+ self._log_config.log_group_id = group_id
87
+ self._log_config.log_stream_id = stream_id
88
+ logger.info("Resolved logGroupId=%s, logStreamId=%s from names", group_id, stream_id)
89
+ else:
90
+ logger.error("Failed to resolve logGroupId/logStreamId from names, response is empty")
91
+ except Exception as e:
92
+ logger.error("Failed to resolve logGroupId/logStreamId from names: %s", str(e))
93
+
94
+ def _query_log_group_stream_info_by_name(
95
+ self, project_id: str, log_group_name: str, log_stream_name: str
96
+ ) -> tuple:
97
+ import requests as req_lib
98
+ from urllib.parse import urlparse
99
+ from huaweicloud_encryptionsdk._lts_sdk.auth.signer import Signer as LtsSigner
100
+ from huaweicloud_encryptionsdk._lts_sdk.producer.lts_http.simple_request import LtsRequest
101
+
102
+ endpoint = self._log_config.lts_endpoint or ""
103
+ if not endpoint:
104
+ raise ValueError("lts_endpoint is required to resolve log group/stream IDs by name")
105
+
106
+ url = "{}/v2/{}/log-group-stream-info".format(endpoint.rstrip("/"), project_id)
107
+ params = {
108
+ "log_group_name": log_group_name,
109
+ "log_stream_name": log_stream_name,
110
+ }
111
+ full_url = "{}?{}".format(url, urlencode(params))
112
+
113
+ parsed = urlparse(full_url)
114
+ host = parsed.netloc
115
+ interface = parsed.path
116
+ query_params = dict(p.split("=", 1) for p in parsed.query.split("&") if "=" in p)
117
+
118
+ lts_request = LtsRequest(
119
+ method="GET",
120
+ host="{}://{}".format(parsed.scheme, host),
121
+ headers={
122
+ "host": host,
123
+ "Content-Type": "application/json",
124
+ },
125
+ body="",
126
+ region=self._region,
127
+ interface=interface,
128
+ ak=self._ak,
129
+ sk=self._sk,
130
+ security_token=self._security_token,
131
+ **query_params,
132
+ )
133
+
134
+ signer = LtsSigner()
135
+ signed_req = signer.sign(lts_request)
136
+
137
+ prepared = signed_req.prepare()
138
+ resp = req_lib.request(
139
+ method=prepared.method,
140
+ url=prepared.url,
141
+ headers=prepared.headers,
142
+ data=prepared.body,
143
+ timeout=(10, 10),
144
+ verify=False,
145
+ )
146
+ logger.info("Query log group stream info response: status=%s, body=%s",
147
+ resp.status_code, resp.text[:500])
148
+
149
+ if resp.status_code == 200:
150
+ data = resp.json()
151
+ info_list = data if isinstance(data, list) else data.get("logGroupStreamInfoList", data.get("results", []))
152
+ if info_list and len(info_list) > 0:
153
+ info = info_list[0]
154
+ return info.get("log_group_id", ""), info.get("log_stream_id", "")
155
+ return "", ""
156
+
157
+ def _create_producer(self, ak: str, sk: str, project_id: str, region: str, config: LogConfig):
158
+ try:
159
+ from huaweicloud_encryptionsdk._lts_sdk.producer.core.producer import Producer
160
+ from huaweicloud_encryptionsdk._lts_sdk.producer.model.config import Config
161
+
162
+ lts_config = Config()
163
+ lts_config.access_key = ak
164
+ lts_config.access_secret = sk
165
+ lts_config.region_id = region
166
+ lts_config.project_id = project_id
167
+ lts_config.endpoint = config.lts_endpoint if hasattr(config, 'lts_endpoint') and config.lts_endpoint else ""
168
+ lts_config.security_token = self._security_token
169
+ lts_config.logGroup = config.log_group_id
170
+ lts_config.logStream = config.log_stream_id
171
+
172
+ producer = Producer.init_producer(lts_config)
173
+ producer.start_producer()
174
+ logger.info("LTS Producer created with config:")
175
+ logger.info(" logGroupId: %s", config.log_group_id)
176
+ logger.info(" logStreamId: %s", config.log_stream_id)
177
+ logger.info(" logGroupName: %s", config.log_group_name)
178
+ logger.info(" logStreamName: %s", config.log_stream_name)
179
+ logger.info(" endpoint: %s", config.lts_endpoint)
180
+ logger.info(" region: %s", region)
181
+ logger.info(" backupDir: %s", config.backup_dir)
182
+ return producer
183
+ except Exception as e:
184
+ logger.warning("Failed to create LTS Producer, LTS logging will be disabled: %s", str(e))
185
+ return None
186
+
187
+ def send_struct_log(self, struct_log: dict) -> None:
188
+ if not self._initialized:
189
+ return
190
+ sanitized = self._sanitize_log(struct_log)
191
+ try:
192
+ if self._producer is not None:
193
+ self._do_send(sanitized)
194
+ self._metrics.add_buffer_log_count(1)
195
+ logger.debug("Sent log to buffer, buffer count: %s", self._metrics.buffer_log_count)
196
+ except Exception as e:
197
+ self._handle_exception(e, [sanitized])
198
+
199
+ def send_batch_struct_log(self, struct_logs: List[dict]) -> None:
200
+ if not self._initialized or struct_logs is None or len(struct_logs) == 0:
201
+ return
202
+ sanitized_logs = [self._sanitize_log(log) for log in struct_logs]
203
+ try:
204
+ if self._producer is not None:
205
+ for log_item in sanitized_logs:
206
+ self._do_send(log_item)
207
+ self._metrics.add_buffer_log_count(len(sanitized_logs))
208
+ logger.debug("Sent batch %d logs to buffer, total buffer count: %s",
209
+ len(sanitized_logs), self._metrics.buffer_log_count)
210
+ except Exception as e:
211
+ self._handle_exception(e, sanitized_logs)
212
+
213
+ def _sanitize_log(self, struct_log: dict) -> dict:
214
+ sanitized = {}
215
+ for key, value in struct_log.items():
216
+ if isinstance(value, str):
217
+ sanitized[key] = sanitize(value)
218
+ else:
219
+ sanitized[key] = value
220
+ return sanitized
221
+
222
+ def _do_send(self, struct_log: dict) -> None:
223
+ try:
224
+ from huaweicloud_encryptionsdk._lts_sdk.producer.model import lts_store
225
+
226
+ log_group_id = self._log_config.log_group_id
227
+ log_stream_id = self._log_config.log_stream_id
228
+
229
+ if not log_group_id or not log_stream_id:
230
+ logger.warning("logGroupId or logStreamId is not configured, skipping log send")
231
+ return
232
+
233
+ content = json.dumps(struct_log, ensure_ascii=False)
234
+ labels = {"source": "EncryptionSDK_PYTHON"}
235
+ log_p = lts_store.generate_log([content], labels)
236
+
237
+ self._producer.send_log_with_callback(log_group_id, log_stream_id, log_p, _LtsLogCallBack(self, [struct_log]))
238
+ except Exception as e:
239
+ self._handle_failure([struct_log], sanitize(str(e)))
240
+
241
+ def _handle_success(self, logs: list) -> None:
242
+ self._metrics.last_report_time = int(time.time() * 1000)
243
+ self._metrics.increment_success_count()
244
+ self._metrics.add_success_log_count(len(logs))
245
+ self._metrics.add_buffer_log_count(-len(logs))
246
+
247
+ def _handle_failure(self, logs: list, error_message: str) -> None:
248
+ self._metrics.increment_failure_count()
249
+ self._metrics.add_failure_log_count(len(logs))
250
+ self._metrics.last_error_message = error_message
251
+ self._metrics.last_error_time = int(time.time() * 1000)
252
+ logger.warn("Log report failed: %d logs, error: %s, saving to backup dir", len(logs), error_message)
253
+ try:
254
+ backup_path = self._log_file_backup.save_failed_logs(logs, int(time.time() * 1000))
255
+ if backup_path:
256
+ logger.info("Failed logs backed up to: %s", backup_path)
257
+ else:
258
+ logger.warn("Failed to save failed logs to backup directory")
259
+ except Exception as e:
260
+ logger.error("Error saving failed logs to backup: %s", str(e))
261
+
262
+ def _handle_exception(self, e: Exception, logs: list) -> None:
263
+ error_message = sanitize(str(e))
264
+ self._metrics.increment_failure_count()
265
+ self._metrics.add_failure_log_count(len(logs))
266
+ self._metrics.last_error_message = error_message
267
+ self._metrics.last_error_time = int(time.time() * 1000)
268
+ logger.warn("Log report exception: %d logs, error: %s", len(logs), error_message, exc_info=e)
269
+ try:
270
+ backup_path = self._log_file_backup.save_failed_logs(logs, int(time.time() * 1000))
271
+ if backup_path:
272
+ logger.info("Failed logs backed up to: %s", backup_path)
273
+ else:
274
+ logger.warn("Failed to save failed logs to backup directory")
275
+ except Exception as ex:
276
+ logger.error("Error saving failed logs to backup: %s", str(ex))
277
+
278
+ def _recover_failed_logs_on_startup(self) -> None:
279
+ if not self._log_config.enable_recovery_on_startup:
280
+ return
281
+ recovered_logs = self._log_file_backup.recover_failed_logs()
282
+ if recovered_logs:
283
+ batch_size = max(1, self._log_config.recovery_batch_size)
284
+ batch_interval = self._log_config.recovery_batch_interval / 1000.0
285
+ for i in range(0, len(recovered_logs), batch_size):
286
+ batch = recovered_logs[i:i + batch_size]
287
+ self.send_batch_struct_log(batch)
288
+ if i + batch_size < len(recovered_logs):
289
+ time.sleep(batch_interval)
290
+
291
+ @property
292
+ def metrics(self) -> LogMetrics:
293
+ return self._metrics
294
+
295
+ def reset_metrics(self) -> None:
296
+ self._metrics.reset()
297
+
298
+ def close(self) -> None:
299
+ if not self._initialized:
300
+ return
301
+ try:
302
+ if self._producer is not None:
303
+ logger.info("LTS Producer closing, flushing remaining logs...")
304
+ self._flush_remaining_logs()
305
+ self._wait_send_complete()
306
+ self._wait_submit_pool_and_save_failed()
307
+ self._producer = None
308
+ logger.info("LTS Producer closed")
309
+ except Exception as e:
310
+ logger.error("Failed to close LTS Producer: %s", str(e))
311
+ finally:
312
+ self._initialized = False
313
+
314
+ def _flush_remaining_logs(self) -> None:
315
+ try:
316
+ accumulator = self._producer.accumulator
317
+ io_thread_pool = self._producer.io_thread_pool
318
+ if accumulator is None or io_thread_pool is None:
319
+ return
320
+ accumulator.accumulator_lock.acquire_write()
321
+ try:
322
+ if accumulator.log_batch_map:
323
+ for key, batch in accumulator.log_batch_map.items():
324
+ io_thread_pool.add_task(batch)
325
+ logger.info("Flushed %d remaining log batches to LTS", len(accumulator.log_batch_map))
326
+ accumulator.log_batch_map = {}
327
+ finally:
328
+ accumulator.accumulator_lock.release_write()
329
+ except Exception as e:
330
+ logger.error("Failed to flush remaining logs: %s", str(e))
331
+
332
+ def _wait_send_complete(self, timeout: int = 30) -> None:
333
+ try:
334
+ io_thread_pool = self._producer.io_thread_pool
335
+ if io_thread_pool is None:
336
+ return
337
+ start = time.time()
338
+ while time.time() - start < timeout:
339
+ queue_size = io_thread_pool.task_queue_size()
340
+ if queue_size <= 0:
341
+ time.sleep(1)
342
+ logger.info("All LTS log tasks completed")
343
+ return
344
+ time.sleep(0.5)
345
+ logger.warning("LTS log tasks not completed within %s seconds, force closing", timeout)
346
+ except Exception as e:
347
+ logger.error("Failed to wait for send complete: %s", str(e))
348
+
349
+ def _wait_submit_pool_and_save_failed(self) -> None:
350
+ try:
351
+ io_thread_pool = self._producer.io_thread_pool
352
+ if io_thread_pool is not None and hasattr(io_thread_pool, '_submit_thread_pool'):
353
+ submit_pool = io_thread_pool._submit_thread_pool
354
+ if submit_pool is not None:
355
+ logger.info("Waiting for in-flight LTS requests to complete...")
356
+ submit_pool.shutdown(wait=True)
357
+ logger.info("All in-flight LTS requests completed")
358
+ except Exception as e:
359
+ logger.error("Failed to wait for submit thread pool: %s", str(e))
360
+
361
+ try:
362
+ retry_queue = None
363
+ io_thread_pool = self._producer.io_thread_pool
364
+ if io_thread_pool is not None and io_thread_pool.io_worker is not None:
365
+ retry_queue = io_thread_pool.io_worker._retry_queue
366
+
367
+ if retry_queue is not None:
368
+ saved_count = 0
369
+ while not retry_queue.empty():
370
+ try:
371
+ batch = retry_queue.get_nowait()
372
+ for call_back in batch.call_back_list:
373
+ if isinstance(call_back, _LtsLogCallBack):
374
+ self._log_file_backup.save_failed_logs(
375
+ call_back._logs, int(time.time() * 1000))
376
+ saved_count += len(call_back._logs)
377
+ except Exception:
378
+ break
379
+ if saved_count > 0:
380
+ logger.info("Saved %d failed logs from retry queue to backup", saved_count)
381
+ except Exception as e:
382
+ logger.error("Failed to save inflight failed logs: %s", str(e))
383
+
384
+ @property
385
+ def producer(self):
386
+ return self._producer
@@ -0,0 +1,106 @@
1
+ import re
2
+ import socket
3
+ import time
4
+ from datetime import datetime, timezone
5
+ from typing import Optional
6
+
7
+
8
+ _CONTROL_CHARS_PATTERN = re.compile(r'[\r\n\t\x00-\x1F\x7F-\x9F]')
9
+
10
+
11
+ def sanitize(input_str: Optional[str]) -> str:
12
+ if input_str is None:
13
+ return ""
14
+ return _CONTROL_CHARS_PATTERN.sub("", input_str)
15
+
16
+
17
+ class LtsStructLogBuilder:
18
+
19
+ @staticmethod
20
+ def build_encrypt_struct_log(
21
+ encrypt_file_name: Optional[str],
22
+ encrypt_data_bytes: Optional[int],
23
+ encrypt_output_file_name: Optional[str],
24
+ encrypt_output_bytes: Optional[int],
25
+ key_id: Optional[str],
26
+ access_point_type: Optional[str],
27
+ instance_id: Optional[str],
28
+ ) -> dict:
29
+ return LtsStructLogBuilder.build_struct_log(
30
+ "encrypt", encrypt_file_name, encrypt_data_bytes,
31
+ encrypt_output_file_name, encrypt_output_bytes,
32
+ key_id, access_point_type, instance_id,
33
+ )
34
+
35
+ @staticmethod
36
+ def build_decrypt_struct_log(
37
+ decrypt_file_name: Optional[str],
38
+ decrypt_data_bytes: Optional[int],
39
+ decrypt_output_file_name: Optional[str],
40
+ decrypt_output_bytes: Optional[int],
41
+ key_id: Optional[str],
42
+ access_point_type: Optional[str],
43
+ instance_id: Optional[str],
44
+ ) -> dict:
45
+ return LtsStructLogBuilder.build_struct_log(
46
+ "decrypt", decrypt_file_name, decrypt_data_bytes,
47
+ decrypt_output_file_name, decrypt_output_bytes,
48
+ key_id, access_point_type, instance_id,
49
+ )
50
+
51
+ @staticmethod
52
+ def build_struct_log(
53
+ operation: str,
54
+ file_name: Optional[str],
55
+ data_bytes: Optional[int],
56
+ output_file_name: Optional[str],
57
+ output_bytes: Optional[int],
58
+ key_id: Optional[str],
59
+ access_point_type: Optional[str],
60
+ instance_id: Optional[str],
61
+ ) -> dict:
62
+ struct_log = {}
63
+ ts = int(time.time() * 1000)
64
+ struct_log["timestamp"] = str(ts)
65
+ struct_log["utcTime"] = datetime.fromtimestamp(ts / 1000, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
66
+ struct_log["operation"] = sanitize(operation)
67
+
68
+ field_prefix = operation
69
+ if file_name:
70
+ struct_log[field_prefix + "FileName"] = sanitize(file_name)
71
+ if data_bytes is not None and data_bytes > 0:
72
+ struct_log[field_prefix + "DataBytes"] = str(data_bytes)
73
+ if output_file_name:
74
+ struct_log[field_prefix + "OutputFileName"] = sanitize(output_file_name)
75
+ if output_bytes is not None and output_bytes > 0:
76
+ struct_log[field_prefix + "OutputBytes"] = str(output_bytes)
77
+ if key_id:
78
+ struct_log["keyId"] = sanitize(key_id)
79
+ if access_point_type:
80
+ struct_log["accessPointType"] = sanitize(access_point_type)
81
+ if instance_id:
82
+ struct_log["instanceId"] = sanitize(instance_id)
83
+
84
+ ip = LtsStructLogBuilder.get_local_ip()
85
+ if ip:
86
+ struct_log["ipAddress"] = ip
87
+ return struct_log
88
+
89
+ @staticmethod
90
+ def get_local_ip() -> Optional[str]:
91
+ try:
92
+ for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET):
93
+ addr = info[4][0]
94
+ if addr and not addr.startswith("127."):
95
+ return addr
96
+ except Exception:
97
+ pass
98
+ try:
99
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
100
+ s.connect(("8.8.8.8", 80))
101
+ ip = s.getsockname()[0]
102
+ s.close()
103
+ return ip
104
+ except Exception:
105
+ pass
106
+ return None
File without changes