Skip to content

feat: add description option and fix resolution noise#11

Merged
Royal-lobster merged 1 commit intomainfrom
feat/description-option-and-resolution-fix
Apr 6, 2026
Merged

feat: add description option and fix resolution noise#11
Royal-lobster merged 1 commit intomainfrom
feat/description-option-and-resolution-fix

Conversation

@Royal-lobster
Copy link
Copy Markdown
Member

@Royal-lobster Royal-lobster commented Apr 6, 2026

Summary

  • Add description field to AlertOptions — lets callers pass a short title + detailed body separately, so Discord embeds have clean titles like [CRITICAL] Market Resolution Failed instead of stuffing the full error message into the title
  • Allow error()/critical() to accept (title, options) directly without an awkward undefined error param in the middle
  • Resolution notifications now only fire for sustained incidents (count > rampThreshold, default 64). One-off or sporadic failures no longer produce noisy "Resolved" messages
  • NestJS exception filter uses {METHOD} {PATH} as the alert title instead of error.message

Test plan

  • All 133 existing tests pass
  • New tests for description option (4 tests)
  • New tests for resolution threshold behavior (3 tests)
  • Verified overloaded error(title, options) works without undefined

🤖 Generated with Claude Code

Closes #15

- Add `description` field to AlertOptions for separating short titles
  from detailed messages in Discord embeds
- Allow error()/critical() to accept (title, options) directly without
  an intermediate undefined error parameter
- Resolution notifications now only fire for sustained incidents
  (count > rampThreshold, default 64) — sporadic failures no longer
  produce noisy "Resolved" messages
- NestJS exception filter uses {METHOD} {PATH} as the alert title
  instead of the full error.message

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Royal-lobster Royal-lobster merged commit 44f5ee8 into main Apr 6, 2026
3 checks passed
@Royal-lobster Royal-lobster deleted the feat/description-option-and-resolution-fix branch April 6, 2026 10:12
@github-actions github-actions bot mentioned this pull request Apr 6, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a description field for detailed alert messages, simplifies method signatures for direct option passing, and restricts resolution notifications to sustained incidents. Feedback highlights a potential memory leak in the aggregator due to incomplete cleanup of low-count alerts and identifies data loss in the new method overloads where the third argument is ignored. Additionally, the reviewer suggests using more stable titles in the NestJS exception filter to avoid degrading deduplication quality with dynamic URL paths.

Comment thread src/core/aggregator.ts
Comment on lines +151 to +159
if (state.count > this.config.rampThreshold) {
resolved.push({
fingerprint,
count: state.count,
firstSeen: state.firstSeen,
lastSeen: state.lastSeen,
peakRate: state.peakRate,
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This conditional logic introduces a memory leak in AlertLogger. The AlertLogger maintains an alertMeta map that is only cleaned up when handleResolution is called. By filtering out low-count resolutions here, the corresponding metadata for sporadic or one-off alerts will never be removed from memory, causing the map to grow indefinitely over time.

To fix this, checkResolutions should return all resolved fingerprints so the logger can prune its metadata, while the logger itself should decide whether to actually send a notification based on the count (e.g., by adding a sustained flag to the ResolvedEntry interface).

Comment thread src/core/alert-logger.ts
Comment on lines +236 to +238
if (error != null && typeof error === 'object' && !(error instanceof Error)) {
return [undefined, error]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of the overload ignores the options argument if the error argument is an object. This means calls like logger.error("title", { description: "..." }, { tags: ["foo"] }) will lose the tags and other options provided in the third argument.

Suggested change
if (error != null && typeof error === 'object' && !(error instanceof Error)) {
return [undefined, error]
}
if (error != null && typeof error === 'object' && !(error instanceof Error)) {
return [undefined, { ...error, ...options }]
}

Comment on lines +21 to +25
if (error != null && typeof error === 'object' && !(error instanceof Error)) {
this.logger.error(title, this.mergeContext(error))
} else {
this.logger.error(title, error, this.mergeContext(options))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This overload implementation ignores the options argument when error is an object. Consider merging the options to ensure no metadata (like tags or custom fields) is lost when using the two-argument style alongside global context.

Suggested change
if (error != null && typeof error === 'object' && !(error instanceof Error)) {
this.logger.error(title, this.mergeContext(error))
} else {
this.logger.error(title, error, this.mergeContext(options))
}
if (error != null && typeof error === 'object' && !(error instanceof Error)) {
this.logger.error(title, this.mergeContext({ ...error, ...options }))
} else {
this.logger.error(title, error, this.mergeContext(options))
}

Comment on lines +29 to +33
if (error != null && typeof error === 'object' && !(error instanceof Error)) {
this.logger.critical(title, this.mergeContext(error))
} else {
this.logger.critical(title, error, this.mergeContext(options))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the error method, this implementation ignores the options argument when error is an object. Merging them ensures consistency and prevents data loss.

Suggested change
if (error != null && typeof error === 'object' && !(error instanceof Error)) {
this.logger.critical(title, this.mergeContext(error))
} else {
this.logger.critical(title, error, this.mergeContext(options))
}
if (error != null && typeof error === 'object' && !(error instanceof Error)) {
this.logger.critical(title, this.mergeContext({ ...error, ...options }))
} else {
this.logger.critical(title, error, this.mergeContext(options))
}

const path = request?.url ?? 'UNKNOWN'
const ip = request?.ip ?? 'UNKNOWN'

const title = `${method} ${path}`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the raw request path in the alert title can significantly degrade deduplication quality if the path contains dynamic parameters (e.g., /users/123). Since the path is already included in the fields metadata (line 34), consider using a more stable title (like the exception name or a generic route pattern) to ensure similar errors are grouped correctly by the aggregator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build out @iqai/alert-logger — health policy, CJS compat, description field, fingerprint dedup

1 participant