azpaddypy 0.1.6__py3-none-any.whl → 0.1.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: azpaddypy
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Add your description here
5
5
  Classifier: Operating System :: OS Independent
6
6
  Requires-Python: >=3.11
@@ -0,0 +1,9 @@
1
+ azpaddypy-0.1.7.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
2
+ mgmt/__init__.py,sha256=5-0eZuJMZlCONZNJ5hEvWXfvLIM36mg7FsEVs32bY_A,183
3
+ mgmt/logging.py,sha256=9u114Ji-cwBMUf8D02i_b_Ei2dZc3TaIUH4uJv_tmSc,25451
4
+ test_function/__init__.py,sha256=0NjUl36wvUWV79GpRwBFkgkBaC6uDZsTdaSVOIHMFEU,3481
5
+ test_function/function_app.py,sha256=6nX54-iq0L1l_hZpD6E744-j79oLxdaldFyWDCpwH7c,3867
6
+ azpaddypy-0.1.7.dist-info/METADATA,sha256=-04_CkdsFyFSbUHzddoqbvL0S4cWx2BfLax8t48ueow,304
7
+ azpaddypy-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ azpaddypy-0.1.7.dist-info/top_level.txt,sha256=wCjIwMZJaxbmRG_W4H2ZR9OmBJP3oKfhtvxrODAZqAo,19
9
+ azpaddypy-0.1.7.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ mgmt
2
+ test_function
@@ -3,6 +3,7 @@ import os
3
3
  import json
4
4
  import functools
5
5
  import time
6
+ import asyncio
6
7
  from typing import Optional, Dict, Any, Union, List
7
8
  from datetime import datetime
8
9
  from azure.monitor.opentelemetry import configure_azure_monitor
@@ -317,7 +318,8 @@ class AzureLogger:
317
318
  log_execution: bool = True,
318
319
  ):
319
320
  """
320
- Decorator to automatically trace function execution with comprehensive logging
321
+ Decorator to automatically trace function execution with comprehensive logging.
322
+ Supports both synchronous and asynchronous functions.
321
323
 
322
324
  Args:
323
325
  function_name: Custom name for the span (defaults to function name)
@@ -328,7 +330,7 @@ class AzureLogger:
328
330
 
329
331
  def decorator(func):
330
332
  @functools.wraps(func)
331
- def wrapper(*args, **kwargs):
333
+ async def async_wrapper(*args, **kwargs):
332
334
  span_name = function_name or f"{func.__module__}.{func.__name__}"
333
335
 
334
336
  with self.tracer.start_as_current_span(span_name) as span:
@@ -336,6 +338,92 @@ class AzureLogger:
336
338
  span.set_attribute("function.name", func.__name__)
337
339
  span.set_attribute("function.module", func.__module__)
338
340
  span.set_attribute("service.name", self.service_name)
341
+ span.set_attribute("function.is_async", True)
342
+
343
+ if self._correlation_id:
344
+ span.set_attribute("correlation.id", self._correlation_id)
345
+
346
+ if log_args and args:
347
+ span.set_attribute("function.args_count", len(args))
348
+ if log_args and kwargs:
349
+ span.set_attribute("function.kwargs_count", len(kwargs))
350
+
351
+ start_time = time.time()
352
+
353
+ try:
354
+ self.debug(
355
+ f"Starting async function execution: {func.__name__}"
356
+ )
357
+
358
+ result = await func(*args, **kwargs)
359
+ duration_ms = (time.time() - start_time) * 1000
360
+
361
+ # Mark span as successful
362
+ span.set_attribute("function.duration_ms", duration_ms)
363
+ span.set_attribute("function.success", True)
364
+ span.set_status(Status(StatusCode.OK))
365
+
366
+ if log_result and result is not None:
367
+ span.set_attribute("function.has_result", True)
368
+ span.set_attribute(
369
+ "function.result_type", type(result).__name__
370
+ )
371
+
372
+ if log_execution:
373
+ self.log_function_execution(
374
+ func.__name__,
375
+ duration_ms,
376
+ True,
377
+ {
378
+ "args_count": len(args) if args else 0,
379
+ "kwargs_count": len(kwargs) if kwargs else 0,
380
+ "is_async": True,
381
+ },
382
+ )
383
+
384
+ self.debug(
385
+ f"Async function execution completed: {func.__name__}"
386
+ )
387
+ return result
388
+
389
+ except Exception as e:
390
+ duration_ms = (time.time() - start_time) * 1000
391
+
392
+ # Mark span as failed
393
+ span.set_status(Status(StatusCode.ERROR, str(e)))
394
+ span.record_exception(e)
395
+ span.set_attribute("function.duration_ms", duration_ms)
396
+ span.set_attribute("function.success", False)
397
+ span.set_attribute("error.type", type(e).__name__)
398
+ span.set_attribute("error.message", str(e))
399
+
400
+ if log_execution:
401
+ self.log_function_execution(
402
+ func.__name__,
403
+ duration_ms,
404
+ False,
405
+ {
406
+ "error_type": type(e).__name__,
407
+ "error_message": str(e),
408
+ "is_async": True,
409
+ },
410
+ )
411
+
412
+ self.error(
413
+ f"Async function execution failed: {func.__name__} - {str(e)}"
414
+ )
415
+ raise
416
+
417
+ @functools.wraps(func)
418
+ def sync_wrapper(*args, **kwargs):
419
+ span_name = function_name or f"{func.__module__}.{func.__name__}"
420
+
421
+ with self.tracer.start_as_current_span(span_name) as span:
422
+ # Add function metadata
423
+ span.set_attribute("function.name", func.__name__)
424
+ span.set_attribute("function.module", func.__module__)
425
+ span.set_attribute("service.name", self.service_name)
426
+ span.set_attribute("function.is_async", False)
339
427
 
340
428
  if self._correlation_id:
341
429
  span.set_attribute("correlation.id", self._correlation_id)
@@ -372,6 +460,7 @@ class AzureLogger:
372
460
  {
373
461
  "args_count": len(args) if args else 0,
374
462
  "kwargs_count": len(kwargs) if kwargs else 0,
463
+ "is_async": False,
375
464
  },
376
465
  )
377
466
 
@@ -397,6 +486,7 @@ class AzureLogger:
397
486
  {
398
487
  "error_type": type(e).__name__,
399
488
  "error_message": str(e),
489
+ "is_async": False,
400
490
  },
401
491
  )
402
492
 
@@ -405,7 +495,10 @@ class AzureLogger:
405
495
  )
406
496
  raise
407
497
 
408
- return wrapper
498
+ # Return the appropriate wrapper based on whether the function is async
499
+ if asyncio.iscoroutinefunction(func):
500
+ return async_wrapper
501
+ return sync_wrapper
409
502
 
410
503
  return decorator
411
504
 
@@ -0,0 +1,129 @@
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
+ )
@@ -1,116 +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
- app = func.FunctionApp()
8
-
9
- # Initialize the logger
10
- logger = create_function_logger(
11
- function_app_name="test-function-app", function_name="test-function"
12
- )
13
-
14
-
15
- @logger.trace_function(log_args=True, log_result=True)
16
- def process_request(req_body: dict) -> dict:
17
- """Process the request body and return a response"""
18
- # Simulate some processing time
19
- time.sleep(0.1)
20
-
21
- # Log the request processing
22
- logger.info(
23
- "Processing request",
24
- extra={
25
- "request_id": req_body.get("request_id", "unknown"),
26
- "action": req_body.get("action", "unknown"),
27
- },
28
- )
29
-
30
- return {
31
- "status": "success",
32
- "message": "Request processed successfully",
33
- "data": req_body,
34
- }
35
-
36
-
37
- @app.function_name(name="test-function")
38
- @app.route(route="test-function", auth_level=func.AuthLevel.ANONYMOUS)
39
- def test_function(req: func.HttpRequest) -> func.HttpResponse:
40
- """Azure Function v2 HTTP trigger with comprehensive logging"""
41
- try:
42
- # Start timing the request
43
- start_time = time.time()
44
-
45
- # Get request details
46
- method = req.method
47
- url = req.url
48
- headers = dict(req.headers)
49
-
50
- # Log the incoming request
51
- logger.log_request(
52
- method=method,
53
- url=url,
54
- status_code=200, # We'll update this if there's an error
55
- duration_ms=0, # We'll update this at the end
56
- extra={"headers": headers, "request_type": "http_trigger"},
57
- )
58
-
59
- # Create a span for the entire function execution
60
- with logger.create_span("function_execution") as span:
61
- # Add request metadata to the span
62
- span.set_attribute("http.method", method)
63
- span.set_attribute("http.url", url)
64
-
65
- # Parse request body
66
- try:
67
- req_body = req.get_json()
68
- except ValueError:
69
- req_body = {}
70
-
71
- # Log the request body
72
- logger.info("Received request body", extra={"body": req_body})
73
-
74
- # Process the request
75
- result = process_request(req_body)
76
-
77
- # Calculate request duration
78
- duration_ms = (time.time() - start_time) * 1000
79
-
80
- # Log successful completion
81
- logger.log_function_execution(
82
- function_name="test_function",
83
- duration_ms=duration_ms,
84
- success=True,
85
- extra={"method": method, "url": url},
86
- )
87
-
88
- # Return the response
89
- return func.HttpResponse(
90
- json.dumps(result), mimetype="application/json", status_code=200
91
- )
92
-
93
- except Exception as e:
94
- # Calculate request duration
95
- duration_ms = (time.time() - start_time) * 1000
96
-
97
- # Log the error
98
- logger.error(
99
- f"Error processing request: {str(e)}",
100
- extra={"method": method, "url": url, "error_type": type(e).__name__},
101
- )
102
-
103
- # Log failed execution
104
- logger.log_function_execution(
105
- function_name="test_function",
106
- duration_ms=duration_ms,
107
- success=False,
108
- extra={"error": str(e), "error_type": type(e).__name__},
109
- )
110
-
111
- # Return error response
112
- return func.HttpResponse(
113
- json.dumps({"status": "error", "message": str(e)}),
114
- mimetype="application/json",
115
- status_code=500,
116
- )
@@ -1,9 +0,0 @@
1
- azpaddypy/mgmt/__init__.py,sha256=5-0eZuJMZlCONZNJ5hEvWXfvLIM36mg7FsEVs32bY_A,183
2
- azpaddypy/mgmt/logging.py,sha256=jQ1jIkdSf7p44_oBJZD3rPodtwkhY5Y9rwRNafwXgcI,21061
3
- azpaddypy/test_function/__init__.py,sha256=0NjUl36wvUWV79GpRwBFkgkBaC6uDZsTdaSVOIHMFEU,3481
4
- azpaddypy/test_function/function_app.py,sha256=-vksGicWKPqEmW8TjV2lQW0hOuGFmLxrk_VnP69dz4Q,3681
5
- azpaddypy-0.1.6.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
6
- azpaddypy-0.1.6.dist-info/METADATA,sha256=q2nHswCgcUTXSohDCW4JnKHZnpxu5hyZxVR_qlxJ-YM,304
7
- azpaddypy-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- azpaddypy-0.1.6.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
9
- azpaddypy-0.1.6.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- azpaddypy
File without changes
File without changes