hobots 1.10.0 → 2.0.1

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
@@ -113,7 +113,7 @@ try {
113
113
  await processarNota(nota);
114
114
  } catch (error) {
115
115
  Hobots.captureException(error, {
116
- level: 'error',
116
+ type: 'error',
117
117
  tags: { cliente: 'acme' },
118
118
  fingerprint: ['sefaz-timeout'], // agrupa manualmente; sem isso o servidor agrupa pelo stack
119
119
  });
@@ -123,9 +123,9 @@ try {
123
123
  Hobots.captureMessage('Lote processado com sucesso', 'success');
124
124
  ```
125
125
 
126
- Ambas retornam o `event_id` (ou `undefined` se não inicializado). O hint aceita `level`, `tags`, `extra` e `fingerprint`. Erros **não tratados** são capturados e flushados automaticamente antes do processo sair — desligue com `captureUnhandled: false`. Os eventos aparecem agrupados em issues no módulo **Issues** do app.
126
+ Ambas retornam o `event_id` (ou `undefined` se não inicializado). O hint aceita `type`, `tags`, `extra` e `fingerprint`. Erros **não tratados** são capturados e flushados automaticamente antes do processo sair — desligue com `captureUnhandled: false`. Os eventos aparecem agrupados em issues no módulo **Issues** do app.
127
127
 
128
- `Severity = 'fatal' | 'error' | 'warning' | 'info' | 'debug' | 'success'`. Use `'success'` para reportar itens concluídos com êxito — contam como eventos, mas **não** viram issue nem disparam alertas.
128
+ `EventType = 'fatal' | 'error' | 'warning' | 'success'`. Não há `'info'`/`'debug'`: narração não abre issue — para isso existem o log da execução e as breadcrumbs. Use `'success'` para reportar itens concluídos com êxito — contam como eventos, mas **não** viram issue nem disparam alertas.
129
129
 
130
130
  ### Breadcrumbs
131
131
 
package/dist/index.cjs CHANGED
@@ -161,7 +161,7 @@ function scrubSecrets(text) {
161
161
 
162
162
  // src/utils.ts
163
163
  var SDK_NAME = "hobots";
164
- var SDK_VERSION = true ? "1.10.0" : "0.0.0-dev";
164
+ var SDK_VERSION = true ? "2.0.1" : "0.0.0-dev";
165
165
  function isNode() {
166
166
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
167
167
  }
@@ -291,34 +291,34 @@ function safeString(value) {
291
291
  }
292
292
  return String(value);
293
293
  }
294
- function eventFromException(error, level = "error") {
295
- let type;
294
+ function eventFromException(error, type = "error") {
295
+ let exceptionType;
296
296
  let value;
297
297
  let frames = [];
298
298
  if (error instanceof Error) {
299
- type = error.name || "Error";
299
+ exceptionType = error.name || "Error";
300
300
  value = scrubSecrets(error.message);
301
301
  frames = parseStacktrace(error.stack);
302
302
  } else {
303
- type = "NonError";
303
+ exceptionType = "NonError";
304
304
  value = scrubSecrets(safeString(error));
305
305
  }
306
306
  return {
307
307
  event_id: uuid4(),
308
308
  timestamp: nowISO(),
309
- level,
309
+ type,
310
310
  exception: {
311
- type,
311
+ type: exceptionType,
312
312
  value,
313
313
  ...frames.length > 0 ? { stacktrace: { frames } } : {}
314
314
  }
315
315
  };
316
316
  }
317
- function eventFromMessage(message, level = "info") {
317
+ function eventFromMessage(message, type = "warning") {
318
318
  return {
319
319
  event_id: uuid4(),
320
320
  timestamp: nowISO(),
321
- level,
321
+ type,
322
322
  message: scrubSecrets(message)
323
323
  };
324
324
  }
@@ -354,7 +354,8 @@ var Transport = class {
354
354
  return;
355
355
  }
356
356
  if (this.buffer.length >= this.maxQueueSize) {
357
- this.buffer.shift();
357
+ const dropIndex = this.buffer.findIndex((queued) => queued.type !== "heartbeat");
358
+ this.buffer.splice(dropIndex === -1 ? 0 : dropIndex, 1);
358
359
  this.logger.warn("fila cheia, item mais antigo descartado");
359
360
  }
360
361
  this.buffer.push(item);
@@ -445,6 +446,8 @@ function runtimeContexts() {
445
446
  }
446
447
  var Client = class {
447
448
  logger;
449
+ /** Eco de lifecycle (Transaction.emit) e afins só imprimem no terminal com debug ligado. */
450
+ debug;
448
451
  // campos privados + getters de protótipo: o clientSecret não aparece em
449
452
  // JSON.stringify/serialização enumerável do Client (ex.: um objeto capturado
450
453
  // que referencie o client), mas client.options/config seguem funcionando
@@ -453,7 +456,8 @@ var Client = class {
453
456
  transport;
454
457
  constructor(options, config = resolveConfig(options)) {
455
458
  this.#options = options;
456
- this.logger = makeLogger(!!options.debug);
459
+ this.debug = !!options.debug;
460
+ this.logger = makeLogger(this.debug);
457
461
  this.#config = config;
458
462
  this.transport = new Transport({
459
463
  endpoint: this.#config.ingestEndpoint,
@@ -477,10 +481,10 @@ var Client = class {
477
481
  return `Client { baseUrl: '${this.#config.baseUrl}' }`;
478
482
  }
479
483
  captureException(error, hint, scope2) {
480
- return this.captureEvent(eventFromException(error, hint?.level ?? "error"), hint, scope2);
484
+ return this.captureEvent(eventFromException(error, hint?.type ?? "error"), hint, scope2);
481
485
  }
482
- captureMessage(message, level, hint, scope2) {
483
- return this.captureEvent(eventFromMessage(message, level), hint, scope2);
486
+ captureMessage(message, type, hint, scope2) {
487
+ return this.captureEvent(eventFromMessage(message, type), hint, scope2);
484
488
  }
485
489
  captureEvent(event, hint, scope2) {
486
490
  const sampleRate = this.options.sampleRate ?? 1;
@@ -502,7 +506,7 @@ var Client = class {
502
506
  const extra = { ...scope2.extra, ...event.extra, ...hint?.extra };
503
507
  return {
504
508
  ...event,
505
- level: hint?.level ?? event.level ?? scope2.level ?? "error",
509
+ type: hint?.type ?? event.type ?? scope2.type ?? "error",
506
510
  release: event.release ?? this.options.release,
507
511
  environment: event.environment ?? this.options.environment ?? "production",
508
512
  tags: Object.keys(tags).length > 0 ? tags : void 0,
@@ -540,6 +544,8 @@ var Client = class {
540
544
 
541
545
  // src/heartbeat.ts
542
546
  var DEFAULT_INTERVAL_MS = 3e4;
547
+ var MIN_REPORTED_INTERVAL_MS = 1e3;
548
+ var MAX_REPORTED_INTERVAL_MS = 9e5;
543
549
  var Heartbeat = class {
544
550
  constructor(client2, options = {}, instanceId) {
545
551
  this.client = client2;
@@ -556,12 +562,22 @@ var Heartbeat = class {
556
562
  start() {
557
563
  if (this.timer) return;
558
564
  this.beat();
559
- this.timer = setInterval(() => this.beat(), this.intervalMs);
565
+ this.schedule();
566
+ }
567
+ /** Jitter de ±10%: uma frota iniciada junto não sincroniza todos os beats no mesmo segundo. */
568
+ schedule() {
569
+ this.timer = setTimeout(
570
+ () => {
571
+ this.beat();
572
+ this.schedule();
573
+ },
574
+ this.intervalMs * (0.9 + Math.random() * 0.2)
575
+ );
560
576
  unrefTimer(this.timer);
561
577
  }
562
578
  stop() {
563
579
  if (this.timer) {
564
- clearInterval(this.timer);
580
+ clearTimeout(this.timer);
565
581
  this.timer = void 0;
566
582
  }
567
583
  }
@@ -569,7 +585,7 @@ var Heartbeat = class {
569
585
  this.client.sendHeartbeat({
570
586
  instance_id: this.instanceId,
571
587
  name: this.name,
572
- interval_ms: this.intervalMs,
588
+ interval_ms: Math.min(Math.max(this.intervalMs, MIN_REPORTED_INTERVAL_MS), MAX_REPORTED_INTERVAL_MS),
573
589
  timestamp: nowISO(),
574
590
  metadata: this.metadata
575
591
  });
@@ -608,7 +624,7 @@ function installGlobalHandlers(client2, getScope) {
608
624
  process.on(
609
625
  "uncaughtException",
610
626
  markInternal((error) => {
611
- client2.captureException(error, { level: "fatal", tags: { mechanism: "uncaughtException" } }, getScope());
627
+ client2.captureException(error, { type: "fatal", tags: { mechanism: "uncaughtException" } }, getScope());
612
628
  void client2.flush(2e3).then(async () => {
613
629
  if (externalListenerCount("uncaughtException") === 0) {
614
630
  await awaitEmergencyTasks(EMERGENCY_REPORT_CAP_MS);
@@ -621,7 +637,7 @@ function installGlobalHandlers(client2, getScope) {
621
637
  process.on(
622
638
  "unhandledRejection",
623
639
  markInternal((reason) => {
624
- client2.captureException(reason, { level: "error", tags: { mechanism: "unhandledRejection" } }, getScope());
640
+ client2.captureException(reason, { type: "error", tags: { mechanism: "unhandledRejection" } }, getScope());
625
641
  void client2.flush(2e3);
626
642
  })
627
643
  );
@@ -652,7 +668,7 @@ var Scope = class _Scope {
652
668
  user;
653
669
  contexts = {};
654
670
  extra = {};
655
- level;
671
+ type;
656
672
  fingerprint;
657
673
  breadcrumbs = [];
658
674
  setTag(key, value) {
@@ -683,8 +699,8 @@ var Scope = class _Scope {
683
699
  Object.assign(this.extra, extras);
684
700
  return this;
685
701
  }
686
- setLevel(level) {
687
- this.level = level;
702
+ setType(type) {
703
+ this.type = type;
688
704
  return this;
689
705
  }
690
706
  setFingerprint(fingerprint) {
@@ -708,7 +724,7 @@ var Scope = class _Scope {
708
724
  next.user = this.user ? { ...this.user } : void 0;
709
725
  next.contexts = Object.fromEntries(Object.entries(this.contexts).map(([k, v]) => [k, { ...v }]));
710
726
  next.extra = { ...this.extra };
711
- next.level = this.level;
727
+ next.type = this.type;
712
728
  next.fingerprint = this.fingerprint ? [...this.fingerprint] : void 0;
713
729
  next.breadcrumbs = [...this.breadcrumbs];
714
730
  return next;
@@ -816,15 +832,18 @@ var Transaction = class {
816
832
  // antes do RunReporter conectar) — replay no setLifecycleSink
817
833
  lifecycleBacklog = [];
818
834
  /**
819
- * Ecoa no terminal o início/fim de uma step ou transaction (sempre, independente de
820
- * sampling ou do debug) e espelha a linha no sink de lifecycle, se conectado — é
821
- * assim que os eventos de performance também aparecem nos logs da run no app.
835
+ * Registra o início/fim de uma step ou transaction: espelha a linha no sink de
836
+ * lifecycle, se conectado (é assim que os eventos de performance aparecem nos
837
+ * logs da run no app), e ecoa no terminal apenas com `debug: true` — o SDK não
838
+ * escreve no stdout do processo do usuário sem permissão.
822
839
  */
823
840
  emit(kind, phase, label, startTs, endTs, status) {
824
841
  const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
825
- const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? colorForStatus(status ?? "ok") : "cyan");
826
842
  const suffix = durationMs != null ? ` (${durationMs}ms)` : "";
827
- console.log(`${tag} ${label}${suffix}`);
843
+ if (this.client?.debug) {
844
+ const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? colorForStatus(status ?? "ok") : "cyan");
845
+ console.log(`${tag} ${label}${suffix}`);
846
+ }
828
847
  const level = phase === "finish" ? levelForStatus(status ?? "ok") : "info";
829
848
  const message = `[${kind}] ${phase.toUpperCase()} ${label}${suffix}`;
830
849
  if (this.lifecycleSink) this.lifecycleSink(level, message);
@@ -1871,13 +1890,17 @@ var TaskAgent = class {
1871
1890
  try {
1872
1891
  const response = await this.api.poll(
1873
1892
  {
1874
- agent: {
1893
+ instance: {
1875
1894
  // identidade canônica do agente no painel — o mesmo instanceId do
1876
1895
  // heartbeat da Telemetria, então os dois canais convergem em um agente
1877
1896
  instance_id: this.instanceId,
1878
1897
  version: SDK_VERSION,
1879
1898
  host: hostname(),
1880
- tasks: [...this.registry.keys()]
1899
+ tasks: [...this.registry.keys()],
1900
+ // o servidor calcula a tolerância de offline (3×) a partir disto em
1901
+ // agentes poll-only — sem o campo, um poll lento flaparia offline/online.
1902
+ // Clampado à faixa aceita pelo schema do poll (1s..15min).
1903
+ poll_interval_ms: Math.min(Math.max(this.pollIntervalMs, 1e3), 9e5)
1881
1904
  },
1882
1905
  max: Math.min(MAX_CONCURRENCY, this.concurrency - this.activeRuns.size),
1883
1906
  tasks: [...this.registry.keys()]
@@ -2061,10 +2084,10 @@ function init(options) {
2061
2084
  function getClient() {
2062
2085
  return client;
2063
2086
  }
2064
- function toRunLogLevel(level) {
2065
- return level === "fatal" ? "error" : level;
2087
+ function toRunLogLevel(type) {
2088
+ return type === "fatal" ? "error" : type;
2066
2089
  }
2067
- function forwardAttachment(attachment, message, level) {
2090
+ function forwardAttachment(attachment, message, type) {
2068
2091
  const attach = currentRunAttach();
2069
2092
  if (!attach) {
2070
2093
  client?.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o de task (register/start)");
@@ -2073,7 +2096,7 @@ function forwardAttachment(attachment, message, level) {
2073
2096
  const withMeta = typeof attachment === "object" && "image" in attachment ? attachment : { image: attachment };
2074
2097
  void attach(withMeta.image, {
2075
2098
  message,
2076
- level: toRunLogLevel(level),
2099
+ level: toRunLogLevel(type),
2077
2100
  caption: withMeta.caption,
2078
2101
  contentType: withMeta.contentType
2079
2102
  });
@@ -2081,15 +2104,15 @@ function forwardAttachment(attachment, message, level) {
2081
2104
  function captureException(error, hint) {
2082
2105
  if (hint?.attachment) {
2083
2106
  const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
2084
- forwardAttachment(hint.attachment, message, hint.level ?? "error");
2107
+ forwardAttachment(hint.attachment, message, hint.type ?? "error");
2085
2108
  }
2086
2109
  return client?.captureException(error, hint, scope);
2087
2110
  }
2088
- function captureMessage(message, level = "info", hint) {
2111
+ function captureMessage(message, type = "warning", hint) {
2089
2112
  if (hint?.attachment) {
2090
- forwardAttachment(hint.attachment, message, hint.level ?? level);
2113
+ forwardAttachment(hint.attachment, message, hint.type ?? type);
2091
2114
  }
2092
- return client?.captureMessage(message, level, hint, scope);
2115
+ return client?.captureMessage(message, type, hint, scope);
2093
2116
  }
2094
2117
  function addBreadcrumb(crumb) {
2095
2118
  scope.addBreadcrumb(crumb, client?.options.maxBreadcrumbs ?? DEFAULT_MAX_BREADCRUMBS);