ai-lls-lib 1.5.0rc4__tar.gz → 1.5.0rc5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ai-lls-lib might be problematic. Click here for more details.

Files changed (33) hide show
  1. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/PKG-INFO +28 -1
  2. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/README.md +27 -0
  3. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/pyproject.toml +1 -1
  4. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/__init__.py +1 -1
  5. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/__main__.py +2 -1
  6. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/aws_client.py +8 -0
  7. ai_lls_lib-1.5.0rc5/src/ai_lls_lib/cli/commands/monitor.py +297 -0
  8. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/auth/__init__.py +0 -0
  9. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/auth/context_parser.py +0 -0
  10. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/__init__.py +0 -0
  11. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/commands/__init__.py +0 -0
  12. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/commands/admin.py +0 -0
  13. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/commands/cache.py +0 -0
  14. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/commands/stripe.py +0 -0
  15. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/commands/test_stack.py +0 -0
  16. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/commands/verify.py +0 -0
  17. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/cli/env_loader.py +0 -0
  18. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/core/__init__.py +0 -0
  19. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/core/cache.py +0 -0
  20. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/core/models.py +0 -0
  21. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/core/processor.py +0 -0
  22. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/core/verifier.py +0 -0
  23. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/payment/__init__.py +0 -0
  24. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/payment/credit_manager.py +0 -0
  25. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/payment/models.py +0 -0
  26. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/payment/stripe_manager.py +0 -0
  27. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/payment/webhook_processor.py +0 -0
  28. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/providers/__init__.py +0 -0
  29. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/providers/base.py +0 -0
  30. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/providers/external.py +0 -0
  31. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/providers/stub.py +0 -0
  32. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/testing/__init__.py +0 -0
  33. {ai_lls_lib-1.5.0rc4 → ai_lls_lib-1.5.0rc5}/src/ai_lls_lib/testing/fixtures.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-lls-lib
3
- Version: 1.5.0rc4
3
+ Version: 1.5.0rc5
4
4
  Summary: Landline Scrubber core library - phone verification and DNC checking
5
5
  Author: LandlineScrubber Team
6
6
  Requires-Python: >=3.12,<4.0
@@ -186,6 +186,33 @@ ai-lls test-stack test
186
186
  ai-lls test-stack delete
187
187
  ```
188
188
 
189
+ ### CloudWatch Log Monitoring
190
+ ```bash
191
+ # Monitor staging environment logs in real-time
192
+ ai-lls monitor logs --staging
193
+
194
+ # Monitor production environment logs
195
+ ai-lls monitor logs --production
196
+
197
+ # Monitor with custom duration (look back 10 minutes)
198
+ ai-lls monitor logs --staging --duration 600
199
+
200
+ # Filter logs for errors only
201
+ ai-lls monitor logs --staging --filter "ERROR"
202
+
203
+ # Use specific AWS profile
204
+ ai-lls monitor logs --staging --profile myprofile
205
+
206
+ # Experimental: Use CloudWatch Logs Live Tail API
207
+ ai-lls monitor live --staging
208
+ ```
209
+
210
+ The monitor command provides real-time log streaming from Lambda functions with:
211
+ - Color-coded output for different event types (external API calls, cache events, errors)
212
+ - Support for multiple log groups simultaneously
213
+ - Rich formatting when the `rich` library is installed
214
+ - Automatic detection of external API calls to landlineremover.com
215
+
189
216
  ## Project Structure
190
217
 
191
218
  ```
@@ -166,6 +166,33 @@ ai-lls test-stack test
166
166
  ai-lls test-stack delete
167
167
  ```
168
168
 
169
+ ### CloudWatch Log Monitoring
170
+ ```bash
171
+ # Monitor staging environment logs in real-time
172
+ ai-lls monitor logs --staging
173
+
174
+ # Monitor production environment logs
175
+ ai-lls monitor logs --production
176
+
177
+ # Monitor with custom duration (look back 10 minutes)
178
+ ai-lls monitor logs --staging --duration 600
179
+
180
+ # Filter logs for errors only
181
+ ai-lls monitor logs --staging --filter "ERROR"
182
+
183
+ # Use specific AWS profile
184
+ ai-lls monitor logs --staging --profile myprofile
185
+
186
+ # Experimental: Use CloudWatch Logs Live Tail API
187
+ ai-lls monitor live --staging
188
+ ```
189
+
190
+ The monitor command provides real-time log streaming from Lambda functions with:
191
+ - Color-coded output for different event types (external API calls, cache events, errors)
192
+ - Support for multiple log groups simultaneously
193
+ - Rich formatting when the `rich` library is installed
194
+ - Automatic detection of external API calls to landlineremover.com
195
+
169
196
  ## Project Structure
170
197
 
171
198
  ```
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ai-lls-lib"
3
- version = "1.5.0-rc.4"
3
+ version = "1.5.0-rc.5"
4
4
  description = "Landline Scrubber core library - phone verification and DNC checking"
5
5
  authors = ["LandlineScrubber Team"]
6
6
  readme = "README.md"
@@ -17,7 +17,7 @@ from ai_lls_lib.core.verifier import PhoneVerifier
17
17
  from ai_lls_lib.core.processor import BulkProcessor
18
18
  from ai_lls_lib.core.cache import DynamoDBCache
19
19
 
20
- __version__ = "1.5.0-rc.4"
20
+ __version__ = "1.5.0-rc.5"
21
21
  __all__ = [
22
22
  "PhoneVerification",
23
23
  "BulkJob",
@@ -5,7 +5,7 @@ import click
5
5
  import sys
6
6
  import os
7
7
  from pathlib import Path
8
- from ai_lls_lib.cli.commands import verify, cache, admin, test_stack, stripe
8
+ from ai_lls_lib.cli.commands import verify, cache, admin, test_stack, stripe, monitor
9
9
  from ai_lls_lib.cli.env_loader import load_env_file
10
10
 
11
11
  def load_env_files(verbose=False):
@@ -49,6 +49,7 @@ cli.add_command(cache.cache_group)
49
49
  cli.add_command(admin.admin_group)
50
50
  cli.add_command(test_stack.test_stack_group)
51
51
  cli.add_command(stripe.stripe_group)
52
+ cli.add_command(monitor.monitor_group)
52
53
 
53
54
  def main():
54
55
  """Main entry point"""
@@ -27,6 +27,7 @@ class AWSClient:
27
27
  self._sqs = None
28
28
  self._secretsmanager = None
29
29
  self._cloudformation = None
30
+ self._logs = None
30
31
 
31
32
  @property
32
33
  def dynamodb(self):
@@ -63,6 +64,13 @@ class AWSClient:
63
64
  self._cloudformation = self.session.client('cloudformation')
64
65
  return self._cloudformation
65
66
 
67
+ @property
68
+ def logs(self):
69
+ """Get CloudWatch Logs client"""
70
+ if not self._logs:
71
+ self._logs = self.session.client('logs')
72
+ return self._logs
73
+
66
74
  def get_stack_outputs(self, stack_name: str) -> Dict[str, str]:
67
75
  """Get CloudFormation stack outputs"""
68
76
  try:
@@ -0,0 +1,297 @@
1
+ """
2
+ Real-time CloudWatch log monitoring for Lambda functions
3
+ """
4
+ import click
5
+ import json
6
+ import time
7
+ from datetime import datetime, timedelta
8
+ from typing import Optional, List, Dict
9
+ from botocore.exceptions import ClientError
10
+ from ai_lls_lib.cli.aws_client import AWSClient
11
+
12
+ try:
13
+ from rich.console import Console
14
+ from rich.syntax import Syntax
15
+ from rich.table import Table
16
+ from rich.live import Live
17
+ from rich.panel import Panel
18
+ from rich.text import Text
19
+ from rich import box
20
+ HAS_RICH = True
21
+ except ImportError:
22
+ HAS_RICH = False
23
+ Console = None
24
+
25
+
26
+ def get_log_groups(aws_client: AWSClient, environment: str) -> List[str]:
27
+ """Get relevant CloudWatch log groups for the environment"""
28
+ # Map environment to stack name pattern
29
+ stack_prefix = "landline-api-staging" if environment == "staging" else "landline-api"
30
+
31
+ # Key Lambda functions to monitor
32
+ lambda_functions = [
33
+ "PhoneVerifyHandler",
34
+ "BulkUploadHandler",
35
+ "BulkProcessHandler",
36
+ "PlansHandler",
37
+ "PaymentHandler",
38
+ "UserProfileHandler"
39
+ ]
40
+
41
+ log_groups = []
42
+ for func in lambda_functions:
43
+ log_group = f"/aws/lambda/{stack_prefix}-{func}"
44
+ log_groups.append(log_group)
45
+
46
+ return log_groups
47
+
48
+
49
+ def format_log_event(event: Dict, use_rich: bool = True) -> str:
50
+ """Format a CloudWatch log event for display"""
51
+ timestamp = datetime.fromtimestamp(event.get('timestamp', 0) / 1000)
52
+ message = event.get('message', '')
53
+
54
+ # Try to parse JSON message
55
+ try:
56
+ if message.strip().startswith('{'):
57
+ parsed = json.loads(message)
58
+
59
+ # Check for external API calls
60
+ if 'external_api_call' in message.lower() or 'landlineremover.com' in message:
61
+ if use_rich and HAS_RICH:
62
+ return f"[bold green]{timestamp.strftime('%H:%M:%S')}[/bold green] [cyan]EXTERNAL API:[/cyan] {message}"
63
+ else:
64
+ return f"{timestamp.strftime('%H:%M:%S')} EXTERNAL API: {message}"
65
+
66
+ # Check for cache events
67
+ if 'cache' in message.lower():
68
+ if use_rich and HAS_RICH:
69
+ return f"[bold blue]{timestamp.strftime('%H:%M:%S')}[/bold blue] [yellow]CACHE:[/yellow] {message}"
70
+ else:
71
+ return f"{timestamp.strftime('%H:%M:%S')} CACHE: {message}"
72
+
73
+ # Check for errors
74
+ if 'error' in message.lower() or 'exception' in message.lower():
75
+ if use_rich and HAS_RICH:
76
+ return f"[bold red]{timestamp.strftime('%H:%M:%S')}[/bold red] [red]ERROR:[/red] {message}"
77
+ else:
78
+ return f"{timestamp.strftime('%H:%M:%S')} ERROR: {message}"
79
+
80
+ # Format structured logs
81
+ if use_rich and HAS_RICH:
82
+ message = json.dumps(parsed, indent=2)
83
+ except (json.JSONDecodeError, ValueError):
84
+ pass
85
+
86
+ # Default formatting
87
+ if use_rich and HAS_RICH:
88
+ return f"[dim]{timestamp.strftime('%H:%M:%S')}[/dim] {message}"
89
+ else:
90
+ return f"{timestamp.strftime('%H:%M:%S')} {message}"
91
+
92
+
93
+ def tail_logs(aws_client: AWSClient, log_groups: List[str], duration: int = 300, follow: bool = True):
94
+ """Tail CloudWatch logs in real-time"""
95
+ console = Console() if HAS_RICH else None
96
+
97
+ # Calculate start time
98
+ start_time = int((datetime.utcnow() - timedelta(seconds=duration)).timestamp() * 1000)
99
+
100
+ # Track last event time for each log group
101
+ last_event_times = {group: start_time for group in log_groups}
102
+
103
+ if console and HAS_RICH:
104
+ console.print(Panel.fit(
105
+ f"[bold cyan]Monitoring {len(log_groups)} Lambda functions[/bold cyan]\n"
106
+ f"[dim]Press Ctrl+C to stop[/dim]",
107
+ title="CloudWatch Log Monitor",
108
+ border_style="cyan"
109
+ ))
110
+ console.print()
111
+ else:
112
+ click.echo(f"Monitoring {len(log_groups)} Lambda functions")
113
+ click.echo("Press Ctrl+C to stop\n")
114
+
115
+ try:
116
+ while True:
117
+ events_found = False
118
+
119
+ for log_group in log_groups:
120
+ try:
121
+ # Use filter_log_events for real-time tailing
122
+ response = aws_client.logs.filter_log_events(
123
+ logGroupName=log_group,
124
+ startTime=last_event_times[log_group],
125
+ limit=100
126
+ )
127
+
128
+ events = response.get('events', [])
129
+
130
+ if events:
131
+ events_found = True
132
+ # Update last event time
133
+ last_event_times[log_group] = events[-1]['timestamp'] + 1
134
+
135
+ # Display log group header
136
+ if console and HAS_RICH:
137
+ console.print(f"\n[bold magenta]═══ {log_group} ═══[/bold magenta]")
138
+ else:
139
+ click.echo(f"\n=== {log_group} ===")
140
+
141
+ # Display events
142
+ for event in events:
143
+ formatted = format_log_event(event, use_rich=(console is not None))
144
+ if console and HAS_RICH:
145
+ console.print(formatted)
146
+ else:
147
+ click.echo(formatted)
148
+
149
+ except ClientError as e:
150
+ if e.response['Error']['Code'] != 'ResourceNotFoundException':
151
+ if console and HAS_RICH:
152
+ console.print(f"[yellow]Warning: {e}[/yellow]")
153
+ else:
154
+ click.echo(f"Warning: {e}", err=True)
155
+
156
+ if not follow:
157
+ break
158
+
159
+ # Sleep briefly before next poll
160
+ time.sleep(2 if events_found else 5)
161
+
162
+ except KeyboardInterrupt:
163
+ if console and HAS_RICH:
164
+ console.print("\n[yellow]Monitoring stopped[/yellow]")
165
+ else:
166
+ click.echo("\nMonitoring stopped")
167
+
168
+
169
+ def start_live_tail(aws_client: AWSClient, log_groups: List[str]):
170
+ """Start a CloudWatch Logs Live Tail session (requires AWS SDK v2)"""
171
+ console = Console() if HAS_RICH else None
172
+
173
+ try:
174
+ # Start live tail session
175
+ response = aws_client.logs.start_live_tail(
176
+ logGroupIdentifiers=log_groups,
177
+ logStreamNamePrefixes=[],
178
+ logEventFilterPattern=""
179
+ )
180
+
181
+ session_id = response.get('sessionId')
182
+ session_url = response.get('sessionUrl')
183
+
184
+ if console and HAS_RICH:
185
+ console.print(Panel.fit(
186
+ f"[bold green]Live Tail session started[/bold green]\n"
187
+ f"Session ID: {session_id}\n"
188
+ f"[dim]Note: Live Tail API requires WebSocket support[/dim]",
189
+ title="CloudWatch Live Tail",
190
+ border_style="green"
191
+ ))
192
+ else:
193
+ click.echo(f"Live Tail session started")
194
+ click.echo(f"Session ID: {session_id}")
195
+ click.echo(f"Note: Live Tail API requires WebSocket support")
196
+
197
+ # Note: Full WebSocket implementation would be needed here
198
+ # For now, fall back to filter_log_events polling
199
+ click.echo("\nFalling back to standard log polling...")
200
+ tail_logs(aws_client, log_groups, duration=300, follow=True)
201
+
202
+ except ClientError as e:
203
+ if 'start_live_tail' in str(e):
204
+ # Fall back to regular tailing if Live Tail not available
205
+ if console and HAS_RICH:
206
+ console.print("[yellow]Live Tail API not available, using standard polling[/yellow]")
207
+ else:
208
+ click.echo("Live Tail API not available, using standard polling")
209
+ tail_logs(aws_client, log_groups, duration=300, follow=True)
210
+ else:
211
+ raise
212
+
213
+
214
+ @click.group(name="monitor")
215
+ def monitor_group():
216
+ """Monitor CloudWatch logs in real-time"""
217
+ pass
218
+
219
+
220
+ @monitor_group.command()
221
+ @click.option('--staging', is_flag=True, help='Monitor staging environment (default: production)')
222
+ @click.option('--production', is_flag=True, help='Monitor production environment')
223
+ @click.option('--duration', default=300, help='How far back to start (seconds, default: 300)')
224
+ @click.option('--follow', is_flag=True, default=True, help='Follow logs in real-time (default: true)')
225
+ @click.option('--filter', 'filter_pattern', help='CloudWatch filter pattern to apply')
226
+ @click.option('--profile', help='AWS profile to use')
227
+ @click.option('--region', default='us-east-1', help='AWS region')
228
+ def logs(staging, production, duration, follow, filter_pattern, profile, region):
229
+ """Tail CloudWatch logs for Lambda functions
230
+
231
+ Examples:
232
+ ai-lls monitor logs --staging
233
+ ai-lls monitor logs --production
234
+ ai-lls monitor logs --staging --filter "ERROR"
235
+ ai-lls monitor logs --duration 600 --follow
236
+ """
237
+ # Determine environment
238
+ if staging and production:
239
+ raise click.ClickException("Cannot specify both --staging and --production")
240
+
241
+ environment = "staging" if staging else "production"
242
+
243
+ # Show warning if Rich not installed
244
+ if not HAS_RICH:
245
+ click.echo("Warning: Install 'rich' library for better output formatting", err=True)
246
+ click.echo("Run: pip install rich", err=True)
247
+ click.echo()
248
+
249
+ # Initialize AWS client
250
+ aws_client = AWSClient(region=region, profile=profile)
251
+
252
+ # Get log groups
253
+ log_groups = get_log_groups(aws_client, environment)
254
+
255
+ click.echo(f"Monitoring {environment} environment...")
256
+ click.echo(f"Log groups: {', '.join([g.split('/')[-1] for g in log_groups])}")
257
+ click.echo()
258
+
259
+ # Start tailing logs
260
+ tail_logs(aws_client, log_groups, duration=duration, follow=follow)
261
+
262
+
263
+ @monitor_group.command()
264
+ @click.option('--staging', is_flag=True, help='Monitor staging environment')
265
+ @click.option('--production', is_flag=True, help='Monitor production environment')
266
+ @click.option('--profile', help='AWS profile to use')
267
+ @click.option('--region', default='us-east-1', help='AWS region')
268
+ def live(staging, production, profile, region):
269
+ """Start CloudWatch Logs Live Tail session (experimental)
270
+
271
+ Uses the CloudWatch Logs Live Tail API for real-time streaming.
272
+ Falls back to standard polling if Live Tail is not available.
273
+
274
+ Examples:
275
+ ai-lls monitor live --staging
276
+ ai-lls monitor live --production
277
+ """
278
+ # Determine environment
279
+ if staging and production:
280
+ raise click.ClickException("Cannot specify both --staging and --production")
281
+
282
+ environment = "staging" if staging else "production"
283
+
284
+ # Initialize AWS client
285
+ aws_client = AWSClient(region=region, profile=profile)
286
+
287
+ # Get log groups
288
+ log_groups = get_log_groups(aws_client, environment)
289
+
290
+ click.echo(f"Starting Live Tail for {environment} environment...")
291
+
292
+ # Start live tail
293
+ start_live_tail(aws_client, log_groups)
294
+
295
+
296
+ # Export the group for registration
297
+ __all__ = ['monitor_group']