azpaddypy 0.3.3__py3-none-any.whl → 0.3.5__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.
- azpaddypy/mgmt/logging.py +93 -9
- azpaddypy/resources/__init__.py +12 -0
- azpaddypy/resources/keyvault.py +530 -0
- {azpaddypy-0.3.3.dist-info → azpaddypy-0.3.5.dist-info}/METADATA +5 -6
- azpaddypy-0.3.5.dist-info/RECORD +10 -0
- azpaddypy/test_function/__init__.py +0 -112
- azpaddypy/test_function/function_app.py +0 -129
- azpaddypy-0.3.3.dist-info/RECORD +0 -10
- {azpaddypy-0.3.3.dist-info → azpaddypy-0.3.5.dist-info}/WHEEL +0 -0
- {azpaddypy-0.3.3.dist-info → azpaddypy-0.3.5.dist-info}/licenses/LICENSE +0 -0
- {azpaddypy-0.3.3.dist-info → azpaddypy-0.3.5.dist-info}/top_level.txt +0 -0
azpaddypy/mgmt/logging.py
CHANGED
@@ -3,6 +3,7 @@ import os
|
|
3
3
|
import functools
|
4
4
|
import time
|
5
5
|
import asyncio
|
6
|
+
import uuid
|
6
7
|
from typing import Optional, Dict, Any, Union, Callable
|
7
8
|
from datetime import datetime
|
8
9
|
from azure.monitor.opentelemetry import configure_azure_monitor
|
@@ -19,12 +20,23 @@ class AzureLogger:
|
|
19
20
|
tracking, baggage propagation, and automated function tracing for Azure
|
20
21
|
applications with seamless local development support.
|
21
22
|
|
23
|
+
CLOUD ROLE NAME INTEGRATION:
|
24
|
+
The service_name parameter automatically sets the cloud role name for
|
25
|
+
Application Insights. When multiple services emit telemetry to the same
|
26
|
+
Application Insights resource, each service will appear as a separate
|
27
|
+
node on the Application Map, enabling proper service topology visualization.
|
28
|
+
|
29
|
+
CORRELATION ID AUTOMATION:
|
30
|
+
The trace_function decorator automatically generates UUID4 correlation IDs
|
31
|
+
when none are manually set, ensuring consistent distributed tracing across
|
32
|
+
all function calls without requiring manual configuration.
|
33
|
+
|
22
34
|
Supports all standard logging levels (debug, info, warning, error, exception,
|
23
35
|
critical) with enhanced context including trace IDs, correlation IDs, and
|
24
36
|
baggage propagation.
|
25
37
|
|
26
38
|
Attributes:
|
27
|
-
service_name: Service identifier for telemetry
|
39
|
+
service_name: Service identifier for telemetry and cloud role name
|
28
40
|
service_version: Service version for context
|
29
41
|
connection_string: Application Insights connection string
|
30
42
|
logger: Python logger instance
|
@@ -40,17 +52,24 @@ class AzureLogger:
|
|
40
52
|
enable_console_logging: bool = True,
|
41
53
|
custom_resource_attributes: Optional[Dict[str, str]] = None,
|
42
54
|
instrumentation_options: Optional[Dict[str, Any]] = None,
|
55
|
+
cloud_role_name: Optional[str] = None,
|
43
56
|
):
|
44
57
|
"""Initialize Azure Logger with OpenTelemetry tracing.
|
45
58
|
|
59
|
+
The service_name parameter automatically sets the cloud role name for
|
60
|
+
Application Insights Application Map visualization. When multiple services
|
61
|
+
emit telemetry to the same Application Insights resource, each service
|
62
|
+
will appear as a separate node on the Application Map.
|
63
|
+
|
46
64
|
Args:
|
47
|
-
service_name: Service identifier for telemetry
|
65
|
+
service_name: Service identifier for telemetry and cloud role name
|
48
66
|
service_version: Service version for metadata
|
49
67
|
connection_string: Application Insights connection string
|
50
68
|
log_level: Python logging level (default: INFO)
|
51
69
|
enable_console_logging: Enable console output for local development
|
52
70
|
custom_resource_attributes: Additional OpenTelemetry resource attributes
|
53
71
|
instrumentation_options: Azure Monitor instrumentation options
|
72
|
+
cloud_role_name: Override cloud role name (defaults to service_name)
|
54
73
|
"""
|
55
74
|
self.service_name = service_name
|
56
75
|
self.service_version = service_version
|
@@ -58,9 +77,14 @@ class AzureLogger:
|
|
58
77
|
"APPLICATIONINSIGHTS_CONNECTION_STRING"
|
59
78
|
)
|
60
79
|
|
80
|
+
# Use explicit cloud role name or default to service name
|
81
|
+
effective_cloud_role_name = cloud_role_name or service_name
|
82
|
+
self.cloud_role_name = effective_cloud_role_name
|
83
|
+
|
61
84
|
# Configure resource attributes
|
85
|
+
# NOTE: service.name automatically maps to cloud role name in Application Insights
|
62
86
|
resource_attributes = {
|
63
|
-
"service.name":
|
87
|
+
"service.name": effective_cloud_role_name,
|
64
88
|
"service.version": service_version,
|
65
89
|
"service.instance.id": os.getenv("WEBSITE_INSTANCE_ID", "local"),
|
66
90
|
}
|
@@ -100,7 +124,8 @@ class AzureLogger:
|
|
100
124
|
self._correlation_id = None
|
101
125
|
|
102
126
|
self.info(
|
103
|
-
f"Azure Logger initialized for service '{service_name}' v{service_version}"
|
127
|
+
f"Azure Logger initialized for service '{service_name}' v{service_version} "
|
128
|
+
f"(cloud role: '{effective_cloud_role_name}')"
|
104
129
|
)
|
105
130
|
|
106
131
|
def _setup_console_handler(self):
|
@@ -115,16 +140,27 @@ class AzureLogger:
|
|
115
140
|
def set_correlation_id(self, correlation_id: str):
|
116
141
|
"""Set correlation ID for request/transaction tracking.
|
117
142
|
|
143
|
+
Manually sets the correlation ID that will be used for all subsequent
|
144
|
+
tracing operations. This value takes precedence over auto-generated
|
145
|
+
correlation IDs in the trace_function decorator.
|
146
|
+
|
118
147
|
Args:
|
119
148
|
correlation_id: Unique identifier for transaction correlation
|
149
|
+
|
150
|
+
Note:
|
151
|
+
If not set manually, the trace_function decorator will automatically
|
152
|
+
generate a UUID4 correlation_id on first use.
|
120
153
|
"""
|
121
154
|
self._correlation_id = correlation_id
|
122
155
|
|
123
156
|
def get_correlation_id(self) -> Optional[str]:
|
124
157
|
"""Get current correlation ID.
|
125
158
|
|
159
|
+
Returns the currently active correlation ID, whether manually set
|
160
|
+
or automatically generated by the trace_function decorator.
|
161
|
+
|
126
162
|
Returns:
|
127
|
-
Current correlation ID if set, otherwise None
|
163
|
+
Current correlation ID if set (manual or auto-generated), otherwise None
|
128
164
|
"""
|
129
165
|
return self._correlation_id
|
130
166
|
|
@@ -510,16 +546,38 @@ class AzureLogger:
|
|
510
546
|
log_args: bool = True,
|
511
547
|
log_result: bool = False
|
512
548
|
) -> Callable:
|
513
|
-
"""Decorator for automatic function execution tracing.
|
549
|
+
"""Decorator for automatic function execution tracing with correlation ID support.
|
514
550
|
|
515
551
|
Supports both synchronous and asynchronous functions with comprehensive
|
516
|
-
logging and OpenTelemetry span creation.
|
552
|
+
logging and OpenTelemetry span creation. Automatically generates and manages
|
553
|
+
correlation IDs for distributed tracing.
|
554
|
+
|
555
|
+
CORRELATION ID AUTOMATION:
|
556
|
+
- If no correlation_id is set, automatically generates a UUID4 correlation_id
|
557
|
+
- If correlation_id is manually set, preserves the existing value
|
558
|
+
- Correlation_id is automatically added to all spans and log entries
|
559
|
+
- Ensures consistent tracing across function calls
|
517
560
|
|
518
561
|
Args:
|
519
562
|
function_name: Custom span name (defaults to function name)
|
520
563
|
log_execution: Whether to log execution metrics
|
521
564
|
log_args: Whether to log function arguments
|
522
565
|
log_result: Whether to log function result
|
566
|
+
|
567
|
+
Returns:
|
568
|
+
Decorated function with automatic tracing and correlation ID support
|
569
|
+
|
570
|
+
Example:
|
571
|
+
# Automatic correlation_id generation
|
572
|
+
@logger.trace_function()
|
573
|
+
def my_function():
|
574
|
+
return "result"
|
575
|
+
|
576
|
+
# Manual correlation_id (preserved)
|
577
|
+
logger.set_correlation_id("manual-id")
|
578
|
+
@logger.trace_function()
|
579
|
+
def my_function():
|
580
|
+
return "result"
|
523
581
|
"""
|
524
582
|
|
525
583
|
def decorator(func):
|
@@ -527,6 +585,11 @@ class AzureLogger:
|
|
527
585
|
async def async_wrapper(*args, **kwargs):
|
528
586
|
span_name = function_name or f"{func.__module__}.{func.__name__}"
|
529
587
|
with self.tracer.start_as_current_span(span_name) as span:
|
588
|
+
# Auto-generate correlation_id if not set - ensures consistent tracing
|
589
|
+
# Manual correlation_ids take precedence over auto-generated ones
|
590
|
+
if not self._correlation_id:
|
591
|
+
self._correlation_id = str(uuid.uuid4())
|
592
|
+
|
530
593
|
self._setup_span_for_function_trace(
|
531
594
|
span, func, True, log_args, args, kwargs, log_result, log_execution
|
532
595
|
)
|
@@ -560,6 +623,11 @@ class AzureLogger:
|
|
560
623
|
def sync_wrapper(*args, **kwargs):
|
561
624
|
span_name = function_name or f"{func.__module__}.{func.__name__}"
|
562
625
|
with self.tracer.start_as_current_span(span_name) as span:
|
626
|
+
# Auto-generate correlation_id if not set - ensures consistent tracing
|
627
|
+
# Manual correlation_ids take precedence over auto-generated ones
|
628
|
+
if not self._correlation_id:
|
629
|
+
self._correlation_id = str(uuid.uuid4())
|
630
|
+
|
563
631
|
self._setup_span_for_function_trace(
|
564
632
|
span, func, False, log_args, args, kwargs, log_result, log_execution
|
565
633
|
)
|
@@ -704,19 +772,23 @@ def create_app_logger(
|
|
704
772
|
enable_console_logging: bool = True,
|
705
773
|
custom_resource_attributes: Optional[Dict[str, str]] = None,
|
706
774
|
instrumentation_options: Optional[Dict[str, Any]] = None,
|
775
|
+
cloud_role_name: Optional[str] = None,
|
707
776
|
) -> AzureLogger:
|
708
777
|
"""Create cached AzureLogger instance for applications.
|
709
778
|
|
710
779
|
Returns existing logger if one with same configuration exists.
|
780
|
+
The service_name automatically becomes the cloud role name in Application Insights
|
781
|
+
unless explicitly overridden with cloud_role_name parameter.
|
711
782
|
|
712
783
|
Args:
|
713
|
-
service_name: Service identifier for telemetry
|
784
|
+
service_name: Service identifier for telemetry and cloud role name
|
714
785
|
service_version: Service version for metadata
|
715
786
|
connection_string: Application Insights connection string
|
716
787
|
log_level: Python logging level
|
717
788
|
enable_console_logging: Enable console output
|
718
789
|
custom_resource_attributes: Additional OpenTelemetry resource attributes
|
719
790
|
instrumentation_options: Azure Monitor instrumentation options
|
791
|
+
cloud_role_name: Override cloud role name (defaults to service_name)
|
720
792
|
|
721
793
|
Returns:
|
722
794
|
Configured AzureLogger instance
|
@@ -738,6 +810,7 @@ def create_app_logger(
|
|
738
810
|
log_level,
|
739
811
|
enable_console_logging,
|
740
812
|
attr_items,
|
813
|
+
cloud_role_name,
|
741
814
|
)
|
742
815
|
|
743
816
|
if params_key in _loggers:
|
@@ -750,6 +823,8 @@ def create_app_logger(
|
|
750
823
|
log_level=log_level,
|
751
824
|
enable_console_logging=enable_console_logging,
|
752
825
|
custom_resource_attributes=custom_resource_attributes,
|
826
|
+
instrumentation_options=instrumentation_options,
|
827
|
+
cloud_role_name=cloud_role_name,
|
753
828
|
)
|
754
829
|
_loggers[params_key] = logger
|
755
830
|
return logger
|
@@ -761,15 +836,21 @@ def create_function_logger(
|
|
761
836
|
service_version: str = "1.0.0",
|
762
837
|
connection_string: Optional[str] = None,
|
763
838
|
instrumentation_options: Optional[Dict[str, Any]] = None,
|
839
|
+
cloud_role_name: Optional[str] = None,
|
764
840
|
) -> AzureLogger:
|
765
841
|
"""Create AzureLogger optimized for Azure Functions.
|
766
842
|
|
843
|
+
Automatically creates cloud role name in the format '{function_app_name}.{function_name}'
|
844
|
+
unless explicitly overridden. This ensures each function appears as a separate
|
845
|
+
component in the Application Insights Application Map.
|
846
|
+
|
767
847
|
Args:
|
768
848
|
function_app_name: Azure Function App name
|
769
849
|
function_name: Specific function name
|
770
850
|
service_version: Service version for metadata
|
771
851
|
connection_string: Application Insights connection string
|
772
852
|
instrumentation_options: Azure Monitor instrumentation options
|
853
|
+
cloud_role_name: Override cloud role name (defaults to '{function_app_name}.{function_name}')
|
773
854
|
|
774
855
|
Returns:
|
775
856
|
Configured AzureLogger with Azure Functions context
|
@@ -780,10 +861,13 @@ def create_function_logger(
|
|
780
861
|
"azure.resource.type": "function",
|
781
862
|
}
|
782
863
|
|
864
|
+
default_service_name = f"{function_app_name}.{function_name}"
|
865
|
+
|
783
866
|
return create_app_logger(
|
784
|
-
service_name=
|
867
|
+
service_name=default_service_name,
|
785
868
|
service_version=service_version,
|
786
869
|
connection_string=connection_string,
|
787
870
|
custom_resource_attributes=custom_attributes,
|
788
871
|
instrumentation_options=instrumentation_options,
|
872
|
+
cloud_role_name=cloud_role_name,
|
789
873
|
)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"""Azure resources package for azpaddypy.
|
2
|
+
|
3
|
+
This package contains modules for interacting with various Azure resources
|
4
|
+
including Key Vault, Storage, and other Azure services.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .keyvault import AzureKeyVault, create_azure_keyvault
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"AzureKeyVault",
|
11
|
+
"create_azure_keyvault",
|
12
|
+
]
|
@@ -0,0 +1,530 @@
|
|
1
|
+
from typing import Optional, Dict, Any, Union, List
|
2
|
+
from azure.keyvault.secrets import SecretClient
|
3
|
+
from azure.keyvault.keys import KeyClient
|
4
|
+
from azure.keyvault.certificates import CertificateClient
|
5
|
+
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
|
6
|
+
from azure.core.credentials import TokenCredential
|
7
|
+
from ..mgmt.logging import AzureLogger
|
8
|
+
from ..mgmt.identity import AzureIdentity
|
9
|
+
|
10
|
+
|
11
|
+
class AzureKeyVault:
|
12
|
+
"""Azure Key Vault management with comprehensive secret, key, and certificate operations.
|
13
|
+
|
14
|
+
Provides standardized Azure Key Vault operations using Azure SDK clients
|
15
|
+
with integrated logging, error handling, and OpenTelemetry tracing support.
|
16
|
+
Supports operations for secrets, keys, and certificates with proper
|
17
|
+
authentication and authorization handling.
|
18
|
+
|
19
|
+
Attributes:
|
20
|
+
vault_url: Azure Key Vault URL
|
21
|
+
service_name: Service identifier for logging and tracing
|
22
|
+
service_version: Service version for context
|
23
|
+
logger: AzureLogger instance for structured logging
|
24
|
+
credential: Azure credential for authentication
|
25
|
+
secret_client: Azure Key Vault SecretClient instance
|
26
|
+
key_client: Azure Key Vault KeyClient instance
|
27
|
+
certificate_client: Azure Key Vault CertificateClient instance
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
vault_url: str,
|
33
|
+
credential: Optional[TokenCredential] = None,
|
34
|
+
azure_identity: Optional[AzureIdentity] = None,
|
35
|
+
service_name: str = "azure_keyvault",
|
36
|
+
service_version: str = "1.0.0",
|
37
|
+
logger: Optional[AzureLogger] = None,
|
38
|
+
connection_string: Optional[str] = None,
|
39
|
+
enable_secrets: bool = True,
|
40
|
+
enable_keys: bool = True,
|
41
|
+
enable_certificates: bool = True,
|
42
|
+
):
|
43
|
+
"""Initialize Azure Key Vault with comprehensive configuration.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
vault_url: Azure Key Vault URL (e.g., https://vault.vault.azure.net/)
|
47
|
+
credential: Azure credential for authentication
|
48
|
+
azure_identity: AzureIdentity instance for credential management
|
49
|
+
service_name: Service name for tracing context
|
50
|
+
service_version: Service version for metadata
|
51
|
+
logger: Optional AzureLogger instance
|
52
|
+
connection_string: Application Insights connection string
|
53
|
+
enable_secrets: Enable secret operations client
|
54
|
+
enable_keys: Enable key operations client
|
55
|
+
enable_certificates: Enable certificate operations client
|
56
|
+
|
57
|
+
Raises:
|
58
|
+
ValueError: If neither credential nor azure_identity is provided
|
59
|
+
Exception: If client initialization fails
|
60
|
+
"""
|
61
|
+
self.vault_url = vault_url
|
62
|
+
self.service_name = service_name
|
63
|
+
self.service_version = service_version
|
64
|
+
self.enable_secrets = enable_secrets
|
65
|
+
self.enable_keys = enable_keys
|
66
|
+
self.enable_certificates = enable_certificates
|
67
|
+
|
68
|
+
# Initialize logger - use provided instance or create new one
|
69
|
+
if logger is not None:
|
70
|
+
self.logger = logger
|
71
|
+
else:
|
72
|
+
self.logger = AzureLogger(
|
73
|
+
service_name=service_name,
|
74
|
+
service_version=service_version,
|
75
|
+
connection_string=connection_string,
|
76
|
+
enable_console_logging=True,
|
77
|
+
)
|
78
|
+
|
79
|
+
# Setup credential
|
80
|
+
if azure_identity is not None:
|
81
|
+
self.credential = azure_identity.get_credential()
|
82
|
+
self.azure_identity = azure_identity
|
83
|
+
elif credential is not None:
|
84
|
+
self.credential = credential
|
85
|
+
self.azure_identity = None
|
86
|
+
else:
|
87
|
+
raise ValueError("Either 'credential' or 'azure_identity' must be provided")
|
88
|
+
|
89
|
+
# Initialize clients
|
90
|
+
self.secret_client = None
|
91
|
+
self.key_client = None
|
92
|
+
self.certificate_client = None
|
93
|
+
|
94
|
+
self._setup_clients()
|
95
|
+
|
96
|
+
self.logger.info(
|
97
|
+
f"Azure Key Vault initialized for service '{service_name}' v{service_version}",
|
98
|
+
extra={
|
99
|
+
"vault_url": vault_url,
|
100
|
+
"secrets_enabled": enable_secrets,
|
101
|
+
"keys_enabled": enable_keys,
|
102
|
+
"certificates_enabled": enable_certificates,
|
103
|
+
}
|
104
|
+
)
|
105
|
+
|
106
|
+
def _setup_clients(self):
|
107
|
+
"""Initialize Key Vault clients based on enabled features.
|
108
|
+
|
109
|
+
Raises:
|
110
|
+
Exception: If client initialization fails
|
111
|
+
"""
|
112
|
+
try:
|
113
|
+
if self.enable_secrets:
|
114
|
+
self.secret_client = SecretClient(
|
115
|
+
vault_url=self.vault_url,
|
116
|
+
credential=self.credential
|
117
|
+
)
|
118
|
+
self.logger.debug("SecretClient initialized successfully")
|
119
|
+
|
120
|
+
if self.enable_keys:
|
121
|
+
self.key_client = KeyClient(
|
122
|
+
vault_url=self.vault_url,
|
123
|
+
credential=self.credential
|
124
|
+
)
|
125
|
+
self.logger.debug("KeyClient initialized successfully")
|
126
|
+
|
127
|
+
if self.enable_certificates:
|
128
|
+
self.certificate_client = CertificateClient(
|
129
|
+
vault_url=self.vault_url,
|
130
|
+
credential=self.credential
|
131
|
+
)
|
132
|
+
self.logger.debug("CertificateClient initialized successfully")
|
133
|
+
|
134
|
+
except Exception as e:
|
135
|
+
self.logger.error(
|
136
|
+
f"Failed to initialize Key Vault clients: {e}",
|
137
|
+
exc_info=True
|
138
|
+
)
|
139
|
+
raise
|
140
|
+
|
141
|
+
# Secret Operations
|
142
|
+
def get_secret(
|
143
|
+
self,
|
144
|
+
secret_name: str,
|
145
|
+
version: Optional[str] = None,
|
146
|
+
**kwargs
|
147
|
+
) -> Optional[str]:
|
148
|
+
"""Retrieve a secret from Azure Key Vault.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
secret_name: Name of the secret
|
152
|
+
version: Optional specific version of the secret
|
153
|
+
**kwargs: Additional parameters for the secret retrieval
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
Secret value if found, None if not found
|
157
|
+
|
158
|
+
Raises:
|
159
|
+
RuntimeError: If secret client is not initialized
|
160
|
+
Exception: If secret retrieval fails for reasons other than not found
|
161
|
+
"""
|
162
|
+
with self.logger.create_span(
|
163
|
+
"AzureKeyVault.get_secret",
|
164
|
+
attributes={
|
165
|
+
"service.name": self.service_name,
|
166
|
+
"operation.type": "secret_retrieval",
|
167
|
+
"keyvault.secret_name": secret_name,
|
168
|
+
"keyvault.version": version or "latest",
|
169
|
+
"keyvault.vault_url": self.vault_url
|
170
|
+
}
|
171
|
+
):
|
172
|
+
if self.secret_client is None:
|
173
|
+
error_msg = "Secret client not initialized. Enable secrets during initialization."
|
174
|
+
self.logger.error(error_msg)
|
175
|
+
raise RuntimeError(error_msg)
|
176
|
+
|
177
|
+
try:
|
178
|
+
self.logger.debug(
|
179
|
+
"Retrieving secret from Key Vault",
|
180
|
+
extra={
|
181
|
+
"secret_name": secret_name,
|
182
|
+
"version": version,
|
183
|
+
"vault_url": self.vault_url
|
184
|
+
}
|
185
|
+
)
|
186
|
+
|
187
|
+
secret = self.secret_client.get_secret(secret_name, version=version, **kwargs)
|
188
|
+
|
189
|
+
self.logger.info(
|
190
|
+
"Secret retrieved successfully",
|
191
|
+
extra={
|
192
|
+
"secret_name": secret_name,
|
193
|
+
"version": secret.properties.version if secret.properties else None,
|
194
|
+
"content_type": secret.properties.content_type if secret.properties else None
|
195
|
+
}
|
196
|
+
)
|
197
|
+
|
198
|
+
return secret.value
|
199
|
+
|
200
|
+
except ResourceNotFoundError:
|
201
|
+
self.logger.warning(
|
202
|
+
f"Secret '{secret_name}' not found in Key Vault",
|
203
|
+
extra={"secret_name": secret_name, "version": version}
|
204
|
+
)
|
205
|
+
return None
|
206
|
+
except ClientAuthenticationError as e:
|
207
|
+
self.logger.error(
|
208
|
+
f"Authentication failed for secret '{secret_name}': {e}",
|
209
|
+
extra={"secret_name": secret_name},
|
210
|
+
exc_info=True
|
211
|
+
)
|
212
|
+
raise
|
213
|
+
except Exception as e:
|
214
|
+
self.logger.error(
|
215
|
+
f"Failed to retrieve secret '{secret_name}': {e}",
|
216
|
+
extra={"secret_name": secret_name, "version": version},
|
217
|
+
exc_info=True
|
218
|
+
)
|
219
|
+
raise
|
220
|
+
|
221
|
+
def set_secret(
|
222
|
+
self,
|
223
|
+
secret_name: str,
|
224
|
+
secret_value: str,
|
225
|
+
content_type: Optional[str] = None,
|
226
|
+
tags: Optional[Dict[str, str]] = None,
|
227
|
+
**kwargs
|
228
|
+
) -> bool:
|
229
|
+
"""Set a secret in Azure Key Vault.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
secret_name: Name of the secret
|
233
|
+
secret_value: Value of the secret
|
234
|
+
content_type: Optional content type for the secret
|
235
|
+
tags: Optional tags for the secret
|
236
|
+
**kwargs: Additional parameters for secret creation
|
237
|
+
|
238
|
+
Returns:
|
239
|
+
True if secret was set successfully
|
240
|
+
|
241
|
+
Raises:
|
242
|
+
RuntimeError: If secret client is not initialized
|
243
|
+
Exception: If secret creation fails
|
244
|
+
"""
|
245
|
+
with self.logger.create_span(
|
246
|
+
"AzureKeyVault.set_secret",
|
247
|
+
attributes={
|
248
|
+
"service.name": self.service_name,
|
249
|
+
"operation.type": "secret_creation",
|
250
|
+
"keyvault.secret_name": secret_name,
|
251
|
+
"keyvault.content_type": content_type or "text/plain",
|
252
|
+
"keyvault.vault_url": self.vault_url
|
253
|
+
}
|
254
|
+
):
|
255
|
+
if self.secret_client is None:
|
256
|
+
error_msg = "Secret client not initialized. Enable secrets during initialization."
|
257
|
+
self.logger.error(error_msg)
|
258
|
+
raise RuntimeError(error_msg)
|
259
|
+
|
260
|
+
try:
|
261
|
+
self.logger.debug(
|
262
|
+
"Setting secret in Key Vault",
|
263
|
+
extra={
|
264
|
+
"secret_name": secret_name,
|
265
|
+
"content_type": content_type,
|
266
|
+
"has_tags": tags is not None,
|
267
|
+
"vault_url": self.vault_url
|
268
|
+
}
|
269
|
+
)
|
270
|
+
|
271
|
+
secret = self.secret_client.set_secret(
|
272
|
+
secret_name,
|
273
|
+
secret_value,
|
274
|
+
content_type=content_type,
|
275
|
+
tags=tags,
|
276
|
+
**kwargs
|
277
|
+
)
|
278
|
+
|
279
|
+
self.logger.info(
|
280
|
+
"Secret set successfully",
|
281
|
+
extra={
|
282
|
+
"secret_name": secret_name,
|
283
|
+
"version": secret.properties.version if secret.properties else None,
|
284
|
+
"content_type": secret.properties.content_type if secret.properties else None
|
285
|
+
}
|
286
|
+
)
|
287
|
+
|
288
|
+
return True
|
289
|
+
|
290
|
+
except Exception as e:
|
291
|
+
self.logger.error(
|
292
|
+
f"Failed to set secret '{secret_name}': {e}",
|
293
|
+
extra={"secret_name": secret_name, "content_type": content_type},
|
294
|
+
exc_info=True
|
295
|
+
)
|
296
|
+
raise
|
297
|
+
|
298
|
+
def delete_secret(self, secret_name: str, **kwargs) -> bool:
|
299
|
+
"""Delete a secret from Azure Key Vault.
|
300
|
+
|
301
|
+
Args:
|
302
|
+
secret_name: Name of the secret to delete
|
303
|
+
**kwargs: Additional parameters for secret deletion
|
304
|
+
|
305
|
+
Returns:
|
306
|
+
True if secret was deleted successfully
|
307
|
+
|
308
|
+
Raises:
|
309
|
+
RuntimeError: If secret client is not initialized
|
310
|
+
Exception: If secret deletion fails
|
311
|
+
"""
|
312
|
+
with self.logger.create_span(
|
313
|
+
"AzureKeyVault.delete_secret",
|
314
|
+
attributes={
|
315
|
+
"service.name": self.service_name,
|
316
|
+
"operation.type": "secret_deletion",
|
317
|
+
"keyvault.secret_name": secret_name,
|
318
|
+
"keyvault.vault_url": self.vault_url
|
319
|
+
}
|
320
|
+
):
|
321
|
+
if self.secret_client is None:
|
322
|
+
error_msg = "Secret client not initialized. Enable secrets during initialization."
|
323
|
+
self.logger.error(error_msg)
|
324
|
+
raise RuntimeError(error_msg)
|
325
|
+
|
326
|
+
try:
|
327
|
+
self.logger.debug(
|
328
|
+
"Deleting secret from Key Vault",
|
329
|
+
extra={"secret_name": secret_name, "vault_url": self.vault_url}
|
330
|
+
)
|
331
|
+
|
332
|
+
self.secret_client.begin_delete_secret(secret_name, **kwargs)
|
333
|
+
|
334
|
+
self.logger.info(
|
335
|
+
"Secret deletion initiated successfully",
|
336
|
+
extra={"secret_name": secret_name}
|
337
|
+
)
|
338
|
+
|
339
|
+
return True
|
340
|
+
|
341
|
+
except ResourceNotFoundError:
|
342
|
+
self.logger.warning(
|
343
|
+
f"Secret '{secret_name}' not found for deletion",
|
344
|
+
extra={"secret_name": secret_name}
|
345
|
+
)
|
346
|
+
return False
|
347
|
+
except Exception as e:
|
348
|
+
self.logger.error(
|
349
|
+
f"Failed to delete secret '{secret_name}': {e}",
|
350
|
+
extra={"secret_name": secret_name},
|
351
|
+
exc_info=True
|
352
|
+
)
|
353
|
+
raise
|
354
|
+
|
355
|
+
def list_secrets(self, **kwargs) -> List[str]:
|
356
|
+
"""List all secrets in the Key Vault.
|
357
|
+
|
358
|
+
Args:
|
359
|
+
**kwargs: Additional parameters for listing secrets
|
360
|
+
|
361
|
+
Returns:
|
362
|
+
List of secret names
|
363
|
+
|
364
|
+
Raises:
|
365
|
+
RuntimeError: If secret client is not initialized
|
366
|
+
Exception: If listing secrets fails
|
367
|
+
"""
|
368
|
+
with self.logger.create_span(
|
369
|
+
"AzureKeyVault.list_secrets",
|
370
|
+
attributes={
|
371
|
+
"service.name": self.service_name,
|
372
|
+
"operation.type": "secret_listing",
|
373
|
+
"keyvault.vault_url": self.vault_url
|
374
|
+
}
|
375
|
+
):
|
376
|
+
if self.secret_client is None:
|
377
|
+
error_msg = "Secret client not initialized. Enable secrets during initialization."
|
378
|
+
self.logger.error(error_msg)
|
379
|
+
raise RuntimeError(error_msg)
|
380
|
+
|
381
|
+
try:
|
382
|
+
self.logger.debug(
|
383
|
+
"Listing secrets from Key Vault",
|
384
|
+
extra={"vault_url": self.vault_url}
|
385
|
+
)
|
386
|
+
|
387
|
+
secret_names = []
|
388
|
+
for secret_property in self.secret_client.list_properties_of_secrets(**kwargs):
|
389
|
+
secret_names.append(secret_property.name)
|
390
|
+
|
391
|
+
self.logger.info(
|
392
|
+
"Secrets listed successfully",
|
393
|
+
extra={"secret_count": len(secret_names)}
|
394
|
+
)
|
395
|
+
|
396
|
+
return secret_names
|
397
|
+
|
398
|
+
except Exception as e:
|
399
|
+
self.logger.error(
|
400
|
+
f"Failed to list secrets: {e}",
|
401
|
+
exc_info=True
|
402
|
+
)
|
403
|
+
raise
|
404
|
+
|
405
|
+
def test_connection(self) -> bool:
|
406
|
+
"""Test connection to Key Vault by attempting to list secrets.
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
True if connection is successful, False otherwise
|
410
|
+
"""
|
411
|
+
with self.logger.create_span(
|
412
|
+
"AzureKeyVault.test_connection",
|
413
|
+
attributes={
|
414
|
+
"service.name": self.service_name,
|
415
|
+
"operation.type": "connection_test",
|
416
|
+
"keyvault.vault_url": self.vault_url
|
417
|
+
}
|
418
|
+
):
|
419
|
+
try:
|
420
|
+
self.logger.debug(
|
421
|
+
"Testing Key Vault connection",
|
422
|
+
extra={"vault_url": self.vault_url}
|
423
|
+
)
|
424
|
+
|
425
|
+
if self.secret_client is not None:
|
426
|
+
# Try to list secrets (limited to 1) to test connection
|
427
|
+
list(self.secret_client.list_properties_of_secrets(max_page_size=1))
|
428
|
+
elif self.key_client is not None:
|
429
|
+
# Try to list keys if secrets are disabled
|
430
|
+
list(self.key_client.list_properties_of_keys(max_page_size=1))
|
431
|
+
elif self.certificate_client is not None:
|
432
|
+
# Try to list certificates if keys are disabled
|
433
|
+
list(self.certificate_client.list_properties_of_certificates(max_page_size=1))
|
434
|
+
else:
|
435
|
+
self.logger.error("No clients available for connection testing")
|
436
|
+
return False
|
437
|
+
|
438
|
+
self.logger.info("Key Vault connection test successful")
|
439
|
+
return True
|
440
|
+
|
441
|
+
except Exception as e:
|
442
|
+
self.logger.warning(
|
443
|
+
f"Key Vault connection test failed: {e}",
|
444
|
+
extra={"vault_url": self.vault_url}
|
445
|
+
)
|
446
|
+
return False
|
447
|
+
|
448
|
+
def set_correlation_id(self, correlation_id: str):
|
449
|
+
"""Set correlation ID for request/transaction tracking.
|
450
|
+
|
451
|
+
Args:
|
452
|
+
correlation_id: Unique identifier for transaction correlation
|
453
|
+
"""
|
454
|
+
self.logger.set_correlation_id(correlation_id)
|
455
|
+
|
456
|
+
def get_correlation_id(self) -> Optional[str]:
|
457
|
+
"""Get current correlation ID.
|
458
|
+
|
459
|
+
Returns:
|
460
|
+
Current correlation ID if set, otherwise None
|
461
|
+
"""
|
462
|
+
return self.logger.get_correlation_id()
|
463
|
+
|
464
|
+
|
465
|
+
def create_azure_keyvault(
|
466
|
+
vault_url: str,
|
467
|
+
credential: Optional[TokenCredential] = None,
|
468
|
+
azure_identity: Optional[AzureIdentity] = None,
|
469
|
+
service_name: str = "azure_keyvault",
|
470
|
+
service_version: str = "1.0.0",
|
471
|
+
logger: Optional[AzureLogger] = None,
|
472
|
+
connection_string: Optional[str] = None,
|
473
|
+
enable_secrets: bool = True,
|
474
|
+
enable_keys: bool = True,
|
475
|
+
enable_certificates: bool = True,
|
476
|
+
) -> AzureKeyVault:
|
477
|
+
"""Factory function to create AzureKeyVault instance.
|
478
|
+
|
479
|
+
Provides a convenient way to create an AzureKeyVault instance with
|
480
|
+
common configuration patterns. If no credential or azure_identity
|
481
|
+
is provided, creates a default AzureIdentity instance.
|
482
|
+
|
483
|
+
Args:
|
484
|
+
vault_url: Azure Key Vault URL
|
485
|
+
credential: Azure credential for authentication
|
486
|
+
azure_identity: AzureIdentity instance for credential management
|
487
|
+
service_name: Service name for tracing context
|
488
|
+
service_version: Service version for metadata
|
489
|
+
logger: Optional AzureLogger instance
|
490
|
+
connection_string: Application Insights connection string
|
491
|
+
enable_secrets: Enable secret operations client
|
492
|
+
enable_keys: Enable key operations client
|
493
|
+
enable_certificates: Enable certificate operations client
|
494
|
+
|
495
|
+
Returns:
|
496
|
+
Configured AzureKeyVault instance
|
497
|
+
|
498
|
+
Example:
|
499
|
+
# Basic usage with default credential
|
500
|
+
kv = create_azure_keyvault("https://vault.vault.azure.net/")
|
501
|
+
|
502
|
+
# With custom service name and specific features
|
503
|
+
kv = create_azure_keyvault(
|
504
|
+
"https://vault.vault.azure.net/",
|
505
|
+
service_name="my_app",
|
506
|
+
enable_keys=False,
|
507
|
+
enable_certificates=False
|
508
|
+
)
|
509
|
+
"""
|
510
|
+
if credential is None and azure_identity is None:
|
511
|
+
# Create default AzureIdentity instance
|
512
|
+
from ..mgmt.identity import create_azure_identity
|
513
|
+
azure_identity = create_azure_identity(
|
514
|
+
service_name=f"{service_name}_identity",
|
515
|
+
service_version=service_version,
|
516
|
+
connection_string=connection_string,
|
517
|
+
)
|
518
|
+
|
519
|
+
return AzureKeyVault(
|
520
|
+
vault_url=vault_url,
|
521
|
+
credential=credential,
|
522
|
+
azure_identity=azure_identity,
|
523
|
+
service_name=service_name,
|
524
|
+
service_version=service_version,
|
525
|
+
logger=logger,
|
526
|
+
connection_string=connection_string,
|
527
|
+
enable_secrets=enable_secrets,
|
528
|
+
enable_keys=enable_keys,
|
529
|
+
enable_certificates=enable_certificates,
|
530
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: azpaddypy
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.5
|
4
4
|
Summary: Comprehensive Python logger for Azure, integrating OpenTelemetry for advanced, structured, and distributed tracing.
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
6
6
|
Classifier: Operating System :: OS Independent
|
@@ -9,9 +9,8 @@ Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
10
10
|
Requires-Dist: azure-monitor-opentelemetry==1.6.10
|
11
11
|
Requires-Dist: azure-functions==1.23.0
|
12
|
-
|
13
|
-
Requires-Dist: azure-
|
14
|
-
Requires-Dist:
|
15
|
-
Requires-Dist:
|
16
|
-
Requires-Dist: anyio==4.9.0; extra == "dev"
|
12
|
+
Requires-Dist: azure-identity==1.23.0
|
13
|
+
Requires-Dist: azure-keyvault-secrets==4.10.0
|
14
|
+
Requires-Dist: azure-keyvault-keys==4.10.0
|
15
|
+
Requires-Dist: azure-keyvault-certificates==4.10.0
|
17
16
|
Dynamic: license-file
|
@@ -0,0 +1,10 @@
|
|
1
|
+
azpaddypy/mgmt/__init__.py,sha256=-jH8Ftx9C8qu4yF5dMVEapVZhNwG7m4QCUjyutesOoY,278
|
2
|
+
azpaddypy/mgmt/identity.py,sha256=mA_krQslMsK_sDob-z-QA0B9khK_JUO2way7xwPopR8,12001
|
3
|
+
azpaddypy/mgmt/logging.py,sha256=pivPsHeySF1Dyx6HKCSos7HBOYyJMVeRP25wYZu3Sno,35117
|
4
|
+
azpaddypy/resources/__init__.py,sha256=Bvt3VK4RqwoxYpoh6EbLXIR18RuFPKaLP6zLL-icyFk,314
|
5
|
+
azpaddypy/resources/keyvault.py,sha256=4J08vLqoLFd1_UUDBji2oG2fatZaPkgnRyT_Z6wHAOc,20312
|
6
|
+
azpaddypy-0.3.5.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
|
7
|
+
azpaddypy-0.3.5.dist-info/METADATA,sha256=m56dD6Ev3sTewyiNU-UgPzw6L73idRtUlCM5o7lOwjg,665
|
8
|
+
azpaddypy-0.3.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
9
|
+
azpaddypy-0.3.5.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
|
10
|
+
azpaddypy-0.3.5.dist-info/RECORD,,
|
@@ -1,112 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import json
|
3
|
-
import time
|
4
|
-
import azure.functions as func
|
5
|
-
from azpaddypy.mgmt.logging import create_function_logger
|
6
|
-
|
7
|
-
# Initialize the logger
|
8
|
-
logger = create_function_logger(
|
9
|
-
function_app_name="test-function-app", function_name="test-function"
|
10
|
-
)
|
11
|
-
|
12
|
-
|
13
|
-
@logger.trace_function(log_args=True, log_result=True)
|
14
|
-
def process_request(req_body: dict) -> dict:
|
15
|
-
"""Process the request body and return a response"""
|
16
|
-
# Simulate some processing time
|
17
|
-
time.sleep(0.1)
|
18
|
-
|
19
|
-
# Log the request processing
|
20
|
-
logger.info(
|
21
|
-
"Processing request",
|
22
|
-
extra={
|
23
|
-
"request_id": req_body.get("request_id", "unknown"),
|
24
|
-
"action": req_body.get("action", "unknown"),
|
25
|
-
},
|
26
|
-
)
|
27
|
-
|
28
|
-
return {
|
29
|
-
"status": "success",
|
30
|
-
"message": "Request processed successfully",
|
31
|
-
"data": req_body,
|
32
|
-
}
|
33
|
-
|
34
|
-
|
35
|
-
def main(req: func.HttpRequest) -> func.HttpResponse:
|
36
|
-
"""Azure Function entry point"""
|
37
|
-
try:
|
38
|
-
# Start timing the request
|
39
|
-
start_time = time.time()
|
40
|
-
|
41
|
-
# Get request details
|
42
|
-
method = req.method
|
43
|
-
url = req.url
|
44
|
-
headers = dict(req.headers)
|
45
|
-
|
46
|
-
# Log the incoming request
|
47
|
-
logger.log_request(
|
48
|
-
method=method,
|
49
|
-
url=url,
|
50
|
-
status_code=200, # We'll update this if there's an error
|
51
|
-
duration_ms=0, # We'll update this at the end
|
52
|
-
extra={"headers": headers, "request_type": "http_trigger"},
|
53
|
-
)
|
54
|
-
|
55
|
-
# Create a span for the entire function execution
|
56
|
-
with logger.create_span("function_execution") as span:
|
57
|
-
# Add request metadata to the span
|
58
|
-
span.set_attribute("http.method", method)
|
59
|
-
span.set_attribute("http.url", url)
|
60
|
-
|
61
|
-
# Parse request body
|
62
|
-
try:
|
63
|
-
req_body = req.get_json()
|
64
|
-
except ValueError:
|
65
|
-
req_body = {}
|
66
|
-
|
67
|
-
# Log the request body
|
68
|
-
logger.info("Received request body", extra={"body": req_body})
|
69
|
-
|
70
|
-
# Process the request
|
71
|
-
result = process_request(req_body)
|
72
|
-
|
73
|
-
# Calculate request duration
|
74
|
-
duration_ms = (time.time() - start_time) * 1000
|
75
|
-
|
76
|
-
# Log successful completion
|
77
|
-
logger.log_function_execution(
|
78
|
-
function_name="main",
|
79
|
-
duration_ms=duration_ms,
|
80
|
-
success=True,
|
81
|
-
extra={"method": method, "url": url},
|
82
|
-
)
|
83
|
-
|
84
|
-
# Return the response
|
85
|
-
return func.HttpResponse(
|
86
|
-
json.dumps(result), mimetype="application/json", status_code=200
|
87
|
-
)
|
88
|
-
|
89
|
-
except Exception as e:
|
90
|
-
# Calculate request duration
|
91
|
-
duration_ms = (time.time() - start_time) * 1000
|
92
|
-
|
93
|
-
# Log the error
|
94
|
-
logger.error(
|
95
|
-
f"Error processing request: {str(e)}",
|
96
|
-
extra={"method": method, "url": url, "error_type": type(e).__name__},
|
97
|
-
)
|
98
|
-
|
99
|
-
# Log failed execution
|
100
|
-
logger.log_function_execution(
|
101
|
-
function_name="main",
|
102
|
-
duration_ms=duration_ms,
|
103
|
-
success=False,
|
104
|
-
extra={"error": str(e), "error_type": type(e).__name__},
|
105
|
-
)
|
106
|
-
|
107
|
-
# Return error response
|
108
|
-
return func.HttpResponse(
|
109
|
-
json.dumps({"status": "error", "message": str(e)}),
|
110
|
-
mimetype="application/json",
|
111
|
-
status_code=500,
|
112
|
-
)
|
@@ -1,129 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import json
|
3
|
-
import time
|
4
|
-
import asyncio
|
5
|
-
import azure.functions as func
|
6
|
-
from azpaddypy.mgmt.logging import create_function_logger
|
7
|
-
|
8
|
-
app = func.FunctionApp()
|
9
|
-
|
10
|
-
# Initialize the logger
|
11
|
-
logger = create_function_logger(
|
12
|
-
function_app_name="test-function-app", function_name="test-function"
|
13
|
-
)
|
14
|
-
|
15
|
-
|
16
|
-
@logger.trace_function(log_args=True, log_result=True)
|
17
|
-
def process_request(req_body: dict) -> dict:
|
18
|
-
"""Process the request body and return a response"""
|
19
|
-
# Simulate some processing time
|
20
|
-
time.sleep(0.1)
|
21
|
-
|
22
|
-
# Log the request processing
|
23
|
-
logger.info(
|
24
|
-
"Processing request",
|
25
|
-
extra={
|
26
|
-
"request_id": req_body.get("request_id", "unknown"),
|
27
|
-
"action": req_body.get("action", "unknown"),
|
28
|
-
},
|
29
|
-
)
|
30
|
-
|
31
|
-
return {
|
32
|
-
"status": "success",
|
33
|
-
"message": "Request processed successfully",
|
34
|
-
"data": req_body,
|
35
|
-
}
|
36
|
-
|
37
|
-
|
38
|
-
@logger.trace_function(log_args=True, log_result=True)
|
39
|
-
async def process_request_async(req_body: dict) -> dict:
|
40
|
-
"""Process the request body asynchronously and return a response"""
|
41
|
-
# Simulate some async processing time
|
42
|
-
await asyncio.sleep(0.1)
|
43
|
-
|
44
|
-
# Log the request processing
|
45
|
-
logger.info(
|
46
|
-
"Processing async request",
|
47
|
-
extra={
|
48
|
-
"request_id": req_body.get("request_id", "unknown"),
|
49
|
-
"action": req_body.get("action", "unknown"),
|
50
|
-
"is_async": True,
|
51
|
-
},
|
52
|
-
)
|
53
|
-
|
54
|
-
return {
|
55
|
-
"status": "success",
|
56
|
-
"message": "Async request processed successfully",
|
57
|
-
"data": req_body,
|
58
|
-
}
|
59
|
-
|
60
|
-
|
61
|
-
@app.function_name(name="test-function")
|
62
|
-
@app.route(route="test-function", auth_level=func.AuthLevel.ANONYMOUS)
|
63
|
-
async def test_function(req: func.HttpRequest) -> func.HttpResponse:
|
64
|
-
"""Azure Function HTTP trigger that processes requests both synchronously and asynchronously"""
|
65
|
-
start_time = time.time()
|
66
|
-
method = req.method
|
67
|
-
url = str(req.url)
|
68
|
-
|
69
|
-
try:
|
70
|
-
# Get request body
|
71
|
-
req_body = req.get_json()
|
72
|
-
|
73
|
-
# Process request based on the action
|
74
|
-
action = req_body.get("action", "").lower()
|
75
|
-
|
76
|
-
if action == "async":
|
77
|
-
# Process request asynchronously
|
78
|
-
result = await process_request_async(req_body)
|
79
|
-
else:
|
80
|
-
# Process request synchronously
|
81
|
-
result = process_request(req_body)
|
82
|
-
|
83
|
-
# Calculate request duration
|
84
|
-
duration_ms = (time.time() - start_time) * 1000
|
85
|
-
|
86
|
-
# Log successful request
|
87
|
-
logger.log_request(
|
88
|
-
method=method,
|
89
|
-
url=url,
|
90
|
-
status_code=200,
|
91
|
-
duration_ms=duration_ms,
|
92
|
-
extra={
|
93
|
-
"request_id": req_body.get("request_id", "unknown"),
|
94
|
-
"action": action,
|
95
|
-
"is_async": action == "async",
|
96
|
-
},
|
97
|
-
)
|
98
|
-
|
99
|
-
# Return success response
|
100
|
-
return func.HttpResponse(
|
101
|
-
json.dumps(result),
|
102
|
-
mimetype="application/json",
|
103
|
-
status_code=200,
|
104
|
-
)
|
105
|
-
|
106
|
-
except Exception as e:
|
107
|
-
# Calculate request duration
|
108
|
-
duration_ms = (time.time() - start_time) * 1000
|
109
|
-
|
110
|
-
# Log the error
|
111
|
-
logger.error(
|
112
|
-
f"Error processing request: {str(e)}",
|
113
|
-
extra={"method": method, "url": url, "error_type": type(e).__name__},
|
114
|
-
)
|
115
|
-
|
116
|
-
# Log failed execution
|
117
|
-
logger.log_function_execution(
|
118
|
-
function_name="test_function",
|
119
|
-
duration_ms=duration_ms,
|
120
|
-
success=False,
|
121
|
-
extra={"error": str(e), "error_type": type(e).__name__},
|
122
|
-
)
|
123
|
-
|
124
|
-
# Return error response
|
125
|
-
return func.HttpResponse(
|
126
|
-
json.dumps({"status": "error", "message": str(e)}),
|
127
|
-
mimetype="application/json",
|
128
|
-
status_code=500,
|
129
|
-
)
|
azpaddypy-0.3.3.dist-info/RECORD
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
azpaddypy/mgmt/__init__.py,sha256=-jH8Ftx9C8qu4yF5dMVEapVZhNwG7m4QCUjyutesOoY,278
|
2
|
-
azpaddypy/mgmt/identity.py,sha256=mA_krQslMsK_sDob-z-QA0B9khK_JUO2way7xwPopR8,12001
|
3
|
-
azpaddypy/mgmt/logging.py,sha256=Cz_xPjFYfiNh2_S7jww8QR1EaGgQfXcFzEWZqCDp8ks,30638
|
4
|
-
azpaddypy/test_function/__init__.py,sha256=0NjUl36wvUWV79GpRwBFkgkBaC6uDZsTdaSVOIHMFEU,3481
|
5
|
-
azpaddypy/test_function/function_app.py,sha256=6nX54-iq0L1l_hZpD6E744-j79oLxdaldFyWDCpwH7c,3867
|
6
|
-
azpaddypy-0.3.3.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
|
7
|
-
azpaddypy-0.3.3.dist-info/METADATA,sha256=8Pnqt9ZOhDafwPlx2VeC_Zknadyb9kHBPv76Ss-OPLQ,705
|
8
|
-
azpaddypy-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
9
|
-
azpaddypy-0.3.3.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
|
10
|
-
azpaddypy-0.3.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|