Add Observability Protocol: Logging, Metrics, Tracing#378
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
This PR is very large. Consider breaking it into smaller PRs for easier review. |
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
This PR is very large. Consider breaking it into smaller PRs for easier review. |
| import { | ||
| LogLevel, | ||
| ExtendedLogLevel, | ||
| LogDestinationType, | ||
| ConsoleDestinationConfigSchema, | ||
| FileDestinationConfigSchema, | ||
| HttpDestinationConfigSchema, | ||
| ExternalServiceDestinationConfigSchema, | ||
| LogDestinationSchema, | ||
| LogEnrichmentConfigSchema, | ||
| StructuredLogEntrySchema, | ||
| LoggingConfigSchema, | ||
| type LogDestination, | ||
| type StructuredLogEntry, | ||
| type LoggingConfig, | ||
| } from './logging.zod'; |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the problem, we should remove the unused named imports ExternalServiceDestinationConfigSchema and LogLevel from the import list pulled from ./logging.zod. This keeps the test file focused only on the schemas and types it actually exercises and eliminates dead code.
Concretely, in packages/spec/src/system/logging.test.ts, on the import block starting at line 2, delete the LogLevel identifier and the ExternalServiceDestinationConfigSchema identifier from the curly-brace list. All other imports remain unchanged. No additional methods, definitions, or imports are required, and this change does not alter runtime behavior because removing unused imports from a test file doesn’t affect what is executed.
| @@ -1,12 +1,10 @@ | ||
| import { describe, it, expect } from 'vitest'; | ||
| import { | ||
| LogLevel, | ||
| ExtendedLogLevel, | ||
| LogDestinationType, | ||
| ConsoleDestinationConfigSchema, | ||
| FileDestinationConfigSchema, | ||
| HttpDestinationConfigSchema, | ||
| ExternalServiceDestinationConfigSchema, | ||
| LogDestinationSchema, | ||
| LogEnrichmentConfigSchema, | ||
| StructuredLogEntrySchema, |
| import { | ||
| MetricType, | ||
| MetricUnit, | ||
| MetricAggregationType, | ||
| HistogramBucketConfigSchema, | ||
| MetricDefinitionSchema, | ||
| MetricDataPointSchema, | ||
| TimeSeriesDataPointSchema, | ||
| TimeSeriesSchema, | ||
| MetricAggregationConfigSchema, | ||
| ServiceLevelIndicatorSchema, | ||
| ServiceLevelObjectiveSchema, | ||
| MetricExportConfigSchema, | ||
| MetricsConfigSchema, | ||
| type MetricDefinition, | ||
| type MetricDataPoint, | ||
| type ServiceLevelIndicator, | ||
| type ServiceLevelObjective, | ||
| type MetricsConfig, | ||
| } from './metrics.zod'; |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, unused imports should be removed from the import list. This eliminates dead code, avoids confusion about what is actually being tested or used, and can slightly improve tooling performance.
For this specific issue, the fix is to modify the named import list from ./metrics.zod at the top of packages/spec/src/system/metrics.test.ts and remove TimeSeriesDataPointSchema from it. No additional code, functions, or imports are required. All other imports in that block should remain unchanged, as they are used in the tests. The change is local to line 9 in the provided snippet and does not alter existing functionality.
| @@ -6,7 +6,6 @@ | ||
| HistogramBucketConfigSchema, | ||
| MetricDefinitionSchema, | ||
| MetricDataPointSchema, | ||
| TimeSeriesDataPointSchema, | ||
| TimeSeriesSchema, | ||
| MetricAggregationConfigSchema, | ||
| ServiceLevelIndicatorSchema, |
| import { | ||
| TraceStateSchema, | ||
| TraceFlagsSchema, | ||
| TraceContextSchema, | ||
| SpanKind, | ||
| SpanStatus, | ||
| SpanAttributeValueSchema, | ||
| SpanEventSchema, | ||
| SpanLinkSchema, | ||
| SpanSchema, | ||
| SamplingDecision, | ||
| SamplingStrategyType, | ||
| TraceSamplingConfigSchema, | ||
| TracePropagationFormat, | ||
| TraceContextPropagationSchema, | ||
| OtelExporterType, | ||
| OpenTelemetryCompatibilitySchema, | ||
| TracingConfigSchema, | ||
| type TraceContext, | ||
| type Span, | ||
| type TraceSamplingConfig, | ||
| type OpenTelemetryCompatibility, | ||
| type TracingConfig, | ||
| } from './tracing.zod'; |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, unused imports should be removed to keep the codebase clean and avoid confusion about what is actually used in a file. This also prevents potential linting and static analysis warnings from obscuring more significant issues.
For this specific file, the best fix without changing functionality is to delete TraceStateSchema from the import list on lines 2–25 of packages/spec/src/system/tracing.test.ts. No other code references TraceStateSchema in the shown snippet, so removing it will not affect runtime behavior or tests. Concretely, edit the multi-line import from './tracing.zod' so that line 3 (the TraceStateSchema identifier) is removed and the rest of the imported names remain unchanged. No new methods, definitions, or imports are required.
| @@ -1,6 +1,5 @@ | ||
| import { describe, it, expect } from 'vitest'; | ||
| import { | ||
| TraceStateSchema, | ||
| TraceFlagsSchema, | ||
| TraceContextSchema, | ||
| SpanKind, |
There was a problem hiding this comment.
Pull request overview
Adds an “Observability Protocol” to packages/spec by introducing Zod schemas (and generated JSON Schemas) for structured logging, metrics, and distributed tracing, plus corresponding Vitest coverage.
Changes:
- Added new Zod protocol definitions for logging, metrics, and tracing under
packages/spec/src/system/. - Added Vitest test suites validating the new schemas and defaults.
- Updated
packages/spec/src/system/index.tsto export the new protocols and added generated JSON Schema artifacts for the new types.
Reviewed changes
Copilot reviewed 49 out of 49 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/spec/src/system/logging.zod.ts | Introduces logging protocol schemas (levels, destinations, enrichment, config, structured log entry). |
| packages/spec/src/system/logging.test.ts | Adds Vitest coverage for logging schemas. |
| packages/spec/src/system/metrics.zod.ts | Introduces metrics protocol schemas (metric types/units, datapoints, timeseries, SLI/SLO, exports, config). |
| packages/spec/src/system/metrics.test.ts | Adds Vitest coverage for metrics schemas. |
| packages/spec/src/system/tracing.zod.ts | Introduces tracing protocol schemas (trace context, spans, sampling, propagation, OTel exporter/config). |
| packages/spec/src/system/tracing.test.ts | Adds Vitest coverage for tracing schemas. |
| packages/spec/src/system/index.ts | Re-exports new observability protocols from the system barrel. |
| packages/spec/json-schema/system/ConsoleDestinationConfig.json | Generated JSON schema for console logging destination config. |
| packages/spec/json-schema/system/ExtendedLogLevel.json | Generated JSON schema for extended log levels. |
| packages/spec/json-schema/system/ExternalServiceDestinationConfig.json | Generated JSON schema for external service logging destination config. |
| packages/spec/json-schema/system/FileDestinationConfig.json | Generated JSON schema for file logging destination config. |
| packages/spec/json-schema/system/HistogramBucketConfig.json | Generated JSON schema for histogram bucket configuration. |
| packages/spec/json-schema/system/HttpDestinationConfig.json | Generated JSON schema for HTTP logging destination config. |
| packages/spec/json-schema/system/LogDestination.json | Generated JSON schema for log destination configuration. |
| packages/spec/json-schema/system/LogDestinationType.json | Generated JSON schema for log destination types. |
| packages/spec/json-schema/system/LogEnrichmentConfig.json | Generated JSON schema for log enrichment configuration. |
| packages/spec/json-schema/system/LoggingConfig.json | Generated JSON schema for logging configuration. |
| packages/spec/json-schema/system/MetricAggregationConfig.json | Generated JSON schema for metric aggregation configuration. |
| packages/spec/json-schema/system/MetricAggregationType.json | Generated JSON schema for metric aggregation types. |
| packages/spec/json-schema/system/MetricDataPoint.json | Generated JSON schema for metric data points. |
| packages/spec/json-schema/system/MetricDefinition.json | Generated JSON schema for metric definitions. |
| packages/spec/json-schema/system/MetricExportConfig.json | Generated JSON schema for metric export configuration. |
| packages/spec/json-schema/system/MetricLabels.json | Generated JSON schema for metric labels. |
| packages/spec/json-schema/system/MetricType.json | Generated JSON schema for metric types. |
| packages/spec/json-schema/system/MetricUnit.json | Generated JSON schema for metric units. |
| packages/spec/json-schema/system/MetricsConfig.json | Generated JSON schema for metrics configuration. |
| packages/spec/json-schema/system/OpenTelemetryCompatibility.json | Generated JSON schema for OpenTelemetry compatibility configuration. |
| packages/spec/json-schema/system/OtelExporterType.json | Generated JSON schema for OTel exporter types. |
| packages/spec/json-schema/system/SamplingDecision.json | Generated JSON schema for sampling decisions. |
| packages/spec/json-schema/system/SamplingStrategyType.json | Generated JSON schema for sampling strategy types. |
| packages/spec/json-schema/system/ServiceLevelIndicator.json | Generated JSON schema for SLI definitions. |
| packages/spec/json-schema/system/ServiceLevelObjective.json | Generated JSON schema for SLO definitions. |
| packages/spec/json-schema/system/Span.json | Generated JSON schema for spans. |
| packages/spec/json-schema/system/SpanAttributeValue.json | Generated JSON schema for span attribute values. |
| packages/spec/json-schema/system/SpanAttributes.json | Generated JSON schema for span attributes. |
| packages/spec/json-schema/system/SpanEvent.json | Generated JSON schema for span events. |
| packages/spec/json-schema/system/SpanKind.json | Generated JSON schema for span kinds. |
| packages/spec/json-schema/system/SpanLink.json | Generated JSON schema for span links. |
| packages/spec/json-schema/system/SpanStatus.json | Generated JSON schema for span statuses. |
| packages/spec/json-schema/system/StructuredLogEntry.json | Generated JSON schema for structured log entries. |
| packages/spec/json-schema/system/TimeSeries.json | Generated JSON schema for time series. |
| packages/spec/json-schema/system/TimeSeriesDataPoint.json | Generated JSON schema for time series data points. |
| packages/spec/json-schema/system/TraceContext.json | Generated JSON schema for trace context. |
| packages/spec/json-schema/system/TraceContextPropagation.json | Generated JSON schema for trace context propagation. |
| packages/spec/json-schema/system/TraceFlags.json | Generated JSON schema for trace flags. |
| packages/spec/json-schema/system/TracePropagationFormat.json | Generated JSON schema for trace propagation formats. |
| packages/spec/json-schema/system/TraceSamplingConfig.json | Generated JSON schema for trace sampling configuration. |
| packages/spec/json-schema/system/TraceState.json | Generated JSON schema for trace state. |
| packages/spec/json-schema/system/TracingConfig.json | Generated JSON schema for tracing configuration. |
| * Trace ID (128-bit identifier, 32 hex chars) | ||
| */ | ||
| traceId: z.string() | ||
| .regex(/^[0-9a-f]{32}$/) | ||
| .describe('Trace ID (32 hex chars)'), |
There was a problem hiding this comment.
W3C Trace Context forbids an all-zero trace-id ("000…000"). The current regex allows it, so an invalid trace context would pass validation. Add an additional refinement to reject the all-zero value (in addition to the hex-length check).
| * Span ID (64-bit identifier, 16 hex chars) | ||
| */ | ||
| spanId: z.string() | ||
| .regex(/^[0-9a-f]{16}$/) | ||
| .describe('Span ID (16 hex chars)'), |
There was a problem hiding this comment.
W3C Trace Context forbids an all-zero span-id ("000…000"). The current regex allows it, so an invalid trace/span context would pass validation. Add a refinement to reject the all-zero value.
| export const HistogramBucketConfigSchema = z.object({ | ||
| /** | ||
| * Bucket type | ||
| */ | ||
| type: z.enum(['linear', 'exponential', 'explicit']).describe('Bucket type'), | ||
|
|
||
| /** | ||
| * Linear bucket configuration | ||
| */ | ||
| linear: z.object({ | ||
| start: z.number().describe('Start value'), | ||
| width: z.number().positive().describe('Bucket width'), | ||
| count: z.number().int().positive().describe('Number of buckets'), | ||
| }).optional(), | ||
|
|
There was a problem hiding this comment.
HistogramBucketConfigSchema allows { type: 'linear' } (or other types) without the corresponding linear/exponential/explicit config being present, since those fields are all optional. This makes type non-functional and permits invalid histogram bucket definitions. Consider a discriminated union on type to require the matching configuration block and disallow the others.
| /** | ||
| * Destination type | ||
| */ | ||
| type: LogDestinationType.describe('Destination type'), | ||
|
|
||
| /** | ||
| * Minimum log level for this destination | ||
| */ | ||
| level: ExtendedLogLevel.optional().default('info'), | ||
|
|
||
| /** | ||
| * Enabled flag | ||
| */ | ||
| enabled: z.boolean().optional().default(true), | ||
|
|
||
| /** | ||
| * Console configuration | ||
| */ | ||
| console: ConsoleDestinationConfigSchema.optional(), | ||
|
|
||
| /** | ||
| * File configuration | ||
| */ | ||
| file: FileDestinationConfigSchema.optional(), | ||
|
|
||
| /** | ||
| * HTTP configuration | ||
| */ | ||
| http: HttpDestinationConfigSchema.optional(), | ||
|
|
||
| /** | ||
| * External service configuration | ||
| */ | ||
| externalService: ExternalServiceDestinationConfigSchema.optional(), | ||
|
|
There was a problem hiding this comment.
LogDestinationSchema does not enforce that the destination-specific config is present for the selected type (e.g., type: 'file' can omit file, type: 'http' can omit http, etc.), and it also permits unrelated configs to be set simultaneously. This makes it easy to create invalid destination configs that still pass validation. Consider a discriminated union on type (or a .superRefine) to require the matching config block and disallow incompatible ones.
| */ | ||
| label: z.string().describe('Display label'), | ||
|
|
||
| /** | ||
| * Enable logging |
There was a problem hiding this comment.
The PR description’s LoggingConfig example omits fields that are required by the schema (e.g., label on LoggingConfig, and name on each LogDestination). Either update the examples in the PR description/docs to include the required fields, or adjust the schemas to make those fields optional if that’s the intended API.
| /** | ||
| * Endpoint URL | ||
| */ | ||
| endpoint: z.string().url().optional().describe('Exporter endpoint'), | ||
|
|
There was a problem hiding this comment.
OpenTelemetryCompatibilitySchema.exporter.endpoint is validated with z.string().url(), but the tests (and common OTLP gRPC configs) use a host:port value like otel-collector:4317, which is not a valid URL and will fail .url() validation. Either relax this schema to accept host:port (or a dedicated host/port shape), or update tests/config examples to use a fully-qualified URL (e.g., including a scheme).
| // Re-export base LogLevel for compatibility | ||
| export { BaseLogLevel as LogLevel }; | ||
|
|
There was a problem hiding this comment.
LogLevel is re-exported here from logger.zod.ts, but packages/spec/src/system/index.ts also exports logger.zod.ts directly. With the new export * from './logging.zod' barrel export, this can lead to a duplicate/ambiguous LogLevel export from system/index.ts. Consider removing this re-export (and have consumers import LogLevel from logger.zod.ts), or renaming it (e.g., BaseLogLevel) to avoid export collisions.
| // Observability Protocol | ||
| export * from './logging.zod'; | ||
| export * from './metrics.zod'; | ||
| export * from './tracing.zod'; |
There was a problem hiding this comment.
export * from './logging.zod' will re-export LogLevel (via logging.zod.ts re-export), while this barrel also re-exports LogLevel from ./logger.zod. This can cause a duplicate export name error for system/index.ts. Either stop re-exporting LogLevel from logging.zod.ts, or avoid exporting both modules’ LogLevel under the same name from this index.
| type: SamplingStrategyType.describe('Sampling strategy'), | ||
|
|
||
| /** | ||
| * Sample ratio (0.0 to 1.0) for trace_id_ratio and probability strategies | ||
| */ | ||
| ratio: z.number().min(0).max(1).optional().describe('Sample ratio (0-1)'), | ||
|
|
||
| /** | ||
| * Rate limit (traces per second) for rate_limiting strategy | ||
| */ | ||
| rateLimit: z.number().positive().optional().describe('Traces per second'), | ||
|
|
There was a problem hiding this comment.
TraceSamplingConfigSchema does not enforce required fields for specific sampler types (e.g., trace_id_ratio/probability should require ratio, rate_limiting should require rateLimit, parent_based should require parentBased, custom should require customSamplerId). As written, many invalid configs will parse successfully. Consider using a discriminated union on type (or a .superRefine) to enforce these constraints.
| name: z.string().describe('Metric name'), | ||
|
|
||
| /** | ||
| * Metric type | ||
| */ | ||
| type: MetricType.describe('Metric type'), | ||
|
|
||
| /** | ||
| * Timestamp (ISO 8601) | ||
| */ | ||
| timestamp: z.string().datetime().describe('Observation timestamp'), | ||
|
|
||
| /** | ||
| * Value (for counter and gauge) | ||
| */ | ||
| value: z.number().optional().describe('Metric value'), | ||
|
|
||
| /** | ||
| * Labels | ||
| */ | ||
| labels: MetricLabelsSchema.optional().describe('Metric labels'), | ||
|
|
||
| /** | ||
| * Histogram data | ||
| */ | ||
| histogram: z.object({ | ||
| count: z.number().int().nonnegative().describe('Total count'), | ||
| sum: z.number().describe('Sum of all values'), | ||
| buckets: z.array(z.object({ | ||
| upperBound: z.number().describe('Upper bound of bucket'), | ||
| count: z.number().int().nonnegative().describe('Count in bucket'), | ||
| })).describe('Histogram buckets'), | ||
| }).optional(), | ||
|
|
There was a problem hiding this comment.
MetricDataPointSchema does not validate shape based on type: e.g., a counter/gauge can omit value, and a histogram/summary can omit their respective payloads (or include both value and histogram). This will allow structurally invalid data points to pass validation. Consider a discriminated union on type to require value vs histogram vs summary appropriately.
Implements comprehensive observability protocols for structured logging, metrics collection, and distributed tracing to enable production-grade monitoring across ObjectStack components.
Logging Protocol (
logging.zod.ts)Metrics Protocol (
metrics.zod.ts)Tracing Protocol (
tracing.zod.ts)Implementation Notes
logger.zod.tsexports base LogLevel; new protocol exports ExtendedLogLevel with 'trace'z.inferfrom Zod schemasOriginal prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.