fg-tracker-sdk 0.11.0 → 0.14.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/CHANGELOG.md +34 -0
- package/README.md +88 -34
- package/dist/context.d.ts +23 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -5
- package/dist/context.js.map +1 -1
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +3 -1
- package/dist/database.js.map +1 -1
- package/dist/express.js +1 -1
- package/dist/express.js.map +1 -1
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +31 -2
- package/dist/fetch.js.map +1 -1
- package/dist/genai/bedrock.d.ts +22 -0
- package/dist/genai/bedrock.d.ts.map +1 -0
- package/dist/genai/bedrock.js +161 -0
- package/dist/genai/bedrock.js.map +1 -0
- package/dist/genai/call.d.ts +96 -0
- package/dist/genai/call.d.ts.map +1 -0
- package/dist/genai/call.js +260 -0
- package/dist/genai/call.js.map +1 -0
- package/dist/genai/config.d.ts +71 -0
- package/dist/genai/config.d.ts.map +1 -0
- package/dist/genai/config.js +110 -0
- package/dist/genai/config.js.map +1 -0
- package/dist/genai/http.d.ts +49 -0
- package/dist/genai/http.d.ts.map +1 -0
- package/dist/genai/http.js +795 -0
- package/dist/genai/http.js.map +1 -0
- package/dist/genai/semconv.d.ts +64 -0
- package/dist/genai/semconv.d.ts.map +1 -0
- package/dist/genai/semconv.js +52 -0
- package/dist/genai/semconv.js.map +1 -0
- package/dist/genai/sse.d.ts +26 -0
- package/dist/genai/sse.d.ts.map +1 -0
- package/dist/genai/sse.js +100 -0
- package/dist/genai/sse.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/infra/aws.d.ts +7 -4
- package/dist/infra/aws.d.ts.map +1 -1
- package/dist/infra/aws.js +50 -9
- package/dist/infra/aws.js.map +1 -1
- package/dist/infra/lambda.js +1 -1
- package/dist/infra/lambda.js.map +1 -1
- package/dist/infra/messaging.d.ts +10 -2
- package/dist/infra/messaging.d.ts.map +1 -1
- package/dist/infra/messaging.js +22 -3
- package/dist/infra/messaging.js.map +1 -1
- package/dist/next.js +1 -1
- package/dist/next.js.map +1 -1
- package/dist/normalize.d.ts +58 -8
- package/dist/normalize.d.ts.map +1 -1
- package/dist/normalize.js +383 -79
- package/dist/normalize.js.map +1 -1
- package/dist/pg.d.ts +12 -2
- package/dist/pg.d.ts.map +1 -1
- package/dist/pg.js +464 -213
- package/dist/pg.js.map +1 -1
- package/dist/tracker.d.ts +36 -5
- package/dist/tracker.d.ts.map +1 -1
- package/dist/tracker.js +55 -6
- package/dist/tracker.js.map +1 -1
- package/dist/types.d.ts +27 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -1
package/dist/pg.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AsyncResource } from 'node:async_hooks';
|
|
2
|
+
import { errorMonitor } from 'node:events';
|
|
2
3
|
import { performance } from 'node:perf_hooks';
|
|
3
4
|
import { databaseHost } from './database.js';
|
|
4
|
-
import {
|
|
5
|
+
import { analyzeSqlCommands, databaseErrorAttributes, sqlSpanFields } from './normalize.js';
|
|
5
6
|
import { MIN_POOL_WAIT_MS } from './pool-wait.js';
|
|
6
7
|
const PATCHED = Symbol.for('fg-tracker.pg.patched');
|
|
7
8
|
/** Marca usada pelo `stread-track-sdk` ≤ 0.3: evita instrumentar duas vezes quando as duas versões convivem. */
|
|
@@ -10,8 +11,13 @@ const CLIENT_PATCHED = Symbol.for('fg-tracker.pg.clientPatched');
|
|
|
10
11
|
function extractSql(arg) {
|
|
11
12
|
if (typeof arg === 'string')
|
|
12
13
|
return arg;
|
|
13
|
-
if (typeof arg === 'object' && arg !== null
|
|
14
|
-
|
|
14
|
+
if (typeof arg === 'object' && arg !== null) {
|
|
15
|
+
if (typeof arg.text === 'string')
|
|
16
|
+
return arg.text;
|
|
17
|
+
// `pg-query-stream`: o texto fica no cursor interno.
|
|
18
|
+
const cursor = arg.cursor;
|
|
19
|
+
if (typeof arg.submit === 'function' && typeof cursor?.text === 'string')
|
|
20
|
+
return cursor.text;
|
|
15
21
|
}
|
|
16
22
|
return null;
|
|
17
23
|
}
|
|
@@ -33,104 +39,205 @@ function recordDbError(span, err) {
|
|
|
33
39
|
for (const [key, value] of Object.entries(databaseErrorAttributes(err)))
|
|
34
40
|
span.setAttribute(key, value);
|
|
35
41
|
}
|
|
42
|
+
function isSubmittable(value) {
|
|
43
|
+
return typeof value === 'object' && value !== null && typeof value.submit === 'function';
|
|
44
|
+
}
|
|
45
|
+
/** Callback da aplicação no contexto de quem enviou a query, com o `this` que o `pg` lhe dá. */
|
|
46
|
+
function bindCallback(cb, before) {
|
|
47
|
+
return AsyncResource.bind(function fgTrackerQueryCallback(...values) {
|
|
48
|
+
before(values);
|
|
49
|
+
return Reflect.apply(cb, this, values);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
36
52
|
/**
|
|
37
53
|
* Troca o callback da query, venha ele no último argumento (`query(text, values, cb)`) ou no
|
|
38
|
-
* objeto de configuração (`query({ text, callback })`, que o `pg` também aceita), pelo
|
|
39
|
-
* `
|
|
54
|
+
* objeto de configuração (`query({ text, callback })`, que o `pg` também aceita), pelo que avisa
|
|
55
|
+
* `finish` antes de chamá-lo. O objeto do usuário não é alterado (vai uma cópia). `false` quando não
|
|
56
|
+
* há callback.
|
|
40
57
|
*/
|
|
41
|
-
function wrapCallback(args,
|
|
42
|
-
|
|
43
|
-
configCallback) {
|
|
58
|
+
function wrapCallback(args, finish, configCallback) {
|
|
59
|
+
const before = (values) => finish(values[0], values[1]);
|
|
44
60
|
const last = args[args.length - 1];
|
|
45
61
|
if (typeof last === 'function') {
|
|
46
|
-
args[args.length - 1] =
|
|
62
|
+
args[args.length - 1] = bindCallback(last, before);
|
|
47
63
|
return true;
|
|
48
64
|
}
|
|
49
65
|
const config = args[0];
|
|
50
66
|
// `query(config)` ou `query(config, values)`: com uma função em outro argumento, ela vence (o `pg` faz igual).
|
|
51
67
|
const onlyValues = args.length === 1 || (args.length === 2 && typeof args[1] !== 'function');
|
|
52
|
-
if (configCallback && onlyValues && config && typeof config === 'object'
|
|
68
|
+
if (configCallback && onlyValues && config && typeof config === 'object') {
|
|
53
69
|
const callback = config.callback;
|
|
54
70
|
if (typeof callback === 'function') {
|
|
55
|
-
args[0] = { ...config, callback:
|
|
71
|
+
args[0] = { ...config, callback: bindCallback(callback, before) };
|
|
56
72
|
return true;
|
|
57
73
|
}
|
|
58
74
|
}
|
|
59
75
|
return false;
|
|
60
76
|
}
|
|
61
77
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
78
|
+
* Observa um Submittable até o fim real, sem mudar o que a aplicação vê. Avisa `finish` uma vez, no
|
|
79
|
+
* primeiro destes sinais:
|
|
80
|
+
* - o callback do objeto (`new pg.Query(text, cb)`) ou o passado em `client.query(q, cb)`: no erro, o
|
|
81
|
+
* `pg` chama o callback e não emite `error`;
|
|
82
|
+
* - `handleReadyForQuery`/`handleError`, que o `pg` chama no objeto ativo (o fim no servidor e o
|
|
83
|
+
* erro, inclusive o do cursor sem listener de `error`, que não emite nada);
|
|
84
|
+
* - os eventos `end`, `close` (stream destruído antes do fim) e o erro por `events.errorMonitor`,
|
|
85
|
+
* que é avisado antes dos listeners e não conta como um: sem listener da aplicação, o erro continua
|
|
86
|
+
* sendo lançado como sem o SDK.
|
|
87
|
+
* Tudo o que foi instalado sai no primeiro sinal.
|
|
65
88
|
*/
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
function observeSubmittable(q, args, finish) {
|
|
90
|
+
const undo = [];
|
|
91
|
+
const done = (err, result) => {
|
|
92
|
+
for (const step of undo.splice(0)) {
|
|
93
|
+
try {
|
|
94
|
+
step();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* nada a desfazer */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
finish(err, result);
|
|
101
|
+
};
|
|
102
|
+
const before = (values) => done(values[0], values[1]);
|
|
103
|
+
if (typeof q.callback === 'function') {
|
|
104
|
+
// Fica no objeto: no erro, o `pg` chama o callback depois do `handleError` (o primeiro sinal), e
|
|
105
|
+
// ele precisa rodar no contexto de quem enviou. Chamado de novo, só repassa.
|
|
106
|
+
q.callback = bindCallback(q.callback, before);
|
|
107
|
+
}
|
|
108
|
+
else if (typeof args[1] === 'function') {
|
|
109
|
+
args[1] = bindCallback(args[1], before);
|
|
110
|
+
}
|
|
111
|
+
else if (typeof args[2] === 'function') {
|
|
112
|
+
args[2] = bindCallback(args[2], before);
|
|
113
|
+
}
|
|
114
|
+
const hook = (name, when) => {
|
|
115
|
+
const original = q[name];
|
|
116
|
+
if (typeof original !== 'function')
|
|
117
|
+
return;
|
|
118
|
+
const own = Object.hasOwn(q, name);
|
|
119
|
+
const hooked = function fgTrackerSubmittableHook(...values) {
|
|
120
|
+
if (when === 'before')
|
|
121
|
+
done(values[0], undefined);
|
|
122
|
+
try {
|
|
123
|
+
return Reflect.apply(original, this, values);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
if (when === 'after')
|
|
127
|
+
done(undefined, q._results ?? q._result);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
q[name] = hooked;
|
|
131
|
+
undo.push(() => {
|
|
132
|
+
if (q[name] !== hooked)
|
|
133
|
+
return;
|
|
134
|
+
if (own)
|
|
135
|
+
q[name] = original;
|
|
136
|
+
else
|
|
137
|
+
delete q[name];
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
hook('handleError', 'before');
|
|
141
|
+
hook('handleReadyForQuery', 'after');
|
|
142
|
+
if (typeof q.once === 'function' && typeof q.removeListener === 'function') {
|
|
143
|
+
const onEnd = (result) => done(undefined, result);
|
|
144
|
+
const onError = (err) => done(err, undefined);
|
|
145
|
+
const onClose = () => done(undefined, undefined);
|
|
146
|
+
q.once('end', onEnd);
|
|
147
|
+
q.once(errorMonitor, onError);
|
|
148
|
+
q.once('close', onClose);
|
|
149
|
+
undo.push(() => {
|
|
150
|
+
q.removeListener('end', onEnd);
|
|
151
|
+
q.removeListener(errorMonitor, onError);
|
|
152
|
+
q.removeListener('close', onClose);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Chama `original(...args)` e avisa `onFinish` uma única vez, quando a query termina de verdade:
|
|
158
|
+
* callback (nos argumentos ou em `config.callback`), Promise, Submittable ({@link observeSubmittable})
|
|
159
|
+
* ou, num client compatível que devolve outra coisa, na hora. `register` guarda a finalização de
|
|
160
|
+
* quem ainda está em voo (a conexão encerrada termina o que sobrar).
|
|
161
|
+
*/
|
|
162
|
+
function callTracked(original, args, onFinish,
|
|
163
|
+
/** `pool.query` ignora `config.callback` (devolve Promise): só o `Client` o usa. */
|
|
164
|
+
configCallback, register,
|
|
165
|
+
/**
|
|
166
|
+
* Executa a chamada ao `pg` (o `runQuery` a põe dentro do span da query, para o que o `pg` fizer
|
|
167
|
+
* nela). Os callbacks e a continuação da Promise são ligados antes, fora dela: rodam no contexto de
|
|
168
|
+
* quem enviou a query, nunca no do span da query, que já terá fechado.
|
|
169
|
+
*/
|
|
170
|
+
invoke = (call) => call()) {
|
|
171
|
+
let finished = false;
|
|
172
|
+
let unregister;
|
|
173
|
+
const finish = (err, result) => {
|
|
174
|
+
if (finished)
|
|
175
|
+
return;
|
|
176
|
+
finished = true;
|
|
177
|
+
unregister?.();
|
|
78
178
|
try {
|
|
79
|
-
|
|
179
|
+
onFinish(err, result);
|
|
80
180
|
}
|
|
81
181
|
catch {
|
|
82
|
-
/* acompanhamento
|
|
182
|
+
/* acompanhamento nunca quebra a query */
|
|
83
183
|
}
|
|
84
184
|
};
|
|
185
|
+
const submittable = isSubmittable(args[0]) ? args[0] : null;
|
|
85
186
|
// O pg chama o callback no contexto do socket (o de quem abriu a conexão): volta ao de quem
|
|
86
187
|
// enviou a query, para a finalização e o código do usuário.
|
|
87
|
-
const withCallback = wrapCallback(args,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return core.runInSpan(span, () => original(...args));
|
|
188
|
+
const withCallback = submittable ? false : wrapCallback(args, finish, configCallback);
|
|
189
|
+
if (submittable)
|
|
190
|
+
observeSubmittable(submittable, args, finish);
|
|
191
|
+
if (submittable || withCallback)
|
|
192
|
+
unregister = register?.(finish);
|
|
93
193
|
let out;
|
|
94
194
|
try {
|
|
95
|
-
out =
|
|
195
|
+
out = invoke(() => original(...args));
|
|
96
196
|
}
|
|
97
197
|
catch (err) {
|
|
98
|
-
|
|
198
|
+
finish(err, undefined);
|
|
99
199
|
throw err;
|
|
100
200
|
}
|
|
201
|
+
if (submittable || withCallback)
|
|
202
|
+
return out;
|
|
101
203
|
if (out && typeof out.then === 'function') {
|
|
102
204
|
return out.then((result) => {
|
|
103
|
-
|
|
205
|
+
finish(undefined, result);
|
|
104
206
|
return result;
|
|
105
207
|
}, (err) => {
|
|
106
|
-
|
|
208
|
+
finish(err, undefined);
|
|
107
209
|
throw err;
|
|
108
210
|
});
|
|
109
211
|
}
|
|
110
|
-
|
|
212
|
+
finish(undefined, out);
|
|
111
213
|
return out;
|
|
112
214
|
}
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Executa uma query dentro de um span `db.query` (nome = SQL normalizado; `rows`, fingerprint,
|
|
217
|
+
* semconv). Suporta as assinaturas (text), (config), (text, values), (text, cb), (text, values, cb)
|
|
218
|
+
* e Submittables; o span fecha no fim real da query.
|
|
219
|
+
*/
|
|
220
|
+
function runQuery(core, original, args, sql, baseAttributes, options = {}) {
|
|
221
|
+
const fields = sqlSpanFields(sql, 'postgresql');
|
|
222
|
+
const span = core.startSpan('db.query', fields.name, { rows: null, ...baseAttributes, ...fields.attributes }, options.parent);
|
|
223
|
+
options.onStart?.(span);
|
|
224
|
+
const settle = (err, result) => {
|
|
225
|
+
if (err)
|
|
226
|
+
recordDbError(span, err);
|
|
227
|
+
else
|
|
228
|
+
span.setAttribute('rows', rowCount(result));
|
|
229
|
+
try {
|
|
230
|
+
options.beforeEnd?.(err, result, span);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
/* atributos são best-effort */
|
|
234
|
+
}
|
|
235
|
+
span.end();
|
|
236
|
+
options.onSettled?.(err, result);
|
|
237
|
+
};
|
|
238
|
+
// Só a chamada ao `pg` roda no span da query; o callback da aplicação volta ao contexto de quem
|
|
239
|
+
// enviou (a raiz dela, com as decisões dela), como antes da 0.14.
|
|
240
|
+
return callTracked(original, args, settle, options.clientQuery === true, options.register, (call) => core.runInSpan(span, call));
|
|
134
241
|
}
|
|
135
242
|
/**
|
|
136
243
|
* Estado da transação no servidor depois da última resposta (`ReadyForQuery`): `I` fora de
|
|
@@ -156,6 +263,17 @@ const SYNTAX_ERROR = '42601';
|
|
|
156
263
|
function errorCode(err) {
|
|
157
264
|
return typeof err === 'object' && err !== null ? err.code : undefined;
|
|
158
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Erro de sintaxe da análise do texto inteiro (nada rodou). O 42601 de um `EXECUTE` dinâmico em
|
|
268
|
+
* PL/pgSQL acontece em tempo de execução, depois do que veio antes, e o servidor o marca com o
|
|
269
|
+
* contexto (`where`) e a query interna (`internalQuery`).
|
|
270
|
+
*/
|
|
271
|
+
function isParseError(err) {
|
|
272
|
+
if (errorCode(err) !== SYNTAX_ERROR || typeof err !== 'object' || err === null)
|
|
273
|
+
return false;
|
|
274
|
+
const { where, internalQuery } = err;
|
|
275
|
+
return !where && !internalQuery;
|
|
276
|
+
}
|
|
159
277
|
/** Texto de um prepared statement executado só pelo nome, como o `pg` o guardou na conexão. */
|
|
160
278
|
function preparedText(client, config) {
|
|
161
279
|
const name = config && typeof config === 'object' ? config.name : undefined;
|
|
@@ -174,36 +292,18 @@ function answeredCommand(result) {
|
|
|
174
292
|
const last = Array.isArray(result) ? result[result.length - 1] : result;
|
|
175
293
|
return last?.command;
|
|
176
294
|
}
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
};
|
|
190
|
-
const withCallback = wrapCallback(args, (cb) => AsyncResource.bind((err, result) => {
|
|
191
|
-
settle(err, result);
|
|
192
|
-
cb(err, result);
|
|
193
|
-
}), true);
|
|
194
|
-
if (withCallback)
|
|
195
|
-
return original(...args);
|
|
196
|
-
const out = original(...args);
|
|
197
|
-
if (out && typeof out.then === 'function') {
|
|
198
|
-
return out.then((result) => {
|
|
199
|
-
settle(undefined, result);
|
|
200
|
-
return result;
|
|
201
|
-
}, (err) => {
|
|
202
|
-
settle(err, undefined);
|
|
203
|
-
throw err;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
return out;
|
|
295
|
+
/** Texto desconhecido (nome preparado fora desta conexão): pode encerrar a transação. */
|
|
296
|
+
const UNKNOWN_TEXT = { count: 1, transaction: [], closes: true, ends: true, startsWithBegin: false };
|
|
297
|
+
/** Contexto que a transação aceita como dona: mesma execução e mesmas decisões de amostragem e exclusão. */
|
|
298
|
+
function sameExecution(core, sender, tx) {
|
|
299
|
+
if (core.localRootOfContext(sender) === tx.localRootId)
|
|
300
|
+
return true;
|
|
301
|
+
// Raiz de mensagem aberta pelo `trackMessage` dentro da raiz da transação (lote com BEGIN antes do laço).
|
|
302
|
+
const execution = core.executionRootOfContext(sender);
|
|
303
|
+
return execution !== undefined && execution === tx.localRootId;
|
|
304
|
+
}
|
|
305
|
+
function sameDecisions(sender, tx) {
|
|
306
|
+
return (sender.sampled !== false) === tx.sampled && (sender.ignored === true) === tx.ignored;
|
|
207
307
|
}
|
|
208
308
|
function poolStats(pool) {
|
|
209
309
|
const out = {};
|
|
@@ -222,8 +322,18 @@ function poolStats(pool) {
|
|
|
222
322
|
* - `pool.connect()`: span `custom` `pg connect` com a espera por conexão (`db.poolWaitMs`) e a
|
|
223
323
|
* ocupação do pool; o client devolvido também gera spans `db.query`;
|
|
224
324
|
* - transação num client (`BEGIN` … `COMMIT`/`ROLLBACK`): span `custom` `pg transaction`, pai das
|
|
225
|
-
* queries dela, com `db.transaction.outcome` (`commit`, `rollback` ou `released_open`
|
|
226
|
-
* client volta ao pool com a transação aberta).
|
|
325
|
+
* queries dela, com `db.transaction.outcome` (`commit`, `rollback`, `prepared` ou `released_open`
|
|
326
|
+
* quando o client volta ao pool com a transação aberta).
|
|
327
|
+
*
|
|
328
|
+
* Quem é pai de uma query num client com transação aberta (CONTRATO.md §16.1):
|
|
329
|
+
* - a mesma execução que abriu a transação (mesma raiz local, ou a raiz de mensagem que o
|
|
330
|
+
* `trackMessage` abriu dentro dela), com as mesmas decisões de amostragem e exclusão: a transação,
|
|
331
|
+
* salvo com um COMMIT/ROLLBACK dela já a caminho;
|
|
332
|
+
* - outra execução (outra requisição, mesmo do mesmo trace distribuído): o próprio contexto, com as
|
|
333
|
+
* próprias decisões; se rodar dentro da transação confirmada, leva `links` para ela e
|
|
334
|
+
* `db.transaction.foreign: true`;
|
|
335
|
+
* - quem envia sem contexto: a transação só na janela confirmada pelo servidor (BEGIN respondido,
|
|
336
|
+
* nenhum comando de encerramento em voo em nenhuma leitura do texto); fora dela, nenhum span.
|
|
227
337
|
*/
|
|
228
338
|
export function instrumentPgPool(core, pool) {
|
|
229
339
|
const target = pool;
|
|
@@ -241,7 +351,7 @@ export function instrumentPgPool(core, pool) {
|
|
|
241
351
|
const stateOf = (client) => {
|
|
242
352
|
let state = states.get(client);
|
|
243
353
|
if (!state) {
|
|
244
|
-
state = { tx: null,
|
|
354
|
+
state = { tx: null, queued: null, internal: false, seq: 0, inFlight: new Map() };
|
|
245
355
|
states.set(client, state);
|
|
246
356
|
}
|
|
247
357
|
return state;
|
|
@@ -251,118 +361,198 @@ export function instrumentPgPool(core, pool) {
|
|
|
251
361
|
* próprio pool. O span da query recebe a espera (`db.poolWaitMs`) em vez de um span `pg connect`.
|
|
252
362
|
*/
|
|
253
363
|
let poolQuery = null;
|
|
254
|
-
const
|
|
364
|
+
const finishSpan = (tx, outcome, err) => {
|
|
365
|
+
tx.span.setAttribute('db.transaction.outcome', outcome);
|
|
366
|
+
if (err)
|
|
367
|
+
recordDbError(tx.span, err);
|
|
368
|
+
tx.span.end();
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Fecha a transação atual. Pelo fim observado no servidor (`promote`), o BEGIN que esperava na fila
|
|
372
|
+
* passa a ser a transação atual (ainda sem confirmação); no release e na conexão encerrada, ele
|
|
373
|
+
* fecha junto.
|
|
374
|
+
*/
|
|
375
|
+
const endTransaction = (state, outcome, err, promote = false) => {
|
|
255
376
|
const tx = state.tx;
|
|
256
|
-
if (
|
|
377
|
+
if (tx) {
|
|
378
|
+
state.tx = null;
|
|
379
|
+
finishSpan(tx, outcome, err);
|
|
380
|
+
}
|
|
381
|
+
const queued = state.queued;
|
|
382
|
+
if (!queued)
|
|
257
383
|
return;
|
|
258
|
-
state.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
tx.end();
|
|
384
|
+
state.queued = null;
|
|
385
|
+
if (promote)
|
|
386
|
+
state.tx = queued;
|
|
387
|
+
else
|
|
388
|
+
finishSpan(queued, outcome);
|
|
264
389
|
};
|
|
265
390
|
/**
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* método e para erros (o callback de erro do `pg` roda antes do `ReadyForQuery`, com o estado
|
|
269
|
-
* anterior): todos os comandos de transação do texto, em ordem.
|
|
391
|
+
* Comando de encerramento de `tx` ainda sem fim real: enviado depois do BEGIN dela ou no mesmo
|
|
392
|
+
* envio (`BEGIN; …; COMMIT`).
|
|
270
393
|
*/
|
|
271
|
-
const
|
|
272
|
-
|
|
394
|
+
const closingAfter = (state, tx, which) => {
|
|
395
|
+
if (!tx)
|
|
396
|
+
return false;
|
|
397
|
+
for (const [seq, query] of state.inFlight)
|
|
398
|
+
if (seq >= tx.seq && query[which])
|
|
399
|
+
return true;
|
|
400
|
+
return false;
|
|
401
|
+
};
|
|
402
|
+
const closingInFlight = (state, which) => closingAfter(state, state.tx, which);
|
|
403
|
+
/**
|
|
404
|
+
* Fecha, abre ou confirma a transação depois do fim real de uma query. Fonte primária: o estado
|
|
405
|
+
* que o servidor devolve no `ReadyForQuery` (`getTransactionStatus()` ou o evento da conexão). Sem
|
|
406
|
+
* estado nenhum (client compatível), os comandos de transação do texto, em ordem.
|
|
407
|
+
*/
|
|
408
|
+
const settleTransaction = (client, state, analysis, err, result,
|
|
409
|
+
/** Abre o span da transação no contexto de quem enviou a query; `null` sem contexto. */
|
|
273
410
|
startTx,
|
|
274
411
|
/** O span da transação foi aberto por esta query (o texto começa com BEGIN). */
|
|
275
412
|
openedHere,
|
|
276
|
-
/** Ordem de envio desta query
|
|
277
|
-
seq
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
};
|
|
286
|
-
const outcomeFor = (last) => {
|
|
287
|
-
if (err)
|
|
288
|
-
return last === 'commit' ? 'commit_failed' : last === 'begin' ? 'begin_failed' : 'rollback';
|
|
413
|
+
/** Ordem de envio desta query. */
|
|
414
|
+
seq,
|
|
415
|
+
/** Fim real: a query sai da lista de em voo (no erro antes do `ReadyForQuery`, só quando ele chega). */
|
|
416
|
+
release) => {
|
|
417
|
+
const failed = err !== undefined && err !== null;
|
|
418
|
+
const last = analysis.transaction[analysis.transaction.length - 1] ?? null;
|
|
419
|
+
const outcomeFor = () => {
|
|
420
|
+
if (failed)
|
|
421
|
+
return last === 'commit' ? 'commit_failed' : last === 'begin' ? 'begin_failed' : last === 'prepare' ? 'prepare_failed' : 'rollback';
|
|
289
422
|
const answered = answeredCommand(result);
|
|
290
|
-
|
|
423
|
+
if (answered === 'ROLLBACK')
|
|
424
|
+
return 'rollback';
|
|
425
|
+
if (last === 'prepare')
|
|
426
|
+
return 'prepared';
|
|
427
|
+
return answered !== 'COMMIT' && last === 'rollback' ? 'rollback' : 'commit';
|
|
428
|
+
};
|
|
429
|
+
const fromServer = (status) => {
|
|
430
|
+
// Query enviada antes do BEGIN que abriu a transação atual (fila do `pg`): o estado que a
|
|
431
|
+
// resposta dela traz é de antes da transação e não a fecha.
|
|
432
|
+
if (state.tx && seq < state.tx.seq)
|
|
433
|
+
return;
|
|
434
|
+
if (status === 'I') {
|
|
435
|
+
if (state.tx)
|
|
436
|
+
endTransaction(state, outcomeFor(), err, true);
|
|
437
|
+
}
|
|
438
|
+
else if (state.tx) {
|
|
439
|
+
state.tx.confirmed = true;
|
|
440
|
+
}
|
|
441
|
+
else if (startTx) {
|
|
442
|
+
// `SELECT 1; BEGIN`, BEGIN preparado: a transação existe a partir desta resposta.
|
|
443
|
+
state.tx = { span: startTx(), seq, confirmed: true };
|
|
444
|
+
}
|
|
291
445
|
};
|
|
292
|
-
const statements = sqlStatements(sql);
|
|
293
|
-
const last = statements.transaction[statements.transaction.length - 1] ?? null;
|
|
294
446
|
const status = serverTransactionStatus(client, state.observedStatus);
|
|
295
447
|
// Erro com o `ReadyForQuery` ainda por chegar: o estado do servidor é o de antes da query.
|
|
296
|
-
const stale =
|
|
448
|
+
const stale = failed && client.readyForQuery === false;
|
|
297
449
|
const connection = client.connection;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (last === 'begin') {
|
|
310
|
-
if (!state.tx && startTx)
|
|
311
|
-
open(startTx);
|
|
450
|
+
if (stale && (state.tx === null || seq >= state.tx.seq)) {
|
|
451
|
+
// O próprio erro já diz o estado em dois casos, sem esperar a resposta (a próxima query de quem
|
|
452
|
+
// recebeu o erro sai antes dela): o texto recusado na análise não rodou nada, e o único comando,
|
|
453
|
+
// um COMMIT/ROLLBACK/PREPARE TRANSACTION que falhou, tirou a sessão da transação.
|
|
454
|
+
if (isParseError(err)) {
|
|
455
|
+
release();
|
|
456
|
+
if (openedHere && state.tx === openedHere)
|
|
457
|
+
endTransaction(state, 'begin_failed', err, true);
|
|
458
|
+
else if (openedHere && state.queued === openedHere) {
|
|
459
|
+
state.queued = null;
|
|
460
|
+
finishSpan(openedHere, 'begin_failed', err);
|
|
312
461
|
}
|
|
313
|
-
|
|
314
|
-
endTransaction(state, outcomeFor(last), err);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
else if (errorCode(err) === SYNTAX_ERROR) {
|
|
318
|
-
// Erro de sintaxe: o Postgres analisa o texto inteiro antes de executar, então nenhum
|
|
319
|
-
// comando rodou e o estado do servidor é o de antes. Só a transação que esta query abriu
|
|
320
|
-
// (o BEGIN dela) deixa de existir.
|
|
321
|
-
if (openedHere && state.tx)
|
|
322
|
-
endTransaction(state, 'begin_failed', err);
|
|
323
|
-
}
|
|
324
|
-
else if (last === 'commit' || last === 'rollback') {
|
|
325
|
-
// COMMIT que falha termina a transação; com vários comandos não se sabe qual falhou, e
|
|
326
|
-
// fechar é o lado seguro (mantida aberta, a próxima requisição herdaria o trace).
|
|
327
|
-
if (state.tx)
|
|
328
|
-
endTransaction(state, outcomeFor(last), err);
|
|
462
|
+
return;
|
|
329
463
|
}
|
|
330
|
-
|
|
331
|
-
|
|
464
|
+
if (analysis.count === 1 && (last === 'commit' || last === 'rollback' || last === 'prepare') && state.tx) {
|
|
465
|
+
release();
|
|
466
|
+
endTransaction(state, outcomeFor(), err, true);
|
|
467
|
+
return;
|
|
332
468
|
}
|
|
333
|
-
// Outro erro num texto que termina com BEGIN: o BEGIN rodou e o comando seguinte falhou, então a
|
|
334
|
-
// transação está abortada (`E`) e continua aberta até o ROLLBACK.
|
|
335
469
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
470
|
+
if (stale && typeof connection?.once === 'function') {
|
|
471
|
+
// A decisão espera o estado do servidor (L2): `SELECT 1/0; COMMIT` deixa a transação abortada
|
|
472
|
+
// aberta, e um 42601 do EXECUTE dinâmico não quer dizer que o BEGIN não rodou. Até lá a query
|
|
473
|
+
// continua em voo (protege a janela de quem vem depois).
|
|
340
474
|
try {
|
|
341
475
|
connection.once('readyForQuery', () => {
|
|
342
|
-
|
|
343
|
-
if (!current || state.tx !== current)
|
|
344
|
-
return;
|
|
476
|
+
release();
|
|
345
477
|
const now = serverTransactionStatus(client, state.observedStatus);
|
|
346
|
-
if (now
|
|
347
|
-
|
|
478
|
+
if (now !== undefined)
|
|
479
|
+
fromServer(now);
|
|
480
|
+
else if (openedHere && state.tx === openedHere)
|
|
481
|
+
endTransaction(state, 'begin_failed', err, true);
|
|
348
482
|
});
|
|
483
|
+
return;
|
|
349
484
|
}
|
|
350
485
|
catch {
|
|
351
|
-
/* sem o evento,
|
|
486
|
+
/* sem o evento, decide agora pelo texto */
|
|
352
487
|
}
|
|
353
488
|
}
|
|
489
|
+
release();
|
|
490
|
+
if (openedHere && state.queued === openedHere) {
|
|
491
|
+
// O BEGIN da fila respondeu antes de a transação anterior fechar: rodou dentro dela e não abriu
|
|
492
|
+
// outra (o servidor só avisa). A transação da fila fecha sem ter existido.
|
|
493
|
+
state.queued = null;
|
|
494
|
+
finishSpan(openedHere, 'begin_failed', failed ? err : undefined);
|
|
495
|
+
}
|
|
496
|
+
if (status !== undefined && !stale) {
|
|
497
|
+
fromServer(status);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (state.tx && seq < state.tx.seq)
|
|
501
|
+
return;
|
|
502
|
+
if (!failed) {
|
|
503
|
+
// Sem erro, todos os comandos rodaram: vale o efeito do último.
|
|
504
|
+
if (last === 'begin' || last === 'chain') {
|
|
505
|
+
if (state.tx)
|
|
506
|
+
state.tx.confirmed = true;
|
|
507
|
+
else if (startTx)
|
|
508
|
+
state.tx = { span: startTx(), seq, confirmed: true };
|
|
509
|
+
}
|
|
510
|
+
else if (last && state.tx) {
|
|
511
|
+
endTransaction(state, outcomeFor(), err, true);
|
|
512
|
+
}
|
|
513
|
+
else if (state.tx && openedHere === state.tx) {
|
|
514
|
+
state.tx.confirmed = true;
|
|
515
|
+
}
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (!state.tx || analysis.transaction.length === 0)
|
|
519
|
+
return;
|
|
520
|
+
if (errorCode(err) === SYNTAX_ERROR) {
|
|
521
|
+
// Erro de sintaxe: o Postgres analisa o texto inteiro antes de executar, então nenhum
|
|
522
|
+
// comando rodou e o estado do servidor é o de antes. Só a transação que esta query abriu
|
|
523
|
+
// (o BEGIN dela) deixa de existir.
|
|
524
|
+
if (openedHere && state.tx === openedHere)
|
|
525
|
+
endTransaction(state, 'begin_failed', err, true);
|
|
526
|
+
}
|
|
527
|
+
else if (last === 'commit' || last === 'rollback' || last === 'prepare') {
|
|
528
|
+
// COMMIT que falha termina a transação; com vários comandos não se sabe qual falhou, e
|
|
529
|
+
// fechar é o lado seguro (mantida aberta, a próxima requisição herdaria o trace).
|
|
530
|
+
endTransaction(state, outcomeFor(), err, true);
|
|
531
|
+
}
|
|
532
|
+
else if (analysis.count === 1 && last === 'begin') {
|
|
533
|
+
endTransaction(state, 'begin_failed', err, true); // só `BEGIN`, e ele falhou
|
|
534
|
+
}
|
|
535
|
+
// Outro erro num texto que termina com BEGIN: o BEGIN rodou e o comando seguinte falhou, então a
|
|
536
|
+
// transação está abortada (`E`) e continua aberta até o ROLLBACK (sem confirmação do servidor).
|
|
354
537
|
};
|
|
355
538
|
const patchClient = (client) => {
|
|
356
539
|
if (!client || typeof client.query !== 'function' || client[CLIENT_PATCHED])
|
|
357
540
|
return;
|
|
358
541
|
client[CLIENT_PATCHED] = true;
|
|
359
542
|
// Conexão encerrada (`client.end()`, queda, término pelo servidor): o Postgres desfaz a
|
|
360
|
-
// transação aberta; o span fecha junto, senão ficaria retido no tracker para sempre.
|
|
543
|
+
// transação aberta; o span fecha junto, senão ficaria retido no tracker para sempre. O que ainda
|
|
544
|
+
// estava em voo (cursor nunca fechado) termina aqui.
|
|
361
545
|
if (typeof client.once === 'function') {
|
|
362
546
|
try {
|
|
363
547
|
client.once('end', () => {
|
|
364
548
|
const state = states.get(client);
|
|
365
|
-
if (state
|
|
549
|
+
if (!state)
|
|
550
|
+
return;
|
|
551
|
+
const closed = new Error('Connection terminated');
|
|
552
|
+
for (const query of [...state.inFlight.values()])
|
|
553
|
+
query.finish?.(closed, undefined);
|
|
554
|
+
state.inFlight.clear();
|
|
555
|
+
if (state.tx || state.queued)
|
|
366
556
|
endTransaction(state, 'connection_closed');
|
|
367
557
|
});
|
|
368
558
|
}
|
|
@@ -370,9 +560,9 @@ export function instrumentPgPool(core, pool) {
|
|
|
370
560
|
/* sem o evento, a transação fecha pela próxima query ou pelo release */
|
|
371
561
|
}
|
|
372
562
|
}
|
|
563
|
+
const connection = client.connection;
|
|
373
564
|
// Estado do servidor em cada `ReadyForQuery`, na frente do listener do próprio client (que
|
|
374
565
|
// resolve a query): vale para as versões do `pg` sem `getTransactionStatus()`.
|
|
375
|
-
const connection = client.connection;
|
|
376
566
|
if (typeof connection?.prependListener === 'function') {
|
|
377
567
|
try {
|
|
378
568
|
connection.prependListener('readyForQuery', (msg) => {
|
|
@@ -385,6 +575,20 @@ export function instrumentPgPool(core, pool) {
|
|
|
385
575
|
/* sem o evento, fica o texto */
|
|
386
576
|
}
|
|
387
577
|
}
|
|
578
|
+
// `standard_conforming_strings` da sessão (início da conexão e cada SET): decide a leitura
|
|
579
|
+
// principal do texto. Com barra invertida no texto, as duas leituras protegem a janela.
|
|
580
|
+
if (typeof connection?.on === 'function') {
|
|
581
|
+
try {
|
|
582
|
+
connection.on('parameterStatus', (msg) => {
|
|
583
|
+
const { parameterName, parameterValue } = (msg ?? {});
|
|
584
|
+
if (parameterName === 'standard_conforming_strings')
|
|
585
|
+
stateOf(client).standardConformingStrings = parameterValue === 'off' ? 'off' : 'on';
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
/* sem o evento, as duas leituras */
|
|
590
|
+
}
|
|
591
|
+
}
|
|
388
592
|
const clientQuery = client.query.bind(client);
|
|
389
593
|
client.query = function fgTrackerClientQuery(...args) {
|
|
390
594
|
const state = stateOf(client);
|
|
@@ -392,7 +596,7 @@ export function instrumentPgPool(core, pool) {
|
|
|
392
596
|
return clientQuery(...args);
|
|
393
597
|
// Rede de segurança: sem query em voo e com o servidor fora de transação, uma transação que
|
|
394
598
|
// ainda conste aberta terminou por um caminho não observado; não pode virar pai desta query.
|
|
395
|
-
if (state.tx && state.
|
|
599
|
+
if (state.tx && state.inFlight.size === 0 && client.readyForQuery !== false && serverTransactionStatus(client, state.observedStatus) === 'I') {
|
|
396
600
|
endTransaction(state, 'unobserved_end');
|
|
397
601
|
}
|
|
398
602
|
// Prepared statement executado só pelo nome: o texto é o que o `pg` guardou ao prepará-lo nesta
|
|
@@ -401,46 +605,91 @@ export function instrumentPgPool(core, pool) {
|
|
|
401
605
|
// O contexto de quem envia a query: o callback do pg roda no contexto do socket, que pode ser
|
|
402
606
|
// o da requisição que abriu a conexão.
|
|
403
607
|
const sender = core.currentContext();
|
|
608
|
+
if (!state.tx && !state.queued && !sender)
|
|
609
|
+
return clientQuery(...args);
|
|
610
|
+
// Análise dos comandos de transação feita uma vez por query, para o envio e para a resposta (O1).
|
|
611
|
+
const analysis = sql === null ? UNKNOWN_TEXT : analyzeSqlCommands(sql, state.standardConformingStrings);
|
|
404
612
|
const startTx = sender ? () => core.startSpan('custom', 'pg transaction', { ...baseAttributes, 'db.operation.name': 'transaction' }, sender) : null;
|
|
405
|
-
if (!sql) {
|
|
406
|
-
// Sem texto conhecido (client compatível, nome preparado fora desta conexão): sem span, mas um
|
|
407
|
-
// BEGIN, COMMIT ou ROLLBACK preparado abre ou termina a transação pelo estado do servidor.
|
|
408
|
-
if (!state.tx && !startTx)
|
|
409
|
-
return clientQuery(...args);
|
|
410
|
-
const seq = ++state.seq;
|
|
411
|
-
state.pending += 1;
|
|
412
|
-
return observeQuery(clientQuery, args, (err, result) => {
|
|
413
|
-
state.pending = Math.max(0, state.pending - 1);
|
|
414
|
-
settleTransaction(client, state, '', err, result, startTx, false, seq);
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
const command = transactionCommand(sql);
|
|
418
613
|
const seq = ++state.seq;
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
614
|
+
const tx = state.tx;
|
|
615
|
+
let parent;
|
|
616
|
+
let foreign = null;
|
|
617
|
+
const queued = state.queued;
|
|
618
|
+
if (queued && sender && sameExecution(core, sender, queued.span) && sameDecisions(sender, queued.span)) {
|
|
619
|
+
// Atrás do BEGIN que esperava na fila: roda na transação que ele vai abrir, salvo com um
|
|
620
|
+
// COMMIT/ROLLBACK dela já a caminho (roda depois dele, fora dela).
|
|
621
|
+
parent = closingAfter(state, queued, 'ends') ? sender : queued.span.context();
|
|
622
|
+
}
|
|
623
|
+
else if (tx && sender && sameExecution(core, sender, tx.span) && sameDecisions(sender, tx.span)) {
|
|
624
|
+
// Com um COMMIT/ROLLBACK da transação já a caminho, esta query roda depois dele, fora dela.
|
|
625
|
+
parent = closingInFlight(state, 'ends') ? sender : tx.span.context();
|
|
626
|
+
}
|
|
627
|
+
else if (tx && tx.confirmed && !closingInFlight(state, 'closes')) {
|
|
628
|
+
// Dentro da janela confirmada pelo servidor: quem envia sem contexto roda na transação e fica
|
|
629
|
+
// sob ela; outra execução fica sob o próprio contexto, com o vínculo.
|
|
630
|
+
if (sender) {
|
|
631
|
+
parent = sender;
|
|
632
|
+
foreign = tx;
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
parent = tx.span.context();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
// Outra execução, ou ninguém, fora da janela (BEGIN sem resposta, encerramento em voo,
|
|
640
|
+
// dúvida): o próprio contexto; sem contexto, nenhum span (como antes do #38).
|
|
641
|
+
parent = sender;
|
|
642
|
+
}
|
|
643
|
+
let openedHere = null;
|
|
644
|
+
if (analysis.startsWithBegin && startTx && (!state.tx || (!state.queued && closingInFlight(state, 'ends')))) {
|
|
645
|
+
openedHere = { span: startTx(), seq, confirmed: false };
|
|
646
|
+
if (state.tx)
|
|
647
|
+
state.queued = openedHere;
|
|
648
|
+
else
|
|
649
|
+
state.tx = openedHere;
|
|
650
|
+
parent = openedHere.span.context();
|
|
651
|
+
}
|
|
652
|
+
const flight = { closes: analysis.closes, ends: analysis.ends };
|
|
653
|
+
state.inFlight.set(seq, flight);
|
|
654
|
+
const register = (finish) => {
|
|
655
|
+
flight.finish = finish;
|
|
656
|
+
return () => {
|
|
657
|
+
flight.finish = undefined;
|
|
658
|
+
};
|
|
659
|
+
};
|
|
660
|
+
let released = false;
|
|
661
|
+
const release = () => {
|
|
662
|
+
if (released)
|
|
663
|
+
return;
|
|
664
|
+
released = true;
|
|
665
|
+
state.inFlight.delete(seq);
|
|
666
|
+
};
|
|
667
|
+
const settle = (err, result) => settleTransaction(client, state, analysis, err, result, startTx, openedHere, seq, release);
|
|
668
|
+
if (!sql || !parent) {
|
|
669
|
+
// Sem texto conhecido ou sem pai: sem span, mas a transação segue o estado do servidor.
|
|
670
|
+
return callTracked(clientQuery, args, settle, true, register);
|
|
671
|
+
}
|
|
672
|
+
return runQuery(core, clientQuery, args, sql, baseAttributes, {
|
|
673
|
+
parent,
|
|
674
|
+
clientQuery: true,
|
|
675
|
+
register,
|
|
676
|
+
onSettled: settle,
|
|
677
|
+
onStart: (span) => {
|
|
678
|
+
if (!foreign)
|
|
679
|
+
return;
|
|
680
|
+
span.setAttribute('links', [{ traceId: foreign.span.traceId, spanId: foreign.span.spanId }]);
|
|
681
|
+
span.setAttribute('db.transaction.foreign', true);
|
|
682
|
+
},
|
|
683
|
+
beforeEnd: (err, _result, span) => {
|
|
684
|
+
// A resposta mostra o servidor fora de transação: a query não rodou na de outra execução.
|
|
685
|
+
if (!foreign || (err !== undefined && err !== null) || analysis.transaction.length > 0)
|
|
686
|
+
return;
|
|
687
|
+
if (serverTransactionStatus(client, state.observedStatus) !== 'I')
|
|
688
|
+
return;
|
|
689
|
+
delete span.attributes.links;
|
|
690
|
+
delete span.attributes['db.transaction.foreign'];
|
|
691
|
+
},
|
|
692
|
+
});
|
|
444
693
|
};
|
|
445
694
|
};
|
|
446
695
|
/** Envolve o `release` desta retirada (o pool troca a função a cada `connect`). */
|
|
@@ -450,7 +699,7 @@ export function instrumentPgPool(core, pool) {
|
|
|
450
699
|
return;
|
|
451
700
|
client.release = function fgTrackerRelease(...args) {
|
|
452
701
|
state.internal = false;
|
|
453
|
-
if (state.tx)
|
|
702
|
+
if (state.tx || state.queued)
|
|
454
703
|
endTransaction(state, 'released_open', args[0] instanceof Error ? args[0] : undefined);
|
|
455
704
|
client.release = release;
|
|
456
705
|
return Reflect.apply(release, this, args);
|
|
@@ -471,8 +720,10 @@ export function instrumentPgPool(core, pool) {
|
|
|
471
720
|
try {
|
|
472
721
|
if (!tracked)
|
|
473
722
|
return original(...args);
|
|
474
|
-
return runQuery(core, original, args, sql, baseAttributes,
|
|
475
|
-
|
|
723
|
+
return runQuery(core, original, args, sql, baseAttributes, {
|
|
724
|
+
onStart: (span) => {
|
|
725
|
+
marker.span = span;
|
|
726
|
+
},
|
|
476
727
|
});
|
|
477
728
|
}
|
|
478
729
|
finally {
|