@monocle.sh/adonisjs-agent 1.0.0-beta.13 → 1.0.0-beta.14

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.
@@ -9,7 +9,6 @@ import { extractTraceContext, getCurrentSpan, injectTraceContext, otelLoggingPre
9
9
  * to extract source context lines from the stack trace.
10
10
  */
11
11
  declare function handleError(span: Span, error: Error): Promise<never>;
12
- type RecordCallback<T> = (span: Span) => T | Promise<T>;
13
12
  /**
14
13
  * Record a code section as a span in your traces.
15
14
  *
@@ -17,8 +16,9 @@ type RecordCallback<T> = (span: Span) => T | Promise<T>;
17
16
  * to capture exceptions with source context lines (pre/post context around
18
17
  * the error location).
19
18
  *
20
- * Always returns a Promise to ensure context lines are properly captured
21
- * even for synchronous errors.
19
+ * Preserves sync/async semantics: if the callback is synchronous, the return
20
+ * type is `T`. If the callback returns a Promise-like value, the return type
21
+ * is async-compatible.
22
22
  *
23
23
  * Automatically handles:
24
24
  * - Creating and closing the span
@@ -29,10 +29,10 @@ type RecordCallback<T> = (span: Span) => T | Promise<T>;
29
29
  * ```ts
30
30
  * import { record } from '@monocle.sh/adonisjs-agent'
31
31
  *
32
- * const result = await record('database.query', () => {
33
- * return db.query('SELECT * FROM users')
34
- * })
32
+ * // Sync callback returns T directly
33
+ * const value = record('compute.hash', () => computeHash(data))
35
34
  *
35
+ * // Async callback returns Promise<T>
36
36
  * const user = await record('user.fetch', async () => {
37
37
  * return await userService.findById(id)
38
38
  * })
@@ -44,6 +44,6 @@ type RecordCallback<T> = (span: Span) => T | Promise<T>;
44
44
  * })
45
45
  * ```
46
46
  */
47
- declare function record<T>(name: string, callback: RecordCallback<T>): Promise<T>;
47
+ declare function record<T>(name: string, callback: (span: Span) => T): T;
48
48
  //#endregion
49
49
  export { extractTraceContext, getCurrentSpan, handleError, injectTraceContext, otelLoggingPreset, record, recordEvent, setAttributes };
package/dist/helpers.mjs CHANGED
@@ -1,8 +1,31 @@
1
1
  import { ExceptionReporter } from "./src/exception_reporter.mjs";
2
- import { SpanStatusCode, trace } from "@opentelemetry/api";
2
+ import { trace } from "@opentelemetry/api";
3
3
  import { extractTraceContext, getCurrentSpan, injectTraceContext, otelLoggingPreset, recordEvent, setAttributes } from "@adonisjs/otel/helpers";
4
4
 
5
5
  //#region src/helpers.ts
6
+ function isPromiseLike(value) {
7
+ if (!value) return false;
8
+ if (typeof value !== "object" && typeof value !== "function") return false;
9
+ return typeof value.then === "function";
10
+ }
11
+ async function reportErrorAndEndSpan(span, error, endTime) {
12
+ const reporter = new ExceptionReporter();
13
+ try {
14
+ await reporter.report({
15
+ span,
16
+ error,
17
+ shouldReport: true
18
+ });
19
+ } catch {
20
+ reporter.reportSync({
21
+ span,
22
+ error,
23
+ shouldReport: true
24
+ });
25
+ } finally {
26
+ span.end(endTime);
27
+ }
28
+ }
6
29
  /**
7
30
  * Handle an error by reporting it with context frames extraction.
8
31
  *
@@ -10,16 +33,7 @@ import { extractTraceContext, getCurrentSpan, injectTraceContext, otelLoggingPre
10
33
  * to extract source context lines from the stack trace.
11
34
  */
12
35
  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();
36
+ await reportErrorAndEndSpan(span, error, /* @__PURE__ */ new Date());
23
37
  throw error;
24
38
  }
25
39
  /**
@@ -29,8 +43,9 @@ async function handleError(span, error) {
29
43
  * to capture exceptions with source context lines (pre/post context around
30
44
  * the error location).
31
45
  *
32
- * Always returns a Promise to ensure context lines are properly captured
33
- * even for synchronous errors.
46
+ * Preserves sync/async semantics: if the callback is synchronous, the return
47
+ * type is `T`. If the callback returns a Promise-like value, the return type
48
+ * is async-compatible.
34
49
  *
35
50
  * Automatically handles:
36
51
  * - Creating and closing the span
@@ -41,10 +56,10 @@ async function handleError(span, error) {
41
56
  * ```ts
42
57
  * import { record } from '@monocle.sh/adonisjs-agent'
43
58
  *
44
- * const result = await record('database.query', () => {
45
- * return db.query('SELECT * FROM users')
46
- * })
59
+ * // Sync callback returns T directly
60
+ * const value = record('compute.hash', () => computeHash(data))
47
61
  *
62
+ * // Async callback returns Promise<T>
48
63
  * const user = await record('user.fetch', async () => {
49
64
  * return await userService.findById(id)
50
65
  * })
@@ -56,14 +71,19 @@ async function handleError(span, error) {
56
71
  * })
57
72
  * ```
58
73
  */
59
- async function record(name, callback) {
60
- return trace.getTracer("@monocle.sh/agent").startActiveSpan(name, async (span) => {
74
+ function record(name, callback) {
75
+ return trace.getTracer("@monocle.sh/agent").startActiveSpan(name, (span) => {
61
76
  try {
62
- const result = await callback(span);
77
+ const result = callback(span);
78
+ if (isPromiseLike(result)) return Promise.resolve(result).then((value) => {
79
+ span.end();
80
+ return value;
81
+ }).catch((error) => handleError(span, error));
63
82
  span.end();
64
83
  return result;
65
84
  } catch (error) {
66
- return handleError(span, error);
85
+ reportErrorAndEndSpan(span, error, /* @__PURE__ */ new Date());
86
+ throw error;
67
87
  }
68
88
  });
69
89
  }
@@ -33,6 +33,23 @@ var ExceptionReporter = class {
33
33
  const attributes = this.#buildAttributes(error, causeChain);
34
34
  span.addEvent("exception", attributes);
35
35
  }
36
+ /**
37
+ * Synchronous version of report. Uses buildCauseChain (no file I/O)
38
+ * so source context frames won't be available, but the full stack trace,
39
+ * cause chain, error status, and exception event are still recorded.
40
+ */
41
+ reportSync(options) {
42
+ const { span, shouldReport } = options;
43
+ const error = toHttpError(options.error);
44
+ span.setStatus({
45
+ code: SpanStatusCode.ERROR,
46
+ message: error.message
47
+ });
48
+ span.setAttribute("monocle.exception.should_report", shouldReport);
49
+ const causeChain = buildCauseChain(error);
50
+ const attributes = this.#buildAttributes(error, causeChain);
51
+ span.addEvent("exception", attributes);
52
+ }
36
53
  #buildAttributes(error, causeChain) {
37
54
  const hasCauses = causeChain.length > 1;
38
55
  const attributes = {
@@ -39,6 +39,15 @@ async function instrumentCache(config, appRoot) {
39
39
  suppressInternalOperations: true
40
40
  });
41
41
  instrumentation.enable();
42
+ /**
43
+ * Eagerly import bentocache to trigger the ESM hook.
44
+ * If the hook didn't patch BentoCache (e.g. race condition),
45
+ * fall back to manual registration.
46
+ */
47
+ try {
48
+ const bentocache = await import("bentocache");
49
+ if (bentocache.BentoCache.name !== "BentoCachePatched") instrumentation.manuallyRegister(bentocache);
50
+ } catch {}
42
51
  return instrumentation;
43
52
  }
44
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monocle.sh/adonisjs-agent",
3
- "version": "1.0.0-beta.13",
3
+ "version": "1.0.0-beta.14",
4
4
  "description": "Monocle agent for AdonisJS - sends telemetry to Monocle cloud",
5
5
  "keywords": [
6
6
  "adonisjs",