Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 6
matrix:
library:
Expand Down
5 changes: 5 additions & 0 deletions src/core/tracing/SpanUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ export class SpanUtils {
logger.debug(
`[SpanUtils] Stopping recording of child spans for span ${spanContext.spanId}, packageName: ${options.packageName}, instrumentationName: ${options.instrumentationName}`,
);
if (mode === TuskDriftMode.REPLAY) {
throw new Error(
`Unexpected child span in replay mode for span ${spanContext.spanId}, packageName: ${options.packageName}, instrumentationName: ${options.instrumentationName}`,
);
}
return originalFunctionCall();
}
}
Expand Down
264 changes: 223 additions & 41 deletions src/instrumentation/libraries/mysql2/Instrumentation.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,26 @@ const server = http.createServer(async (req, res) => {
return;
}

if (url === "/test/pool-execute-singleton-values" && method === "GET") {
pool.execute("SELECT * FROM test_users WHERE id = ?", [1], (error, results) => {
if (error) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ success: false, error: error.message }));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
success: true,
data: results,
rowCount: Array.isArray(results) ? results.length : 0,
queryType: "pool-execute-singleton-values",
}),
);
});
return;
}

// 404 for unknown routes
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Not found" }));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
import { makeRequest, printRequestSummary } from '/app/test-utils.mjs';
import { makeRequest, printRequestSummary } from "/app/test-utils.mjs";

await makeRequest('GET', '/health');
await makeRequest('GET', '/test/connection-query');
await makeRequest('POST', '/test/connection-parameterized', { body: { userId: 1 } });
await makeRequest('GET', '/test/connection-execute');
await makeRequest('POST', '/test/connection-execute-params', { body: { userId: 2 } });
await makeRequest('GET', '/test/pool-query');
await makeRequest('POST', '/test/pool-parameterized', { body: { userId: 1 } });
await makeRequest('GET', '/test/pool-execute');
await makeRequest('POST', '/test/pool-execute-params', { body: { userId: 2 } });
await makeRequest('GET', '/test/pool-getConnection');
await makeRequest('GET', '/test/connection-connect');
await makeRequest('GET', '/test/connection-ping');
await makeRequest('GET', '/test/stream-query');
await makeRequest('GET', '/test/sequelize-authenticate');
await makeRequest('GET', '/test/sequelize-findall');
await makeRequest('POST', '/test/sequelize-findone', { body: { userId: 1 } });
await makeRequest('GET', '/test/sequelize-complex');
await makeRequest('GET', '/test/sequelize-raw');
await makeRequest('POST', '/test/sequelize-transaction');
await makeRequest('GET', '/test/promise-connection-query');
await makeRequest('GET', '/test/promise-pool-query');
await makeRequest('GET', '/test/promise-pool-getconnection');
await makeRequest('GET', '/test/transaction-methods');
await makeRequest('GET', '/test/prepare-statement');
await makeRequest('GET', '/test/change-user');
await makeRequest('GET', '/test/nested-null-values');
await makeRequest('GET', '/test/binary-data');
await makeRequest('GET', '/test/knex-raw-query');
await makeRequest('POST', '/test/knex-savepoint');
await makeRequest('GET', '/test/knex-streaming');
await makeRequest("GET", "/health");
await makeRequest("GET", "/test/connection-query");
await makeRequest("POST", "/test/connection-parameterized", { body: { userId: 1 } });
await makeRequest("GET", "/test/connection-execute");
await makeRequest("POST", "/test/connection-execute-params", { body: { userId: 2 } });
await makeRequest("GET", "/test/pool-query");
await makeRequest("POST", "/test/pool-parameterized", { body: { userId: 1 } });
await makeRequest("GET", "/test/pool-execute");
await makeRequest("POST", "/test/pool-execute-params", { body: { userId: 2 } });
await makeRequest("GET", "/test/pool-getConnection");
await makeRequest("GET", "/test/connection-connect");
await makeRequest("GET", "/test/connection-ping");
await makeRequest("GET", "/test/stream-query");
await makeRequest("GET", "/test/sequelize-authenticate");
await makeRequest("GET", "/test/sequelize-findall");
await makeRequest("POST", "/test/sequelize-findone", { body: { userId: 1 } });
await makeRequest("GET", "/test/sequelize-complex");
await makeRequest("GET", "/test/sequelize-raw");
await makeRequest("POST", "/test/sequelize-transaction");
await makeRequest("GET", "/test/promise-connection-query");
await makeRequest("GET", "/test/promise-pool-query");
await makeRequest("GET", "/test/promise-pool-getconnection");
await makeRequest("GET", "/test/transaction-methods");
await makeRequest("GET", "/test/prepare-statement");
await makeRequest("GET", "/test/change-user");
await makeRequest("GET", "/test/nested-null-values");
await makeRequest("GET", "/test/binary-data");
await makeRequest("GET", "/test/knex-raw-query");
await makeRequest("POST", "/test/knex-savepoint");
await makeRequest("GET", "/test/knex-streaming");
await makeRequest("GET", "/test/pool-execute-singleton-values");

printRequestSummary();
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,26 @@ const server = http.createServer(async (req, res) => {
return;
}

if (url === "/test/pool-execute-singleton-values" && method === "GET") {
pool.execute("SELECT * FROM test_users WHERE id = ?", [1], (error, results) => {
if (error) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ success: false, error: error.message }));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
success: true,
data: results,
rowCount: Array.isArray(results) ? results.length : 0,
queryType: "pool-execute-singleton-values",
}),
);
});
return;
}

// 404 for unknown routes
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Not found" }));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
import { makeRequest, printRequestSummary } from '/app/test-utils.mjs';
import { makeRequest, printRequestSummary } from "/app/test-utils.mjs";

await makeRequest('GET', '/health');
await makeRequest('GET', '/test/connection-query');
await makeRequest('POST', '/test/connection-parameterized', { body: { userId: 1 } });
await makeRequest('GET', '/test/connection-execute');
await makeRequest('POST', '/test/connection-execute-params', { body: { userId: 2 } });
await makeRequest('GET', '/test/pool-query');
await makeRequest('POST', '/test/pool-parameterized', { body: { userId: 1 } });
await makeRequest('GET', '/test/pool-execute');
await makeRequest('POST', '/test/pool-execute-params', { body: { userId: 2 } });
await makeRequest('GET', '/test/pool-getConnection');
await makeRequest('GET', '/test/connection-connect');
await makeRequest('GET', '/test/connection-ping');
await makeRequest('GET', '/test/stream-query');
await makeRequest('GET', '/test/sequelize-authenticate');
await makeRequest('GET', '/test/sequelize-findall');
await makeRequest('POST', '/test/sequelize-findone', { body: { userId: 1 } });
await makeRequest('GET', '/test/sequelize-complex');
await makeRequest('GET', '/test/sequelize-raw');
await makeRequest('POST', '/test/sequelize-transaction');
await makeRequest('GET', '/test/promise-connection-query');
await makeRequest('GET', '/test/promise-pool-query');
await makeRequest('GET', '/test/promise-pool-getconnection');
await makeRequest('GET', '/test/transaction-methods');
await makeRequest('GET', '/test/prepare-statement');
await makeRequest('GET', '/test/change-user');
await makeRequest('GET', '/test/nested-null-values');
await makeRequest('GET', '/test/binary-data');
await makeRequest('GET', '/test/knex-raw-query');
await makeRequest('POST', '/test/knex-savepoint');
await makeRequest('GET', '/test/knex-streaming');
await makeRequest("GET", "/health");
await makeRequest("GET", "/test/connection-query");
await makeRequest("POST", "/test/connection-parameterized", { body: { userId: 1 } });
await makeRequest("GET", "/test/connection-execute");
await makeRequest("POST", "/test/connection-execute-params", { body: { userId: 2 } });
await makeRequest("GET", "/test/pool-query");
await makeRequest("POST", "/test/pool-parameterized", { body: { userId: 1 } });
await makeRequest("GET", "/test/pool-execute");
await makeRequest("POST", "/test/pool-execute-params", { body: { userId: 2 } });
await makeRequest("GET", "/test/pool-getConnection");
await makeRequest("GET", "/test/connection-connect");
await makeRequest("GET", "/test/connection-ping");
await makeRequest("GET", "/test/stream-query");
await makeRequest("GET", "/test/sequelize-authenticate");
await makeRequest("GET", "/test/sequelize-findall");
await makeRequest("POST", "/test/sequelize-findone", { body: { userId: 1 } });
await makeRequest("GET", "/test/sequelize-complex");
await makeRequest("GET", "/test/sequelize-raw");
await makeRequest("POST", "/test/sequelize-transaction");
await makeRequest("GET", "/test/promise-connection-query");
await makeRequest("GET", "/test/promise-pool-query");
await makeRequest("GET", "/test/promise-pool-getconnection");
await makeRequest("GET", "/test/transaction-methods");
await makeRequest("GET", "/test/prepare-statement");
await makeRequest("GET", "/test/change-user");
await makeRequest("GET", "/test/nested-null-values");
await makeRequest("GET", "/test/binary-data");
await makeRequest("GET", "/test/knex-raw-query");
await makeRequest("POST", "/test/knex-savepoint");
await makeRequest("GET", "/test/knex-streaming");
await makeRequest("GET", "/test/pool-execute-singleton-values");

printRequestSummary();
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ TuskDrift.markAppAsReady();

import test from "ava";
import { SpanKind } from "@opentelemetry/api";
import diagnosticsChannel from "node:diagnostics_channel";
import { SpanUtils } from "../../../../core/tracing/SpanUtils";
import { TuskDriftMode } from "../../../../core/TuskDrift";
import {
Expand Down Expand Up @@ -588,6 +589,72 @@ test.serial("should handle streaming queries", async (t) => {
t.true(mysql2Spans.length > 0);
});

test.serial("should emit native mysql2 tracing events for streaming queries", async (t) => {
if (typeof diagnosticsChannel.tracingChannel !== "function") {
t.pass();
return;
}

const events: Array<{ type: string; ctx: any }> = [];
const queryChannel = diagnosticsChannel.tracingChannel("mysql2:query");
const subscriber = {
start(ctx: object) {
events.push({ type: "start", ctx });
},
end(ctx: object) {
events.push({ type: "end", ctx });
},
asyncStart(ctx: object) {
events.push({ type: "asyncStart", ctx });
},
asyncEnd(ctx: object) {
events.push({ type: "asyncEnd", ctx });
},
error(ctx: object) {
events.push({ type: "error", ctx });
},
};

queryChannel.subscribe(subscriber);

try {
const rows: any[] = [];

await new Promise<void>((resolve, reject) => {
withRootSpan(() => {
const query = connection.query("SELECT * FROM test_users ORDER BY id");

query
.on("error", (err: any) => {
reject(err);
})
.on("result", (row: any) => {
rows.push(row);
})
.on("end", () => {
resolve();
});
});
});

t.is(rows.length, 2);

const eventTypes = events.map((event) => event.type);
t.true(eventTypes.includes("start"));
t.true(eventTypes.includes("asyncEnd"));
t.false(eventTypes.includes("error"));

const startEvent = events.find((event) => event.type === "start");
t.truthy(startEvent);
t.true(String(startEvent?.ctx?.query || "").includes("SELECT * FROM test_users"));
t.is(startEvent?.ctx?.database, TEST_MYSQL_CONFIG.database);
t.is(startEvent?.ctx?.serverAddress, TEST_MYSQL_CONFIG.host);
t.is(startEvent?.ctx?.serverPort, TEST_MYSQL_CONFIG.port);
} finally {
queryChannel.unsubscribe(subscriber);
}
});

test.serial("should handle connection.ping", async (t) => {
await new Promise<void>((resolve, reject) => {
withRootSpan(() => {
Expand Down
Loading