Skip to content

Commit 78c9454

Browse files
committed
Fixed lots of actionmailer issues and other linting things
1 parent 6195db9 commit 78c9454

File tree

36 files changed

+651
-226
lines changed

36 files changed

+651
-226
lines changed

.vscode/settings.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,20 @@
44
"editor.formatOnSave": true,
55
"yaml.schemas": {
66
"./schemas/meta/log-source-schema.json": ["schemas/log_sources/*.yml"]
7+
},
8+
"[javascript]": {
9+
"editor.defaultFormatter": "biomejs.biome"
10+
},
11+
"[typescript]": {
12+
"editor.defaultFormatter": "biomejs.biome"
13+
},
14+
"[json]": {
15+
"editor.defaultFormatter": "biomejs.biome"
16+
},
17+
"[javascriptreact]": {
18+
"editor.defaultFormatter": "biomejs.biome"
19+
},
20+
"[typescriptreact]": {
21+
"editor.defaultFormatter": "biomejs.biome"
722
}
823
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { CodeBlock } from '@/components/code-block';
2+
import { EditPageLink } from '@/components/edit-page-link';
3+
import { HeadingWithAnchor } from '@/components/heading-with-anchor';
4+
5+
export default function PostmarkErrorHandlingPage() {
6+
return (
7+
<div className="space-y-6">
8+
<HeadingWithAnchor id="postmark-error-handling" level={1}>
9+
Handling Postmark Errors
10+
</HeadingWithAnchor>
11+
<p className="text-lg text-neutral-600 dark:text-neutral-300">
12+
A guide to handling Postmark bounce errors and sending custom notifications using LogStruct.
13+
</p>
14+
15+
<HeadingWithAnchor id="error-handling-methods" level={2}>
16+
Error Handling Methods
17+
</HeadingWithAnchor>
18+
<p className="text-neutral-600 dark:text-neutral-300 mb-4">
19+
LogStruct provides three error handling methods you can use in <code>rescue_from</code>{' '}
20+
handlers:
21+
</p>
22+
<ul className="list-disc list-inside space-y-2 text-neutral-600 dark:text-neutral-300 mb-4">
23+
<li>
24+
<code>log_and_ignore_error(error)</code> - Logs the error but doesn't report to Sentry or
25+
reraise
26+
</li>
27+
<li>
28+
<code>log_and_report_error(error)</code> - Logs the error, reports to Sentry, but doesn't
29+
reraise
30+
</li>
31+
<li>
32+
<code>log_and_reraise_error(error)</code> - Logs the error, reports to Sentry, and
33+
reraises (for retry)
34+
</li>
35+
</ul>
36+
37+
<HeadingWithAnchor id="example-postmark-bounces" level={2}>
38+
Example: Handling Postmark Bounce Errors
39+
</HeadingWithAnchor>
40+
<p className="text-neutral-600 dark:text-neutral-300 mb-4">
41+
When using Postmark, you may want to handle bounce errors (inactive recipients, invalid
42+
emails) differently than other errors:
43+
</p>
44+
<CodeBlock language="ruby">{`# config/initializers/action_mailer_postmark_errors.rb
45+
Rails.application.config.after_initialize do
46+
ActiveSupport.on_load(:action_mailer) do
47+
if defined?(Postmark)
48+
# Postmark bounce errors - log but don't reraise (can't be retried)
49+
rescue_from Postmark::InactiveRecipientError, with: :log_and_ignore_error
50+
rescue_from Postmark::InvalidEmailRequestError, with: :log_and_ignore_error
51+
end
52+
end
53+
end`}</CodeBlock>
54+
55+
<HeadingWithAnchor id="custom-notifications" level={2}>
56+
Custom Notifications (e.g., Slack)
57+
</HeadingWithAnchor>
58+
<p className="text-neutral-600 dark:text-neutral-300 mb-4">
59+
LogStruct logs structured error data but doesn't send notifications directly. To send custom
60+
notifications (like Slack alerts), subscribe to LogStruct logs:
61+
</p>
62+
<CodeBlock language="ruby">{`# config/initializers/mailer_notifications.rb
63+
Rails.application.config.after_initialize do
64+
# Subscribe to LogStruct logs
65+
ActiveSupport::Notifications.subscribe('log.logstruct') do |_name, _start, _finish, _id, payload|
66+
log = payload[:log]
67+
68+
# Send Slack notification for Postmark errors
69+
if log.is_a?(LogStruct::Log::ActionMailer::Error) &&
70+
[Postmark::InactiveRecipientError, Postmark::InvalidEmailRequestError].include?(log.error_class)
71+
72+
InternalSlackNotificationJob.perform_async(
73+
channel: '#alerts',
74+
text: "Email delivery error: #{log.message}"
75+
)
76+
end
77+
end
78+
end`}</CodeBlock>
79+
80+
<HeadingWithAnchor id="error-log-structure" level={2}>
81+
Error Log Structure
82+
</HeadingWithAnchor>
83+
<p className="text-neutral-600 dark:text-neutral-300 mb-4">
84+
When a mailer error occurs, LogStruct creates a{' '}
85+
<code>LogStruct::Log::ActionMailer::Error</code> with:
86+
</p>
87+
<ul className="list-disc list-inside space-y-2 text-neutral-600 dark:text-neutral-300 mb-4">
88+
<li>
89+
<code>error_class</code> - The exception class (e.g.,{' '}
90+
<code>Postmark::InactiveRecipientError</code>)
91+
</li>
92+
<li>
93+
<code>message</code> - Formatted message including context and the actual exception
94+
message
95+
</li>
96+
<li>
97+
<code>backtrace</code> - Stack trace
98+
</li>
99+
<li>
100+
<code>mailer_class</code> - The mailer class name
101+
</li>
102+
<li>
103+
<code>mailer_action</code> - The mailer action/method name
104+
</li>
105+
<li>
106+
<code>to</code>, <code>from</code>, <code>subject</code> - Email metadata
107+
</li>
108+
<li>
109+
<code>attachment_count</code> - Number of attachments
110+
</li>
111+
<li>
112+
<code>additional_data</code> - Custom context (account_id, user_id, etc.)
113+
</li>
114+
</ul>
115+
116+
<HeadingWithAnchor id="error-handling-flow" level={2}>
117+
Error Handling Flow
118+
</HeadingWithAnchor>
119+
<ol className="list-decimal list-inside space-y-2 text-neutral-600 dark:text-neutral-300 mb-4">
120+
<li>Email delivery fails with an exception</li>
121+
<li>
122+
Rails <code>rescue_from</code> catches the exception
123+
</li>
124+
<li>
125+
LogStruct method is called (e.g., <code>log_and_ignore_error</code>)
126+
</li>
127+
<li>LogStruct creates structured error log</li>
128+
<li>Your notification subscriber receives the log</li>
129+
<li>You send custom notifications based on error type</li>
130+
</ol>
131+
132+
<EditPageLink path="docs/app/docs/guides/postmark-error-handling/page.tsx" />
133+
</div>
134+
);
135+
}

docs/app/docs/layout.tsx

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -134,29 +134,6 @@ export default function DocsLayout({ children }: { children: React.ReactNode })
134134
},
135135
]}
136136
/>
137-
<NestedDocNavItem
138-
href="/docs/soc2-compliance"
139-
title="SOC 2 Compliance"
140-
active={pathname.startsWith('/docs/soc2-compliance')}
141-
subHeadings={[
142-
{
143-
id: 'why-logging-matters-for-soc2',
144-
title: 'Why Logging Matters for SOC 2',
145-
},
146-
{
147-
id: 'type-safe-logs-for-forensics',
148-
title: 'Type-Safe Logs for Forensics',
149-
},
150-
{
151-
id: 'sensitive-data-protection',
152-
title: 'Sensitive Data Protection',
153-
},
154-
{
155-
id: 'soc2-audit-benefits',
156-
title: 'Benefits for SOC 2 Audits',
157-
},
158-
]}
159-
/>
160137
<NestedDocNavItem
161138
href="/docs/sorbet-types"
162139
title="Sorbet Types"
@@ -215,6 +192,38 @@ export default function DocsLayout({ children }: { children: React.ReactNode })
215192
/>
216193
</nav>
217194

195+
<h2 className="mt-6 mb-3 text-lg font-semibold">Guides</h2>
196+
<nav className="space-y-1">
197+
<NestedDocNavItem
198+
href="/docs/soc2-compliance"
199+
title="SOC 2 Compliance"
200+
active={pathname.startsWith('/docs/soc2-compliance')}
201+
subHeadings={[
202+
{
203+
id: 'why-logging-matters-for-soc2',
204+
title: 'Why Logging Matters for SOC 2',
205+
},
206+
{
207+
id: 'type-safe-logs-for-forensics',
208+
title: 'Type-Safe Logs for Forensics',
209+
},
210+
{
211+
id: 'sensitive-data-protection',
212+
title: 'Sensitive Data Protection',
213+
},
214+
{
215+
id: 'soc2-audit-benefits',
216+
title: 'Benefits for SOC 2 Audits',
217+
},
218+
]}
219+
/>
220+
<DocNavItem
221+
href="/docs/guides/postmark-error-handling"
222+
title="Handling Postmark Errors"
223+
active={pathname.startsWith('/docs/guides/postmark-error-handling')}
224+
/>
225+
</nav>
226+
218227
<h2 className="mt-6 mb-3 text-lg font-semibold">Reference</h2>
219228
<nav className="space-y-1">
220229
<DocNavItem href="/yard/index.html" title="YARD Documentation" active={false} />

docs/components/main-nav.tsx

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ import Link from 'next/link';
66
import { usePathname } from 'next/navigation';
77
import {
88
NavigationMenu,
9-
NavigationMenuContent,
109
NavigationMenuItem,
1110
NavigationMenuLink,
1211
NavigationMenuList,
13-
NavigationMenuTrigger,
1412
navigationMenuTriggerStyle,
1513
} from '@/components/ui/navigation-menu';
1614
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
@@ -56,67 +54,6 @@ export function MainNav() {
5654
</NavigationMenuLink>
5755
</Link>
5856
</NavigationMenuItem>
59-
<NavigationMenuItem>
60-
<NavigationMenuTrigger>Guides</NavigationMenuTrigger>
61-
<NavigationMenuContent>
62-
<ul className="grid gap-3 p-4 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
63-
<li className="row-span-3">
64-
<NavigationMenuLink asChild>
65-
<a
66-
className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-neutral-900 to-neutral-700 p-6 no-underline outline-none focus:shadow-md"
67-
href="/docs/getting-started"
68-
>
69-
<div className="mt-4 mb-2 text-lg font-medium text-white">
70-
Getting Started
71-
</div>
72-
<p className="text-sm leading-tight text-white/90">
73-
Quick setup guide for adding LogStruct to your Rails application
74-
</p>
75-
</a>
76-
</NavigationMenuLink>
77-
</li>
78-
<li>
79-
<NavigationMenuLink asChild>
80-
<a
81-
href="/docs/configuration"
82-
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-neutral-100 focus:bg-neutral-100 dark:hover:bg-neutral-800 dark:focus:bg-neutral-800"
83-
>
84-
<div className="text-sm font-medium leading-none">Configuration</div>
85-
<p className="line-clamp-2 text-sm leading-snug text-neutral-500 dark:text-neutral-300">
86-
Learn how to customize LogStruct for your application
87-
</p>
88-
</a>
89-
</NavigationMenuLink>
90-
</li>
91-
<li>
92-
<NavigationMenuLink asChild>
93-
<a
94-
href="/docs/integrations"
95-
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-neutral-100 focus:bg-neutral-100 dark:hover:bg-neutral-800 dark:focus:bg-neutral-800"
96-
>
97-
<div className="text-sm font-medium leading-none">Integrations</div>
98-
<p className="line-clamp-2 text-sm leading-snug text-neutral-500 dark:text-neutral-300">
99-
Explore the built-in integrations with popular gems
100-
</p>
101-
</a>
102-
</NavigationMenuLink>
103-
</li>
104-
<li>
105-
<NavigationMenuLink asChild>
106-
<a
107-
href="/docs/sorbet-types"
108-
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-neutral-100 focus:bg-neutral-100 dark:hover:bg-neutral-800 dark:focus:bg-neutral-800"
109-
>
110-
<div className="text-sm font-medium leading-none">Type Safety</div>
111-
<p className="line-clamp-2 text-sm leading-snug text-neutral-500 dark:text-neutral-300">
112-
Using LogStruct with Sorbet for type-safe logging
113-
</p>
114-
</a>
115-
</NavigationMenuLink>
116-
</li>
117-
</ul>
118-
</NavigationMenuContent>
119-
</NavigationMenuItem>
12057
<NavigationMenuItem>
12158
<a
12259
href="/yard/index.html"

docs/lib/log-generation/__tests__/log-generator.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe('LogGenerator', () => {
3333
expect(requestLog).toHaveProperty('status');
3434

3535
const errorLog = generator.generateLog(LogType.ERROR);
36-
expect(errorLog).toHaveProperty('err_class');
36+
expect(errorLog).toHaveProperty('error_class');
3737
expect(errorLog).toHaveProperty('msg');
3838
expect(errorLog).toHaveProperty('backtrace');
3939

docs/lib/log-generation/sample-data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ export const SampleByLogField: Readonly<Record<LogField, (gen: RandomGen) => unk
207207
[LogField.MailerClass]: (_gen: RandomGen) => 'UserMailer',
208208
[LogField.MailerAction]: (_gen: RandomGen) => 'welcome_email',
209209
[LogField.AttachmentCount]: (gen: RandomGen) => gen.randomInt(0, 3),
210-
[LogField.ErrClass]: SampleHelpers.errClass,
210+
[LogField.ErrorClass]: SampleHelpers.errClass,
211211
[LogField.JobId]: SampleHelpers.hex8,
212212
[LogField.JobClass]: (_gen: RandomGen) => 'HardJob',
213213
[LogField.QueueName]: SampleHelpers.queue,

lefthook.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ pre-commit:
2828
glob: 'lib/log_struct/version.rb'
2929
run: ruby scripts/check_changelog_version.rb
3030

31-
eslint:
32-
glob: 'docs/**/*.{ts,tsx,js,jsx}'
33-
run: cd docs && pnpm run lint
31+
biome:
32+
glob: '**/*.{ts,tsx,js,jsx,json}'
33+
root: docs/
34+
run: pnpm exec biome check --write {staged_files}
35+
stage_fixed: true
3436

3537
typescript-tests:
3638
glob: 'docs/**/*.{ts,tsx}'
@@ -72,7 +74,7 @@ pre-commit:
7274
stage_fixed: true
7375

7476
prettier:
75-
glob: '*.{js,jsx,ts,tsx,json,yml,yaml,md}'
77+
glob: '*.{yml,yaml,md}'
7678
run: scripts/prettier.sh --write {staged_files}
7779
stage_fixed: true
7880

lib/log_struct/enums/log_field.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class LogField < T::Enum
6060
AttachmentCount = new(:attachments)
6161

6262
# Error fields
63-
ErrClass = new(:err_class)
63+
ErrorClass = new(:error_class)
6464
Backtrace = new(:backtrace)
6565

6666
# Job-specific fields

0 commit comments

Comments
 (0)