@monocle-app/agent 1.0.0-beta.0 → 1.0.0-beta.2

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 CHANGED
@@ -1,3 +1,133 @@
1
1
  # @monocle-app/agent
2
2
 
3
- TODO.
3
+ Monocle agent for AdonisJS - sends telemetry to Monocle cloud.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @monocle-app/agent
9
+ # or
10
+ yarn add @monocle-app/agent
11
+ # or
12
+ pnpm add @monocle-app/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-app/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-app/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-app/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-app/agent'
85
+ import env from '#start/env'
86
+
87
+ export default defineConfig({
88
+ // Required: 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 | Yes |
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,7 @@
1
+ import ConfigureCommand from "@adonisjs/core/commands/configure";
2
+
3
+ //#region configure.d.ts
4
+
5
+ declare function configure(command: ConfigureCommand): Promise<void>;
6
+ //#endregion
7
+ export { configure };
@@ -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-app/agent/monocle_provider");
32
+ });
33
+ /**
34
+ * Register the middleware as FIRST router middleware
35
+ */
36
+ await codemods.registerMiddleware("router", [{
37
+ path: "@monocle-app/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()"
57
+ } });
58
+ }
59
+
60
+ //#endregion
61
+ export { configure };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { BatchConfig, CliTracingConfig, HostMetricsConfig, MonocleConfig } from "./src/types.mjs";
2
2
  import { defineConfig } from "./src/define_config.mjs";
3
3
  import { Monocle } from "./src/monocle.mjs";
4
- export { type BatchConfig, type CliTracingConfig, type HostMetricsConfig, Monocle, type MonocleConfig, defineConfig };
4
+ import { configure } from "./configure.mjs";
5
+ export { type BatchConfig, type CliTracingConfig, type HostMetricsConfig, Monocle, type MonocleConfig, configure, defineConfig };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { defineConfig } from "./src/define_config.mjs";
2
2
  import { Monocle } from "./src/monocle.mjs";
3
+ import { configure } from "./configure.mjs";
3
4
 
4
- export { Monocle, defineConfig };
5
+ export { Monocle, configure, defineConfig };
package/dist/init.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { register } from "node:module";
2
+ import { pathToFileURL } from "node:url";
2
3
  import { join } from "node:path";
3
4
  import { createAddHookMessageChannel } from "import-in-the-middle";
4
5
 
@@ -14,8 +15,8 @@ const DEFAULT_BATCH_CONFIG = {
14
15
  maxQueueSize: 2048
15
16
  };
16
17
  async function loadConfig(path) {
17
- return await import(path).then((mod) => mod.default || mod).catch((error) => {
18
- throw new Error(`Failed to load Monocle config file at "${path}": ${error.message}`);
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 });
19
20
  });
20
21
  }
21
22
  function setupHooks() {
@@ -52,10 +53,10 @@ async function createSpanProcessor(config) {
52
53
  maxQueueSize: batchConfig.maxQueueSize
53
54
  });
54
55
  }
55
- async function init(dirname) {
56
+ async function init(dirname$1) {
56
57
  const waitForAllMessagesAcknowledged = setupHooks();
57
58
  const { OtelManager } = await import("@adonisjs/otel/manager");
58
- const config = await loadConfig(join(dirname, "config/monocle.js"));
59
+ const config = await loadConfig(join(dirname$1, "config/monocle.js"));
59
60
  if (!config) return;
60
61
  if (!OtelManager.isEnabled(config)) return;
61
62
  const metricReader = await createMetricReader(config);
@@ -102,7 +103,7 @@ async function init(dirname) {
102
103
  const cliConfig = config.cli;
103
104
  if (cliConfig !== false && cliConfig?.enabled) {
104
105
  const { instrumentCliCommands } = await import("./src/cli_instrumentation.mjs");
105
- await instrumentCliCommands(cliConfig, dirname);
106
+ await instrumentCliCommands(cliConfig, dirname$1);
106
107
  }
107
108
  await waitForAllMessagesAcknowledged();
108
109
  }
@@ -1,6 +1,6 @@
1
1
  import { SpanStatusCode } from "@opentelemetry/api";
2
- import OtelMiddleware from "@adonisjs/otel/otel_middleware";
3
2
  import { getCurrentSpan } from "@adonisjs/otel/helpers";
3
+ import OtelMiddleware from "@adonisjs/otel/otel_middleware";
4
4
  import { OtelManager } from "@adonisjs/otel";
5
5
  import { ExceptionHandler } from "@adonisjs/core/http";
6
6
  import { configProvider } from "@adonisjs/core";
@@ -1,3 +1,5 @@
1
+ import { UserContextResult } from "@adonisjs/otel/types";
2
+
1
3
  //#region src/monocle.d.ts
2
4
  interface CaptureExceptionContext {
3
5
  user?: {
@@ -20,11 +22,7 @@ declare class Monocle {
20
22
  /**
21
23
  * Set user information on the current active span.
22
24
  */
23
- static setUser(user: {
24
- id: string;
25
- email?: string;
26
- name?: string;
27
- }): void;
25
+ static setUser(user: UserContextResult): void;
28
26
  }
29
27
  //#endregion
30
28
  export { Monocle };
@@ -1,4 +1,5 @@
1
1
  import { SpanStatusCode, trace } from "@opentelemetry/api";
2
+ import { setUser } from "@adonisjs/otel/helpers";
2
3
 
3
4
  //#region src/monocle.ts
4
5
  /**
@@ -30,11 +31,7 @@ var Monocle = class {
30
31
  * Set user information on the current active span.
31
32
  */
32
33
  static setUser(user) {
33
- const span = trace.getActiveSpan();
34
- if (!span) return;
35
- span.setAttribute("user.id", user.id);
36
- if (user.email) span.setAttribute("user.email", user.email);
37
- if (user.name) span.setAttribute("user.name", user.name);
34
+ setUser(user);
38
35
  }
39
36
  };
40
37
 
@@ -70,6 +70,12 @@ interface MonocleConfig extends Omit<OtelConfig, 'traceExporter' | 'metricExport
70
70
  * Format: mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
71
71
  */
72
72
  apiKey?: string;
73
+ /**
74
+ * Ignore OPTIONS (CORS preflight) requests.
75
+ * These requests are typically not useful for tracing.
76
+ * @default true
77
+ */
78
+ ignoreOptionsRequests?: boolean;
73
79
  /**
74
80
  * Monocle ingestion endpoint.
75
81
  * @default 'https://ingest.monocle.adonisjs.com'
@@ -0,0 +1,13 @@
1
+ {{{
2
+ exports({ to: app.configPath('monocle.ts') })
3
+ }}}
4
+ import { defineConfig } from '@monocle-app/agent'
5
+ import env from '#start/env'
6
+
7
+ export default defineConfig({
8
+ apiKey: env.get('MONOCLE_API_KEY'),
9
+
10
+ serviceName: env.get('APP_NAME'),
11
+ serviceVersion: env.get('APP_VERSION'),
12
+ environment: env.get('APP_ENV'),
13
+ })
@@ -0,0 +1,8 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { dirname } from "node:path";
3
+
4
+ //#region stubs/main.ts
5
+ const stubsRoot = dirname(fileURLToPath(import.meta.url));
6
+
7
+ //#endregion
8
+ export { stubsRoot };
@@ -0,0 +1,4 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { dirname } from 'node:path'
3
+
4
+ export const stubsRoot = dirname(fileURLToPath(import.meta.url))
@@ -0,0 +1,12 @@
1
+ {{{
2
+ exports({ to: app.makePath('otel.ts') })
3
+ }}}
4
+ /**
5
+ * Monocle agent initialization file.
6
+ *
7
+ * IMPORTANT: This file must be imported FIRST in bin/server.ts
8
+ * for auto-instrumentation to work correctly.
9
+ */
10
+ import { init } from '@monocle-app/agent/init'
11
+
12
+ await init(import.meta.dirname)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monocle-app/agent",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "Monocle agent for AdonisJS - sends telemetry to Monocle cloud",
5
5
  "keywords": [
6
6
  "adonisjs",
@@ -11,13 +11,13 @@
11
11
  ],
12
12
  "license": "ISC",
13
13
  "author": "Julien Ripouteau <julien@ripouteau.com>",
14
+ "files": [
15
+ "dist"
16
+ ],
14
17
  "type": "module",
15
18
  "main": "./dist/index.mjs",
16
19
  "module": "./dist/index.mjs",
17
20
  "types": "./dist/index.d.mts",
18
- "files": [
19
- "dist"
20
- ],
21
21
  "exports": {
22
22
  ".": {
23
23
  "dev": "./index.ts",
@@ -38,7 +38,6 @@
38
38
  "./package.json": "./package.json"
39
39
  },
40
40
  "publishConfig": {
41
- "tag": "beta",
42
41
  "access": "public",
43
42
  "exports": {
44
43
  ".": "./dist/index.mjs",
@@ -46,7 +45,8 @@
46
45
  "./monocle_middleware": "./dist/monocle_middleware.mjs",
47
46
  "./monocle_provider": "./dist/monocle_provider.mjs",
48
47
  "./package.json": "./package.json"
49
- }
48
+ },
49
+ "tag": "beta"
50
50
  },
51
51
  "scripts": {
52
52
  "build": "tsdown",
@@ -56,13 +56,15 @@
56
56
  "prepublishOnly": "pnpm run build"
57
57
  },
58
58
  "dependencies": {
59
- "@adonisjs/otel": "1.0.0",
59
+ "@adonisjs/otel": "1.1.0",
60
60
  "@opentelemetry/api": "^1.9.0",
61
+ "@opentelemetry/core": "^2.2.0",
61
62
  "@opentelemetry/exporter-metrics-otlp-http": "^0.208.0",
62
63
  "@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
63
64
  "@opentelemetry/host-metrics": "^0.38.0",
64
65
  "@opentelemetry/sdk-metrics": "^2.2.0",
65
66
  "@opentelemetry/sdk-trace-base": "^2.2.0",
67
+ "@opentelemetry/semantic-conventions": "^1.38.0",
66
68
  "import-in-the-middle": "^2.0.1"
67
69
  },
68
70
  "devDependencies": {
@@ -73,5 +75,5 @@
73
75
  "peerDependencies": {
74
76
  "@adonisjs/core": "^6.2.0 || ^7.0.0"
75
77
  },
76
- "packageManager": "pnpm@10.26.1"
78
+ "packageManager": "pnpm@10.26.2"
77
79
  }