@monocle.sh/adonisjs-agent 1.2.2 → 1.3.0

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/LICENSE.md ADDED
@@ -0,0 +1,53 @@
1
+ ## Elastic License 2.0 (ELv2)
2
+
3
+ ### Acceptance
4
+
5
+ By using the software, you agree to all of the terms and conditions below.
6
+
7
+ ### Copyright License
8
+
9
+ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below.
10
+
11
+ ### Limitations
12
+
13
+ You may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
14
+
15
+ You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key.
16
+
17
+ You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor's trademarks is subject to applicable law.
18
+
19
+ ### Patents
20
+
21
+ The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
22
+
23
+ ### Notices
24
+
25
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms.
26
+
27
+ If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software.
28
+
29
+ ### No Other Rights
30
+
31
+ These terms do not imply any licenses other than those expressly granted in these terms.
32
+
33
+ ### Termination
34
+
35
+ If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently.
36
+
37
+ ### No Liability
38
+
39
+ _As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim._
40
+
41
+ ### Definitions
42
+
43
+ The **licensor** is the entity offering these terms, and the **software** is the software the licensor makes available under these terms, including any portion of it.
44
+
45
+ **You** refers to the individual or entity agreeing to these terms.
46
+
47
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
48
+
49
+ **Your licenses** are all the licenses granted to you for the software under these terms.
50
+
51
+ **Use** means anything you do with the software requiring one of your licenses.
52
+
53
+ **Trademark** means trademarks, service marks, and similar rights.
@@ -0,0 +1,28 @@
1
+ import "node:module";
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __exportAll = (all, no_symbols) => {
8
+ let target = {};
9
+ for (var name in all) __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true
12
+ });
13
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
14
+ return target;
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
18
+ key = keys[i];
19
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
20
+ get: ((k) => from[k]).bind(null, key),
21
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
22
+ });
23
+ }
24
+ return to;
25
+ };
26
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
27
+ //#endregion
28
+ export { __exportAll, __reExport };
package/dist/index.d.mts CHANGED
@@ -1,9 +1,7 @@
1
- import { BatchConfig, CliTracingConfig, HostMetricsConfig, MonocleConfig } from "./src/types/config.mjs";
2
1
  import { defineConfig } from "./src/define_config.mjs";
3
- import { SpanAllOptions, SpanOptions } from "./src/types/decorators.mjs";
4
2
  import { Monocle } from "./src/monocle.mjs";
5
3
  import { configure } from "./configure.mjs";
6
4
  import { extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes } from "./helpers.mjs";
7
5
  import { span, spanAll } from "./decorators.mjs";
8
6
  import { destinations } from "@adonisjs/otel";
9
- export { type BatchConfig, type CliTracingConfig, type HostMetricsConfig, Monocle, type MonocleConfig, type SpanAllOptions, type SpanOptions, configure, defineConfig, destinations, extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes, span, spanAll };
7
+ export { Monocle, configure, defineConfig, destinations, extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes, span, spanAll };
@@ -14,29 +14,30 @@ var OtelProvider = class {
14
14
  this.app = app;
15
15
  }
16
16
  /**
17
- * Hook into ExceptionHandler to record exceptions in spans.
17
+ * Hook into ExceptionHandler to record exceptions as LogRecords.
18
18
  *
19
- * We always record the exception on the span (for trace visibility), but we also
20
- * add an attribute `monocle.exception.should_report` to indicate whether this
21
- * exception should appear in the Exceptions dashboard.
19
+ * Our ExceptionReporter emits a structured LogRecord (with
20
+ * `monocle.log.source = 'exception_reporter'`) into otel_logs.
21
+ * The original report() also logs via Pino, which the OTEL Pino
22
+ * instrumentation converts to a second LogRecord. The `/logs`
23
+ * view filters out `monocle.log.source = 'exception_reporter'`
24
+ * entries since they already appear in `/exceptions`.
22
25
  *
23
- * This respects the AdonisJS `ignoreExceptions`, `ignoreStatuses`, and `ignoreCodes`
24
- * configuration from the ExceptionHandler.
26
+ * This respects the AdonisJS `ignoreExceptions`, `ignoreStatuses`,
27
+ * and `ignoreCodes` configuration from the ExceptionHandler.
25
28
  */
26
29
  #registerExceptionHandler() {
27
30
  const originalReport = ExceptionHandler.prototype.report;
28
31
  const reporter = new ExceptionReporter();
29
32
  ExceptionHandler.macro("report", async function(error, ctx) {
30
33
  const span = getCurrentSpan();
31
- if (span) {
32
- const httpError = toHttpError(error);
33
- const shouldReport = this.shouldReport(httpError);
34
- await reporter.report({
35
- span,
36
- error,
37
- shouldReport
38
- });
39
- }
34
+ const httpError = toHttpError(error);
35
+ const shouldReport = this.shouldReport(httpError);
36
+ await reporter.report({
37
+ span,
38
+ error,
39
+ shouldReport
40
+ });
40
41
  return originalReport.call(this, error, ctx);
41
42
  });
42
43
  }
@@ -1,3 +1,4 @@
1
+ import { buildHttpInstrumentationConfig } from "./http_instrumentation_config.mjs";
1
2
  import { destinations } from "@adonisjs/otel";
2
3
  //#region src/define_config.ts
3
4
  const DEFAULT_BATCH_CONFIG = {
@@ -6,46 +7,17 @@ const DEFAULT_BATCH_CONFIG = {
6
7
  exportTimeoutMillis: 3e4,
7
8
  maxQueueSize: 2048
8
9
  };
9
- /**
10
- * Extracts a header value from either ServerResponse (getHeader) or IncomingMessage (headers object).
11
- */
12
- function getHeader(response, name) {
13
- if ("getHeader" in response && typeof response.getHeader === "function") {
14
- const value = response.getHeader(name);
15
- if (typeof value === "string") return value;
16
- }
17
- if ("headers" in response && response.headers) {
18
- const value = response.headers[name];
19
- if (typeof value === "string") return value;
20
- }
21
- }
22
- /**
23
- * Detects the connection type based on response headers.
24
- * Returns 'sse' for Server-Sent Events, 'websocket' for WebSocket upgrades,
25
- * or undefined for standard HTTP connections.
26
- */
27
- function detectConnectionType(response) {
28
- if (getHeader(response, "content-type")?.includes("text/event-stream")) return "sse";
29
- if (getHeader(response, "upgrade")?.toLowerCase() === "websocket") return "websocket";
10
+ function resolveSdkVersion() {
11
+ return "1.3.0";
30
12
  }
31
- /**
32
- * Creates a response hook that detects long-running HTTP connections
33
- * (SSE and WebSocket) and adds the `http.connection_type` attribute.
34
- */
35
- function createConnectionTypeHook(userHook) {
36
- return (span, response) => {
37
- const connectionType = detectConnectionType(response);
38
- if (connectionType) span.setAttribute("http.connection_type", connectionType);
39
- userHook?.(span, response);
13
+ function buildNodejsResourceAttributes() {
14
+ const attributes = {
15
+ "monocle.language.name": "javascript",
16
+ "monocle.framework.name": "adonisjs"
40
17
  };
41
- }
42
- /**
43
- * Extracts the user-defined response hook from HTTP instrumentation config.
44
- */
45
- function extractUserResponseHook(httpConfig) {
46
- if (typeof httpConfig !== "object" || httpConfig === null) return;
47
- if (!("responseHook" in httpConfig)) return;
48
- return httpConfig.responseHook;
18
+ const sdkVersion = resolveSdkVersion();
19
+ if (sdkVersion) attributes["telemetry.sdk.version"] = sdkVersion;
20
+ return attributes;
49
21
  }
50
22
  /**
51
23
  * Define and validate Monocle agent configuration.
@@ -66,14 +38,14 @@ function defineConfig(config) {
66
38
  ...isDev ? devBatchConfig : DEFAULT_BATCH_CONFIG,
67
39
  ...config.batch
68
40
  };
69
- const httpConfig = config.instrumentations?.["@opentelemetry/instrumentation-http"];
70
- const userResponseHook = extractUserResponseHook(httpConfig);
41
+ const httpInstrumentationConfig = buildHttpInstrumentationConfig(config);
42
+ const resourceAttributes = {
43
+ ...buildNodejsResourceAttributes(),
44
+ ...config.resourceAttributes
45
+ };
71
46
  const instrumentations = {
72
47
  ...config.instrumentations,
73
- "@opentelemetry/instrumentation-http": {
74
- ...typeof httpConfig === "object" && httpConfig !== null ? httpConfig : {},
75
- responseHook: createConnectionTypeHook(userResponseHook)
76
- }
48
+ "@opentelemetry/instrumentation-http": { ...httpInstrumentationConfig }
77
49
  };
78
50
  const headers = { "x-monocle-env": environment };
79
51
  if (config.apiKey) headers["x-api-key"] = config.apiKey;
@@ -91,6 +63,7 @@ function defineConfig(config) {
91
63
  ...config,
92
64
  endpoint,
93
65
  environment,
66
+ resourceAttributes,
94
67
  instrumentations,
95
68
  destinations: {
96
69
  ...config.destinations,
@@ -1,5 +1,5 @@
1
- import is from "@sindresorhus/is";
2
1
  import { ExceptionReporter } from "@monocle.sh/otel-utils";
2
+ import is from "@sindresorhus/is";
3
3
  //#region src/exception_reporter.ts
4
4
  /**
5
5
  * Convert any unknown error to an HttpError-like object.
@@ -0,0 +1,196 @@
1
+ import { HttpBodyCapture, HttpUrlSanitizer } from "@monocle.sh/otel-utils";
2
+ //#region src/http_instrumentation_config.ts
3
+ /**
4
+ * Builds the effective HTTP instrumentation config used by the Adonis agent.
5
+ */
6
+ var HttpInstrumentationConfigBuilder = class {
7
+ #config;
8
+ #httpConfig;
9
+ constructor(config) {
10
+ this.#config = config;
11
+ this.#httpConfig = this.#resolveHttpConfig();
12
+ }
13
+ /**
14
+ * Resolves the effective HTTP config, with low-level instrumentation overrides winning.
15
+ */
16
+ #resolveHttpConfig() {
17
+ const topLevelHttpConfig = this.#config.http;
18
+ const instrumentationHttpConfig = this.#config.instrumentations?.["@opentelemetry/instrumentation-http"];
19
+ if (instrumentationHttpConfig?.enabled === false) return instrumentationHttpConfig;
20
+ if (typeof instrumentationHttpConfig === "object" && instrumentationHttpConfig !== null) {
21
+ if (topLevelHttpConfig === false) return instrumentationHttpConfig;
22
+ if (typeof topLevelHttpConfig !== "object" || topLevelHttpConfig === null) return instrumentationHttpConfig;
23
+ return {
24
+ ...topLevelHttpConfig,
25
+ ...instrumentationHttpConfig
26
+ };
27
+ }
28
+ if (topLevelHttpConfig === false) return topLevelHttpConfig;
29
+ if (typeof topLevelHttpConfig !== "object" || topLevelHttpConfig === null) return instrumentationHttpConfig;
30
+ return topLevelHttpConfig;
31
+ }
32
+ /**
33
+ * Reads custom URL sanitization rules from the effective config.
34
+ */
35
+ #resolveHttpUrlConfig() {
36
+ if (typeof this.#httpConfig !== "object" || this.#httpConfig === null) return;
37
+ return this.#httpConfig.sanitizeUrls;
38
+ }
39
+ /**
40
+ * Reads custom body capture rules from the effective config.
41
+ */
42
+ #resolveHttpBodyConfig() {
43
+ if (typeof this.#httpConfig !== "object" || this.#httpConfig === null) return;
44
+ return this.#httpConfig.captureBodies;
45
+ }
46
+ /**
47
+ * Determines whether HTTP instrumentation should remain enabled.
48
+ */
49
+ #resolveEnabled() {
50
+ if (this.#httpConfig === false) return false;
51
+ if (typeof this.#httpConfig !== "object" || this.#httpConfig === null) return true;
52
+ if ("enabled" in this.#httpConfig && this.#httpConfig.enabled === false) return false;
53
+ return true;
54
+ }
55
+ /**
56
+ * Returns the object-shaped portion of the effective config for spreading into the final config.
57
+ */
58
+ #getObjectConfig() {
59
+ if (typeof this.#httpConfig !== "object" || this.#httpConfig === null) return {};
60
+ return this.#httpConfig;
61
+ }
62
+ /**
63
+ * Extracts a user-defined request hook from the effective config.
64
+ */
65
+ #extractUserRequestHook() {
66
+ if (typeof this.#httpConfig !== "object" || this.#httpConfig === null) return;
67
+ if (!("requestHook" in this.#httpConfig)) return;
68
+ return this.#httpConfig.requestHook;
69
+ }
70
+ /**
71
+ * Extracts a user-defined response hook from the effective config.
72
+ */
73
+ #extractUserResponseHook() {
74
+ if (typeof this.#httpConfig !== "object" || this.#httpConfig === null) return;
75
+ if (!("responseHook" in this.#httpConfig)) return;
76
+ return this.#httpConfig.responseHook;
77
+ }
78
+ /**
79
+ * Extracts a user-defined custom-attributes hook from the effective config.
80
+ */
81
+ #extractUserApplyCustomAttributesOnSpan() {
82
+ if (typeof this.#httpConfig !== "object" || this.#httpConfig === null) return;
83
+ if (!("applyCustomAttributesOnSpan" in this.#httpConfig)) return;
84
+ return this.#httpConfig.applyCustomAttributesOnSpan;
85
+ }
86
+ /**
87
+ * Extracts a header value from either ServerResponse or IncomingMessage.
88
+ */
89
+ #getHeader(response, name) {
90
+ if ("getHeader" in response && typeof response.getHeader === "function") {
91
+ const value = response.getHeader(name);
92
+ if (typeof value === "string") return value;
93
+ }
94
+ if ("headers" in response && response.headers) {
95
+ const value = response.headers[name];
96
+ if (typeof value === "string") return value;
97
+ }
98
+ }
99
+ /**
100
+ * Detects whether the response is an SSE or websocket connection.
101
+ */
102
+ #detectConnectionType(response) {
103
+ if (this.#getHeader(response, "content-type")?.includes("text/event-stream")) return "sse";
104
+ if (this.#getHeader(response, "upgrade")?.toLowerCase() === "websocket") return "websocket";
105
+ }
106
+ /**
107
+ * Builds the hook that tags long-lived HTTP connections.
108
+ */
109
+ #createConnectionTypeHook(userHook) {
110
+ return (span, response) => {
111
+ const connectionType = this.#detectConnectionType(response);
112
+ if (connectionType) span.setAttribute("http.connection_type", connectionType);
113
+ userHook?.(span, response);
114
+ };
115
+ }
116
+ /**
117
+ * Builds the effective request hook with URL sanitization, body capture, and user hooks.
118
+ */
119
+ #createRequestHook(options) {
120
+ const bodyCaptureHook = options.bodyCapture.createRequestHook();
121
+ if (!bodyCaptureHook && !options.userHook && !options.urlSanitizationEnabled) return;
122
+ return (span, request) => {
123
+ options.urlSanitizer.sanitizeSpanFromRequest({
124
+ span,
125
+ request
126
+ });
127
+ bodyCaptureHook?.(span, request);
128
+ options.userHook?.(span, request);
129
+ };
130
+ }
131
+ /**
132
+ * Builds the effective response hook with body capture and connection tagging.
133
+ */
134
+ #createResponseHook(options) {
135
+ const bodyCaptureHook = options.bodyCapture.createResponseHook();
136
+ const connectionTypeHook = this.#createConnectionTypeHook(options.userHook);
137
+ if (!bodyCaptureHook && !options.userHook) return connectionTypeHook;
138
+ return (span, response) => {
139
+ bodyCaptureHook?.(span, response);
140
+ connectionTypeHook(span, response);
141
+ };
142
+ }
143
+ /**
144
+ * Builds the hook that flushes body capture before the span closes.
145
+ */
146
+ #createApplyCustomAttributesOnSpan(options) {
147
+ return (span, request, response) => {
148
+ options.bodyCapture.applyCustomAttributesOnSpan({
149
+ span,
150
+ request,
151
+ response
152
+ });
153
+ options.userHook?.(span, request, response);
154
+ };
155
+ }
156
+ /**
157
+ * Produces the final HTTP instrumentation config consumed by Adonis OTel.
158
+ */
159
+ build() {
160
+ const enabled = this.#resolveEnabled();
161
+ const baseConfig = this.#getObjectConfig();
162
+ if (!enabled) return {
163
+ ...baseConfig,
164
+ enabled: false
165
+ };
166
+ const bodyCapture = new HttpBodyCapture(this.#resolveHttpBodyConfig());
167
+ const urlConfig = this.#resolveHttpUrlConfig();
168
+ const urlSanitizer = new HttpUrlSanitizer(urlConfig);
169
+ return {
170
+ ...baseConfig,
171
+ enabled: true,
172
+ requestHook: this.#createRequestHook({
173
+ bodyCapture,
174
+ urlSanitizer,
175
+ urlSanitizationEnabled: urlConfig !== void 0,
176
+ userHook: this.#extractUserRequestHook()
177
+ }),
178
+ responseHook: this.#createResponseHook({
179
+ bodyCapture,
180
+ userHook: this.#extractUserResponseHook()
181
+ }),
182
+ applyCustomAttributesOnSpan: this.#createApplyCustomAttributesOnSpan({
183
+ bodyCapture,
184
+ userHook: this.#extractUserApplyCustomAttributesOnSpan()
185
+ })
186
+ };
187
+ }
188
+ };
189
+ /**
190
+ * Builds the effective HTTP instrumentation config from top-level and low-level options.
191
+ */
192
+ function buildHttpInstrumentationConfig(config) {
193
+ return new HttpInstrumentationConfigBuilder(config).build();
194
+ }
195
+ //#endregion
196
+ export { buildHttpInstrumentationConfig };
@@ -7,19 +7,21 @@ import { CaptureExceptionContext, CaptureMessageContext, MessageLevel, MonocleUs
7
7
  declare class Monocle {
8
8
  #private;
9
9
  /**
10
- * Capture an exception and record it on the current active span.
11
- * If no span is active, the exception is silently ignored.
10
+ * Capture an exception and record it as a LogRecord.
11
+ * If a span is active, it is also marked with ERROR status.
12
+ *
13
+ * Works with or without an active span — exceptions in event
14
+ * handlers, cron jobs, or background tasks are no longer dropped.
12
15
  *
13
16
  * This method is async to allow for source context extraction.
14
17
  * You can choose to await it or fire-and-forget.
15
18
  */
16
19
  static captureException(error: unknown, ctx?: CaptureExceptionContext): Promise<void>;
17
20
  /**
18
- * Capture a message and record it on the current active span.
19
- * If no span is active, the message is silently ignored.
21
+ * Capture a message and record it as a LogRecord.
20
22
  *
21
- * Messages are stored using the same format as exceptions for unified querying.
22
- * They are identified by exception.type = 'CapturedMessage'.
23
+ * Messages are stored using the same format as exceptions for
24
+ * unified querying. They are identified by exception.type = 'CapturedMessage'.
23
25
  *
24
26
  * This method is async to allow for source context extraction.
25
27
  * You can choose to await it or fire-and-forget.
@@ -1,9 +1,12 @@
1
1
  import { ExceptionReporter } from "./exception_reporter.mjs";
2
+ import { extractContextLines } from "@monocle.sh/otel-utils";
3
+ import { randomUUID } from "node:crypto";
2
4
  import { trace } from "@opentelemetry/api";
5
+ import { SeverityNumber, logs } from "@opentelemetry/api-logs";
3
6
  import { setUser } from "@adonisjs/otel/helpers";
4
- import { extractContextLines } from "@monocle.sh/otel-utils";
5
7
  //#region src/monocle.ts
6
8
  const DEFAULT_CONTEXT_LINES = 7;
9
+ const LOGGER_NAME = "@monocle.sh/agent";
7
10
  /**
8
11
  * Internal patterns to filter from stack traces.
9
12
  * These are frames from the Monocle SDK itself.
@@ -17,11 +20,18 @@ const INTERNAL_FRAME_PATTERNS = [
17
20
  * Monocle helper class for manual instrumentation.
18
21
  */
19
22
  var Monocle = class {
23
+ static #buildUserAttributes(user) {
24
+ const attributes = {};
25
+ if (!user) return attributes;
26
+ if (user.id) attributes["user.id"] = user.id;
27
+ if (user.email) attributes["user.email"] = user.email;
28
+ if (user.name) attributes["user.name"] = user.name;
29
+ return attributes;
30
+ }
20
31
  static #applyUserToSpan(span, user) {
21
32
  if (!user) return;
22
- if (user.id) span.setAttribute("user.id", user.id);
23
- if (user.email) span.setAttribute("user.email", user.email);
24
- if (user.name) span.setAttribute("user.name", user.name);
33
+ const attrs = this.#buildUserAttributes(user);
34
+ for (const [key, value] of Object.entries(attrs)) span.setAttribute(key, value);
25
35
  }
26
36
  static #buildContextAttributes(ctx) {
27
37
  const attributes = {};
@@ -46,41 +56,43 @@ var Monocle = class {
46
56
  }).join("\n");
47
57
  }
48
58
  /**
49
- * Capture an exception and record it on the current active span.
50
- * If no span is active, the exception is silently ignored.
59
+ * Capture an exception and record it as a LogRecord.
60
+ * If a span is active, it is also marked with ERROR status.
61
+ *
62
+ * Works with or without an active span — exceptions in event
63
+ * handlers, cron jobs, or background tasks are no longer dropped.
51
64
  *
52
65
  * This method is async to allow for source context extraction.
53
66
  * You can choose to await it or fire-and-forget.
54
67
  */
55
68
  static async captureException(error, ctx) {
56
69
  const span = trace.getActiveSpan();
57
- if (!span) return;
70
+ const extraAttributes = {
71
+ ...this.#buildUserAttributes(ctx?.user),
72
+ ...this.#buildContextAttributes(ctx)
73
+ };
74
+ if (span) this.#applyUserToSpan(span, ctx?.user);
58
75
  await new ExceptionReporter().report({
59
76
  span,
60
77
  error,
61
- shouldReport: true
78
+ shouldReport: true,
79
+ extraAttributes
62
80
  });
63
- this.#applyUserToSpan(span, ctx?.user);
64
- const contextAttrs = this.#buildContextAttributes(ctx);
65
- for (const [key, value] of Object.entries(contextAttrs)) span.setAttribute(key, value);
66
81
  }
67
82
  /**
68
- * Capture a message and record it on the current active span.
69
- * If no span is active, the message is silently ignored.
83
+ * Capture a message and record it as a LogRecord.
70
84
  *
71
- * Messages are stored using the same format as exceptions for unified querying.
72
- * They are identified by exception.type = 'CapturedMessage'.
85
+ * Messages are stored using the same format as exceptions for
86
+ * unified querying. They are identified by exception.type = 'CapturedMessage'.
73
87
  *
74
88
  * This method is async to allow for source context extraction.
75
89
  * You can choose to await it or fire-and-forget.
76
90
  */
77
91
  static async captureMessage(message, levelOrContext) {
78
92
  const span = trace.getActiveSpan();
79
- if (!span) return;
80
93
  const ctx = typeof levelOrContext === "string" ? { level: levelOrContext } : levelOrContext;
81
94
  const level = ctx?.level ?? "info";
82
- this.#applyUserToSpan(span, ctx?.user);
83
- span.setAttribute("monocle.exception.should_report", true);
95
+ if (span) this.#applyUserToSpan(span, ctx?.user);
84
96
  const syntheticError = new Error(message);
85
97
  let frames = [];
86
98
  if (syntheticError.stack) try {
@@ -91,11 +103,20 @@ var Monocle = class {
91
103
  "exception.type": "CapturedMessage",
92
104
  "exception.message": message,
93
105
  "monocle.message.level": level,
106
+ "monocle.exception.should_report": "true",
107
+ ...this.#buildUserAttributes(ctx?.user),
94
108
  ...this.#buildContextAttributes(ctx)
95
109
  };
96
110
  if (syntheticError.stack) attributes["exception.stacktrace"] = this.#filterInternalStack(syntheticError.stack);
97
111
  if (frames.length > 0) attributes["monocle.exception.frames"] = JSON.stringify(frames);
98
- span.addEvent("exception", attributes);
112
+ attributes["monocle.log.source"] = "exception_reporter";
113
+ attributes["monocle.exception.occurrence_id"] = randomUUID();
114
+ logs.getLogger(LOGGER_NAME).emit({
115
+ severityNumber: SeverityNumber.ERROR,
116
+ severityText: "ERROR",
117
+ body: message,
118
+ attributes
119
+ });
99
120
  }
100
121
  /**
101
122
  * Set user information on the current active span.
@@ -1,6 +1,7 @@
1
- import { BullMQInstrumentationConfig } from "@monocle.sh/instrumentation-bullmq";
1
+ import { HttpBodyCaptureConfig, HttpUrlSanitizationConfig } from "@monocle.sh/otel-utils";
2
2
  import { BentoCacheInstrumentationConfig } from "@bentocache/otel/types";
3
- import { DestinationMap, OtelConfig } from "@adonisjs/otel/types";
3
+ import { DestinationMap, HttpInstrumentationConfig, InstrumentationsConfig, OtelConfig } from "@adonisjs/otel/types";
4
+ import { BullMQInstrumentationConfig } from "@monocle.sh/instrumentation-bullmq";
4
5
 
5
6
  //#region src/types/config.d.ts
6
7
  /**
@@ -103,7 +104,32 @@ interface AiInstrumentationConfig {
103
104
  recordOutputs?: boolean;
104
105
  }
105
106
  type BullMQAgentConfig = BullMQInstrumentationConfig;
106
- interface MonocleConfig extends Omit<OtelConfig, 'traceExporter' | 'metricExporter'> {
107
+ /**
108
+ * Extended HTTP instrumentation config consumed by the Monocle agent.
109
+ */
110
+ interface MonocleHttpInstrumentationConfig extends HttpInstrumentationConfig {
111
+ /**
112
+ * URL sanitization rules applied before HTTP spans are exported.
113
+ */
114
+ sanitizeUrls?: false | HttpUrlSanitizationConfig;
115
+ /**
116
+ * HTTP body capture rules for request and response payloads.
117
+ */
118
+ captureBodies?: false | HttpBodyCaptureConfig;
119
+ }
120
+ /**
121
+ * Instrumentation map extended with Monocle's HTTP instrumentation options.
122
+ */
123
+ type BaseInstrumentationValue = NonNullable<InstrumentationsConfig>[string & {}];
124
+ type MonocleInstrumentationsConfig = Omit<NonNullable<InstrumentationsConfig>, '@opentelemetry/instrumentation-http'> & {
125
+ '@opentelemetry/instrumentation-http'?: MonocleHttpInstrumentationConfig | {
126
+ enabled: false;
127
+ };
128
+ [key: string & {}]: BaseInstrumentationValue | MonocleHttpInstrumentationConfig | {
129
+ enabled: false;
130
+ } | undefined;
131
+ };
132
+ interface MonocleConfig extends Omit<OtelConfig, 'traceExporter' | 'metricExporter' | 'instrumentations'> {
107
133
  /**
108
134
  * Enable local DevTools mode.
109
135
  * Sends telemetry to Monocle DevTools (localhost:4200) with no compression.
@@ -137,6 +163,16 @@ interface MonocleConfig extends Omit<OtelConfig, 'traceExporter' | 'metricExport
137
163
  * Monocle destination is always injected automatically and cannot be removed.
138
164
  */
139
165
  destinations?: DestinationMap;
166
+ /**
167
+ * Instrumentation overrides and low-level escape hatches.
168
+ */
169
+ instrumentations?: MonocleInstrumentationsConfig;
170
+ /**
171
+ * High-level HTTP tracing configuration.
172
+ * This is the recommended way to configure ignored URLs, URL sanitization,
173
+ * and HTTP body capture.
174
+ */
175
+ http?: false | MonocleHttpInstrumentationConfig;
140
176
  /**
141
177
  * Host metrics configuration (CPU, Memory, Network, etc.).
142
178
  * Set to `false` to disable, or pass config object.
@@ -197,4 +233,4 @@ interface MonocleConfig extends Omit<OtelConfig, 'traceExporter' | 'metricExport
197
233
  ai?: false | AiInstrumentationConfig;
198
234
  }
199
235
  //#endregion
200
- export { BatchConfig, CliTracingConfig, HostMetricsConfig, MonocleConfig };
236
+ export { AiInstrumentationConfig, BatchConfig, BullMQAgentConfig, CacheInstrumentationConfig, CliTracingConfig, HostMetricsConfig, MailInstrumentationConfig, MonocleConfig, MonocleHttpInstrumentationConfig, MonocleInstrumentationsConfig, QueueInstrumentationConfig };
@@ -1,3 +1,12 @@
1
- import { BatchConfig, CliTracingConfig, HostMetricsConfig, MonocleConfig } from "./config.mjs";
1
+ import { AiInstrumentationConfig, BatchConfig, BullMQAgentConfig, CacheInstrumentationConfig, CliTracingConfig, HostMetricsConfig, MailInstrumentationConfig, MonocleConfig, MonocleHttpInstrumentationConfig, MonocleInstrumentationsConfig, QueueInstrumentationConfig } from "./config.mjs";
2
+ import { CaptureContext, CaptureExceptionContext, CaptureMessageContext, MessageLevel, MonocleUser } from "./monocle.mjs";
2
3
  import { SpanAllOptions, SpanOptions } from "./decorators.mjs";
3
- import { CaptureContext, CaptureExceptionContext, CaptureMessageContext, MessageLevel, MonocleUser } from "./monocle.mjs";
4
+ export * from "@monocle.sh/otel-utils";
5
+
6
+ //#region src/types/main.d.ts
7
+ declare namespace main_d_exports {
8
+ export { AiInstrumentationConfig, BatchConfig, BullMQAgentConfig, CacheInstrumentationConfig, CaptureContext, CaptureExceptionContext, CaptureMessageContext, CliTracingConfig, HostMetricsConfig, MailInstrumentationConfig, MessageLevel, MonocleConfig, MonocleHttpInstrumentationConfig, MonocleInstrumentationsConfig, MonocleUser, QueueInstrumentationConfig, SpanAllOptions, SpanOptions };
9
+ }
10
+ import * as import__monocle_sh_otel_utils from "@monocle.sh/otel-utils";
11
+ //#endregion
12
+ export { import__monocle_sh_otel_utils as main_d_exports };
@@ -5,6 +5,11 @@ import { defineConfig } from '@monocle.sh/adonisjs-agent'
5
5
  import env from '#start/env'
6
6
 
7
7
  export default defineConfig({
8
+ /**
9
+ * Enable Monocle Studio for local development.
10
+ * @see https://docs.monocle.sh/studio/overview
11
+ */
12
+ dev: process.env.NODE_ENV === 'development',
8
13
  apiKey: env.get('MONOCLE_API_KEY'),
9
14
 
10
15
  serviceName: env.get('APP_NAME'),
package/dist/types.d.mts CHANGED
@@ -1,5 +1,14 @@
1
- import { BatchConfig, CliTracingConfig, HostMetricsConfig, MonocleConfig } from "./src/types/config.mjs";
2
- import { SpanAllOptions, SpanOptions } from "./src/types/decorators.mjs";
1
+ import { BatchConfig, CliTracingConfig, HostMetricsConfig, MonocleConfig, MonocleHttpInstrumentationConfig, MonocleInstrumentationsConfig } from "./src/types/config.mjs";
3
2
  import { MonocleUser } from "./src/types/monocle.mjs";
3
+ import { SpanAllOptions, SpanOptions } from "./src/types/decorators.mjs";
4
+ import { main_d_exports } from "./src/types/main.mjs";
4
5
  import { DestinationConfig, DestinationMap, DestinationSignal, DestinationSignals, HeadersCarrier, OtelLoggingPresetOptions, OtlpDestinationConfig, OtlpDestinationOptions, UserContextResult } from "@adonisjs/otel/types";
5
- export { type BatchConfig, type CliTracingConfig, type DestinationConfig, type DestinationMap, type DestinationSignal, type DestinationSignals, type HeadersCarrier, type HostMetricsConfig, type MonocleConfig, type MonocleUser, type OtelLoggingPresetOptions, type OtlpDestinationConfig, type OtlpDestinationOptions, type SpanAllOptions, type SpanOptions, type UserContextResult };
6
+ type HttpBodyCaptureConfig = main_d_exports.HttpBodyCaptureConfig;
7
+ type HttpBodyCaptureSideConfig = main_d_exports.HttpBodyCaptureSideConfig;
8
+ type HttpBodyInfo = main_d_exports.HttpBodyInfo;
9
+ type HttpBodyKind = main_d_exports.HttpBodyKind;
10
+ type HttpBodyRedactionConfig = main_d_exports.HttpBodyRedactionConfig;
11
+ type HttpBodyRedactor = main_d_exports.HttpBodyRedactor;
12
+ type HttpUrlSanitizationConfig = main_d_exports.HttpUrlSanitizationConfig;
13
+ type HttpUrlSanitizerHook = main_d_exports.HttpUrlSanitizerHook;
14
+ export { type BatchConfig, type CliTracingConfig, type DestinationConfig, type DestinationMap, type DestinationSignal, type DestinationSignals, type HeadersCarrier, type HostMetricsConfig, type HttpBodyCaptureConfig, type HttpBodyCaptureSideConfig, type HttpBodyInfo, type HttpBodyKind, type HttpBodyRedactionConfig, type HttpBodyRedactor, type HttpUrlSanitizationConfig, type HttpUrlSanitizerHook, type MonocleConfig, type MonocleHttpInstrumentationConfig, type MonocleInstrumentationsConfig, type MonocleUser, type OtelLoggingPresetOptions, type OtlpDestinationConfig, type OtlpDestinationOptions, type SpanAllOptions, type SpanOptions, type UserContextResult };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monocle.sh/adonisjs-agent",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "Monocle agent for AdonisJS - sends telemetry to Monocle cloud",
5
5
  "keywords": [
6
6
  "adonisjs",
@@ -9,13 +9,13 @@
9
9
  "opentelemetry",
10
10
  "tracing"
11
11
  ],
12
- "homepage": "https://github.com/Julien-R44/monocle/tree/main/packages/agent",
12
+ "homepage": "https://github.com/monocle-sh/js/tree/main/packages/agents/adonisjs",
13
13
  "license": "ISC",
14
14
  "author": "Julien Ripouteau <julien@ripouteau.com>",
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "git+https://github.com/Julien-R44/monocle.git",
18
- "directory": "packages/agent"
17
+ "url": "git+https://github.com/monocle-sh/js.git",
18
+ "directory": "packages/agents/adonisjs"
19
19
  },
20
20
  "files": [
21
21
  "dist"
@@ -41,32 +41,33 @@
41
41
  "dependencies": {
42
42
  "@adonisjs/otel": "^1.2.3",
43
43
  "@bentocache/otel": "^0.1.2",
44
- "@opentelemetry/api": "^1.9.0",
45
- "@opentelemetry/core": "^2.6.0",
46
- "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
47
- "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
44
+ "@opentelemetry/api": "^1.9.1",
45
+ "@opentelemetry/api-logs": "^0.214.0",
46
+ "@opentelemetry/core": "^2.6.1",
47
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.214.0",
48
+ "@opentelemetry/exporter-trace-otlp-http": "^0.214.0",
48
49
  "@opentelemetry/host-metrics": "^0.38.3",
49
- "@opentelemetry/instrumentation": "^0.213.0",
50
- "@opentelemetry/sdk-metrics": "^2.6.0",
51
- "@opentelemetry/sdk-trace-base": "^2.6.0",
50
+ "@opentelemetry/instrumentation": "^0.214.0",
51
+ "@opentelemetry/sdk-metrics": "^2.6.1",
52
+ "@opentelemetry/sdk-trace-base": "^2.6.1",
52
53
  "@opentelemetry/semantic-conventions": "^1.40.0",
53
54
  "@sindresorhus/is": "^7.2.0",
54
55
  "error-stack-parser-es": "^1.0.5",
55
56
  "import-in-the-middle": "^3.0.0",
56
- "@monocle.sh/instrumentation-bullmq": "^0.3.0",
57
- "@monocle.sh/instrumentation-mcp": "^1.0.0",
58
- "@monocle.sh/instrumentation-vercel-ai": "^1.1.0",
59
- "@monocle.sh/otel-utils": "^1.0.1"
57
+ "@monocle.sh/instrumentation-bullmq": "^0.3.1",
58
+ "@monocle.sh/instrumentation-mcp": "^1.0.1",
59
+ "@monocle.sh/instrumentation-vercel-ai": "^1.1.2",
60
+ "@monocle.sh/otel-utils": "^1.1.0"
60
61
  },
61
62
  "devDependencies": {
62
- "@adonisjs/core": "^7.1.1",
63
+ "@adonisjs/core": "^7.3.0",
63
64
  "@adonisjs/tsconfig": "^2.0.0",
64
65
  "@japa/assert": "^4.2.0",
65
66
  "@japa/file-system": "^3.0.0",
66
67
  "@japa/runner": "^5.3.0",
67
68
  "@japa/snapshot": "^2.0.10",
68
69
  "@poppinss/ts-exec": "^1.4.4",
69
- "release-it": "^19.2.4"
70
+ "tsdown": "^0.21.7"
70
71
  },
71
72
  "peerDependencies": {
72
73
  "@adonisjs/core": "^6.2.0 || ^7.0.0",
@@ -81,8 +82,6 @@
81
82
  "build": "tsdown",
82
83
  "dev": "tsdown",
83
84
  "typecheck": "tsgo --noEmit",
84
- "test": "node --import @poppinss/ts-exec bin/test.ts",
85
- "quick:test": "node --import @poppinss/ts-exec bin/test.ts",
86
- "release": "release-it"
85
+ "test": "node --import @poppinss/ts-exec bin/test.ts"
87
86
  }
88
87
  }
package/LICENSE DELETED
@@ -1,15 +0,0 @@
1
- ISC License
2
-
3
- Copyright (c) 2024-present, Julien Ripouteau
4
-
5
- Permission to use, copy, modify, and/or distribute this software for any
6
- purpose with or without fee is hereby granted, provided that the above
7
- copyright notice and this permission notice appear in all copies.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.