@monocle.sh/adonisjs-agent 1.0.0-beta.10
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.
- package/README.md +133 -0
- package/dist/configure.d.mts +6 -0
- package/dist/configure.mjs +61 -0
- package/dist/decorators.d.mts +83 -0
- package/dist/decorators.mjs +110 -0
- package/dist/helpers.d.mts +49 -0
- package/dist/helpers.mjs +72 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +7 -0
- package/dist/init.d.mts +8 -0
- package/dist/init.mjs +123 -0
- package/dist/monocle_middleware.d.mts +2 -0
- package/dist/monocle_middleware.mjs +7 -0
- package/dist/monocle_provider.d.mts +19 -0
- package/dist/monocle_provider.mjs +66 -0
- package/dist/src/cli_instrumentation.mjs +127 -0
- package/dist/src/context_lines.mjs +269 -0
- package/dist/src/define_config.d.mts +11 -0
- package/dist/src/define_config.mjs +72 -0
- package/dist/src/error_serializer.mjs +77 -0
- package/dist/src/exception_reporter.mjs +52 -0
- package/dist/src/host_metrics.mjs +27 -0
- package/dist/src/instrumentations/mail/instrumentation.mjs +158 -0
- package/dist/src/instrumentations/mail/types.mjs +21 -0
- package/dist/src/monocle.d.mts +48 -0
- package/dist/src/monocle.mjs +110 -0
- package/dist/src/types.d.mts +130 -0
- package/dist/stubs/config.stub +13 -0
- package/dist/stubs/main.mjs +8 -0
- package/dist/stubs/main.ts +4 -0
- package/dist/stubs/otel.stub +12 -0
- package/dist/types.d.mts +4 -0
- package/dist/types.mjs +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# @monocle.sh/adonisjs-agent
|
|
2
|
+
|
|
3
|
+
Monocle agent for AdonisJS - sends telemetry to Monocle cloud.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @monocle.sh/adonisjs-agent
|
|
9
|
+
# or
|
|
10
|
+
yarn add @monocle.sh/adonisjs-agent
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @monocle.sh/adonisjs-agent
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
Run the configure command to set up the agent automatically:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
node ace configure @monocle.sh/adonisjs-agent
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This will:
|
|
24
|
+
|
|
25
|
+
1. Create `config/monocle.ts` configuration file
|
|
26
|
+
2. Create `otel.ts` initialization file at project root
|
|
27
|
+
3. Add the otel.ts import as the first import in `bin/server.ts`
|
|
28
|
+
4. Register the Monocle provider in `adonisrc.ts`
|
|
29
|
+
5. Register the Monocle middleware as the first router middleware
|
|
30
|
+
6. Add required environment variables to `.env` and `start/env.ts`
|
|
31
|
+
|
|
32
|
+
After configuration, add your API key to `.env`:
|
|
33
|
+
|
|
34
|
+
```env
|
|
35
|
+
MONOCLE_API_KEY=mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## User Identification
|
|
39
|
+
|
|
40
|
+
To associate telemetry data with authenticated users, add `Monocle.setUser()` in your authentication middleware:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import type { NextFn } from '@adonisjs/core/types/http'
|
|
44
|
+
import type { HttpContext } from '@adonisjs/core/http'
|
|
45
|
+
import { Monocle } from '@monocle.sh/adonisjs-agent'
|
|
46
|
+
|
|
47
|
+
export default class SilentAuthMiddleware {
|
|
48
|
+
async handle(ctx: HttpContext, next: NextFn) {
|
|
49
|
+
await ctx.auth.check()
|
|
50
|
+
|
|
51
|
+
if (ctx.auth.user) Monocle.setUser(ctx.auth.user)
|
|
52
|
+
|
|
53
|
+
return next()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Exception Tracking
|
|
59
|
+
|
|
60
|
+
Exceptions are automatically recorded in spans when thrown during a request. The agent hooks into AdonisJS's `ExceptionHandler.report()` method to capture exceptions with their stack traces.
|
|
61
|
+
|
|
62
|
+
If you want to manually capture exceptions in custom code:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { Monocle } from '@monocle.sh/adonisjs-agent'
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// your code
|
|
69
|
+
} catch (error) {
|
|
70
|
+
Monocle.captureException(error, {
|
|
71
|
+
user: { id: '123', email: 'user@example.com' },
|
|
72
|
+
tags: { component: 'payment' },
|
|
73
|
+
extra: { orderId: 456 },
|
|
74
|
+
})
|
|
75
|
+
throw error
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration Options
|
|
80
|
+
|
|
81
|
+
The `config/monocle.ts` file supports the following options:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { defineConfig } from '@monocle.sh/adonisjs-agent'
|
|
85
|
+
import env from '#start/env'
|
|
86
|
+
|
|
87
|
+
export default defineConfig({
|
|
88
|
+
// Optional: Your Monocle API key
|
|
89
|
+
apiKey: env.get('MONOCLE_API_KEY'),
|
|
90
|
+
|
|
91
|
+
// Optional: Custom ingestion endpoint (for development)
|
|
92
|
+
// endpoint: 'http://localhost:4318',
|
|
93
|
+
|
|
94
|
+
// Service identification
|
|
95
|
+
serviceName: env.get('APP_NAME'),
|
|
96
|
+
serviceVersion: env.get('APP_VERSION'),
|
|
97
|
+
environment: env.get('APP_ENV'),
|
|
98
|
+
|
|
99
|
+
// Host metrics (CPU, Memory, Network, etc.)
|
|
100
|
+
// Set to false to disable
|
|
101
|
+
hostMetrics: {
|
|
102
|
+
enabled: true,
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// CLI command tracing
|
|
106
|
+
cli: {
|
|
107
|
+
enabled: false,
|
|
108
|
+
exclude: ['make:*', 'generate:*', 'queue:work', 'queue:listen'],
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// Trace batching configuration
|
|
112
|
+
batch: {
|
|
113
|
+
maxExportBatchSize: 512,
|
|
114
|
+
scheduledDelayMillis: 5000,
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Enable gzip compression (default: true)
|
|
118
|
+
compression: true,
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Environment Variables
|
|
123
|
+
|
|
124
|
+
| Variable | Description | Required |
|
|
125
|
+
| ----------------- | ------------------------------------------------------ | -------- |
|
|
126
|
+
| `MONOCLE_API_KEY` | Your Monocle API key | No |
|
|
127
|
+
| `APP_NAME` | Service name for identification | Yes |
|
|
128
|
+
| `APP_VERSION` | Service version (e.g., git sha, semver) | Yes |
|
|
129
|
+
| `APP_ENV` | Environment: `development`, `staging`, or `production` | Yes |
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
ISC
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { stubsRoot } from "./stubs/main.mjs";
|
|
2
|
+
|
|
3
|
+
//#region configure.ts
|
|
4
|
+
async function configure(command) {
|
|
5
|
+
const codemods = await command.createCodemods();
|
|
6
|
+
/**
|
|
7
|
+
* Publish config/monocle.ts
|
|
8
|
+
*/
|
|
9
|
+
await codemods.makeUsingStub(stubsRoot, "config.stub", {});
|
|
10
|
+
/**
|
|
11
|
+
* Publish otel.ts at project root
|
|
12
|
+
*/
|
|
13
|
+
await codemods.makeUsingStub(stubsRoot, "otel.stub", {});
|
|
14
|
+
const serverFile = (await codemods.getTsMorphProject())?.getSourceFile(command.app.makePath("bin/server.ts"));
|
|
15
|
+
if (serverFile) {
|
|
16
|
+
const insertIndex = serverFile.getImportDeclarations()[0]?.getChildIndex() ?? 0;
|
|
17
|
+
serverFile.insertStatements(insertIndex, [
|
|
18
|
+
"/**",
|
|
19
|
+
" * OpenTelemetry initialization - MUST be the first import",
|
|
20
|
+
" * @see https://opentelemetry.io/docs/languages/js/getting-started/nodejs/",
|
|
21
|
+
" */",
|
|
22
|
+
`import '../otel.js'`,
|
|
23
|
+
""
|
|
24
|
+
]);
|
|
25
|
+
await serverFile.save();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register the provider in adonisrc.ts
|
|
29
|
+
*/
|
|
30
|
+
await codemods.updateRcFile((rcFile) => {
|
|
31
|
+
rcFile.addProvider("@monocle.sh/adonisjs-agent/monocle_provider");
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Register the middleware as FIRST router middleware
|
|
35
|
+
*/
|
|
36
|
+
await codemods.registerMiddleware("router", [{
|
|
37
|
+
path: "@monocle.sh/adonisjs-agent/monocle_middleware",
|
|
38
|
+
position: "before"
|
|
39
|
+
}]);
|
|
40
|
+
/**
|
|
41
|
+
* Define environment variables in .env
|
|
42
|
+
*/
|
|
43
|
+
await codemods.defineEnvVariables({
|
|
44
|
+
APP_NAME: command.app.appName,
|
|
45
|
+
APP_VERSION: "0.0.1",
|
|
46
|
+
APP_ENV: "development",
|
|
47
|
+
MONOCLE_API_KEY: ""
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Define environment validations in start/env.ts
|
|
51
|
+
*/
|
|
52
|
+
await codemods.defineEnvValidations({ variables: {
|
|
53
|
+
APP_NAME: "Env.schema.string()",
|
|
54
|
+
APP_VERSION: "Env.schema.string()",
|
|
55
|
+
APP_ENV: `Env.schema.enum(['development', 'staging', 'production'] as const)`,
|
|
56
|
+
MONOCLE_API_KEY: "Env.schema.string.optional()"
|
|
57
|
+
} });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { configure };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Attributes } from "@opentelemetry/api";
|
|
2
|
+
|
|
3
|
+
//#region src/decorators.d.ts
|
|
4
|
+
interface SpanOptions {
|
|
5
|
+
name?: string;
|
|
6
|
+
attributes?: Attributes;
|
|
7
|
+
}
|
|
8
|
+
interface SpanAllOptions {
|
|
9
|
+
prefix?: string;
|
|
10
|
+
attributes?: Attributes;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Decorator to create a span around a method.
|
|
14
|
+
*
|
|
15
|
+
* Unlike the @adonisjs/otel version, this uses Monocle's ExceptionReporter
|
|
16
|
+
* to capture exceptions with source context lines.
|
|
17
|
+
*
|
|
18
|
+
* Automatically handles:
|
|
19
|
+
* - Creating and closing the span
|
|
20
|
+
* - Capturing exceptions with context frames
|
|
21
|
+
* - Setting error status
|
|
22
|
+
* - Async/await support
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { span } from '@monocle.sh/adonisjs-agent'
|
|
27
|
+
*
|
|
28
|
+
* class UserService {
|
|
29
|
+
* @span()
|
|
30
|
+
* async findById(id: string) {
|
|
31
|
+
* // Span name: "UserService.findById"
|
|
32
|
+
* return db.users.find(id)
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* @span({ name: 'user.create', attributes: { operation: 'create' } })
|
|
36
|
+
* async create(data: UserData) {
|
|
37
|
+
* return db.users.create(data)
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function span(options?: SpanOptions): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
43
|
+
/**
|
|
44
|
+
* Decorator to create spans around all methods of a class.
|
|
45
|
+
*
|
|
46
|
+
* Unlike the @adonisjs/otel version, this uses Monocle's ExceptionReporter
|
|
47
|
+
* to capture exceptions with source context lines.
|
|
48
|
+
*
|
|
49
|
+
* Automatically handles:
|
|
50
|
+
* - Creating and closing spans for each method
|
|
51
|
+
* - Capturing exceptions with context frames
|
|
52
|
+
* - Setting error status
|
|
53
|
+
* - Async/await support
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { spanAll } from '@monocle.sh/adonisjs-agent'
|
|
58
|
+
*
|
|
59
|
+
* @spanAll()
|
|
60
|
+
* class OrderService {
|
|
61
|
+
* async create(data: OrderData) {
|
|
62
|
+
* // Span name: "OrderService.create"
|
|
63
|
+
* return db.orders.create(data)
|
|
64
|
+
* }
|
|
65
|
+
*
|
|
66
|
+
* async findById(id: string) {
|
|
67
|
+
* // Span name: "OrderService.findById"
|
|
68
|
+
* return db.orders.find(id)
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* @spanAll({ prefix: 'order' })
|
|
73
|
+
* class OrderService {
|
|
74
|
+
* async create(data: OrderData) {
|
|
75
|
+
* // Span name: "order.create"
|
|
76
|
+
* return db.orders.create(data)
|
|
77
|
+
* }
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function spanAll(options?: SpanAllOptions): <T extends new (...args: any[]) => any>(constructor: T) => T;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { SpanAllOptions, SpanOptions, span, spanAll };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { record } from "./helpers.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/decorators.ts
|
|
4
|
+
/**
|
|
5
|
+
* Wrap a method to create a span around its execution.
|
|
6
|
+
*/
|
|
7
|
+
function wrapMethod(target, propertyKey, descriptor, options) {
|
|
8
|
+
const originalMethod = descriptor.value;
|
|
9
|
+
const className = target.constructor.name;
|
|
10
|
+
descriptor.value = function(...args) {
|
|
11
|
+
return record(options?.name ?? `${className}.${propertyKey}`, (activeSpan) => {
|
|
12
|
+
if (options?.attributes) activeSpan.setAttributes(options.attributes);
|
|
13
|
+
return originalMethod.apply(this, args);
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
return descriptor;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Decorator to create a span around a method.
|
|
20
|
+
*
|
|
21
|
+
* Unlike the @adonisjs/otel version, this uses Monocle's ExceptionReporter
|
|
22
|
+
* to capture exceptions with source context lines.
|
|
23
|
+
*
|
|
24
|
+
* Automatically handles:
|
|
25
|
+
* - Creating and closing the span
|
|
26
|
+
* - Capturing exceptions with context frames
|
|
27
|
+
* - Setting error status
|
|
28
|
+
* - Async/await support
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { span } from '@monocle.sh/adonisjs-agent'
|
|
33
|
+
*
|
|
34
|
+
* class UserService {
|
|
35
|
+
* @span()
|
|
36
|
+
* async findById(id: string) {
|
|
37
|
+
* // Span name: "UserService.findById"
|
|
38
|
+
* return db.users.find(id)
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* @span({ name: 'user.create', attributes: { operation: 'create' } })
|
|
42
|
+
* async create(data: UserData) {
|
|
43
|
+
* return db.users.create(data)
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function span(options) {
|
|
49
|
+
return function(target, propertyKey, descriptor) {
|
|
50
|
+
return wrapMethod(target, propertyKey, descriptor, options);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Decorator to create spans around all methods of a class.
|
|
55
|
+
*
|
|
56
|
+
* Unlike the @adonisjs/otel version, this uses Monocle's ExceptionReporter
|
|
57
|
+
* to capture exceptions with source context lines.
|
|
58
|
+
*
|
|
59
|
+
* Automatically handles:
|
|
60
|
+
* - Creating and closing spans for each method
|
|
61
|
+
* - Capturing exceptions with context frames
|
|
62
|
+
* - Setting error status
|
|
63
|
+
* - Async/await support
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { spanAll } from '@monocle.sh/adonisjs-agent'
|
|
68
|
+
*
|
|
69
|
+
* @spanAll()
|
|
70
|
+
* class OrderService {
|
|
71
|
+
* async create(data: OrderData) {
|
|
72
|
+
* // Span name: "OrderService.create"
|
|
73
|
+
* return db.orders.create(data)
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* async findById(id: string) {
|
|
77
|
+
* // Span name: "OrderService.findById"
|
|
78
|
+
* return db.orders.find(id)
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
*
|
|
82
|
+
* @spanAll({ prefix: 'order' })
|
|
83
|
+
* class OrderService {
|
|
84
|
+
* async create(data: OrderData) {
|
|
85
|
+
* // Span name: "order.create"
|
|
86
|
+
* return db.orders.create(data)
|
|
87
|
+
* }
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
function spanAll(options) {
|
|
92
|
+
return function(constructor) {
|
|
93
|
+
const prototype = constructor.prototype;
|
|
94
|
+
const propertyNames = Object.getOwnPropertyNames(prototype);
|
|
95
|
+
for (const propertyName of propertyNames) {
|
|
96
|
+
if (propertyName === "constructor") continue;
|
|
97
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, propertyName);
|
|
98
|
+
if (!descriptor || typeof descriptor.value !== "function") continue;
|
|
99
|
+
const wrappedDescriptor = wrapMethod(prototype, propertyName, descriptor, {
|
|
100
|
+
name: options?.prefix ? `${options.prefix}.${propertyName}` : void 0,
|
|
101
|
+
attributes: options?.attributes
|
|
102
|
+
});
|
|
103
|
+
Object.defineProperty(prototype, propertyName, wrappedDescriptor);
|
|
104
|
+
}
|
|
105
|
+
return constructor;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//#endregion
|
|
110
|
+
export { span, spanAll };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Span } from "@opentelemetry/api";
|
|
2
|
+
import { extractTraceContext, getCurrentSpan, injectTraceContext, otelLoggingPreset, recordEvent, setAttributes } from "@adonisjs/otel/helpers";
|
|
3
|
+
|
|
4
|
+
//#region src/helpers.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Handle an error by reporting it with context frames extraction.
|
|
7
|
+
*
|
|
8
|
+
* Unlike the @adonisjs/otel version, this uses Monocle's ExceptionReporter
|
|
9
|
+
* to extract source context lines from the stack trace.
|
|
10
|
+
*/
|
|
11
|
+
declare function handleError(span: Span, error: Error): Promise<never>;
|
|
12
|
+
type RecordCallback<T> = (span: Span) => T | Promise<T>;
|
|
13
|
+
/**
|
|
14
|
+
* Record a code section as a span in your traces.
|
|
15
|
+
*
|
|
16
|
+
* Unlike the @adonisjs/otel version, this uses Monocle's ExceptionReporter
|
|
17
|
+
* to capture exceptions with source context lines (pre/post context around
|
|
18
|
+
* the error location).
|
|
19
|
+
*
|
|
20
|
+
* Always returns a Promise to ensure context lines are properly captured
|
|
21
|
+
* even for synchronous errors.
|
|
22
|
+
*
|
|
23
|
+
* Automatically handles:
|
|
24
|
+
* - Creating and closing the span
|
|
25
|
+
* - Capturing exceptions with context frames
|
|
26
|
+
* - Setting error status
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { record } from '@monocle.sh/adonisjs-agent'
|
|
31
|
+
*
|
|
32
|
+
* const result = await record('database.query', () => {
|
|
33
|
+
* return db.query('SELECT * FROM users')
|
|
34
|
+
* })
|
|
35
|
+
*
|
|
36
|
+
* const user = await record('user.fetch', async () => {
|
|
37
|
+
* return await userService.findById(id)
|
|
38
|
+
* })
|
|
39
|
+
*
|
|
40
|
+
* // With attributes
|
|
41
|
+
* const order = await record('order.process', async (span) => {
|
|
42
|
+
* span.setAttributes({ 'order.id': orderId })
|
|
43
|
+
* return await processOrder(orderId)
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function record<T>(name: string, callback: RecordCallback<T>): Promise<T>;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes };
|
package/dist/helpers.mjs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ExceptionReporter } from "./src/exception_reporter.mjs";
|
|
2
|
+
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
3
|
+
import { extractTraceContext, getCurrentSpan, injectTraceContext, otelLoggingPreset, recordEvent, setAttributes } from "@adonisjs/otel/helpers";
|
|
4
|
+
|
|
5
|
+
//#region src/helpers.ts
|
|
6
|
+
/**
|
|
7
|
+
* Handle an error by reporting it with context frames extraction.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the @adonisjs/otel version, this uses Monocle's ExceptionReporter
|
|
10
|
+
* to extract source context lines from the stack trace.
|
|
11
|
+
*/
|
|
12
|
+
async function handleError(span, error) {
|
|
13
|
+
await new ExceptionReporter().report({
|
|
14
|
+
span,
|
|
15
|
+
error,
|
|
16
|
+
shouldReport: true
|
|
17
|
+
});
|
|
18
|
+
span.setStatus({
|
|
19
|
+
code: SpanStatusCode.ERROR,
|
|
20
|
+
message: error?.message
|
|
21
|
+
});
|
|
22
|
+
span.end();
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Record a code section as a span in your traces.
|
|
27
|
+
*
|
|
28
|
+
* Unlike the @adonisjs/otel version, this uses Monocle's ExceptionReporter
|
|
29
|
+
* to capture exceptions with source context lines (pre/post context around
|
|
30
|
+
* the error location).
|
|
31
|
+
*
|
|
32
|
+
* Always returns a Promise to ensure context lines are properly captured
|
|
33
|
+
* even for synchronous errors.
|
|
34
|
+
*
|
|
35
|
+
* Automatically handles:
|
|
36
|
+
* - Creating and closing the span
|
|
37
|
+
* - Capturing exceptions with context frames
|
|
38
|
+
* - Setting error status
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { record } from '@monocle.sh/adonisjs-agent'
|
|
43
|
+
*
|
|
44
|
+
* const result = await record('database.query', () => {
|
|
45
|
+
* return db.query('SELECT * FROM users')
|
|
46
|
+
* })
|
|
47
|
+
*
|
|
48
|
+
* const user = await record('user.fetch', async () => {
|
|
49
|
+
* return await userService.findById(id)
|
|
50
|
+
* })
|
|
51
|
+
*
|
|
52
|
+
* // With attributes
|
|
53
|
+
* const order = await record('order.process', async (span) => {
|
|
54
|
+
* span.setAttributes({ 'order.id': orderId })
|
|
55
|
+
* return await processOrder(orderId)
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
async function record(name, callback) {
|
|
60
|
+
return trace.getTracer("@monocle.sh/agent").startActiveSpan(name, async (span) => {
|
|
61
|
+
try {
|
|
62
|
+
const result = await callback(span);
|
|
63
|
+
span.end();
|
|
64
|
+
return result;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return handleError(span, error);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BatchConfig, CliTracingConfig, HostMetricsConfig, MonocleConfig } from "./src/types.mjs";
|
|
2
|
+
import { defineConfig } from "./src/define_config.mjs";
|
|
3
|
+
import { Monocle } from "./src/monocle.mjs";
|
|
4
|
+
import { configure } from "./configure.mjs";
|
|
5
|
+
import { extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes } from "./helpers.mjs";
|
|
6
|
+
import { SpanAllOptions, SpanOptions, span, spanAll } from "./decorators.mjs";
|
|
7
|
+
export { type BatchConfig, type CliTracingConfig, type HostMetricsConfig, Monocle, type MonocleConfig, type SpanAllOptions, type SpanOptions, configure, defineConfig, extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes, span, spanAll };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineConfig } from "./src/define_config.mjs";
|
|
2
|
+
import { Monocle } from "./src/monocle.mjs";
|
|
3
|
+
import { configure } from "./configure.mjs";
|
|
4
|
+
import { extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes } from "./helpers.mjs";
|
|
5
|
+
import { span, spanAll } from "./decorators.mjs";
|
|
6
|
+
|
|
7
|
+
export { Monocle, configure, defineConfig, extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes, span, spanAll };
|
package/dist/init.d.mts
ADDED
package/dist/init.mjs
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { register } from "node:module";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createAddHookMessageChannel } from "import-in-the-middle";
|
|
5
|
+
|
|
6
|
+
//#region src/init.ts
|
|
7
|
+
/**
|
|
8
|
+
* Also stolen and tweaked from @adonisjs/otel in order to use our own config file.
|
|
9
|
+
* Need to be able to remove this file in the future.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_BATCH_CONFIG = {
|
|
12
|
+
maxExportBatchSize: 512,
|
|
13
|
+
scheduledDelayMillis: 5e3,
|
|
14
|
+
exportTimeoutMillis: 3e4,
|
|
15
|
+
maxQueueSize: 2048
|
|
16
|
+
};
|
|
17
|
+
async function loadConfig(path) {
|
|
18
|
+
return await import(pathToFileURL(path).href).then((mod) => mod.default || mod).catch((error) => {
|
|
19
|
+
throw new Error(`Failed to load Monocle config file at "${path}"`, { cause: error });
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function setupHooks() {
|
|
23
|
+
const { registerOptions, waitForAllMessagesAcknowledged } = createAddHookMessageChannel();
|
|
24
|
+
register("import-in-the-middle/hook.mjs", import.meta.url, registerOptions);
|
|
25
|
+
return waitForAllMessagesAcknowledged;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a metric reader for exporting metrics via OTLP
|
|
29
|
+
*/
|
|
30
|
+
async function createMetricReader(config) {
|
|
31
|
+
const { PeriodicExportingMetricReader } = await import("@opentelemetry/sdk-metrics");
|
|
32
|
+
const { OTLPMetricExporter } = await import("@opentelemetry/exporter-metrics-otlp-http");
|
|
33
|
+
return new PeriodicExportingMetricReader({
|
|
34
|
+
exporter: new OTLPMetricExporter({ compression: config.compression !== false ? "gzip" : void 0 }),
|
|
35
|
+
exportIntervalMillis: 6e4
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a BatchSpanProcessor for efficient trace export with compression
|
|
40
|
+
*/
|
|
41
|
+
async function createSpanProcessor(config) {
|
|
42
|
+
const { BatchSpanProcessor } = await import("@opentelemetry/sdk-trace-base");
|
|
43
|
+
const { OTLPTraceExporter } = await import("@opentelemetry/exporter-trace-otlp-http");
|
|
44
|
+
const compression = config.compression !== false ? "gzip" : void 0;
|
|
45
|
+
const batchConfig = {
|
|
46
|
+
...DEFAULT_BATCH_CONFIG,
|
|
47
|
+
...config.batch
|
|
48
|
+
};
|
|
49
|
+
return new BatchSpanProcessor(new OTLPTraceExporter({ compression }), {
|
|
50
|
+
maxExportBatchSize: batchConfig.maxExportBatchSize,
|
|
51
|
+
scheduledDelayMillis: batchConfig.scheduledDelayMillis,
|
|
52
|
+
exportTimeoutMillis: batchConfig.exportTimeoutMillis,
|
|
53
|
+
maxQueueSize: batchConfig.maxQueueSize
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async function init(dirname) {
|
|
57
|
+
const waitForAllMessagesAcknowledged = setupHooks();
|
|
58
|
+
const { OtelManager } = await import("@adonisjs/otel/manager");
|
|
59
|
+
const config = await loadConfig(join(dirname, "config/monocle.js"));
|
|
60
|
+
if (!config) return;
|
|
61
|
+
if (!OtelManager.isEnabled(config)) return;
|
|
62
|
+
const metricReader = await createMetricReader(config);
|
|
63
|
+
const spanProcessor = await createSpanProcessor(config);
|
|
64
|
+
const configWithProcessors = {
|
|
65
|
+
...config,
|
|
66
|
+
metricReader,
|
|
67
|
+
spanProcessors: [spanProcessor, ...config.spanProcessors || []]
|
|
68
|
+
};
|
|
69
|
+
const manager = OtelManager.create(configWithProcessors);
|
|
70
|
+
manager?.start();
|
|
71
|
+
const shutdown = async () => {
|
|
72
|
+
await manager?.shutdown().catch(() => {});
|
|
73
|
+
};
|
|
74
|
+
process.on("beforeExit", shutdown);
|
|
75
|
+
process.on("SIGINT", async () => {
|
|
76
|
+
await shutdown();
|
|
77
|
+
process.exit(0);
|
|
78
|
+
});
|
|
79
|
+
process.on("SIGTERM", async () => {
|
|
80
|
+
await shutdown();
|
|
81
|
+
process.exit(0);
|
|
82
|
+
});
|
|
83
|
+
const hostMetricsConfig = config.hostMetrics;
|
|
84
|
+
if (hostMetricsConfig !== false) {
|
|
85
|
+
const { startHostMetrics } = await import("./src/host_metrics.mjs");
|
|
86
|
+
startHostMetrics(hostMetricsConfig === void 0 ? {} : hostMetricsConfig);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* CLI Command Instrumentation
|
|
90
|
+
*
|
|
91
|
+
* When enabled, this patches AdonisJS Ace commands to create OTEL spans.
|
|
92
|
+
* The `dirname` parameter is passed so the instrumentation can resolve
|
|
93
|
+
* @adonisjs/core/ace from the app's node_modules (pnpm isolation).
|
|
94
|
+
*
|
|
95
|
+
* Prerequisites for this to work:
|
|
96
|
+
* 1. App's `bin/console.ts` must import `otel.ts` FIRST (before reflect-metadata)
|
|
97
|
+
* 2. Config must have `cli.enabled: true`
|
|
98
|
+
*
|
|
99
|
+
* @see ./cli_instrumentation.ts for implementation details
|
|
100
|
+
*/
|
|
101
|
+
const cliConfig = config.cli;
|
|
102
|
+
if (cliConfig !== false && cliConfig?.enabled) {
|
|
103
|
+
const { instrumentCliCommands } = await import("./src/cli_instrumentation.mjs");
|
|
104
|
+
await instrumentCliCommands(cliConfig, dirname);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Mail Instrumentation
|
|
108
|
+
*
|
|
109
|
+
* Automatically instruments @adonisjs/mail if installed.
|
|
110
|
+
* Creates spans for email send operations with metadata attributes.
|
|
111
|
+
*
|
|
112
|
+
* @see ./instrumentations/mail/instrumentation.ts for implementation details
|
|
113
|
+
*/
|
|
114
|
+
const mailConfig = config.mail;
|
|
115
|
+
if (mailConfig !== false) {
|
|
116
|
+
const { instrumentMail } = await import("./src/instrumentations/mail/instrumentation.mjs");
|
|
117
|
+
await instrumentMail(mailConfig ?? { enabled: true }, dirname);
|
|
118
|
+
}
|
|
119
|
+
await waitForAllMessagesAcknowledged();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
export { init };
|