fg-tracker-sdk 0.4.0 → 0.5.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.
@@ -0,0 +1,154 @@
1
+ import { als } from '../context.js';
2
+ import { detectArchitecture } from './aggregator.js';
3
+ const DEFAULT_INTERVAL_MS = 10_000;
4
+ const MB = 1024 * 1024;
5
+ async function getJson(url) {
6
+ try {
7
+ const res = await fetch(url, { signal: AbortSignal.timeout(2000) });
8
+ if (!res.ok)
9
+ return null;
10
+ return (await res.json());
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ /**
17
+ * Reporter de infraestrutura do processo. Em ECS/Fargate lê `/task` uma vez e `/task/stats`
18
+ * periodicamente → agregado `ecs_task` (CPU, memória, rede e heartbeat com o ARN da task).
19
+ */
20
+ export function startInfraReporter(core, options = {}) {
21
+ const noop = { stop() { } };
22
+ const infra = core.infra;
23
+ const metadataUri = process.env.ECS_CONTAINER_METADATA_URI_V4?.trim();
24
+ if (!infra || !core.enabled || options.ecs === false || !metadataUri) {
25
+ if (core.options.debug && options.ecs !== false && !metadataUri) {
26
+ console.info('[fg-tracker] startInfraReporter: ECS_CONTAINER_METADATA_URI_V4 ausente; nada a reportar.');
27
+ }
28
+ return noop;
29
+ }
30
+ let stopped = false;
31
+ let ref = null;
32
+ let task = null;
33
+ let previousNet = null;
34
+ let previousCpu = null;
35
+ let running = false;
36
+ const loadTask = async () => {
37
+ if (ref)
38
+ return true;
39
+ task = await getJson(`${metadataUri}/task`);
40
+ if (!task?.TaskARN)
41
+ return false;
42
+ const region = task.TaskARN.split(':')[3] || process.env.AWS_REGION || undefined;
43
+ const family = task.Containers?.map((c) => c.Labels?.['com.amazonaws.ecs.task-definition-family']).find(Boolean);
44
+ const serviceName = process.env.ECS_SERVICE_NAME?.trim() || task.ServiceName || family || task.Family;
45
+ ref = { kind: 'ecs_task', resourceArn: task.TaskARN, resourceName: serviceName, region };
46
+ infra.learnAccount(task.TaskARN);
47
+ return true;
48
+ };
49
+ const tick = async () => {
50
+ if (stopped || running)
51
+ return;
52
+ running = true;
53
+ try {
54
+ if (!(await loadTask()) || !ref || !task)
55
+ return;
56
+ const stats = await getJson(`${metadataUri}/task/stats`);
57
+ const r = ref;
58
+ infra.last(r, 'taskArn', task.TaskARN);
59
+ if (task.Cluster)
60
+ infra.last(r, 'clusterArn', task.Cluster);
61
+ if (r.resourceName)
62
+ infra.last(r, 'serviceName', r.resourceName);
63
+ if (task.LaunchType)
64
+ infra.last(r, 'launchType', task.LaunchType);
65
+ infra.last(r, 'architecture', detectArchitecture());
66
+ const cpuLimit = typeof task.Limits?.CPU === 'number' && task.Limits.CPU > 0 ? task.Limits.CPU : undefined;
67
+ if (cpuLimit)
68
+ infra.last(r, 'cpuUnits', Math.round(cpuLimit * 1024));
69
+ if (typeof task.Limits?.Memory === 'number' && task.Limits.Memory > 0)
70
+ infra.last(r, 'memoryLimitMb', task.Limits.Memory);
71
+ if (!stats)
72
+ return;
73
+ let cores = 0;
74
+ let cpuMeasured = false;
75
+ let memoryBytes = 0;
76
+ const net = new Map();
77
+ let aggTotal = 0;
78
+ let aggSystem = 0;
79
+ let aggOnline = 0;
80
+ for (const container of Object.values(stats)) {
81
+ if (!container)
82
+ continue;
83
+ const cpu = container.cpu_stats;
84
+ const pre = container.precpu_stats;
85
+ const online = cpu?.online_cpus || 1;
86
+ const total = cpu?.cpu_usage?.total_usage ?? 0;
87
+ const system = cpu?.system_cpu_usage ?? 0;
88
+ aggTotal += total;
89
+ aggSystem = Math.max(aggSystem, system);
90
+ aggOnline = Math.max(aggOnline, online);
91
+ const cpuDelta = total - (pre?.cpu_usage?.total_usage ?? 0);
92
+ const systemDelta = system - (pre?.system_cpu_usage ?? 0);
93
+ if (pre?.system_cpu_usage && systemDelta > 0 && cpuDelta >= 0) {
94
+ cores += (cpuDelta / systemDelta) * online;
95
+ cpuMeasured = true;
96
+ }
97
+ const mem = container.memory_stats;
98
+ if (typeof mem?.usage === 'number') {
99
+ const cache = mem.stats?.cache ?? mem.stats?.inactive_file ?? mem.stats?.total_inactive_file ?? 0;
100
+ memoryBytes += Math.max(0, mem.usage - cache);
101
+ }
102
+ // Em awsvpc os containers compartilham a ENI: deduplica por interface.
103
+ for (const [iface, counters] of Object.entries(container.networks ?? {})) {
104
+ const current = net.get(iface) ?? { rx: 0, tx: 0 };
105
+ net.set(iface, { rx: Math.max(current.rx, counters.rx_bytes ?? 0), tx: Math.max(current.tx, counters.tx_bytes ?? 0) });
106
+ }
107
+ }
108
+ if (!cpuMeasured && previousCpu && aggSystem > previousCpu.system) {
109
+ // precpu_stats vazio: usa a leitura anterior deste reporter.
110
+ cores = ((aggTotal - previousCpu.total) / (aggSystem - previousCpu.system)) * aggOnline;
111
+ cpuMeasured = cores >= 0;
112
+ }
113
+ previousCpu = { total: aggTotal, system: aggSystem };
114
+ if (cpuMeasured) {
115
+ const percent = cpuLimit ? (cores / cpuLimit) * 100 : cores * 100;
116
+ infra.max(r, 'cpuPercent', Math.round(percent * 100) / 100);
117
+ }
118
+ if (memoryBytes > 0)
119
+ infra.max(r, 'memoryMb', Math.round((memoryBytes / MB) * 100) / 100);
120
+ let rx = 0;
121
+ let tx = 0;
122
+ for (const counters of net.values()) {
123
+ rx += counters.rx;
124
+ tx += counters.tx;
125
+ }
126
+ if (previousNet && rx >= previousNet.rx && tx >= previousNet.tx) {
127
+ infra.add(r, 'networkRxBytes', rx - previousNet.rx);
128
+ infra.add(r, 'networkTxBytes', tx - previousNet.tx);
129
+ }
130
+ if (net.size > 0)
131
+ previousNet = { rx, tx };
132
+ }
133
+ catch (err) {
134
+ if (core.options.debug)
135
+ console.warn(`[fg-tracker] startInfraReporter: ${err?.message ?? err}`);
136
+ }
137
+ finally {
138
+ running = false;
139
+ }
140
+ };
141
+ const timer = als.exit(() => setInterval(() => void tick(), Math.max(1000, options.intervalMs ?? DEFAULT_INTERVAL_MS)));
142
+ timer.unref?.();
143
+ als.exit(() => void tick());
144
+ const stop = () => {
145
+ if (stopped)
146
+ return;
147
+ stopped = true;
148
+ clearInterval(timer);
149
+ unregister();
150
+ };
151
+ const unregister = core.onShutdown(stop);
152
+ return { stop };
153
+ }
154
+ //# sourceMappingURL=ecs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecs.js","sourceRoot":"","sources":["../../src/infra/ecs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { TrackerCore } from '../tracker.js';
2
+ /** Contexto mínimo da Lambda (tipo estrutural; sem `@types/aws-lambda`). */
3
+ export interface LambdaContextLike {
4
+ awsRequestId?: string;
5
+ functionName?: string;
6
+ invokedFunctionArn?: string;
7
+ getRemainingTimeInMillis?: () => number;
8
+ [key: string]: unknown;
9
+ }
10
+ export type LambdaHandler<E = unknown, R = unknown, C extends LambdaContextLike = LambdaContextLike> = (event: E, context: C) => R | Promise<R>;
11
+ export interface WrapLambdaOptions {
12
+ /** Tempo máximo esperando o `flush()` no fim da invocação. Default 1000 ms. */
13
+ flushTimeoutMs?: number;
14
+ /** Cria um span raiz `custom` por invocação (spans de AWS/db/fetch passam a formar um trace). Default `true`. */
15
+ span?: boolean;
16
+ }
17
+ /** Caminho do executável da camada `fg-tracker-lambda-extension`. */
18
+ export declare const LAMBDA_EXTENSION_PATH = "/opt/extensions/fg-tracker-extension";
19
+ export declare function hasLambdaExtension(): boolean;
20
+ export declare function flushWithTimeout(core: TrackerCore, timeoutMs: number): Promise<void>;
21
+ /**
22
+ * Envolve um handler async da Lambda. Sem a extensão do FG Tracker, aguarda o `flush()` (com timeout
23
+ * curto) ao fim de cada invocação para não perder spans no congelamento do ambiente; com a extensão,
24
+ * a fila sobrevive ao congelamento e é enviada no SIGTERM do encerramento.
25
+ */
26
+ export declare function wrapLambdaHandler<E, R, C extends LambdaContextLike>(core: TrackerCore, handler: LambdaHandler<E, R, C>, options?: WrapLambdaOptions): LambdaHandler<E, R, C>;
27
+ //# sourceMappingURL=lambda.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lambda.d.ts","sourceRoot":"","sources":["../../src/infra/lambda.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wBAAwB,CAAC,EAAE,MAAM,MAAM,CAAC;IACxC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,iBAAiB,GAAG,iBAAiB,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhJ,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iHAAiH;IACjH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,yCAAyC,CAAC;AAI5E,wBAAgB,kBAAkB,IAAI,OAAO,CAS5C;AAgBD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW1F;AAID;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,iBAAiB,EACjE,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC/B,OAAO,GAAE,iBAAsB,GAC9B,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAiCxB"}
@@ -0,0 +1,83 @@
1
+ import { existsSync } from 'node:fs';
2
+ /** Caminho do executável da camada `fg-tracker-lambda-extension`. */
3
+ export const LAMBDA_EXTENSION_PATH = '/opt/extensions/fg-tracker-extension';
4
+ let extensionDetected;
5
+ export function hasLambdaExtension() {
6
+ if (extensionDetected === undefined) {
7
+ try {
8
+ extensionDetected = existsSync(LAMBDA_EXTENSION_PATH);
9
+ }
10
+ catch {
11
+ extensionDetected = false;
12
+ }
13
+ }
14
+ return extensionDetected;
15
+ }
16
+ let sigtermInstalled = false;
17
+ /**
18
+ * Com a extensão registrada, a Lambda envia SIGTERM ao runtime no encerramento do ambiente:
19
+ * é a última chance de enviar a fila. Só instala quando ninguém mais trata SIGTERM.
20
+ */
21
+ function installSigtermFlush(core, timeoutMs) {
22
+ if (sigtermInstalled || process.listenerCount('SIGTERM') > 0)
23
+ return;
24
+ sigtermInstalled = true;
25
+ process.once('SIGTERM', () => {
26
+ void flushWithTimeout(core, timeoutMs).finally(() => process.exit(0));
27
+ });
28
+ }
29
+ export async function flushWithTimeout(core, timeoutMs) {
30
+ let timer;
31
+ const timeout = new Promise((resolve) => {
32
+ timer = setTimeout(resolve, timeoutMs);
33
+ timer.unref?.();
34
+ });
35
+ try {
36
+ await Promise.race([core.flush(true).catch(() => undefined), timeout]);
37
+ }
38
+ finally {
39
+ if (timer)
40
+ clearTimeout(timer);
41
+ }
42
+ }
43
+ let coldStart = true;
44
+ /**
45
+ * Envolve um handler async da Lambda. Sem a extensão do FG Tracker, aguarda o `flush()` (com timeout
46
+ * curto) ao fim de cada invocação para não perder spans no congelamento do ambiente; com a extensão,
47
+ * a fila sobrevive ao congelamento e é enviada no SIGTERM do encerramento.
48
+ */
49
+ export function wrapLambdaHandler(core, handler, options = {}) {
50
+ const flushTimeoutMs = Math.max(50, options.flushTimeoutMs ?? 1000);
51
+ const withSpan = options.span ?? true;
52
+ return async function fgTrackerLambda(event, context) {
53
+ if (!core.enabled)
54
+ return handler(event, context);
55
+ const extension = hasLambdaExtension();
56
+ if (extension)
57
+ installSigtermFlush(core, flushTimeoutMs);
58
+ const functionName = context?.functionName ?? process.env.AWS_LAMBDA_FUNCTION_NAME ?? 'lambda';
59
+ const span = withSpan
60
+ ? core.startSpan('custom', `lambda ${functionName}`, {
61
+ 'faas.name': functionName,
62
+ 'faas.coldStart': coldStart,
63
+ ...(context?.awsRequestId ? { 'faas.requestId': context.awsRequestId } : {}),
64
+ ...(context?.invokedFunctionArn ? { 'aws.resourceArn': context.invokedFunctionArn } : {}),
65
+ ...(process.env.AWS_REGION ? { 'aws.region': process.env.AWS_REGION } : {}),
66
+ }, null)
67
+ : null;
68
+ coldStart = false;
69
+ try {
70
+ return await (span ? core.runInSpan(span, () => handler(event, context)) : handler(event, context));
71
+ }
72
+ catch (err) {
73
+ span?.setError(err);
74
+ throw err;
75
+ }
76
+ finally {
77
+ span?.end();
78
+ if (!extension)
79
+ await flushWithTimeout(core, flushTimeoutMs);
80
+ }
81
+ };
82
+ }
83
+ //# sourceMappingURL=lambda.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lambda.js","sourceRoot":"","sources":["../../src/infra/lambda.ts"],"names":[],"mappings":"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"}
package/dist/tracker.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type SpanContext } from './context.js';
2
+ import { InfraAggregator } from './infra/aggregator.js';
2
3
  import type { ActiveSpan, PropagationTargets, SamplingContext, Span, SpanError, SpanKind, TrackerOptions, UserRef } from './types.js';
3
4
  export interface ResolvedOptions {
4
5
  apiKey: string;
@@ -19,6 +20,8 @@ export interface ResolvedOptions {
19
20
  enabled: boolean;
20
21
  debug: boolean;
21
22
  fetch: typeof globalThis.fetch;
23
+ infraEnabled: boolean;
24
+ infraIdleIntervalMs: number;
22
25
  }
23
26
  /** Release detectada do ambiente (CONTRATO.md §15): `${serviceName}@${sha}` para SHAs de CI/hosting. */
24
27
  export declare function detectRelease(serviceName: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
@@ -40,6 +43,19 @@ export declare class TrackerCore {
40
43
  private stopped;
41
44
  /** Spans ainda abertos, por spanId — permite `currentSpan()` a partir do contexto assíncrono. */
42
45
  private readonly active;
46
+ /** Agregados de infra (nunca amostrados); `null` quando `infra.enabled === false`. */
47
+ readonly infra: InfraAggregator | null;
48
+ /** Agregados já fechados aguardando envio (retentativa após falha). */
49
+ private infraPending;
50
+ /** A janela de infra venceu: o próximo flush fecha o agregador e envia junto dos spans. */
51
+ private infraDue;
52
+ private infraTimer;
53
+ private infraTimerMs;
54
+ /** Última resposta da ingestão: há sessão de observação ativa no painel (§27.4). */
55
+ live: boolean;
56
+ liveIntervalMs: number;
57
+ /** Callbacks de encerramento (reporters, timers de pool). */
58
+ private readonly disposers;
43
59
  constructor(options: TrackerOptions);
44
60
  get enabled(): boolean;
45
61
  currentContext(): SpanContext | undefined;
@@ -65,8 +81,24 @@ export declare class TrackerCore {
65
81
  /** Executa `fn` com `span` como contexto pai das operações internas. */
66
82
  runInSpan<T>(span: ActiveSpanImpl, fn: () => T): T;
67
83
  enqueue(span: Span): void;
68
- flush(): Promise<void>;
84
+ /**
85
+ * Envia a fila. `includeInfra` (default `true`) também fecha a janela de infra e envia os
86
+ * agregados no mesmo lote; o timer de spans chama com `false` para respeitar a janela de 60 s/5 s.
87
+ */
88
+ flush(includeInfra?: boolean): Promise<void>;
89
+ private hasInfraToSend;
69
90
  private doFlush;
91
+ private pushInfraPending;
92
+ /** Mesma política dos spans: acima do limite, descarta os agregados mais antigos. */
93
+ private trimInfraPending;
94
+ /** Intervalo atual da janela de infra: `liveIntervalMs` com o painel aberto, senão `idleIntervalMs`. */
95
+ infraIntervalMs(): number;
96
+ /** Arma (ou rearma, se o intervalo mudou) o timer que fecha a janela de infra. */
97
+ ensureInfraTimer(): void;
98
+ /** Lê `live`/`liveIntervalMs` da resposta da ingestão (piggyback, sem chamada extra). */
99
+ private readLiveFlag;
100
+ /** Registra uma função chamada no `shutdown()` (timers de reporters/pools). */
101
+ onShutdown(dispose: () => void): () => void;
70
102
  /** POST autenticado na API (ingest, check-ins, releases). Devolve a resposta ou `null` em falha de rede. */
71
103
  post(path: string, body: unknown, timeoutMs?: number): Promise<Response | null>;
72
104
  private send;
@@ -1 +1 @@
1
- {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../src/tracker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../src/tracker.ts"],"names":[],"mappings":"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"}
package/dist/tracker.js CHANGED
@@ -3,10 +3,17 @@ import { als, formatTraceparent, newSpanId, newTraceId, TRACEPARENT_HEADER } fro
3
3
  import { toSpanError } from './normalize.js';
4
4
  import { DEFAULT_SCRUB_FIELDS, matchesAny, scrubAttributes } from './scrub.js';
5
5
  import { API_KEY_HEADER, DEFAULT_ENDPOINT, readEnv } from './constants.js';
6
+ import { InfraAggregator } from './infra/aggregator.js';
6
7
  const DEFAULT_FLUSH_INTERVAL_MS = 2000;
7
8
  const DEFAULT_MAX_BATCH = 200;
8
9
  const DEFAULT_MAX_QUEUE = 5000;
9
10
  const MAX_SPANS_PER_REQUEST = 1000;
11
+ /** Limites de agregados de infra (CONTRATO.md §27.4: ≤ 500 por requisição). */
12
+ const MAX_INFRA_PER_REQUEST = 500;
13
+ const MAX_INFRA_PENDING = 2000;
14
+ const DEFAULT_INFRA_IDLE_INTERVAL_MS = 60_000;
15
+ const DEFAULT_INFRA_LIVE_INTERVAL_MS = 5_000;
16
+ const MIN_INFRA_INTERVAL_MS = 1_000;
10
17
  const RELEASE_ENV_VARS = [
11
18
  'VERCEL_GIT_COMMIT_SHA',
12
19
  'RAILWAY_GIT_COMMIT_SHA',
@@ -56,6 +63,8 @@ export function resolveOptions(options) {
56
63
  enabled: (options.enabled ?? true) && Boolean(options.apiKey),
57
64
  debug: options.debug ?? false,
58
65
  fetch: options.fetch ?? globalThis.fetch,
66
+ infraEnabled: options.infra?.enabled ?? true,
67
+ infraIdleIntervalMs: Math.max(MIN_INFRA_INTERVAL_MS, options.infra?.idleIntervalMs ?? DEFAULT_INFRA_IDLE_INTERVAL_MS),
59
68
  };
60
69
  }
61
70
  /** Decide se um destino recebe o `traceparent` (CONTRATO.md §16). */
@@ -89,8 +98,23 @@ export class TrackerCore {
89
98
  stopped = false;
90
99
  /** Spans ainda abertos, por spanId — permite `currentSpan()` a partir do contexto assíncrono. */
91
100
  active = new Map();
101
+ /** Agregados de infra (nunca amostrados); `null` quando `infra.enabled === false`. */
102
+ infra;
103
+ /** Agregados já fechados aguardando envio (retentativa após falha). */
104
+ infraPending = [];
105
+ /** A janela de infra venceu: o próximo flush fecha o agregador e envia junto dos spans. */
106
+ infraDue = false;
107
+ infraTimer = null;
108
+ infraTimerMs = 0;
109
+ /** Última resposta da ingestão: há sessão de observação ativa no painel (§27.4). */
110
+ live = false;
111
+ liveIntervalMs = DEFAULT_INFRA_LIVE_INTERVAL_MS;
112
+ /** Callbacks de encerramento (reporters, timers de pool). */
113
+ disposers = new Set();
92
114
  constructor(options) {
93
115
  this.options = resolveOptions(options);
116
+ this.infra =
117
+ this.options.enabled && this.options.infraEnabled ? new InfraAggregator({ onRecord: () => this.ensureInfraTimer() }) : null;
94
118
  if (!this.options.enabled) {
95
119
  if (!options.apiKey && (options.enabled ?? true)) {
96
120
  console.warn('[fg-tracker] apiKey ausente: tracker desabilitado.');
@@ -98,7 +122,7 @@ export class TrackerCore {
98
122
  return;
99
123
  }
100
124
  this.timer = setInterval(() => {
101
- void this.flush();
125
+ void this.flush(false);
102
126
  }, this.options.flushIntervalMs);
103
127
  // Não segura o processo vivo só por causa do timer.
104
128
  if (typeof this.timer === 'object' && this.timer && 'unref' in this.timer)
@@ -226,34 +250,123 @@ export class TrackerCore {
226
250
  this.queue.splice(0, this.queue.length - this.options.maxQueueSize);
227
251
  }
228
252
  if (this.queue.length >= this.options.maxBatchSize)
229
- void this.flush();
253
+ void this.flush(false);
230
254
  }
231
- async flush() {
255
+ /**
256
+ * Envia a fila. `includeInfra` (default `true`) também fecha a janela de infra e envia os
257
+ * agregados no mesmo lote; o timer de spans chama com `false` para respeitar a janela de 60 s/5 s.
258
+ */
259
+ async flush(includeInfra = true) {
232
260
  if (!this.options.enabled)
233
261
  return;
234
- if (this.flushing)
235
- return this.flushing;
236
- if (this.queue.length === 0)
262
+ if (includeInfra && this.infra)
263
+ this.infraDue = true;
264
+ if (this.flushing) {
265
+ // Um envio em andamento pode já ter passado da checagem de infra: encadeia outro.
266
+ return includeInfra ? this.flushing.then(() => (this.hasInfraToSend() ? this.flush(true) : undefined)) : this.flushing;
267
+ }
268
+ if (this.queue.length === 0 && !this.hasInfraToSend()) {
269
+ this.infraDue = false;
237
270
  return;
271
+ }
238
272
  this.flushing = this.doFlush().finally(() => {
239
273
  this.flushing = null;
240
274
  });
241
275
  return this.flushing;
242
276
  }
277
+ hasInfraToSend() {
278
+ return this.infraPending.length > 0 || (this.infraDue && Boolean(this.infra?.hasData()));
279
+ }
243
280
  async doFlush() {
244
- while (this.queue.length > 0) {
281
+ for (;;) {
282
+ if (this.infraDue && this.infra) {
283
+ this.infraDue = false;
284
+ if (this.infra.hasData())
285
+ this.pushInfraPending(this.infra.drain());
286
+ }
245
287
  const batch = this.queue.splice(0, MAX_SPANS_PER_REQUEST);
246
- const ok = await this.send(batch);
288
+ const infra = this.infraPending.splice(0, MAX_INFRA_PER_REQUEST);
289
+ if (batch.length === 0 && infra.length === 0)
290
+ return;
291
+ const ok = await this.send(batch, infra);
247
292
  if (!ok) {
248
293
  // Devolve para a frente da fila e tenta no próximo flush.
249
294
  this.queue.unshift(...batch);
250
295
  if (this.queue.length > this.options.maxQueueSize) {
251
296
  this.queue.splice(0, this.queue.length - this.options.maxQueueSize);
252
297
  }
298
+ if (infra.length > 0) {
299
+ this.infraPending.unshift(...infra);
300
+ this.trimInfraPending();
301
+ }
253
302
  return;
254
303
  }
255
304
  }
256
305
  }
306
+ pushInfraPending(items) {
307
+ this.infraPending.push(...items);
308
+ this.trimInfraPending();
309
+ }
310
+ /** Mesma política dos spans: acima do limite, descarta os agregados mais antigos. */
311
+ trimInfraPending() {
312
+ if (this.infraPending.length > MAX_INFRA_PENDING) {
313
+ this.infraPending.splice(0, this.infraPending.length - MAX_INFRA_PENDING);
314
+ }
315
+ }
316
+ /** Intervalo atual da janela de infra: `liveIntervalMs` com o painel aberto, senão `idleIntervalMs`. */
317
+ infraIntervalMs() {
318
+ return this.live ? this.liveIntervalMs : this.options.infraIdleIntervalMs;
319
+ }
320
+ /** Arma (ou rearma, se o intervalo mudou) o timer que fecha a janela de infra. */
321
+ ensureInfraTimer() {
322
+ if (!this.infra || !this.enabled)
323
+ return;
324
+ const interval = this.infraIntervalMs();
325
+ if (this.infraTimer && this.infraTimerMs === interval)
326
+ return;
327
+ if (this.infraTimer)
328
+ clearTimeout(this.infraTimer);
329
+ this.infraTimerMs = interval;
330
+ this.infraTimer = setTimeout(() => {
331
+ this.infraTimer = null;
332
+ void this.flush(true).finally(() => {
333
+ // Com painel aberto mantém o ritmo mesmo sem dados; ocioso, só rearma quando houver algo.
334
+ if (this.enabled && (this.live || this.infra?.hasData() || this.infraPending.length > 0))
335
+ this.ensureInfraTimer();
336
+ });
337
+ }, interval);
338
+ if (typeof this.infraTimer === 'object' && this.infraTimer && 'unref' in this.infraTimer)
339
+ this.infraTimer.unref();
340
+ }
341
+ /** Lê `live`/`liveIntervalMs` da resposta da ingestão (piggyback, sem chamada extra). */
342
+ async readLiveFlag(res) {
343
+ if (!this.infra || res.status === 204 || typeof res.json !== 'function')
344
+ return;
345
+ let body;
346
+ try {
347
+ body = await res.json();
348
+ }
349
+ catch {
350
+ return;
351
+ }
352
+ if (!body || typeof body !== 'object')
353
+ return;
354
+ const { live, liveIntervalMs } = body;
355
+ if (typeof live !== 'boolean')
356
+ return;
357
+ const previousInterval = this.infraIntervalMs();
358
+ this.live = live;
359
+ if (typeof liveIntervalMs === 'number' && Number.isFinite(liveIntervalMs)) {
360
+ this.liveIntervalMs = Math.max(MIN_INFRA_INTERVAL_MS, liveIntervalMs);
361
+ }
362
+ if (this.infraIntervalMs() !== previousInterval || (live && !this.infraTimer))
363
+ this.ensureInfraTimer();
364
+ }
365
+ /** Registra uma função chamada no `shutdown()` (timers de reporters/pools). */
366
+ onShutdown(dispose) {
367
+ this.disposers.add(dispose);
368
+ return () => this.disposers.delete(dispose);
369
+ }
257
370
  /** POST autenticado na API (ingest, check-ins, releases). Devolve a resposta ou `null` em falha de rede. */
258
371
  async post(path, body, timeoutMs = 10_000) {
259
372
  try {
@@ -269,7 +382,7 @@ export class TrackerCore {
269
382
  return null;
270
383
  }
271
384
  }
272
- async send(spans) {
385
+ async send(spans, infra = []) {
273
386
  const byTrace = new Map();
274
387
  for (const span of spans) {
275
388
  const { traceId, ...rest } = span;
@@ -280,7 +393,9 @@ export class TrackerCore {
280
393
  }
281
394
  list.push(rest);
282
395
  }
283
- const body = JSON.stringify({ traces: [...byTrace].map(([traceId, s]) => ({ traceId, spans: s })) });
396
+ const traces = [...byTrace].map(([traceId, s]) => ({ traceId, spans: s }));
397
+ // Sem agregados o body continua idêntico ao das versões anteriores.
398
+ const body = JSON.stringify(infra.length > 0 ? { traces, infra } : { traces });
284
399
  try {
285
400
  const res = await this.options.fetch(`${this.options.endpoint}/v1/ingest`, {
286
401
  method: 'POST',
@@ -298,8 +413,10 @@ export class TrackerCore {
298
413
  if (this.options.debug)
299
414
  console.info('[fg-tracker] envio restabelecido.');
300
415
  }
301
- if (this.options.debug)
302
- console.info(`[fg-tracker] enviados ${spans.length} spans em ${byTrace.size} traces.`);
416
+ await this.readLiveFlag(res);
417
+ if (this.options.debug) {
418
+ console.info(`[fg-tracker] enviados ${spans.length} spans em ${byTrace.size} traces${infra.length > 0 ? ` e ${infra.length} agregados de infra` : ''}.`);
419
+ }
303
420
  return true;
304
421
  }
305
422
  catch (err) {
@@ -318,7 +435,20 @@ export class TrackerCore {
318
435
  clearInterval(this.timer);
319
436
  this.timer = null;
320
437
  }
321
- await this.flush();
438
+ if (this.infraTimer) {
439
+ clearTimeout(this.infraTimer);
440
+ this.infraTimer = null;
441
+ }
442
+ for (const dispose of this.disposers) {
443
+ try {
444
+ dispose();
445
+ }
446
+ catch {
447
+ /* encerramento nunca lança */
448
+ }
449
+ }
450
+ this.disposers.clear();
451
+ await this.flush(true);
322
452
  this.stopped = true;
323
453
  }
324
454
  }