Skip to content

Commit c6cd386

Browse files
committed
add sentry error reporting.
1 parent 2ea1bfe commit c6cd386

File tree

8 files changed

+919
-0
lines changed

8 files changed

+919
-0
lines changed

docs/SentryIntegration.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Sentry.io Integration
2+
3+
MyCoder CLI now includes integration with Sentry.io for error tracking and monitoring.
4+
5+
## How It Works
6+
7+
The Sentry.io integration is initialized at the start of the CLI application to capture any errors that occur during execution. This helps us identify and fix issues more quickly.
8+
9+
## Installation
10+
11+
The Sentry Node SDK is included as a dependency in the CLI package:
12+
13+
```bash
14+
npm install @sentry/node --save
15+
```
16+
17+
## Configuration
18+
19+
By default, Sentry is:
20+
- Enabled in production environments
21+
- Disabled in development environments (unless explicitly enabled)
22+
- Configured to capture 100% of transactions
23+
24+
### Environment Variables
25+
26+
You can control Sentry behavior with the following environment variables:
27+
28+
- `NODE_ENV`: Set to "production" to enable Sentry (default behavior)
29+
- `ENABLE_SENTRY`: Set to "true" to explicitly enable Sentry in any environment
30+
- `SENTRY_DSN`: Override the default Sentry DSN (optional)
31+
32+
### Command Line Options
33+
34+
You can also configure Sentry through command-line options:
35+
36+
```bash
37+
# Use a custom Sentry DSN
38+
mycoder --sentryDsn="https://[email protected]/project"
39+
```
40+
41+
## Version Tracking
42+
43+
All errors reported to Sentry include the package version information in the format `[email protected]`. This allows us to trace errors to specific releases and understand which versions of the software are affected by particular issues.
44+
45+
## Implementation Details
46+
47+
The Sentry SDK is initialized as early as possible in the application lifecycle:
48+
49+
```javascript
50+
import * as Sentry from '@sentry/node';
51+
import { createRequire } from 'module';
52+
53+
// Initialize Sentry with version information
54+
Sentry.init({
55+
dsn: 'https://2873d2518b60f645918b6a08ae5e69ae@o4508898407481344.ingest.us.sentry.io/4508898476687360',
56+
tracesSampleRate: 1.0,
57+
environment: process.env.NODE_ENV || 'development',
58+
release: `mycoder@${packageVersion}`,
59+
enabled: process.env.NODE_ENV !== 'development' || process.env.ENABLE_SENTRY === 'true',
60+
});
61+
62+
// Capture errors
63+
try {
64+
// Application code
65+
} catch (error) {
66+
Sentry.captureException(error);
67+
}
68+
```
69+
70+
## Testing Sentry Integration
71+
72+
A hidden command is available to test the Sentry integration:
73+
74+
```bash
75+
mycoder test-sentry
76+
```
77+
78+
This command will:
79+
1. Generate a test error that includes the package version
80+
2. Report it to Sentry.io
81+
3. Output the result to the console
82+
83+
Note: In development environments, you may need to set `ENABLE_SENTRY=true` for the test to actually send data to Sentry.
84+
85+
## Privacy
86+
87+
Error reports sent to Sentry include:
88+
- Stack traces
89+
- Error messages
90+
- Environment information
91+
- Release version
92+
93+
Personal or sensitive information is not intentionally collected. If you discover any privacy concerns with the Sentry integration, please report them to the project maintainers.

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"author": "Ben Houston",
4848
"license": "MIT",
4949
"dependencies": {
50+
"@sentry/node": "^9.3.0",
5051
"chalk": "^5",
5152
"dotenv": "^16",
5253
"mycoder-agent": "workspace:*",

packages/cli/src/commands/$default.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { TokenTracker } from 'mycoder-agent/dist/core/tokens.js';
1515

1616
import { SharedOptions } from '../options.js';
17+
import { initSentry, captureException } from '../sentry/index.js';
1718
import { hasUserConsented, saveUserConsent } from '../settings/settings.js';
1819
import { nameToLogIndex } from '../utils/nameToLogIndex.js';
1920
import { checkForUpdates, getPackageInfo } from '../utils/versionCheck.js';
@@ -34,6 +35,11 @@ export const command: CommandModule<SharedOptions, DefaultArgs> = {
3435
}) as Argv<DefaultArgs>;
3536
},
3637
handler: async (argv) => {
38+
// Initialize Sentry with custom DSN if provided
39+
if (argv.sentryDsn) {
40+
initSentry(argv.sentryDsn);
41+
}
42+
3743
const logger = new Logger({
3844
name: 'Default',
3945
logLevel: nameToLogIndex(argv.logLevel),
@@ -143,6 +149,8 @@ export const command: CommandModule<SharedOptions, DefaultArgs> = {
143149
logger.info('\n=== Result ===\n', output);
144150
} catch (error) {
145151
logger.error('An error occurred:', error);
152+
// Capture the error with Sentry
153+
captureException(error);
146154
}
147155

148156
logger.log(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import chalk from 'chalk';
2+
import { Logger } from 'mycoder-agent';
3+
4+
import { SharedOptions } from '../options.js';
5+
import { testSentryErrorReporting } from '../sentry/index.js';
6+
import { nameToLogIndex } from '../utils/nameToLogIndex.js';
7+
8+
import type { CommandModule } from 'yargs';
9+
10+
type TestSentryArgs = SharedOptions;
11+
12+
export const command: CommandModule<SharedOptions, TestSentryArgs> = {
13+
command: 'test-sentry',
14+
describe: false, // Hide from help output
15+
handler: async (argv) => {
16+
const logger = new Logger({
17+
name: 'TestSentry',
18+
logLevel: nameToLogIndex(argv.logLevel),
19+
});
20+
21+
logger.info(chalk.yellow('Testing Sentry.io error reporting...'));
22+
23+
try {
24+
// Test error reporting
25+
const error = testSentryErrorReporting();
26+
27+
logger.info(
28+
chalk.green('Successfully sent test error to Sentry.io:'),
29+
chalk.red(error instanceof Error ? error.message : String(error)),
30+
);
31+
32+
logger.info(
33+
chalk.blue('Note:'),
34+
'If this is a development environment, the error may not be sent to Sentry unless ENABLE_SENTRY=true is set.',
35+
);
36+
} catch (error) {
37+
logger.error('Failed to test Sentry error reporting:', error);
38+
}
39+
},
40+
};

packages/cli/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import yargs from 'yargs';
88
import { hideBin } from 'yargs/helpers';
99
import { fileCommands } from 'yargs-file-commands';
1010

11+
// Initialize Sentry as early as possible
12+
import { initSentry, captureException } from './sentry/index.js';
13+
initSentry();
14+
1115
import { sharedOptions } from './options.js';
1216

1317
import type { PackageJson } from 'type-fest';
@@ -46,5 +50,7 @@ const main = async () => {
4650

4751
await main().catch((error) => {
4852
console.error(error);
53+
// Capture the error with Sentry
54+
captureException(error);
4955
process.exit(1);
5056
});

packages/cli/src/options.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type SharedOptions = {
66
readonly headless?: boolean;
77
readonly userSession?: boolean;
88
readonly pageFilter?: 'simple' | 'none' | 'readability';
9+
readonly sentryDsn?: string;
910
};
1011

1112
export const sharedOptions = {
@@ -49,4 +50,9 @@ export const sharedOptions = {
4950
default: 'none',
5051
choices: ['simple', 'none', 'readability'],
5152
} as const,
53+
sentryDsn: {
54+
type: 'string',
55+
description: 'Custom Sentry DSN for error tracking',
56+
hidden: true,
57+
} as const,
5258
};

packages/cli/src/sentry/index.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import * as Sentry from '@sentry/node';
2+
import { createRequire } from 'module';
3+
4+
/**
5+
* Initialize Sentry for error tracking
6+
* @param dsn Optional custom DSN to use instead of the default
7+
*/
8+
export function initSentry(dsn?: string) {
9+
// Get package version using createRequire for ES modules
10+
let packageVersion = 'unknown';
11+
try {
12+
const require = createRequire(import.meta.url);
13+
packageVersion = process.env.npm_package_version || require('../../package.json').version;
14+
} catch (error) {
15+
console.warn('Could not determine package version for Sentry:', error);
16+
}
17+
18+
// Initialize Sentry
19+
Sentry.init({
20+
// Default DSN from Sentry.io integration instructions
21+
dsn: dsn || 'https://2873d2518b60f645918b6a08ae5e69ae@o4508898407481344.ingest.us.sentry.io/4508898476687360',
22+
23+
// No profiling integration as requested
24+
25+
// Capture 100% of the transactions
26+
tracesSampleRate: 1.0,
27+
28+
// Set environment based on NODE_ENV
29+
environment: process.env.NODE_ENV || 'development',
30+
31+
// Add release version from package.json
32+
release: `mycoder@${packageVersion}`,
33+
34+
// Don't capture errors in development mode unless explicitly enabled
35+
enabled: process.env.NODE_ENV !== 'development' || process.env.ENABLE_SENTRY === 'true',
36+
});
37+
38+
// Log confirmation that Sentry is initialized with version info
39+
if (process.env.NODE_ENV !== 'test') {
40+
console.log(`Sentry initialized for mycoder@${packageVersion}`);
41+
}
42+
}
43+
44+
/**
45+
* Capture an exception with Sentry
46+
* @param error Error to capture
47+
*/
48+
export function captureException(error: Error | unknown) {
49+
Sentry.captureException(error);
50+
}
51+
52+
/**
53+
* Capture a message with Sentry
54+
* @param message Message to capture
55+
* @param level Optional severity level
56+
*/
57+
export function captureMessage(message: string, level?: Sentry.SeverityLevel) {
58+
Sentry.captureMessage(message, level);
59+
}
60+
61+
/**
62+
* Test Sentry error reporting by throwing a test error
63+
*/
64+
export function testSentryErrorReporting() {
65+
try {
66+
// Get package version for the error message
67+
let packageVersion = 'unknown';
68+
try {
69+
const require = createRequire(import.meta.url);
70+
packageVersion = process.env.npm_package_version || require('../../package.json').version;
71+
} catch (error) {
72+
console.warn('Could not determine package version for test error:', error);
73+
}
74+
75+
// Throw a test error with version information
76+
throw new Error(`Test error for Sentry.io integration from mycoder@${packageVersion}`);
77+
} catch (error) {
78+
// Capture the error with Sentry
79+
Sentry.captureException(error);
80+
81+
// Log a message about the test
82+
console.log('Test error sent to Sentry.io');
83+
84+
// Return the error for inspection
85+
return error;
86+
}
87+
}

0 commit comments

Comments
 (0)