-
Notifications
You must be signed in to change notification settings - Fork 194
fix: default pm #883
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: default pm #883
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| # ALB Lambda Logging Fix | ||
|
|
||
| **Date:** 2026-03-04 | ||
| **Status:** ✅ Fixed | ||
|
|
||
| ## Problem | ||
|
|
||
| Express logs contain `req.id` (AWS ALB trace IDs) that don't exist in the `alb` Axiom dataset. Investigation showed ~40-60% of ALB logs for high-traffic orgs (e.g., `0pCIbS4AMAFDB1iBMNhARWZt2gDtVwQx`) were missing. | ||
|
|
||
| ## Root Cause | ||
|
|
||
| The Lambda functions processing ALB logs (S3 → Lambda → Axiom) were **timing out and running out of memory** on large log files. | ||
|
|
||
| ### Lambda Config | ||
| | Setting | Before | After | | ||
| |---------|--------|-------| | ||
| | Memory | 256 MB | 1024 MB | | ||
| | Timeout | 60 sec | 180 sec | | ||
|
|
||
| ### Evidence | ||
| - CloudWatch showed ~100% error rate (72 invocations, 72 errors per hour) | ||
| - Errors: `Status: timeout` and `Runtime.OutOfMemory` | ||
| - Small files (164KB) processed successfully (~2000 logs in 3s) | ||
| - Large files (6-7MB compressed) failed consistently | ||
| - Same files retried multiple times before being abandoned | ||
|
|
||
| ## Fix Applied | ||
|
|
||
| Lambda configuration updated via AWS Console: | ||
| - `alb-listener-us-east-2`: Memory 1024MB, Timeout 180s | ||
| - `alb-listener-us-west-2`: Check if same fix needed | ||
|
|
||
| ## Verification (run after 24 hours) | ||
|
|
||
| ### 1. Check Lambda errors are gone: | ||
| ```bash | ||
| aws cloudwatch get-metric-statistics --region us-east-2 \ | ||
| --namespace AWS/Lambda \ | ||
| --metric-name Errors \ | ||
| --dimensions Name=FunctionName,Value=alb-listener-us-east-2 \ | ||
| --start-time $(date -u -v-24H +%Y-%m-%dT%H:%M:%SZ) \ | ||
| --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \ | ||
| --period 3600 \ | ||
| --statistics Sum | ||
| ``` | ||
|
|
||
| ### 2. Compare ALB vs Express trace IDs in Axiom: | ||
| ```apl | ||
| // Sample express trace IDs for an org | ||
| ['express'] | ||
| | where ['context.org_id'] == '0pCIbS4AMAFDB1iBMNhARWZt2gDtVwQx' | ||
| | where isnotnull(['req.id']) and ['req.id'] startswith "Root=" | ||
| | where ['_time'] > ago(1h) | ||
| | summarize count() by ['req.id'] | ||
| | take 10 | ||
|
|
||
| // Then verify each exists in ALB | ||
| ['alb'] | ||
| | where ['trace_id'] == "<trace_id_from_above>" | ||
| ``` | ||
|
|
||
| ### 3. Check invocation success rate: | ||
| ```bash | ||
| # Invocations | ||
| aws cloudwatch get-metric-statistics --region us-east-2 \ | ||
| --namespace AWS/Lambda --metric-name Invocations \ | ||
| --dimensions Name=FunctionName,Value=alb-listener-us-east-2 \ | ||
| --start-time $(date -u -v-6H +%Y-%m-%dT%H:%M:%SZ) \ | ||
| --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \ | ||
| --period 3600 --statistics Sum | ||
|
|
||
| # Errors (should be 0 or near 0) | ||
| aws cloudwatch get-metric-statistics --region us-east-2 \ | ||
| --namespace AWS/Lambda --metric-name Errors \ | ||
| --dimensions Name=FunctionName,Value=alb-listener-us-east-2 \ | ||
| --start-time $(date -u -v-6H +%Y-%m-%dT%H:%M:%SZ) \ | ||
| --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \ | ||
| --period 3600 --statistics Sum | ||
| ``` | ||
|
|
||
| ## Related Resources | ||
| - S3 Buckets: `autumn-alb-us-east-2`, `autumn-alb-us-west-2` | ||
| - Lambdas: `alb-listener-us-east-2`, `alb-listener-us-west-2` | ||
| - ALBs: `fc-server-oyknwa-9b105z0` (us-east-2), `fc-server-ndcdwy-65bl04in` (us-west-2) | ||
|
|
||
| ## Notes | ||
| - Backfilling missed logs is possible but tedious (manual Lambda re-invocation per S3 file) | ||
| - No DLQ was configured, so failed events were discarded after retries | ||
| - Consider adding a DLQ in future to catch failed processing attempts | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,65 @@ | ||
| import { test } from "bun:test"; | ||
| import { createPercentCoupon } from "@tests/integration/billing/utils/discounts/discountTestUtils"; | ||
| import { items } from "@tests/utils/fixtures/items"; | ||
| import { products } from "@tests/utils/fixtures/products"; | ||
| import { initScenario, s } from "@tests/utils/testInitUtils/initScenario"; | ||
| import { initScenario } from "@tests/utils/testInitUtils/initScenario"; | ||
| import chalk from "chalk"; | ||
| import type { AutumnInt } from "@/external/autumn/autumnCli"; | ||
| import { createStripeCli } from "@/external/connect/createStripeCli"; | ||
| import type { AutumnContext } from "@/honoUtils/HonoEnv"; | ||
|
|
||
| const testStripeCustomerWithGBP = async ({ | ||
| ctx, | ||
| autumn, | ||
| }: { | ||
| ctx: AutumnContext; | ||
| autumn: AutumnInt; | ||
| }) => { | ||
| const stripeCli = createStripeCli({ org: ctx.org, env: ctx.env }); | ||
| // 1. Create Stripe customer | ||
| const stripeCus = await stripeCli.customers.create({ | ||
| email: "test-gbp@example.com", | ||
| name: "GBP Test Customer", | ||
| }); | ||
|
|
||
| const paymentMethod = await stripeCli.paymentMethods.create({ | ||
| type: "card", | ||
| card: { | ||
| token: "tok_visa", | ||
| }, | ||
| }); | ||
|
|
||
| await stripeCli.paymentMethods.attach(paymentMethod.id, { | ||
| customer: stripeCus.id, | ||
| }); | ||
|
|
||
| const subscription = await stripeCli.subscriptions.create({ | ||
| customer: stripeCus.id, | ||
| items: [ | ||
| { | ||
| price_data: { | ||
| currency: "gbp", | ||
| unit_amount: 1000, | ||
| recurring: { | ||
| interval: "month", | ||
| interval_count: 1, | ||
| }, | ||
| product: "prod_U5XwDFBQB4TQJ7", | ||
| }, | ||
| quantity: 1, | ||
| }, | ||
| ], | ||
| default_payment_method: paymentMethod.id, | ||
| }); | ||
|
|
||
| console.log("Subscription created", subscription); | ||
|
|
||
| // 2. Create Autumn customer with stripe_id | ||
| const autumnCus = await autumn.customers.create({ | ||
| id: "gbp-test-customer", | ||
| name: "GBP Test Customer", | ||
| email: "test-gbp@example.com", | ||
| stripe_id: stripeCus.id, | ||
| }); | ||
| console.log("Created:", { stripeCus: stripeCus.id, autumnCus }); | ||
| }; | ||
|
|
||
| /** | ||
| * Scenario: | ||
|
|
@@ -21,54 +76,14 @@ import { createStripeCli } from "@/external/connect/createStripeCli"; | |
| test.concurrent(`${chalk.yellowBright("immediate-switch-discounts 3: upgrade carries over discount when coupon is deleted")}`, async () => { | ||
| const customerId = "temp"; | ||
|
|
||
| const pro = products.pro({ | ||
| id: "pro", | ||
| items: [items.monthlyMessages({ includedUsage: 500 })], | ||
| }); | ||
|
|
||
| const premium = products.premium({ | ||
| id: "premium", | ||
| items: [items.monthlyMessages({ includedUsage: 1000 })], | ||
| }); | ||
|
|
||
| const { autumnV1, testClockId, ctx } = await initScenario({ | ||
| customerId, | ||
| // customerId, | ||
| setup: [ | ||
| s.customer({ paymentMethod: "success" }), | ||
| s.products({ list: [pro, premium] }), | ||
| // s.customer({ paymentMethod: "success" }), | ||
| // s.products({ list: [pro, premium] }), | ||
| ], | ||
| actions: [], | ||
| }); | ||
|
|
||
| const stripeCli = createStripeCli({ org: ctx.org, env: ctx.env }); | ||
|
|
||
| const coupon = await createPercentCoupon({ | ||
| stripeCli, | ||
| percentOff: 20, | ||
| duration: "repeating", | ||
| durationInMonths: 1, | ||
| }); | ||
|
|
||
| await autumnV1.billing.attach({ | ||
| customer_id: customerId, | ||
| product_id: pro.id, | ||
| redirect_mode: "if_required", | ||
| discounts: [ | ||
| { | ||
| reward_id: coupon.id, | ||
| }, | ||
| ], | ||
| }); | ||
|
|
||
| await autumnV1.billing.attach({ | ||
| customer_id: customerId, | ||
| product_id: premium.id, | ||
| redirect_mode: "if_required", | ||
| }); | ||
|
|
||
| await autumnV1.billing.attach({ | ||
| customer_id: customerId, | ||
| product_id: pro.id, | ||
| redirect_mode: "if_required", | ||
| }); | ||
| await testStripeCustomerWithGBP({ ctx, autumn: autumnV1 }); | ||
| }); | ||
|
Comment on lines
77
to
89
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Work-in-progress test committed with commented-out setup The Prompt To Fix With AIThis is a comment left during a code review.
Path: server/tests/_temp/temp.test.ts
Line: 77-89
Comment:
**Work-in-progress test committed with commented-out setup**
The `initScenario` call has `customerId`, `s.customer`, and `s.products` all commented out. This means the scenario initialises with no customer and no products, so the `ctx` object passed to `testStripeCustomerWithGBP` may lack a properly configured org/environment. Since the hardcoded Stripe `product` ID (`"prod_U5XwDFBQB4TQJ7"`) also looks environment-specific, this test is likely to silently no-op or fail in other environments. Consider either fully enabling it (with proper fixtures) or keeping it out of the committed codebase until it's ready.
How can I resolve this? If you propose a fix, please make it concise. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| /** | ||
| * Attach Edge Case - Payment method exists but invoice_settings.default_payment_method is null | ||
| * | ||
| * Scenario: Customer has a payment method on file (card attached), but the | ||
| * Stripe customer's invoice_settings.default_payment_method has been cleared. | ||
| * Verifies that billing.attach still succeeds (Stripe can still charge the customer). | ||
| */ | ||
|
|
||
| import { test } from "bun:test"; | ||
| import { items } from "@tests/utils/fixtures/items"; | ||
| import { products } from "@tests/utils/fixtures/products"; | ||
| import { initScenario, s } from "@tests/utils/testInitUtils/initScenario"; | ||
| import chalk from "chalk"; | ||
| import { expectCustomerProductCorrect } from "../../utils/expectCustomerProductCorrect"; | ||
| import { expectStripeSubscriptionCorrect } from "../../utils/expectStripeSubCorrect"; | ||
|
|
||
| test.concurrent(`${chalk.yellowBright("pm-edge-case 1: attach succeeds when invoice_settings.default_payment_method is null")}`, async () => { | ||
| const customerId = "attach-pm-edge-null-default"; | ||
|
|
||
| const proMessagesItem = items.monthlyMessages({ includedUsage: 100 }); | ||
| const pro = products.pro({ | ||
| id: "pro", | ||
| items: [proMessagesItem], | ||
| }); | ||
|
|
||
| const { autumnV1, ctx, customer } = await initScenario({ | ||
| customerId, | ||
| setup: [ | ||
| s.customer({ paymentMethod: "success" }), | ||
| s.products({ list: [pro] }), | ||
| ], | ||
| actions: [], | ||
| }); | ||
|
|
||
| const stripeCustomerId = customer.processor?.id; | ||
| if (!stripeCustomerId) throw new Error("No stripe customer id"); | ||
|
|
||
| await ctx.stripeCli.customers.update(stripeCustomerId, { | ||
| invoice_settings: { | ||
| default_payment_method: "" as string, | ||
| }, | ||
| }); | ||
|
|
||
| await autumnV1.billing.attach({ | ||
| customer_id: customerId, | ||
| product_id: pro.id, | ||
| }); | ||
|
|
||
| const customerAfter = await autumnV1.customers.get(customerId); | ||
| await expectCustomerProductCorrect({ | ||
| customerId, | ||
| customer: customerAfter, | ||
| productId: pro.id, | ||
| state: "active", | ||
| }); | ||
|
|
||
| await expectStripeSubscriptionCorrect({ ctx, customerId }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The CloudWatch command uses a macOS-specific
date -vflag, so the verification steps fail on GNU/Linux environments.Prompt for AI agents