azpaddypy 0.1.5__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,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: azpaddypy
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Add your description here
5
- Author-email: Patrik <patrikhartl@gmail.com>
6
5
  Classifier: Operating System :: OS Independent
7
6
  Requires-Python: >=3.11
8
7
  Description-Content-Type: text/markdown
9
8
  License-File: LICENSE
9
+ Requires-Dist: azure-monitor-opentelemetry==1.6.10
10
10
  Dynamic: license-file
@@ -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
mgmt/logging.py CHANGED
@@ -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,112 @@
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
+ )
@@ -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,7 +0,0 @@
1
- azpaddypy-0.1.5.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
2
- mgmt/__init__.py,sha256=5-0eZuJMZlCONZNJ5hEvWXfvLIM36mg7FsEVs32bY_A,183
3
- mgmt/logging.py,sha256=jQ1jIkdSf7p44_oBJZD3rPodtwkhY5Y9rwRNafwXgcI,21061
4
- azpaddypy-0.1.5.dist-info/METADATA,sha256=aAN8735C6AhacUNVM36ZaUKW4wwq9onAMPROaJf5Oxo,298
5
- azpaddypy-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- azpaddypy-0.1.5.dist-info/top_level.txt,sha256=Xqg3gg53XEnWtdRarYe2ubJO0NDuer-pkr8RN2y7G-c,5
7
- azpaddypy-0.1.5.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- mgmt