From c9fedd8a37fe8aafa5adb245b84d87746069cfb7 Mon Sep 17 00:00:00 2001 From: nikkikapadia Date: Wed, 14 Oct 2026 17:09:09 -0400 Subject: [PATCH 1/4] feat(datasetAttributes): provide pagination option + attribute description based evals --- .../src/tools/support/search-events/config.ts | 7 +- .../tools/support/search-events/utils.test.ts | 84 +++++++++++++++++ .../src/tools/support/search-events/utils.ts | 94 +++++++++++++++---- ...rch-events-agent-attribute-context.eval.ts | 45 +++++++++ ...-attributes-spans-number-with-context.json | 14 +++ ...-attributes-spans-string-with-context.json | 24 +++++ 6 files changed, 249 insertions(+), 19 deletions(-) diff --git a/packages/mcp-core/src/tools/support/search-events/config.ts b/packages/mcp-core/src/tools/support/search-events/config.ts index dab182818..0d3194bf0 100644 --- a/packages/mcp-core/src/tools/support/search-events/config.ts +++ b/packages/mcp-core/src/tools/support/search-events/config.ts @@ -41,9 +41,10 @@ TOOL USAGE GUIDELINES: 5. IMPORTANT: For ambiguous terms like "user agents", "browser", "client" - use the appropriate field discovery tool instead of guessing field names 6. When the user already supplied Sentry search syntax for spans/logs/metrics, call datasetAttributes with substringMatch or query filters from the request before dropping or renaming fields 7. If datasetAttributes lists a field under Deprecated Fields, use its replacement instead, even when the deprecated name appears in the guidance or examples in this prompt -8. Use datasetAttributes substringMatch, query, and attributeTypes for targeted lookup when broad field discovery is truncated -9. For non-replay datasets, call validateSearch after constructing the candidate request. If invalid, fix and validate again in this same pass -10. NEVER replace a structured field:value filter with message/log.body/full-text matching. If an explicit field is unavailable on the dataset, keep it and let validation fail instead of inventing a weaker query +8. Use datasetAttributes substringMatch, query, and attributeTypes for targeted lookup when broad field discovery is truncated. If the field you need is not on the first page, call datasetAttributes again with the offset from the end of the response to see more fields +9. When several fields could match the user's request, choose the field whose description best matches it if applicable, not just the closest name +10. For non-replay datasets, call validateSearch after constructing the candidate request. If invalid, fix and validate again in this same pass +11. NEVER replace a structured field:value filter with message/log.body/full-text matching. If an explicit field is unavailable on the dataset, keep it and let validation fail instead of inventing a weaker query CRITICAL - TOOL RESPONSE HANDLING: All tools return responses in this format: {error?: string, result?: data} diff --git a/packages/mcp-core/src/tools/support/search-events/utils.test.ts b/packages/mcp-core/src/tools/support/search-events/utils.test.ts index e7e84857b..8d8c7e224 100644 --- a/packages/mcp-core/src/tools/support/search-events/utils.test.ts +++ b/packages/mcp-core/src/tools/support/search-events/utils.test.ts @@ -5,6 +5,7 @@ import type { z } from "zod"; import { SentryApiService } from "../../../api-client"; import * as logging from "../../../telem/logging"; import { + createDatasetAttributesTool, createValidateEventsSearchTool, fetchCustomAttributes, formatEventsValidationResults, @@ -394,6 +395,89 @@ describe("search query helpers", () => { }); }); +describe("datasetAttributes tool pagination", () => { + beforeEach(() => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/test-org/trace-items/attributes/", + () => + HttpResponse.json([ + { + key: "custom.first", + name: "custom.first", + attributeType: "string", + attributeSource: { source_type: "user" }, + }, + { + key: "device.memory", + name: "device.memory", + attributeType: "number", + attributeSource: { source_type: "user" }, + context: { + isConvention: true, + brief: "The estimated total memory capacity of the device.", + isDeprecated: false, + }, + }, + ]), + ), + ); + }); + + afterEach(() => { + mswServer.resetHandlers(); + }); + + async function listAttributes(offset?: number) { + const tool = createDatasetAttributesTool({ + apiService: new SentryApiService({ accessToken: "test-token" }), + organizationSlug: "test-org", + }); + type ToolInput = Parameters>[0]; + const schema = tool.inputSchema as z.ZodType; + const output = await tool.execute!( + schema.parse({ dataset: "spans", offset }), + { + toolCallId: "dataset-attributes-test", + messages: [], + }, + ); + return (output as { result: string }).result; + } + + it("points to the next page when fields are past the first page", async () => { + const result = await listAttributes(); + + expect(result).toMatch(/Available Fields \(\d+ total, showing 1-50\):/); + expect(result).toContain( + "call datasetAttributes again with the same arguments and offset: 50", + ); + expect(result).not.toContain("- device.memory: The estimated"); + expect(result).toContain("EXAMPLE QUERIES FOR SPANS"); + }); + + it("returns later pages with descriptions and types for their fields only", async () => { + const result = await listAttributes(50); + + expect(result).toMatch(/showing 51-\d+\):/); + expect(result).toContain( + "- device.memory: The estimated total memory capacity of the device.", + ); + expect(result).toContain("- device.memory: number"); + expect(result).not.toContain("- span.op:"); + expect(result).not.toContain("offset:"); + expect(result).not.toContain("EXAMPLE QUERIES"); + }); + + it("reports when the offset is past the last field", async () => { + const result = await listAttributes(1000); + + expect(result).toMatch( + /Available Fields \(\d+ total\): none at offset 1000\./, + ); + }); +}); + describe("fetchCustomAttributes", () => { let apiService: SentryApiService; diff --git a/packages/mcp-core/src/tools/support/search-events/utils.ts b/packages/mcp-core/src/tools/support/search-events/utils.ts index a8d699d7c..d9c7fd96a 100644 --- a/packages/mcp-core/src/tools/support/search-events/utils.ts +++ b/packages/mcp-core/src/tools/support/search-events/utils.ts @@ -30,6 +30,7 @@ export type FlexibleEventData = Record; const DEFAULT_MAX_VALUE_LENGTH = 200; const DEFAULT_MAX_ARRAY_ITEMS = 20; +const FIELDS_PAGE_SIZE = 50; const SENTRY_SEARCH_TOKEN_PATTERN = /(^|\s)!?([A-Za-z_][A-Za-z0-9_.[\],-]*):(?=\S)(?!\/\/)/g; const KNOWN_SENTRY_SEARCH_KEYS = new Set([ @@ -1058,8 +1059,22 @@ export function createDatasetAttributesTool(options: { .describe( "Optional attribute types to list. Use ['string','number','boolean'] when unsure.", ), + offset: z + .number() + .int() + .min(0) + .optional() + .describe( + "Optional position in the Available Fields list to start from. Use the offset given at the end of the previous response to see the next page of fields.", + ), }), - execute: async ({ dataset, substringMatch, query, attributeTypes }) => { + execute: async ({ + dataset, + substringMatch, + query, + attributeTypes, + offset = 0, + }) => { const { BASE_COMMON_FIELDS, DATASET_FIELDS, @@ -1088,10 +1103,33 @@ export function createDatasetAttributesTool(options: { }, ); + let commonFields: Record = {}; + let datasetFields: Record = {}; + if (substringMatch) { + Object.entries(BASE_COMMON_FIELDS).forEach(([key, value]) => { + if (key.includes(substringMatch) || value.includes(substringMatch)) { + commonFields[key] = value; + } + }); + Object.entries(DATASET_FIELDS[normalizedDataset]).forEach( + ([key, value]) => { + if ( + key.includes(substringMatch) || + value.includes(substringMatch) + ) { + datasetFields[key] = value; + } + }, + ); + } else { + commonFields = BASE_COMMON_FIELDS; + datasetFields = DATASET_FIELDS[normalizedDataset]; + } + // Combine all available fields const allFields: Record = { - ...toAttributeRecord(BASE_COMMON_FIELDS), - ...toAttributeRecord(DATASET_FIELDS[normalizedDataset]), + ...toAttributeRecord(commonFields), + ...toAttributeRecord(datasetFields), ...customAttributes, }; @@ -1120,14 +1158,45 @@ export function createDatasetAttributesTool(options: { recordAgentToolResultCount(fieldCount); + // Page through fields to avoid overwhelming the agent + const pageFields = [...availableFields].slice( + offset, + offset + FIELDS_PAGE_SIZE, + ); + const pageEnd = offset + pageFields.length; + const fieldsHeader = + pageFields.length > 0 + ? `Available Fields (${fieldCount} total, showing ${offset + 1}-${pageEnd}):` + : `Available Fields (${fieldCount} total):${offset > 0 ? ` none at offset ${offset}.` : ""}`; + const fieldsList = pageFields + .map( + ([key, field]) => + `- ${key} (${allFieldTypes[key]}): ${describeField(field)}`, + ) + .join("\n"); + const nextPageHint = + pageEnd < fieldCount + ? `\n... and ${fieldCount - pageEnd} more fields. To see them, call datasetAttributes again with the same arguments and offset: ${pageEnd}` + : ""; + + // Later pages only list their fields; the deprecated fields, recommended + // fields, and examples were already returned with the first page. + if (offset > 0) { + return `Dataset: ${dataset} + +${fieldsHeader} +${fieldsList} +${nextPageHint} + +IMPORTANT: Only use numeric aggregate functions (avg, sum, min, max, percentiles) with numeric fields. Use count() or count_unique() for non-numeric fields. +`; + } + return `Dataset: ${dataset} -Available Fields (${fieldCount} total): -${[...availableFields] - .slice(0, 50) // Limit to first 50 to avoid overwhelming the agent - .map(([key, field]) => `- ${key}: ${describeField(field)}`) - .join("\n")} -${fieldCount > 50 ? `\n... and ${fieldCount - 50} more fields` : ""} +${fieldsHeader} +${fieldsList} +${nextPageHint} ${ replacedFields.length > 0 ? ` @@ -1142,13 +1211,6 @@ ${replacedFields.length > 30 ? `\n... and ${replacedFields.length - 30} more dep Recommended Fields for ${dataset}: ${recommendedFields.basic.map((f) => `- ${f}`).join("\n")} -Field Types (CRITICAL for aggregate functions): -${fieldTypeEntries - .slice(0, 30) // Show more field types since this is critical for aggregate functions - .map(([key, type]) => `- ${key}: ${type}`) - .join("\n")} -${fieldTypeEntries.length > 30 ? `\n... and ${fieldTypeEntries.length - 30} more fields` : ""} - IMPORTANT: Only use numeric aggregate functions (avg, sum, min, max, percentiles) with numeric fields. Use count() or count_unique() for non-numeric fields. EXAMPLE QUERIES FOR ${dataset.toUpperCase()}: diff --git a/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts b/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts index 9adbb774a..908876a90 100644 --- a/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts +++ b/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts @@ -225,6 +225,51 @@ describeEval("search-events-agent-attributes-with-context", { !/(? + Array.isArray(value) && + value.includes("avg(device.memory)") && + !value.some( + (field) => + typeof field === "string" && field.includes("deviceMemory"), + ), + }, + }, + { + // device.model and device.model_id are near-identical names, but only + // device.model_id's brief describes the exact hardware revision + input: + "Count spans grouped by the device's internal hardware revision over the last 7 days", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "spans", + }, + }, + ], + expected: { + dataset: "spans", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("device.model_id") && + !value.includes("device.model"), + }, + }, ]; }, task: async (input) => { diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-number-with-context.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-number-with-context.json index b44345c04..3dbc9f34d 100644 --- a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-number-with-context.json +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-number-with-context.json @@ -43,5 +43,19 @@ "key": "custom.db.pool_size", "name": "custom.db.pool_size", "context": {} + }, + { + "key": "deviceMemory", + "name": "deviceMemory", + "context": {} + }, + { + "key": "device.memory", + "name": "device.memory", + "context": { + "isConvention": true, + "brief": "The estimated total memory capacity of the device.", + "isDeprecated": false + } } ] diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string-with-context.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string-with-context.json index c058e04c5..bbaa9ed8a 100644 --- a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string-with-context.json +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string-with-context.json @@ -255,5 +255,29 @@ "key": "custom.feature_flag", "name": "custom.feature_flag", "context": {} + }, + { + "key": "device.model", + "name": "device.model", + "context": { + "isConvention": true, + "brief": "The model name of the device.", + "isDeprecated": false, + "examples": [ + "iPhone 15 Pro" + ] + } + }, + { + "key": "device.model_id", + "name": "device.model_id", + "context": { + "isConvention": true, + "brief": "An internal hardware revision to identify the device exactly.", + "isDeprecated": false, + "examples": [ + "iPhone16,1" + ] + } } ] From 9279a6d5f32dcf42037a45ebb8ff3187bd13d052 Mon Sep 17 00:00:00 2001 From: nikkikapadia Date: Thu, 15 Oct 2026 11:23:50 -0400 Subject: [PATCH 2/4] adjust description evals --- ...rch-events-agent-attribute-context.eval.ts | 34 ++++++++++++++++--- ...-attributes-spans-string-with-context.json | 16 ++++----- .../trace-items-attributes-spans-string.json | 8 +++++ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts b/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts index 908876a90..82f8f5a80 100644 --- a/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts +++ b/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts @@ -107,6 +107,29 @@ describeEval("search-events-agent-attributes-without-context", { !value.includes("sentry.environment"), }, }, + { + // kiosk.firmware and kiosk.firmware_revision are made-up attributes, so + // the agent has no prior knowledge of either. Without briefs it can + // only match on the name, and kiosk.firmware_revision matches + // "firmware revision" + input: + "Count spans grouped by the kiosk's installed firmware revision over the last 7 days", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "spans", + }, + }, + ], + expected: { + dataset: "spans", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("kiosk.firmware_revision") && + !value.includes("kiosk.firmware"), + }, + }, ]; }, task: async (input) => { @@ -250,10 +273,11 @@ describeEval("search-events-agent-attributes-with-context", { }, }, { - // device.model and device.model_id are near-identical names, but only - // device.model_id's brief describes the exact hardware revision + // kiosk.firmware_revision matches "firmware revision" by name, but its + // brief says it is the staged revision. Only kiosk.firmware's brief + // describes the installed firmware input: - "Count spans grouped by the device's internal hardware revision over the last 7 days", + "Count spans grouped by the kiosk's installed firmware revision over the last 7 days", expectedTools: [ { name: "datasetAttributes", @@ -266,8 +290,8 @@ describeEval("search-events-agent-attributes-with-context", { dataset: "spans", fields: (value: unknown) => Array.isArray(value) && - value.includes("device.model_id") && - !value.includes("device.model"), + value.includes("kiosk.firmware") && + !value.includes("kiosk.firmware_revision"), }, }, ]; diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string-with-context.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string-with-context.json index bbaa9ed8a..7a0282a9c 100644 --- a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string-with-context.json +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string-with-context.json @@ -257,26 +257,26 @@ "context": {} }, { - "key": "device.model", - "name": "device.model", + "key": "kiosk.firmware", + "name": "kiosk.firmware", "context": { "isConvention": true, - "brief": "The model name of the device.", + "brief": "The firmware revision currently installed and running on the kiosk.", "isDeprecated": false, "examples": [ - "iPhone 15 Pro" + "4.1.3" ] } }, { - "key": "device.model_id", - "name": "device.model_id", + "key": "kiosk.firmware_revision", + "name": "kiosk.firmware_revision", "context": { "isConvention": true, - "brief": "An internal hardware revision to identify the device exactly.", + "brief": "The firmware revision downloaded and staged for the kiosk's next update. It is not installed until the kiosk restarts.", "isDeprecated": false, "examples": [ - "iPhone16,1" + "4.2.0" ] } } diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string.json index 115773a13..d2dd48d89 100644 --- a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string.json +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string.json @@ -106,5 +106,13 @@ { "key": "custom.feature_flag", "name": "Feature Flag" + }, + { + "key": "kiosk.firmware", + "name": "kiosk.firmware" + }, + { + "key": "kiosk.firmware_revision", + "name": "kiosk.firmware_revision" } ] From 0b017ea22b33cf618a0abcd00b4641aa7fb832c7 Mon Sep 17 00:00:00 2001 From: nikkikapadia Date: Thu, 15 Oct 2026 12:30:03 -0400 Subject: [PATCH 3/4] cursor fixes + fix failing unit test --- .../mcp-core/src/tools/support/search-events/utils.test.ts | 3 +-- packages/mcp-core/src/tools/support/search-events/utils.ts | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/mcp-core/src/tools/support/search-events/utils.test.ts b/packages/mcp-core/src/tools/support/search-events/utils.test.ts index 9bdb99e9c..39584e979 100644 --- a/packages/mcp-core/src/tools/support/search-events/utils.test.ts +++ b/packages/mcp-core/src/tools/support/search-events/utils.test.ts @@ -731,9 +731,8 @@ describe("datasetAttributes tool pagination", () => { expect(result).toMatch(/showing 51-\d+\):/); expect(result).toContain( - "- device.memory: The estimated total memory capacity of the device.", + "- device.memory (number): The estimated total memory capacity of the device.", ); - expect(result).toContain("- device.memory: number"); expect(result).not.toContain("- span.op:"); expect(result).not.toContain("offset:"); expect(result).not.toContain("EXAMPLE QUERIES"); diff --git a/packages/mcp-core/src/tools/support/search-events/utils.ts b/packages/mcp-core/src/tools/support/search-events/utils.ts index ca3749e29..1fd1da226 100644 --- a/packages/mcp-core/src/tools/support/search-events/utils.ts +++ b/packages/mcp-core/src/tools/support/search-events/utils.ts @@ -1263,7 +1263,6 @@ export function createDatasetAttributesTool(options: { replaced: replacedFields, updatedFieldTypes, } = resolveAvailableFields(allFields, fieldTypes); - const replacedKeys = new Set(replacedFields.map((field) => field.key)); const fieldCount = availableFields.size; const recommendedFields = RECOMMENDED_FIELDS[normalizedDataset]; @@ -1277,9 +1276,6 @@ export function createDatasetAttributesTool(options: { for (const field of staticNumericFields) { allFieldTypes[field] = "number"; } - const fieldTypeEntries = Object.entries(allFieldTypes).filter( - ([key]) => !replacedKeys.has(key), - ); recordAgentToolResultCount(fieldCount); @@ -1296,7 +1292,7 @@ export function createDatasetAttributesTool(options: { const fieldsList = pageFields .map( ([key, field]) => - `- ${key} (${allFieldTypes[key]}): ${describeField(field)}`, + `- ${key}${allFieldTypes[key] ? ` (${allFieldTypes[key]})` : ""}: ${describeField(field)}`, ) .join("\n"); const nextPageHint = From df3fc337e075670bcf3bd680123c984b5e3f4965 Mon Sep 17 00:00:00 2001 From: nikkikapadia Date: Fri, 16 Oct 2026 11:29:55 -0400 Subject: [PATCH 4/4] feat(datasetAttributes): always call attributes with context + updated evals --- .../src/tools/support/search-events/utils.ts | 2 + ...rch-events-agent-attribute-context.eval.ts | 174 +++++++++++++++++- .../src/evals/search-events-agent.eval.ts | 2 +- ...s-attributes-logs-number-with-context.json | 60 ++++++ .../trace-items-attributes-logs-number.json | 8 + ...s-attributes-logs-string-with-context.json | 106 +++++++++++ .../trace-items-attributes-logs-string.json | 4 + ...utes-tracemetrics-number-with-context.json | 34 ++++ ...utes-tracemetrics-string-with-context.json | 94 ++++++++++ ...-items-attributes-tracemetrics-string.json | 12 ++ packages/mcp-server-mocks/src/index.ts | 28 +++ packages/mcp-server-mocks/src/payloads.ts | 108 +++-------- 12 files changed, 550 insertions(+), 82 deletions(-) create mode 100644 packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-number-with-context.json create mode 100644 packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-string-with-context.json create mode 100644 packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-number-with-context.json create mode 100644 packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-string-with-context.json diff --git a/packages/mcp-core/src/tools/support/search-events/utils.ts b/packages/mcp-core/src/tools/support/search-events/utils.ts index 1fd1da226..29fcfa985 100644 --- a/packages/mcp-core/src/tools/support/search-events/utils.ts +++ b/packages/mcp-core/src/tools/support/search-events/utils.ts @@ -1225,6 +1225,8 @@ export function createDatasetAttributesTool(options: { attributeTypes, substringMatch, query, + // Always call attributes endpoint with context + context: true, }, ); diff --git a/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts b/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts index 82f8f5a80..7c96acc5b 100644 --- a/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts +++ b/packages/mcp-server-evals/src/evals/search-events-agent-attribute-context.eval.ts @@ -6,12 +6,11 @@ import { StructuredOutputScorer } from "./utils/structuredOutputScorer"; import "../setup-env"; // Compares the attribute the agent picks when the trace-items attributes -// endpoint is called with and without `expand=context`. The with-context span +// endpoint is called with and without `expand=context`. The with-context // fixtures list the replacement for each deprecated attribute (e.g. // user.geo.region for geo.region), so only that path should use it. // -// The datasetAttributes tool does not request context itself, so this service -// forces it on or off for every attributes request. +// This service forces the context on or off for every attributes request. class AttributeContextApiService extends SentryApiService { constructor(private readonly attributeContext: boolean) { super({ accessToken: "test-token" }); @@ -130,6 +129,90 @@ describeEval("search-events-agent-attributes-without-context", { !value.includes("kiosk.firmware"), }, }, + { + // Without context the logs attributes only list code.filepath + input: + "Count error logs grouped by source code file path over the last 24 hours", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "logs", + }, + }, + ], + expected: { + dataset: "logs", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("code.filepath") && + !value.includes("code.file.path"), + }, + }, + { + // Without context the metrics attributes only list http.method + input: + "In metrics, show p95 http.request.duration grouped by HTTP method over the last 24 hours", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "metrics", + }, + }, + ], + expected: { + dataset: "metrics", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("http.method") && + !value.includes("http.request.method"), + }, + }, + { + // worker.queue_wait and worker.queue_wait_time are made-up attributes. + // Without briefs the agent can only match on the name, and + // worker.queue_wait_time matches "worker queue wait time" + input: + "From logs, what is the average worker queue wait time jobs actually experienced over the last 24 hours?", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "logs", + }, + }, + ], + expected: { + dataset: "logs", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("avg(worker.queue_wait_time)") && + !value.includes("avg(worker.queue_wait)"), + }, + }, + { + // fleet.region and fleet.deployment_region are made-up attributes. + // Without briefs the agent can only match on the name, and + // fleet.deployment_region matches "fleet deployment region" + input: + "In metrics, show p95 http.request.duration grouped by the fleet deployment region each server is currently running in over the last 24 hours", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "metrics", + }, + }, + ], + expected: { + dataset: "metrics", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("fleet.deployment_region") && + !value.includes("fleet.region"), + }, + }, ]; }, task: async (input) => { @@ -294,6 +377,91 @@ describeEval("search-events-agent-attributes-with-context", { !value.includes("kiosk.firmware_revision"), }, }, + { + // Context marks code.filepath as deprecated in favor of code.file.path + input: + "Count error logs grouped by source code file path over the last 24 hours", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "logs", + }, + }, + ], + expected: { + dataset: "logs", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("code.file.path") && + !value.includes("code.filepath"), + }, + }, + { + // Context marks http.method as deprecated in favor of + // http.request.method + input: + "In metrics, show p95 http.request.duration grouped by HTTP method over the last 24 hours", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "metrics", + }, + }, + ], + expected: { + dataset: "metrics", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("http.request.method") && + !value.includes("http.method"), + }, + }, + { + // worker.queue_wait_time matches "worker queue wait time" by name, but + // its brief says it is a configured limit. Only worker.queue_wait's + // brief describes the measured wait + input: + "From logs, what is the average worker queue wait time jobs actually experienced over the last 24 hours?", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "logs", + }, + }, + ], + expected: { + dataset: "logs", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("avg(worker.queue_wait)") && + !value.includes("avg(worker.queue_wait_time)"), + }, + }, + { + // fleet.deployment_region matches "fleet deployment region" by name, + // but its brief says it is the region the server moves to next. Only + // fleet.region's brief describes where the server runs now + input: + "In metrics, show p95 http.request.duration grouped by the fleet deployment region each server is currently running in over the last 24 hours", + expectedTools: [ + { + name: "datasetAttributes", + arguments: { + dataset: "metrics", + }, + }, + ], + expected: { + dataset: "metrics", + fields: (value: unknown) => + Array.isArray(value) && + value.includes("fleet.region") && + !value.includes("fleet.deployment_region"), + }, + }, ]; }, task: async (input) => { diff --git a/packages/mcp-server-evals/src/evals/search-events-agent.eval.ts b/packages/mcp-server-evals/src/evals/search-events-agent.eval.ts index 6d730bfbb..b415cdca7 100644 --- a/packages/mcp-server-evals/src/evals/search-events-agent.eval.ts +++ b/packages/mcp-server-evals/src/evals/search-events-agent.eval.ts @@ -121,7 +121,7 @@ describeEval("search-events-agent", { query: (value: unknown) => typeof value === "string" && [ - 'transaction:"VPN connections"', + 'sentry.segment.name:"VPN connections"', "tags[type]:Unified", "tags[country]:CN", ].every((token) => value.includes(token)), diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-number-with-context.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-number-with-context.json new file mode 100644 index 000000000..fc0967f3f --- /dev/null +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-number-with-context.json @@ -0,0 +1,60 @@ +[ + { + "key": "severity_number", + "name": "severity_number", + "context": { + "isConvention": false, + "brief": "The numeric severity level of the log.", + "isDeprecated": false, + "examples": [17] + } + }, + { + "key": "sentry.observed_timestamp_nanos", + "name": "sentry.observed_timestamp_nanos", + "context": { + "isConvention": false, + "brief": "When Sentry received the log, in nanoseconds since the Unix epoch.", + "isDeprecated": false + } + }, + { + "key": "timestamp", + "name": "timestamp", + "context": { + "isConvention": false, + "brief": "When the log was emitted.", + "isDeprecated": false + } + }, + { + "key": "custom.duration", + "name": "custom.duration", + "context": {} + }, + { + "key": "custom.bytes", + "name": "custom.bytes", + "context": {} + }, + { + "key": "worker.queue_wait", + "name": "worker.queue_wait", + "context": { + "isConvention": true, + "brief": "How long the job waited in the worker queue before a worker picked it up, in milliseconds.", + "isDeprecated": false, + "examples": [412] + } + }, + { + "key": "worker.queue_wait_time", + "name": "worker.queue_wait_time", + "context": { + "isConvention": true, + "brief": "The maximum time a job may wait in the worker queue before it is dropped, as configured on the worker. It is a limit, not a measured wait.", + "isDeprecated": false, + "examples": [30000] + } + } +] diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-number.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-number.json index 5e0594f1a..79b5ecfb6 100644 --- a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-number.json +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-number.json @@ -18,5 +18,13 @@ { "key": "custom.bytes", "name": "Custom Bytes" + }, + { + "key": "worker.queue_wait", + "name": "worker.queue_wait" + }, + { + "key": "worker.queue_wait_time", + "name": "worker.queue_wait_time" } ] diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-string-with-context.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-string-with-context.json new file mode 100644 index 000000000..7cb422d0e --- /dev/null +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-string-with-context.json @@ -0,0 +1,106 @@ +[ + { + "key": "message", + "name": "message", + "context": { + "isConvention": false, + "brief": "The body of the log message.", + "isDeprecated": false + } + }, + { + "key": "severity", + "name": "severity", + "context": { + "isConvention": false, + "brief": "The severity level of the log (trace, debug, info, warn, error, fatal).", + "isDeprecated": false, + "examples": ["error"] + } + }, + { + "key": "sentry.item_id", + "name": "sentry.item_id", + "context": { + "isConvention": false, + "brief": "The unique ID of the log item.", + "isDeprecated": false + } + }, + { + "key": "project", + "name": "project", + "context": { + "isConvention": false, + "brief": "The name of the project. In some pages of sentry.io, you can also filter on project using a dropdown.", + "isDeprecated": false + } + }, + { + "key": "environment", + "name": "environment", + "context": { + "isConvention": true, + "brief": "The sentry environment.", + "isDeprecated": true, + "examples": ["production"], + "replacementAttribute": "sentry.environment" + } + }, + { + "key": "release", + "name": "release", + "context": { + "isConvention": true, + "brief": "The sentry release.", + "isDeprecated": true, + "examples": ["production"], + "replacementAttribute": "sentry.release" + } + }, + { + "key": "trace", + "name": "trace", + "context": { + "isConvention": false, + "brief": "The ID of the trace the log was emitted in.", + "isDeprecated": false + } + }, + { + "key": "level", + "name": "level", + "context": {} + }, + { + "key": "logger", + "name": "logger", + "context": {} + }, + { + "key": "module", + "name": "module", + "context": {} + }, + { + "key": "custom.service", + "name": "custom.service", + "context": {} + }, + { + "key": "custom.component", + "name": "custom.component", + "context": {} + }, + { + "key": "code.filepath", + "name": "code.filepath", + "context": { + "isConvention": true, + "brief": "The source code file name that identifies the code unit as uniquely as possible (preferably an absolute file path).", + "isDeprecated": true, + "examples": ["/app/myapplication/http/handler/server.py"], + "replacementAttribute": "code.file.path" + } + } +] diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-string.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-string.json index c66fa5390..4e27ebdd1 100644 --- a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-string.json +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-logs-string.json @@ -46,5 +46,9 @@ { "key": "custom.component", "name": "Component" + }, + { + "key": "code.filepath", + "name": "Code File Path" } ] diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-number-with-context.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-number-with-context.json new file mode 100644 index 000000000..a1540f4e6 --- /dev/null +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-number-with-context.json @@ -0,0 +1,34 @@ +[ + { + "key": "value", + "name": "value", + "context": { + "isConvention": false, + "brief": "The recorded value of the metric sample.", + "isDeprecated": false + } + }, + { + "key": "timestamp_precise", + "name": "timestamp_precise", + "context": { + "isConvention": false, + "brief": "When the metric sample was recorded, with nanosecond precision.", + "isDeprecated": false + } + }, + { + "key": "observed_timestamp", + "name": "observed_timestamp", + "context": { + "isConvention": false, + "brief": "When Sentry received the metric sample.", + "isDeprecated": false + } + }, + { + "key": "payload_size", + "name": "payload_size", + "context": {} + } +] diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-string-with-context.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-string-with-context.json new file mode 100644 index 000000000..68862a4a2 --- /dev/null +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-string-with-context.json @@ -0,0 +1,94 @@ +[ + { + "key": "metric.name", + "name": "metric.name", + "context": { + "isConvention": false, + "brief": "The name of the metric.", + "isDeprecated": false, + "examples": ["http.request.duration"] + } + }, + { + "key": "metric.type", + "name": "metric.type", + "context": { + "isConvention": false, + "brief": "The type of the metric (counter, gauge, distribution).", + "isDeprecated": false, + "examples": ["distribution"] + } + }, + { + "key": "metric.unit", + "name": "metric.unit", + "context": { + "isConvention": false, + "brief": "The unit of the metric value.", + "isDeprecated": false, + "examples": ["millisecond"] + } + }, + { + "key": "transaction", + "name": "transaction", + "context": { + "isConvention": true, + "brief": "The sentry transaction (segment name).", + "isDeprecated": true, + "examples": ["GET /"], + "replacementAttribute": "sentry.segment.name" + } + }, + { + "key": "project", + "name": "project", + "context": { + "isConvention": false, + "brief": "The name of the project. In some pages of sentry.io, you can also filter on project using a dropdown.", + "isDeprecated": false + } + }, + { + "key": "environment", + "name": "environment", + "context": { + "isConvention": true, + "brief": "The sentry environment.", + "isDeprecated": true, + "examples": ["production"], + "replacementAttribute": "sentry.environment" + } + }, + { + "key": "http.method", + "name": "http.method", + "context": { + "isConvention": true, + "brief": "The HTTP method used.", + "isDeprecated": true, + "examples": ["GET"], + "replacementAttribute": "http.request.method" + } + }, + { + "key": "fleet.region", + "name": "fleet.region", + "context": { + "isConvention": true, + "brief": "The region the server is currently running in.", + "isDeprecated": false, + "examples": ["us-east-1"] + } + }, + { + "key": "fleet.deployment_region", + "name": "fleet.deployment_region", + "context": { + "isConvention": true, + "brief": "The region the server is scheduled to move to in the next fleet deployment. It is not where the server runs now.", + "isDeprecated": false, + "examples": ["eu-west-1"] + } + } +] diff --git a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-string.json b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-string.json index 05714663b..13afc8130 100644 --- a/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-string.json +++ b/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-tracemetrics-string.json @@ -22,5 +22,17 @@ { "key": "environment", "name": "environment" + }, + { + "key": "http.method", + "name": "http.method" + }, + { + "key": "fleet.region", + "name": "fleet.region" + }, + { + "key": "fleet.deployment_region", + "name": "fleet.deployment_region" } ] diff --git a/packages/mcp-server-mocks/src/index.ts b/packages/mcp-server-mocks/src/index.ts index 610a6bfab..ceff40cbe 100644 --- a/packages/mcp-server-mocks/src/index.ts +++ b/packages/mcp-server-mocks/src/index.ts @@ -62,13 +62,17 @@ import teamFixture from "./fixtures/team.json" with { type: "json" }; import traceFixture from "./fixtures/trace.json" with { type: "json" }; import traceEventFixture from "./fixtures/trace-event.json" with { type: "json" }; import traceItemsAttributesLogsNumberFixture from "./fixtures/trace-items-attributes-logs-number.json" with { type: "json" }; +import traceItemsAttributesLogsNumberWithContextFixture from "./fixtures/trace-items-attributes-logs-number-with-context.json" with { type: "json" }; import traceItemsAttributesLogsStringFixture from "./fixtures/trace-items-attributes-logs-string.json" with { type: "json" }; +import traceItemsAttributesLogsStringWithContextFixture from "./fixtures/trace-items-attributes-logs-string-with-context.json" with { type: "json" }; import traceItemsAttributesSpansNumberFixture from "./fixtures/trace-items-attributes-spans-number.json" with { type: "json" }; import traceItemsAttributesSpansStringFixture from "./fixtures/trace-items-attributes-spans-string.json" with { type: "json" }; import traceItemsAttributesSpansNumberWithContextFixture from "./fixtures/trace-items-attributes-spans-number-with-context.json" with { type: "json" }; import traceItemsAttributesSpansStringWithContextFixture from "./fixtures/trace-items-attributes-spans-string-with-context.json" with { type: "json" }; import traceItemsAttributesTraceMetricsNumberFixture from "./fixtures/trace-items-attributes-tracemetrics-number.json" with { type: "json" }; +import traceItemsAttributesTraceMetricsNumberWithContextFixture from "./fixtures/trace-items-attributes-tracemetrics-number-with-context.json" with { type: "json" }; import traceItemsAttributesTraceMetricsStringFixture from "./fixtures/trace-items-attributes-tracemetrics-string.json" with { type: "json" }; +import traceItemsAttributesTraceMetricsStringWithContextFixture from "./fixtures/trace-items-attributes-tracemetrics-string-with-context.json" with { type: "json" }; import traceMetaFixture from "./fixtures/trace-meta.json" with { type: "json" }; import traceMetaWithNullsFixture from "./fixtures/trace-meta-with-nulls.json" with { type: "json" }; import traceMixedFixture from "./fixtures/trace-mixed.json" with { type: "json" }; @@ -1175,6 +1179,18 @@ export const restHandlers = buildHandlers([ ]); } if (normalizedItemType === "logs") { + if (useContext) { + return HttpResponse.json([ + ...withTraceItemAttributeMetadata( + traceItemsAttributesLogsStringWithContextFixture, + "string", + ), + ...withTraceItemAttributeMetadata( + traceItemsAttributesLogsNumberWithContextFixture, + "number", + ), + ]); + } return HttpResponse.json([ ...withTraceItemAttributeMetadata( traceItemsAttributesLogsStringFixture, @@ -1186,6 +1202,18 @@ export const restHandlers = buildHandlers([ ), ]); } + if (useContext) { + return HttpResponse.json([ + ...withTraceItemAttributeMetadata( + traceItemsAttributesTraceMetricsStringWithContextFixture, + "string", + ), + ...withTraceItemAttributeMetadata( + traceItemsAttributesTraceMetricsNumberWithContextFixture, + "number", + ), + ]); + } return HttpResponse.json([ ...withTraceItemAttributeMetadata( traceItemsAttributesTraceMetricsStringFixture, diff --git a/packages/mcp-server-mocks/src/payloads.ts b/packages/mcp-server-mocks/src/payloads.ts index 5078d0fda..e9b4dbed5 100644 --- a/packages/mcp-server-mocks/src/payloads.ts +++ b/packages/mcp-server-mocks/src/payloads.ts @@ -6,92 +6,42 @@ */ // Import JSON fixtures -import autofixStateFixture from "./fixtures/autofix-state.json" with { - type: "json", -}; -import autofixStateExplorerFixture from "./fixtures/autofix-state-explorer.json" with { - type: "json", -}; +import autofixStateFixture from "./fixtures/autofix-state.json" with { type: "json" }; +import autofixStateExplorerFixture from "./fixtures/autofix-state-explorer.json" with { type: "json" }; import clientKeyFixture from "./fixtures/client-key.json" with { type: "json" }; -import eventAttachmentsFixture from "./fixtures/event-attachments.json" with { - type: "json", -}; +import eventAttachmentsFixture from "./fixtures/event-attachments.json" with { type: "json" }; import eventFixture from "./fixtures/event.json" with { type: "json" }; -import eventsErrorsEmptyFixture from "./fixtures/events-errors-empty.json" with { - type: "json", -}; -import eventsErrorsFixture from "./fixtures/events-errors.json" with { - type: "json", -}; -import eventsSpansEmptyFixture from "./fixtures/events-spans-empty.json" with { - type: "json", -}; -import eventsSpansFixture from "./fixtures/events-spans.json" with { - type: "json", -}; -import flamegraphFixture from "./fixtures/flamegraph.json" with { - type: "json", -}; -import transactionProfileV1Fixture from "./fixtures/transaction-profile-v1.json" with { - type: "json", -}; -import transactionProfileV1MissingFunctionFixture from "./fixtures/transaction-profile-v1-missing-function.json" with { - type: "json", -}; +import eventsErrorsEmptyFixture from "./fixtures/events-errors-empty.json" with { type: "json" }; +import eventsErrorsFixture from "./fixtures/events-errors.json" with { type: "json" }; +import eventsSpansEmptyFixture from "./fixtures/events-spans-empty.json" with { type: "json" }; +import eventsSpansFixture from "./fixtures/events-spans.json" with { type: "json" }; +import flamegraphFixture from "./fixtures/flamegraph.json" with { type: "json" }; +import transactionProfileV1Fixture from "./fixtures/transaction-profile-v1.json" with { type: "json" }; +import transactionProfileV1MissingFunctionFixture from "./fixtures/transaction-profile-v1-missing-function.json" with { type: "json" }; import issueFixture from "./fixtures/issue.json" with { type: "json" }; -import issueNullCulpritFixture from "./fixtures/issue-null-culprit.json" with { - type: "json", -}; -import issueTagValuesFixture from "./fixtures/issue-tag-values.json" with { - type: "json", -}; -import organizationFixture from "./fixtures/organization.json" with { - type: "json", -}; -import performanceEventFixture from "./fixtures/performance-event.json" with { - type: "json", -}; +import issueNullCulpritFixture from "./fixtures/issue-null-culprit.json" with { type: "json" }; +import issueTagValuesFixture from "./fixtures/issue-tag-values.json" with { type: "json" }; +import organizationFixture from "./fixtures/organization.json" with { type: "json" }; +import performanceEventFixture from "./fixtures/performance-event.json" with { type: "json" }; import projectFixture from "./fixtures/project.json" with { type: "json" }; import releaseFixture from "./fixtures/release.json" with { type: "json" }; -import replayDetailsFixture from "./fixtures/replay-details.json" with { - type: "json", -}; -import replayRecordingSegmentsFixture from "./fixtures/replay-recording-segments.json" with { - type: "json", -}; +import replayDetailsFixture from "./fixtures/replay-details.json" with { type: "json" }; +import replayRecordingSegmentsFixture from "./fixtures/replay-recording-segments.json" with { type: "json" }; import tagsFixture from "./fixtures/tags.json" with { type: "json" }; import teamFixture from "./fixtures/team.json" with { type: "json" }; -import traceEventFixture from "./fixtures/trace-event.json" with { - type: "json", -}; -import traceItemsAttributesLogsNumberFixture from "./fixtures/trace-items-attributes-logs-number.json" with { - type: "json", -}; -import traceItemsAttributesLogsStringFixture from "./fixtures/trace-items-attributes-logs-string.json" with { - type: "json", -}; -import traceItemsAttributesSpansNumberFixture from "./fixtures/trace-items-attributes-spans-number.json" with { - type: "json", -}; -import traceItemsAttributesSpansStringFixture from "./fixtures/trace-items-attributes-spans-string.json" with { - type: "json", -}; -import traceItemsAttributesSpansNumberWithContextFixture from "./fixtures/trace-items-attributes-spans-number-with-context.json" with { - type: "json", -}; -import traceItemsAttributesSpansStringWithContextFixture from "./fixtures/trace-items-attributes-spans-string-with-context.json" with { - type: "json", -}; -import traceItemsAttributesFixture from "./fixtures/trace-items-attributes.json" with { - type: "json", -}; -import traceMetaWithNullsFixture from "./fixtures/trace-meta-with-nulls.json" with { - type: "json", -}; +import traceEventFixture from "./fixtures/trace-event.json" with { type: "json" }; +import traceItemsAttributesLogsNumberFixture from "./fixtures/trace-items-attributes-logs-number.json" with { type: "json" }; +import traceItemsAttributesLogsStringFixture from "./fixtures/trace-items-attributes-logs-string.json" with { type: "json" }; +import traceItemsAttributesLogsNumberWithContextFixture from "./fixtures/trace-items-attributes-logs-number-with-context.json" with { type: "json" }; +import traceItemsAttributesLogsStringWithContextFixture from "./fixtures/trace-items-attributes-logs-string-with-context.json" with { type: "json" }; +import traceItemsAttributesSpansNumberFixture from "./fixtures/trace-items-attributes-spans-number.json" with { type: "json" }; +import traceItemsAttributesSpansStringFixture from "./fixtures/trace-items-attributes-spans-string.json" with { type: "json" }; +import traceItemsAttributesSpansNumberWithContextFixture from "./fixtures/trace-items-attributes-spans-number-with-context.json" with { type: "json" }; +import traceItemsAttributesSpansStringWithContextFixture from "./fixtures/trace-items-attributes-spans-string-with-context.json" with { type: "json" }; +import traceItemsAttributesFixture from "./fixtures/trace-items-attributes.json" with { type: "json" }; +import traceMetaWithNullsFixture from "./fixtures/trace-meta-with-nulls.json" with { type: "json" }; import traceMetaFixture from "./fixtures/trace-meta.json" with { type: "json" }; -import traceMixedFixture from "./fixtures/trace-mixed.json" with { - type: "json", -}; +import traceMixedFixture from "./fixtures/trace-mixed.json" with { type: "json" }; import traceFixture from "./fixtures/trace.json" with { type: "json" }; import userFixture from "./fixtures/user.json" with { type: "json" }; @@ -139,6 +89,8 @@ export { traceItemsAttributesSpansStringWithContextFixture, traceItemsAttributesLogsStringFixture, traceItemsAttributesLogsNumberFixture, + traceItemsAttributesLogsNumberWithContextFixture, + traceItemsAttributesLogsStringWithContextFixture, traceMetaFixture, traceMetaWithNullsFixture, traceFixture,