@glasstrace/sdk 1.3.3 → 1.3.4

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
@@ -163,6 +163,94 @@ GLASSTRACE_SUPPRESS_ACTION_NUDGE=1
163
163
  The nudge never fires in production (detected via `NODE_ENV` or
164
164
  `VERCEL_ENV`) unless `GLASSTRACE_FORCE_ENABLE=true` is also set.
165
165
 
166
+ ## Production deployment under Next 16
167
+
168
+ Next 16 (`next build && next start`) registers an OpenTelemetry
169
+ TracerProvider before user code runs. When `registerGlasstrace()` then
170
+ detects that provider, the SDK attempts to attach its span processor to
171
+ the existing pipeline. On most providers this auto-attach succeeds and
172
+ no further action is required; on a small number of provider shapes —
173
+ including Next 16's production-runtime provider in some versions — the
174
+ provider exposes no injection point and auto-attach returns
175
+ unsuccessfully. In that case spans flow through the existing pipeline
176
+ without reaching the Glasstrace exporter, so no traces appear in MCP
177
+ queries or the dashboard.
178
+
179
+ The SDK signals this case in three ways:
180
+
181
+ 1. **Log line.** The SDK logs a guidance message at `warn` level in
182
+ development and `error` level under `NODE_ENV=production`:
183
+
184
+ ```text
185
+ [glasstrace] An existing OTel TracerProvider is registered but
186
+ Glasstrace could not auto-attach its span processor.
187
+ Add Glasstrace to your provider configuration:
188
+ ...
189
+ ```
190
+
191
+ 2. **Programmatic signal.** `getStatus().tracing === "not-configured"`
192
+ after `registerGlasstrace()` has resolved indicates spans are not
193
+ reaching the Glasstrace exporter. Poll this from a health endpoint
194
+ or a startup readiness check:
195
+
196
+ ```ts
197
+ import { getStatus } from "@glasstrace/sdk";
198
+
199
+ const { tracing } = getStatus();
200
+ if (tracing === "not-configured") {
201
+ // Spans are not being exported. Apply the manual workaround below.
202
+ }
203
+ ```
204
+
205
+ 3. **CLI bridge.** `.glasstrace/runtime-state.json` carries a
206
+ structured `lastError` record that downstream tooling (custom
207
+ dashboards, CI assertions, the `npx @glasstrace/sdk status`
208
+ command in future releases) can surface verbatim:
209
+
210
+ ```json
211
+ {
212
+ "otel": { "state": "COEXISTENCE_FAILED", "scenario": "C/F" },
213
+ "lastError": {
214
+ "category": "auto-attach-returned-null",
215
+ "message": "tryAutoAttachGlasstraceProcessor returned null — ...",
216
+ "timestamp": "2026-05-04T12:34:56.789Z",
217
+ "providerClass": "BasicTracerProvider"
218
+ }
219
+ }
220
+ ```
221
+
222
+ The `providerClass` field is the constructor name of the existing
223
+ provider's delegate. URLs, headers, and credentials are never
224
+ captured.
225
+
226
+ ### Manual workaround
227
+
228
+ When auto-attach cannot succeed, register Glasstrace's span processor
229
+ on the provider you already own:
230
+
231
+ ```ts
232
+ import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
233
+ import { createGlasstraceSpanProcessor } from "@glasstrace/sdk";
234
+
235
+ const provider = new BasicTracerProvider({
236
+ spanProcessors: [
237
+ // ... your existing processors,
238
+ createGlasstraceSpanProcessor(),
239
+ ],
240
+ });
241
+ ```
242
+
243
+ `createGlasstraceSpanProcessor()` produces a processor with the same
244
+ branded exporter the auto-attach path uses, so duplicate
245
+ `registerGlasstrace()` calls remain idempotent. `registerGlasstrace()`
246
+ is still required when wiring the processor manually — it handles the
247
+ init handshake, anonymous-key resolution, session management, and
248
+ discovery endpoint, none of which are owned by the span processor.
249
+
250
+ A future SDK release may extend the auto-attach detection to recognize
251
+ additional Next 16 provider shapes; until that ships, the manual path
252
+ above is the production-supported integration.
253
+
166
254
  ## Capturing error response bodies
167
255
 
168
256
  When debugging a 4xx or 5xx, the response body is often the most useful
@@ -3763,11 +3763,27 @@ async function runCoexistencePath(existingProvider, config) {
3763
3763
  setOtelState(OtelState.COEXISTENCE_FAILED);
3764
3764
  emitLifecycleEvent("otel:configured", { state: OtelState.COEXISTENCE_FAILED, scenario: "C/F" });
3765
3765
  emitLifecycleEvent("otel:injection_failed", { reason: "provider internals inaccessible" });
3766
+ emitLifecycleEvent("otel:failed", {
3767
+ category: "auto-attach-returned-null",
3768
+ message: "tryAutoAttachGlasstraceProcessor returned null \u2014 the existing OTel TracerProvider exposed no injection point. Spans are not reaching the Glasstrace exporter. Apply the manual createGlasstraceSpanProcessor() workaround documented in the SDK README.",
3769
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3770
+ providerClass: readProviderClass(existingProvider)
3771
+ });
3766
3772
  const coreState = getCoreState();
3767
3773
  if (coreState === CoreState.ACTIVE || coreState === CoreState.KEY_RESOLVED) {
3768
3774
  setCoreState(CoreState.ACTIVE_DEGRADED);
3769
3775
  }
3770
3776
  }
3777
+ function readProviderClass(tracerProvider) {
3778
+ try {
3779
+ const proxy = tracerProvider;
3780
+ const delegate = typeof proxy.getDelegate === "function" ? proxy.getDelegate() : tracerProvider;
3781
+ const name = delegate?.constructor?.name;
3782
+ return typeof name === "string" && name.length > 0 ? name : void 0;
3783
+ } catch {
3784
+ return void 0;
3785
+ }
3786
+ }
3771
3787
  async function runRegistrationPath(config, sessionManager) {
3772
3788
  const exporterUrl = `${config.endpoint}/v1/traces`;
3773
3789
  const createOtlpExporter = (url, headers) => new OTLPTraceExporter({ url, headers });
@@ -4152,6 +4168,7 @@ import { join } from "node:path";
4152
4168
  var _projectRoot = null;
4153
4169
  var _sdkVersion = "unknown";
4154
4170
  var _lastScenario;
4171
+ var _lastError;
4155
4172
  var _debounceTimer = null;
4156
4173
  var _started = false;
4157
4174
  function startRuntimeStateWriter(options) {
@@ -4174,6 +4191,10 @@ function startRuntimeStateWriter(options) {
4174
4191
  _lastScenario = scenario;
4175
4192
  debouncedWrite();
4176
4193
  });
4194
+ onLifecycleEvent("otel:failed", (payload) => {
4195
+ _lastError = { ...payload };
4196
+ debouncedWrite();
4197
+ });
4177
4198
  onLifecycleEvent("auth:key_resolved", () => debouncedWrite());
4178
4199
  onLifecycleEvent("auth:claim_started", () => debouncedWrite());
4179
4200
  onLifecycleEvent("auth:claim_completed", () => debouncedWrite());
@@ -4201,6 +4222,9 @@ function writeStateNow() {
4201
4222
  auth: { state: state.auth },
4202
4223
  otel: { state: state.otel, scenario: _lastScenario }
4203
4224
  };
4225
+ if (_lastError) {
4226
+ runtimeState.lastError = _lastError;
4227
+ }
4204
4228
  const dir = join(_projectRoot, ".glasstrace");
4205
4229
  const filePath = join(dir, "runtime-state.json");
4206
4230
  mkdirSync(dir, { recursive: true, mode: 448 });
@@ -4245,7 +4269,7 @@ function registerGlasstrace(options) {
4245
4269
  setCoreState(CoreState.REGISTERING);
4246
4270
  startRuntimeStateWriter({
4247
4271
  projectRoot: process.cwd(),
4248
- sdkVersion: "1.3.3"
4272
+ sdkVersion: "1.3.4"
4249
4273
  });
4250
4274
  const config = resolveConfig(options);
4251
4275
  if (config.verbose) {
@@ -4411,8 +4435,8 @@ async function backgroundInit(config, anonKeyForInit, generation) {
4411
4435
  if (config.verbose) {
4412
4436
  console.info("[glasstrace] Background init firing.");
4413
4437
  }
4414
- const healthReport = collectHealthReport("1.3.3");
4415
- const initResult = await performInit(config, anonKeyForInit, "1.3.3", healthReport);
4438
+ const healthReport = collectHealthReport("1.3.4");
4439
+ const initResult = await performInit(config, anonKeyForInit, "1.3.4", healthReport);
4416
4440
  if (generation !== registrationGeneration) return;
4417
4441
  const currentState = getCoreState();
4418
4442
  if (currentState === CoreState.SHUTTING_DOWN || currentState === CoreState.SHUTDOWN) {
@@ -4435,7 +4459,7 @@ async function backgroundInit(config, anonKeyForInit, generation) {
4435
4459
  }
4436
4460
  maybeInstallConsoleCapture();
4437
4461
  if (didLastInitSucceed()) {
4438
- startHeartbeat(config, anonKeyForInit, "1.3.3", generation, (newApiKey, accountId) => {
4462
+ startHeartbeat(config, anonKeyForInit, "1.3.4", generation, (newApiKey, accountId) => {
4439
4463
  setAuthState(AuthState.CLAIMING);
4440
4464
  emitLifecycleEvent("auth:claim_started", { accountId });
4441
4465
  setResolvedApiKey(newApiKey);
@@ -4572,9 +4596,11 @@ This message will not appear once Glasstrace is added to your provider config.`
4572
4596
  }
4573
4597
  function emitGuidanceMessage() {
4574
4598
  const isSentry = detectSentry();
4599
+ const isProduction = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
4600
+ const level = isProduction ? "error" : "warn";
4575
4601
  if (isSentry) {
4576
4602
  sdkLog(
4577
- "warn",
4603
+ level,
4578
4604
  `[glasstrace] An existing OTel TracerProvider is registered but Glasstrace could not auto-attach its span processor.
4579
4605
  Add Glasstrace to your Sentry config:
4580
4606
 
@@ -4587,7 +4613,7 @@ Add Glasstrace to your Sentry config:
4587
4613
  );
4588
4614
  } else {
4589
4615
  sdkLog(
4590
- "warn",
4616
+ level,
4591
4617
  `[glasstrace] An existing OTel TracerProvider is registered but Glasstrace could not auto-attach its span processor.
4592
4618
  Add Glasstrace to your provider configuration:
4593
4619
 
@@ -4833,4 +4859,4 @@ export {
4833
4859
  withGlasstraceConfig,
4834
4860
  captureError
4835
4861
  };
4836
- //# sourceMappingURL=chunk-XNKG4WNQ.js.map
4862
+ //# sourceMappingURL=chunk-XFNK4YEW.js.map