diff --git a/packages/spec/json-schema/system/ConsoleDestinationConfig.json b/packages/spec/json-schema/system/ConsoleDestinationConfig.json new file mode 100644 index 000000000..baccfc463 --- /dev/null +++ b/packages/spec/json-schema/system/ConsoleDestinationConfig.json @@ -0,0 +1,29 @@ +{ + "$ref": "#/definitions/ConsoleDestinationConfig", + "definitions": { + "ConsoleDestinationConfig": { + "type": "object", + "properties": { + "stream": { + "type": "string", + "enum": [ + "stdout", + "stderr" + ], + "default": "stdout" + }, + "colors": { + "type": "boolean", + "default": true + }, + "prettyPrint": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false, + "description": "Console destination configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ExtendedLogLevel.json b/packages/spec/json-schema/system/ExtendedLogLevel.json new file mode 100644 index 000000000..792e7a8a7 --- /dev/null +++ b/packages/spec/json-schema/system/ExtendedLogLevel.json @@ -0,0 +1,18 @@ +{ + "$ref": "#/definitions/ExtendedLogLevel", + "definitions": { + "ExtendedLogLevel": { + "type": "string", + "enum": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal" + ], + "description": "Extended log severity level" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ExternalServiceDestinationConfig.json b/packages/spec/json-schema/system/ExternalServiceDestinationConfig.json new file mode 100644 index 000000000..3f7bd2319 --- /dev/null +++ b/packages/spec/json-schema/system/ExternalServiceDestinationConfig.json @@ -0,0 +1,51 @@ +{ + "$ref": "#/definitions/ExternalServiceDestinationConfig", + "definitions": { + "ExternalServiceDestinationConfig": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "format": "uri" + }, + "region": { + "type": "string" + }, + "credentials": { + "type": "object", + "properties": { + "accessKeyId": { + "type": "string" + }, + "secretAccessKey": { + "type": "string" + }, + "apiKey": { + "type": "string" + }, + "projectId": { + "type": "string" + } + }, + "additionalProperties": false + }, + "logGroup": { + "type": "string" + }, + "logStream": { + "type": "string" + }, + "index": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false, + "description": "External service destination configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/FileDestinationConfig.json b/packages/spec/json-schema/system/FileDestinationConfig.json new file mode 100644 index 000000000..43251555b --- /dev/null +++ b/packages/spec/json-schema/system/FileDestinationConfig.json @@ -0,0 +1,56 @@ +{ + "$ref": "#/definitions/FileDestinationConfig", + "definitions": { + "FileDestinationConfig": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Log file path" + }, + "rotation": { + "type": "object", + "properties": { + "maxSize": { + "type": "string", + "default": "10m" + }, + "maxFiles": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5 + }, + "compress": { + "type": "boolean", + "default": true + }, + "interval": { + "type": "string", + "enum": [ + "hourly", + "daily", + "weekly", + "monthly" + ] + } + }, + "additionalProperties": false + }, + "encoding": { + "type": "string", + "default": "utf8" + }, + "append": { + "type": "boolean", + "default": true + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "File destination configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/HistogramBucketConfig.json b/packages/spec/json-schema/system/HistogramBucketConfig.json new file mode 100644 index 000000000..3886e5cd2 --- /dev/null +++ b/packages/spec/json-schema/system/HistogramBucketConfig.json @@ -0,0 +1,92 @@ +{ + "$ref": "#/definitions/HistogramBucketConfig", + "definitions": { + "HistogramBucketConfig": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "exponential", + "explicit" + ], + "description": "Bucket type" + }, + "linear": { + "type": "object", + "properties": { + "start": { + "type": "number", + "description": "Start value" + }, + "width": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Bucket width" + }, + "count": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Number of buckets" + } + }, + "required": [ + "start", + "width", + "count" + ], + "additionalProperties": false + }, + "exponential": { + "type": "object", + "properties": { + "start": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Start value" + }, + "factor": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Growth factor" + }, + "count": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Number of buckets" + } + }, + "required": [ + "start", + "factor", + "count" + ], + "additionalProperties": false + }, + "explicit": { + "type": "object", + "properties": { + "boundaries": { + "type": "array", + "items": { + "type": "number" + }, + "description": "Bucket boundaries" + } + }, + "required": [ + "boundaries" + ], + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Histogram bucket configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/HttpDestinationConfig.json b/packages/spec/json-schema/system/HttpDestinationConfig.json new file mode 100644 index 000000000..dcb3a30e7 --- /dev/null +++ b/packages/spec/json-schema/system/HttpDestinationConfig.json @@ -0,0 +1,111 @@ +{ + "$ref": "#/definitions/HttpDestinationConfig", + "definitions": { + "HttpDestinationConfig": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "HTTP endpoint URL" + }, + "method": { + "type": "string", + "enum": [ + "POST", + "PUT" + ], + "default": "POST" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "auth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "basic", + "bearer", + "api_key" + ], + "description": "Auth type" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "token": { + "type": "string" + }, + "apiKey": { + "type": "string" + }, + "apiKeyHeader": { + "type": "string", + "default": "X-API-Key" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "batch": { + "type": "object", + "properties": { + "maxSize": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 100 + }, + "flushInterval": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5000 + } + }, + "additionalProperties": false + }, + "retry": { + "type": "object", + "properties": { + "maxAttempts": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 3 + }, + "initialDelay": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 1000 + }, + "backoffMultiplier": { + "type": "number", + "exclusiveMinimum": 0, + "default": 2 + } + }, + "additionalProperties": false + }, + "timeout": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 30000 + } + }, + "required": [ + "url" + ], + "additionalProperties": false, + "description": "HTTP destination configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/LogDestination.json b/packages/spec/json-schema/system/LogDestination.json new file mode 100644 index 000000000..e92fea1ce --- /dev/null +++ b/packages/spec/json-schema/system/LogDestination.json @@ -0,0 +1,295 @@ +{ + "$ref": "#/definitions/LogDestination", + "definitions": { + "LogDestination": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "description": "Destination name (snake_case)" + }, + "type": { + "type": "string", + "enum": [ + "console", + "file", + "syslog", + "elasticsearch", + "cloudwatch", + "stackdriver", + "azure_monitor", + "datadog", + "splunk", + "loki", + "http", + "kafka", + "redis", + "custom" + ], + "description": "Destination type" + }, + "level": { + "type": "string", + "enum": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal" + ], + "description": "Extended log severity level", + "default": "info" + }, + "enabled": { + "type": "boolean", + "default": true + }, + "console": { + "type": "object", + "properties": { + "stream": { + "type": "string", + "enum": [ + "stdout", + "stderr" + ], + "default": "stdout" + }, + "colors": { + "type": "boolean", + "default": true + }, + "prettyPrint": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false, + "description": "Console destination configuration" + }, + "file": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Log file path" + }, + "rotation": { + "type": "object", + "properties": { + "maxSize": { + "type": "string", + "default": "10m" + }, + "maxFiles": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5 + }, + "compress": { + "type": "boolean", + "default": true + }, + "interval": { + "type": "string", + "enum": [ + "hourly", + "daily", + "weekly", + "monthly" + ] + } + }, + "additionalProperties": false + }, + "encoding": { + "type": "string", + "default": "utf8" + }, + "append": { + "type": "boolean", + "default": true + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "File destination configuration" + }, + "http": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "HTTP endpoint URL" + }, + "method": { + "type": "string", + "enum": [ + "POST", + "PUT" + ], + "default": "POST" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "auth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "basic", + "bearer", + "api_key" + ], + "description": "Auth type" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "token": { + "type": "string" + }, + "apiKey": { + "type": "string" + }, + "apiKeyHeader": { + "type": "string", + "default": "X-API-Key" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "batch": { + "type": "object", + "properties": { + "maxSize": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 100 + }, + "flushInterval": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5000 + } + }, + "additionalProperties": false + }, + "retry": { + "type": "object", + "properties": { + "maxAttempts": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 3 + }, + "initialDelay": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 1000 + }, + "backoffMultiplier": { + "type": "number", + "exclusiveMinimum": 0, + "default": 2 + } + }, + "additionalProperties": false + }, + "timeout": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 30000 + } + }, + "required": [ + "url" + ], + "additionalProperties": false, + "description": "HTTP destination configuration" + }, + "externalService": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "format": "uri" + }, + "region": { + "type": "string" + }, + "credentials": { + "type": "object", + "properties": { + "accessKeyId": { + "type": "string" + }, + "secretAccessKey": { + "type": "string" + }, + "apiKey": { + "type": "string" + }, + "projectId": { + "type": "string" + } + }, + "additionalProperties": false + }, + "logGroup": { + "type": "string" + }, + "logStream": { + "type": "string" + }, + "index": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false, + "description": "External service destination configuration" + }, + "format": { + "type": "string", + "enum": [ + "json", + "text", + "pretty" + ], + "default": "json" + }, + "filterId": { + "type": "string", + "description": "Filter function identifier" + } + }, + "required": [ + "name", + "type" + ], + "additionalProperties": false, + "description": "Log destination configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/LogDestinationType.json b/packages/spec/json-schema/system/LogDestinationType.json new file mode 100644 index 000000000..c98e58747 --- /dev/null +++ b/packages/spec/json-schema/system/LogDestinationType.json @@ -0,0 +1,26 @@ +{ + "$ref": "#/definitions/LogDestinationType", + "definitions": { + "LogDestinationType": { + "type": "string", + "enum": [ + "console", + "file", + "syslog", + "elasticsearch", + "cloudwatch", + "stackdriver", + "azure_monitor", + "datadog", + "splunk", + "loki", + "http", + "kafka", + "redis", + "custom" + ], + "description": "Log destination type" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/LogEnrichmentConfig.json b/packages/spec/json-schema/system/LogEnrichmentConfig.json new file mode 100644 index 000000000..0f68a440a --- /dev/null +++ b/packages/spec/json-schema/system/LogEnrichmentConfig.json @@ -0,0 +1,59 @@ +{ + "$ref": "#/definitions/LogEnrichmentConfig", + "definitions": { + "LogEnrichmentConfig": { + "type": "object", + "properties": { + "staticFields": { + "type": "object", + "additionalProperties": {}, + "description": "Static fields added to every log" + }, + "dynamicEnrichers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Dynamic enricher function IDs" + }, + "addHostname": { + "type": "boolean", + "default": true + }, + "addProcessId": { + "type": "boolean", + "default": true + }, + "addEnvironment": { + "type": "boolean", + "default": true + }, + "addTimestampFormats": { + "type": "object", + "properties": { + "unix": { + "type": "boolean", + "default": false + }, + "iso": { + "type": "boolean", + "default": true + } + }, + "additionalProperties": false + }, + "addCaller": { + "type": "boolean", + "default": false + }, + "addCorrelationIds": { + "type": "boolean", + "default": true + } + }, + "additionalProperties": false, + "description": "Log enrichment configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/LoggingConfig.json b/packages/spec/json-schema/system/LoggingConfig.json new file mode 100644 index 000000000..b4e24c607 --- /dev/null +++ b/packages/spec/json-schema/system/LoggingConfig.json @@ -0,0 +1,472 @@ +{ + "$ref": "#/definitions/LoggingConfig", + "definitions": { + "LoggingConfig": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "maxLength": 64, + "description": "Configuration name (snake_case, max 64 chars)" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "enabled": { + "type": "boolean", + "default": true + }, + "level": { + "type": "string", + "enum": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal" + ], + "description": "Extended log severity level", + "default": "info" + }, + "destinations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "description": "Destination name (snake_case)" + }, + "type": { + "type": "string", + "enum": [ + "console", + "file", + "syslog", + "elasticsearch", + "cloudwatch", + "stackdriver", + "azure_monitor", + "datadog", + "splunk", + "loki", + "http", + "kafka", + "redis", + "custom" + ], + "description": "Destination type" + }, + "level": { + "type": "string", + "enum": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal" + ], + "description": "Extended log severity level", + "default": "info" + }, + "enabled": { + "type": "boolean", + "default": true + }, + "console": { + "type": "object", + "properties": { + "stream": { + "type": "string", + "enum": [ + "stdout", + "stderr" + ], + "default": "stdout" + }, + "colors": { + "type": "boolean", + "default": true + }, + "prettyPrint": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false, + "description": "Console destination configuration" + }, + "file": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Log file path" + }, + "rotation": { + "type": "object", + "properties": { + "maxSize": { + "type": "string", + "default": "10m" + }, + "maxFiles": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5 + }, + "compress": { + "type": "boolean", + "default": true + }, + "interval": { + "type": "string", + "enum": [ + "hourly", + "daily", + "weekly", + "monthly" + ] + } + }, + "additionalProperties": false + }, + "encoding": { + "type": "string", + "default": "utf8" + }, + "append": { + "type": "boolean", + "default": true + } + }, + "required": [ + "path" + ], + "additionalProperties": false, + "description": "File destination configuration" + }, + "http": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "HTTP endpoint URL" + }, + "method": { + "type": "string", + "enum": [ + "POST", + "PUT" + ], + "default": "POST" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "auth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "basic", + "bearer", + "api_key" + ], + "description": "Auth type" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "token": { + "type": "string" + }, + "apiKey": { + "type": "string" + }, + "apiKeyHeader": { + "type": "string", + "default": "X-API-Key" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "batch": { + "type": "object", + "properties": { + "maxSize": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 100 + }, + "flushInterval": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5000 + } + }, + "additionalProperties": false + }, + "retry": { + "type": "object", + "properties": { + "maxAttempts": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 3 + }, + "initialDelay": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 1000 + }, + "backoffMultiplier": { + "type": "number", + "exclusiveMinimum": 0, + "default": 2 + } + }, + "additionalProperties": false + }, + "timeout": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 30000 + } + }, + "required": [ + "url" + ], + "additionalProperties": false, + "description": "HTTP destination configuration" + }, + "externalService": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "format": "uri" + }, + "region": { + "type": "string" + }, + "credentials": { + "type": "object", + "properties": { + "accessKeyId": { + "type": "string" + }, + "secretAccessKey": { + "type": "string" + }, + "apiKey": { + "type": "string" + }, + "projectId": { + "type": "string" + } + }, + "additionalProperties": false + }, + "logGroup": { + "type": "string" + }, + "logStream": { + "type": "string" + }, + "index": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false, + "description": "External service destination configuration" + }, + "format": { + "type": "string", + "enum": [ + "json", + "text", + "pretty" + ], + "default": "json" + }, + "filterId": { + "type": "string", + "description": "Filter function identifier" + } + }, + "required": [ + "name", + "type" + ], + "additionalProperties": false, + "description": "Log destination configuration" + }, + "description": "Log destinations" + }, + "enrichment": { + "type": "object", + "properties": { + "staticFields": { + "type": "object", + "additionalProperties": {}, + "description": "Static fields added to every log" + }, + "dynamicEnrichers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Dynamic enricher function IDs" + }, + "addHostname": { + "type": "boolean", + "default": true + }, + "addProcessId": { + "type": "boolean", + "default": true + }, + "addEnvironment": { + "type": "boolean", + "default": true + }, + "addTimestampFormats": { + "type": "object", + "properties": { + "unix": { + "type": "boolean", + "default": false + }, + "iso": { + "type": "boolean", + "default": true + } + }, + "additionalProperties": false + }, + "addCaller": { + "type": "boolean", + "default": false + }, + "addCorrelationIds": { + "type": "boolean", + "default": true + } + }, + "additionalProperties": false, + "description": "Log enrichment configuration", + "default": {} + }, + "redact": { + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "password", + "passwordHash", + "token", + "apiKey", + "secret", + "creditCard", + "ssn", + "authorization" + ], + "description": "Fields to redact" + }, + "sampling": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "rate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 1 + }, + "rateByLevel": { + "type": "object", + "additionalProperties": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + } + }, + "additionalProperties": false + }, + "buffer": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "size": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 1000 + }, + "flushInterval": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 1000 + }, + "flushOnShutdown": { + "type": "boolean", + "default": true + } + }, + "additionalProperties": false + }, + "performance": { + "type": "object", + "properties": { + "async": { + "type": "boolean", + "default": true + }, + "workers": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 1 + } + }, + "additionalProperties": false + } + }, + "required": [ + "name", + "label", + "destinations" + ], + "additionalProperties": false, + "description": "Logging configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MetricAggregationConfig.json b/packages/spec/json-schema/system/MetricAggregationConfig.json new file mode 100644 index 000000000..07e3d09ba --- /dev/null +++ b/packages/spec/json-schema/system/MetricAggregationConfig.json @@ -0,0 +1,69 @@ +{ + "$ref": "#/definitions/MetricAggregationConfig", + "definitions": { + "MetricAggregationConfig": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "sum", + "avg", + "min", + "max", + "count", + "p50", + "p75", + "p90", + "p95", + "p99", + "p999", + "rate", + "stddev" + ], + "description": "Aggregation type" + }, + "window": { + "type": "object", + "properties": { + "size": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Window size in seconds" + }, + "sliding": { + "type": "boolean", + "default": false + }, + "slideInterval": { + "type": "integer", + "exclusiveMinimum": 0 + } + }, + "required": [ + "size" + ], + "additionalProperties": false + }, + "groupBy": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Group by label names" + }, + "filters": { + "type": "object", + "additionalProperties": {}, + "description": "Filter criteria" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Metric aggregation configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MetricAggregationType.json b/packages/spec/json-schema/system/MetricAggregationType.json new file mode 100644 index 000000000..cc0e65e7d --- /dev/null +++ b/packages/spec/json-schema/system/MetricAggregationType.json @@ -0,0 +1,25 @@ +{ + "$ref": "#/definitions/MetricAggregationType", + "definitions": { + "MetricAggregationType": { + "type": "string", + "enum": [ + "sum", + "avg", + "min", + "max", + "count", + "p50", + "p75", + "p90", + "p95", + "p99", + "p999", + "rate", + "stddev" + ], + "description": "Metric aggregation type" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MetricDataPoint.json b/packages/spec/json-schema/system/MetricDataPoint.json new file mode 100644 index 000000000..73a87027a --- /dev/null +++ b/packages/spec/json-schema/system/MetricDataPoint.json @@ -0,0 +1,135 @@ +{ + "$ref": "#/definitions/MetricDataPoint", + "definitions": { + "MetricDataPoint": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Metric name" + }, + "type": { + "type": "string", + "enum": [ + "counter", + "gauge", + "histogram", + "summary" + ], + "description": "Metric type" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Observation timestamp" + }, + "value": { + "type": "number", + "description": "Metric value" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Metric labels" + }, + "histogram": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total count" + }, + "sum": { + "type": "number", + "description": "Sum of all values" + }, + "buckets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "upperBound": { + "type": "number", + "description": "Upper bound of bucket" + }, + "count": { + "type": "integer", + "minimum": 0, + "description": "Count in bucket" + } + }, + "required": [ + "upperBound", + "count" + ], + "additionalProperties": false + }, + "description": "Histogram buckets" + } + }, + "required": [ + "count", + "sum", + "buckets" + ], + "additionalProperties": false + }, + "summary": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total count" + }, + "sum": { + "type": "number", + "description": "Sum of all values" + }, + "quantiles": { + "type": "array", + "items": { + "type": "object", + "properties": { + "quantile": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Quantile (0-1)" + }, + "value": { + "type": "number", + "description": "Quantile value" + } + }, + "required": [ + "quantile", + "value" + ], + "additionalProperties": false + }, + "description": "Summary quantiles" + } + }, + "required": [ + "count", + "sum", + "quantiles" + ], + "additionalProperties": false + } + }, + "required": [ + "name", + "type", + "timestamp" + ], + "additionalProperties": false, + "description": "Metric data point" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MetricDefinition.json b/packages/spec/json-schema/system/MetricDefinition.json new file mode 100644 index 000000000..2e0fed2b4 --- /dev/null +++ b/packages/spec/json-schema/system/MetricDefinition.json @@ -0,0 +1,193 @@ +{ + "$ref": "#/definitions/MetricDefinition", + "definitions": { + "MetricDefinition": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "description": "Metric name (snake_case)" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "type": { + "type": "string", + "enum": [ + "counter", + "gauge", + "histogram", + "summary" + ], + "description": "Metric type" + }, + "unit": { + "type": "string", + "enum": [ + "nanoseconds", + "microseconds", + "milliseconds", + "seconds", + "minutes", + "hours", + "days", + "bytes", + "kilobytes", + "megabytes", + "gigabytes", + "terabytes", + "requests_per_second", + "events_per_second", + "bytes_per_second", + "percent", + "ratio", + "count", + "operations", + "custom" + ], + "description": "Metric unit" + }, + "description": { + "type": "string", + "description": "Metric description" + }, + "labelNames": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "Label names" + }, + "histogram": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "exponential", + "explicit" + ], + "description": "Bucket type" + }, + "linear": { + "type": "object", + "properties": { + "start": { + "type": "number", + "description": "Start value" + }, + "width": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Bucket width" + }, + "count": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Number of buckets" + } + }, + "required": [ + "start", + "width", + "count" + ], + "additionalProperties": false + }, + "exponential": { + "type": "object", + "properties": { + "start": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Start value" + }, + "factor": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Growth factor" + }, + "count": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Number of buckets" + } + }, + "required": [ + "start", + "factor", + "count" + ], + "additionalProperties": false + }, + "explicit": { + "type": "object", + "properties": { + "boundaries": { + "type": "array", + "items": { + "type": "number" + }, + "description": "Bucket boundaries" + } + }, + "required": [ + "boundaries" + ], + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Histogram bucket configuration" + }, + "summary": { + "type": "object", + "properties": { + "quantiles": { + "type": "array", + "items": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "default": [ + 0.5, + 0.9, + 0.99 + ] + }, + "maxAge": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 600 + }, + "ageBuckets": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5 + } + }, + "additionalProperties": false + }, + "enabled": { + "type": "boolean", + "default": true + } + }, + "required": [ + "name", + "type" + ], + "additionalProperties": false, + "description": "Metric definition" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MetricExportConfig.json b/packages/spec/json-schema/system/MetricExportConfig.json new file mode 100644 index 000000000..ee43677f5 --- /dev/null +++ b/packages/spec/json-schema/system/MetricExportConfig.json @@ -0,0 +1,93 @@ +{ + "$ref": "#/definitions/MetricExportConfig", + "definitions": { + "MetricExportConfig": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "prometheus", + "openmetrics", + "graphite", + "statsd", + "influxdb", + "datadog", + "cloudwatch", + "stackdriver", + "azure_monitor", + "http", + "custom" + ], + "description": "Export type" + }, + "endpoint": { + "type": "string", + "description": "Export endpoint" + }, + "interval": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 60 + }, + "batch": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "size": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 1000 + } + }, + "additionalProperties": false + }, + "auth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "none", + "basic", + "bearer", + "api_key" + ], + "description": "Auth type" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "token": { + "type": "string" + }, + "apiKey": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "config": { + "type": "object", + "additionalProperties": {}, + "description": "Additional configuration" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Metric export configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MetricLabels.json b/packages/spec/json-schema/system/MetricLabels.json new file mode 100644 index 000000000..f410dd225 --- /dev/null +++ b/packages/spec/json-schema/system/MetricLabels.json @@ -0,0 +1,13 @@ +{ + "$ref": "#/definitions/MetricLabels", + "definitions": { + "MetricLabels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Metric labels" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MetricType.json b/packages/spec/json-schema/system/MetricType.json new file mode 100644 index 000000000..1238e26db --- /dev/null +++ b/packages/spec/json-schema/system/MetricType.json @@ -0,0 +1,16 @@ +{ + "$ref": "#/definitions/MetricType", + "definitions": { + "MetricType": { + "type": "string", + "enum": [ + "counter", + "gauge", + "histogram", + "summary" + ], + "description": "Metric type" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MetricUnit.json b/packages/spec/json-schema/system/MetricUnit.json new file mode 100644 index 000000000..eaf599516 --- /dev/null +++ b/packages/spec/json-schema/system/MetricUnit.json @@ -0,0 +1,32 @@ +{ + "$ref": "#/definitions/MetricUnit", + "definitions": { + "MetricUnit": { + "type": "string", + "enum": [ + "nanoseconds", + "microseconds", + "milliseconds", + "seconds", + "minutes", + "hours", + "days", + "bytes", + "kilobytes", + "megabytes", + "gigabytes", + "terabytes", + "requests_per_second", + "events_per_second", + "bytes_per_second", + "percent", + "ratio", + "count", + "operations", + "custom" + ], + "description": "Metric unit" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MetricsConfig.json b/packages/spec/json-schema/system/MetricsConfig.json new file mode 100644 index 000000000..b11d5b2db --- /dev/null +++ b/packages/spec/json-schema/system/MetricsConfig.json @@ -0,0 +1,718 @@ +{ + "$ref": "#/definitions/MetricsConfig", + "definitions": { + "MetricsConfig": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "maxLength": 64, + "description": "Configuration name (snake_case, max 64 chars)" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "enabled": { + "type": "boolean", + "default": true + }, + "metrics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "description": "Metric name (snake_case)" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "type": { + "type": "string", + "enum": [ + "counter", + "gauge", + "histogram", + "summary" + ], + "description": "Metric type" + }, + "unit": { + "type": "string", + "enum": [ + "nanoseconds", + "microseconds", + "milliseconds", + "seconds", + "minutes", + "hours", + "days", + "bytes", + "kilobytes", + "megabytes", + "gigabytes", + "terabytes", + "requests_per_second", + "events_per_second", + "bytes_per_second", + "percent", + "ratio", + "count", + "operations", + "custom" + ], + "description": "Metric unit" + }, + "description": { + "type": "string", + "description": "Metric description" + }, + "labelNames": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "Label names" + }, + "histogram": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "exponential", + "explicit" + ], + "description": "Bucket type" + }, + "linear": { + "type": "object", + "properties": { + "start": { + "type": "number", + "description": "Start value" + }, + "width": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Bucket width" + }, + "count": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Number of buckets" + } + }, + "required": [ + "start", + "width", + "count" + ], + "additionalProperties": false + }, + "exponential": { + "type": "object", + "properties": { + "start": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Start value" + }, + "factor": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Growth factor" + }, + "count": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Number of buckets" + } + }, + "required": [ + "start", + "factor", + "count" + ], + "additionalProperties": false + }, + "explicit": { + "type": "object", + "properties": { + "boundaries": { + "type": "array", + "items": { + "type": "number" + }, + "description": "Bucket boundaries" + } + }, + "required": [ + "boundaries" + ], + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Histogram bucket configuration" + }, + "summary": { + "type": "object", + "properties": { + "quantiles": { + "type": "array", + "items": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "default": [ + 0.5, + 0.9, + 0.99 + ] + }, + "maxAge": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 600 + }, + "ageBuckets": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5 + } + }, + "additionalProperties": false + }, + "enabled": { + "type": "boolean", + "default": true + } + }, + "required": [ + "name", + "type" + ], + "additionalProperties": false, + "description": "Metric definition" + }, + "default": [] + }, + "defaultLabels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Metric labels", + "default": {} + }, + "aggregations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "sum", + "avg", + "min", + "max", + "count", + "p50", + "p75", + "p90", + "p95", + "p99", + "p999", + "rate", + "stddev" + ], + "description": "Aggregation type" + }, + "window": { + "type": "object", + "properties": { + "size": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Window size in seconds" + }, + "sliding": { + "type": "boolean", + "default": false + }, + "slideInterval": { + "type": "integer", + "exclusiveMinimum": 0 + } + }, + "required": [ + "size" + ], + "additionalProperties": false + }, + "groupBy": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Group by label names" + }, + "filters": { + "type": "object", + "additionalProperties": {}, + "description": "Filter criteria" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Metric aggregation configuration" + }, + "default": [] + }, + "slis": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "description": "SLI name (snake_case)" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "description": { + "type": "string", + "description": "SLI description" + }, + "metric": { + "type": "string", + "description": "Base metric name" + }, + "type": { + "type": "string", + "enum": [ + "availability", + "latency", + "throughput", + "error_rate", + "saturation", + "custom" + ], + "description": "SLI type" + }, + "successCriteria": { + "type": "object", + "properties": { + "threshold": { + "type": "number", + "description": "Threshold value" + }, + "operator": { + "type": "string", + "enum": [ + "lt", + "lte", + "gt", + "gte", + "eq" + ], + "description": "Comparison operator" + }, + "percentile": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Percentile (0-1)" + } + }, + "required": [ + "threshold", + "operator" + ], + "additionalProperties": false, + "description": "Success criteria" + }, + "window": { + "type": "object", + "properties": { + "size": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Window size in seconds" + }, + "rolling": { + "type": "boolean", + "default": true + } + }, + "required": [ + "size" + ], + "additionalProperties": false, + "description": "Measurement window" + }, + "enabled": { + "type": "boolean", + "default": true + } + }, + "required": [ + "name", + "label", + "metric", + "type", + "successCriteria", + "window" + ], + "additionalProperties": false, + "description": "Service Level Indicator" + }, + "default": [] + }, + "slos": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "description": "SLO name (snake_case)" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "description": { + "type": "string", + "description": "SLO description" + }, + "sli": { + "type": "string", + "description": "SLI name" + }, + "target": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Target percentage" + }, + "period": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "rolling", + "calendar" + ], + "description": "Period type" + }, + "duration": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Duration in seconds" + }, + "calendar": { + "type": "string", + "enum": [ + "daily", + "weekly", + "monthly", + "quarterly", + "yearly" + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Time period" + }, + "errorBudget": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "alertThreshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 80 + }, + "burnRateWindows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "window": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Window size" + }, + "threshold": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Burn rate threshold" + } + }, + "required": [ + "window", + "threshold" + ], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "alerts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Alert name" + }, + "severity": { + "type": "string", + "enum": [ + "info", + "warning", + "critical" + ], + "description": "Alert severity" + }, + "condition": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "slo_breach", + "error_budget", + "burn_rate" + ], + "description": "Condition type" + }, + "threshold": { + "type": "number", + "description": "Threshold value" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Alert condition" + } + }, + "required": [ + "name", + "severity", + "condition" + ], + "additionalProperties": false + }, + "default": [] + }, + "enabled": { + "type": "boolean", + "default": true + } + }, + "required": [ + "name", + "label", + "sli", + "target", + "period" + ], + "additionalProperties": false, + "description": "Service Level Objective" + }, + "default": [] + }, + "exports": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "prometheus", + "openmetrics", + "graphite", + "statsd", + "influxdb", + "datadog", + "cloudwatch", + "stackdriver", + "azure_monitor", + "http", + "custom" + ], + "description": "Export type" + }, + "endpoint": { + "type": "string", + "description": "Export endpoint" + }, + "interval": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 60 + }, + "batch": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "size": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 1000 + } + }, + "additionalProperties": false + }, + "auth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "none", + "basic", + "bearer", + "api_key" + ], + "description": "Auth type" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "token": { + "type": "string" + }, + "apiKey": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "config": { + "type": "object", + "additionalProperties": {}, + "description": "Additional configuration" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Metric export configuration" + }, + "default": [] + }, + "collectionInterval": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 15 + }, + "retention": { + "type": "object", + "properties": { + "period": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 604800 + }, + "downsampling": { + "type": "array", + "items": { + "type": "object", + "properties": { + "afterSeconds": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Downsample after seconds" + }, + "resolution": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Downsampled resolution" + } + }, + "required": [ + "afterSeconds", + "resolution" + ], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "cardinalityLimits": { + "type": "object", + "properties": { + "maxLabelCombinations": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 10000 + }, + "onLimitExceeded": { + "type": "string", + "enum": [ + "drop", + "sample", + "alert" + ], + "default": "alert" + } + }, + "additionalProperties": false + } + }, + "required": [ + "name", + "label" + ], + "additionalProperties": false, + "description": "Metrics configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/OpenTelemetryCompatibility.json b/packages/spec/json-schema/system/OpenTelemetryCompatibility.json new file mode 100644 index 000000000..8d438da12 --- /dev/null +++ b/packages/spec/json-schema/system/OpenTelemetryCompatibility.json @@ -0,0 +1,196 @@ +{ + "$ref": "#/definitions/OpenTelemetryCompatibility", + "definitions": { + "OpenTelemetryCompatibility": { + "type": "object", + "properties": { + "sdkVersion": { + "type": "string", + "description": "OTel SDK version" + }, + "exporter": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "otlp_http", + "otlp_grpc", + "jaeger", + "zipkin", + "console", + "datadog", + "honeycomb", + "lightstep", + "newrelic", + "custom" + ], + "description": "Exporter type" + }, + "endpoint": { + "type": "string", + "format": "uri", + "description": "Exporter endpoint" + }, + "protocol": { + "type": "string", + "description": "Protocol version" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "HTTP headers" + }, + "timeout": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 10000 + }, + "compression": { + "type": "string", + "enum": [ + "none", + "gzip" + ], + "default": "none" + }, + "batch": { + "type": "object", + "properties": { + "maxBatchSize": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 512 + }, + "maxQueueSize": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 2048 + }, + "exportTimeout": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 30000 + }, + "scheduledDelay": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5000 + } + }, + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Exporter configuration" + }, + "resource": { + "type": "object", + "properties": { + "serviceName": { + "type": "string", + "description": "Service name" + }, + "serviceVersion": { + "type": "string", + "description": "Service version" + }, + "serviceInstanceId": { + "type": "string", + "description": "Service instance ID" + }, + "serviceNamespace": { + "type": "string", + "description": "Service namespace" + }, + "deploymentEnvironment": { + "type": "string", + "description": "Deployment environment" + }, + "attributes": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + }, + "description": "Additional resource attributes" + } + }, + "required": [ + "serviceName" + ], + "additionalProperties": false, + "description": "Resource attributes" + }, + "instrumentation": { + "type": "object", + "properties": { + "autoInstrumentation": { + "type": "boolean", + "default": true + }, + "libraries": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Enabled libraries" + }, + "disabledLibraries": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Disabled libraries" + } + }, + "additionalProperties": false + }, + "semanticConventionsVersion": { + "type": "string", + "description": "Semantic conventions version" + } + }, + "required": [ + "exporter", + "resource" + ], + "additionalProperties": false, + "description": "OpenTelemetry compatibility configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/OtelExporterType.json b/packages/spec/json-schema/system/OtelExporterType.json new file mode 100644 index 000000000..d1ad43ed7 --- /dev/null +++ b/packages/spec/json-schema/system/OtelExporterType.json @@ -0,0 +1,22 @@ +{ + "$ref": "#/definitions/OtelExporterType", + "definitions": { + "OtelExporterType": { + "type": "string", + "enum": [ + "otlp_http", + "otlp_grpc", + "jaeger", + "zipkin", + "console", + "datadog", + "honeycomb", + "lightstep", + "newrelic", + "custom" + ], + "description": "OpenTelemetry exporter type" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SamplingDecision.json b/packages/spec/json-schema/system/SamplingDecision.json new file mode 100644 index 000000000..b006b764f --- /dev/null +++ b/packages/spec/json-schema/system/SamplingDecision.json @@ -0,0 +1,15 @@ +{ + "$ref": "#/definitions/SamplingDecision", + "definitions": { + "SamplingDecision": { + "type": "string", + "enum": [ + "drop", + "record_only", + "record_and_sample" + ], + "description": "Sampling decision" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SamplingStrategyType.json b/packages/spec/json-schema/system/SamplingStrategyType.json new file mode 100644 index 000000000..621e44384 --- /dev/null +++ b/packages/spec/json-schema/system/SamplingStrategyType.json @@ -0,0 +1,20 @@ +{ + "$ref": "#/definitions/SamplingStrategyType", + "definitions": { + "SamplingStrategyType": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Sampling strategy type" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ServiceLevelIndicator.json b/packages/spec/json-schema/system/ServiceLevelIndicator.json new file mode 100644 index 000000000..4038c4d16 --- /dev/null +++ b/packages/spec/json-schema/system/ServiceLevelIndicator.json @@ -0,0 +1,105 @@ +{ + "$ref": "#/definitions/ServiceLevelIndicator", + "definitions": { + "ServiceLevelIndicator": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "description": "SLI name (snake_case)" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "description": { + "type": "string", + "description": "SLI description" + }, + "metric": { + "type": "string", + "description": "Base metric name" + }, + "type": { + "type": "string", + "enum": [ + "availability", + "latency", + "throughput", + "error_rate", + "saturation", + "custom" + ], + "description": "SLI type" + }, + "successCriteria": { + "type": "object", + "properties": { + "threshold": { + "type": "number", + "description": "Threshold value" + }, + "operator": { + "type": "string", + "enum": [ + "lt", + "lte", + "gt", + "gte", + "eq" + ], + "description": "Comparison operator" + }, + "percentile": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Percentile (0-1)" + } + }, + "required": [ + "threshold", + "operator" + ], + "additionalProperties": false, + "description": "Success criteria" + }, + "window": { + "type": "object", + "properties": { + "size": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Window size in seconds" + }, + "rolling": { + "type": "boolean", + "default": true + } + }, + "required": [ + "size" + ], + "additionalProperties": false, + "description": "Measurement window" + }, + "enabled": { + "type": "boolean", + "default": true + } + }, + "required": [ + "name", + "label", + "metric", + "type", + "successCriteria", + "window" + ], + "additionalProperties": false, + "description": "Service Level Indicator" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ServiceLevelObjective.json b/packages/spec/json-schema/system/ServiceLevelObjective.json new file mode 100644 index 000000000..799b9adf1 --- /dev/null +++ b/packages/spec/json-schema/system/ServiceLevelObjective.json @@ -0,0 +1,170 @@ +{ + "$ref": "#/definitions/ServiceLevelObjective", + "definitions": { + "ServiceLevelObjective": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "description": "SLO name (snake_case)" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "description": { + "type": "string", + "description": "SLO description" + }, + "sli": { + "type": "string", + "description": "SLI name" + }, + "target": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Target percentage" + }, + "period": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "rolling", + "calendar" + ], + "description": "Period type" + }, + "duration": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Duration in seconds" + }, + "calendar": { + "type": "string", + "enum": [ + "daily", + "weekly", + "monthly", + "quarterly", + "yearly" + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Time period" + }, + "errorBudget": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "alertThreshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 80 + }, + "burnRateWindows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "window": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Window size" + }, + "threshold": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Burn rate threshold" + } + }, + "required": [ + "window", + "threshold" + ], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "alerts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Alert name" + }, + "severity": { + "type": "string", + "enum": [ + "info", + "warning", + "critical" + ], + "description": "Alert severity" + }, + "condition": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "slo_breach", + "error_budget", + "burn_rate" + ], + "description": "Condition type" + }, + "threshold": { + "type": "number", + "description": "Threshold value" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Alert condition" + } + }, + "required": [ + "name", + "severity", + "condition" + ], + "additionalProperties": false + }, + "default": [] + }, + "enabled": { + "type": "boolean", + "default": true + } + }, + "required": [ + "name", + "label", + "sli", + "target", + "period" + ], + "additionalProperties": false, + "description": "Service Level Objective" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/Span.json b/packages/spec/json-schema/system/Span.json new file mode 100644 index 000000000..ab3329870 --- /dev/null +++ b/packages/spec/json-schema/system/Span.json @@ -0,0 +1,387 @@ +{ + "$ref": "#/definitions/Span", + "definitions": { + "Span": { + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "pattern": "^[0-9a-f]{32}$", + "description": "Trace ID (32 hex chars)" + }, + "spanId": { + "type": "string", + "pattern": "^[0-9a-f]{16}$", + "description": "Span ID (16 hex chars)" + }, + "traceFlags": { + "type": "integer", + "minimum": 0, + "maximum": 255, + "description": "Trace flags bitmap", + "default": 1 + }, + "traceState": { + "type": "object", + "properties": { + "entries": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Trace state entries" + } + }, + "required": [ + "entries" + ], + "additionalProperties": false, + "description": "Trace state" + }, + "parentSpanId": { + "type": "string", + "pattern": "^[0-9a-f]{16}$", + "description": "Parent span ID (16 hex chars)" + }, + "sampled": { + "type": "boolean", + "default": true + }, + "remote": { + "type": "boolean", + "default": false + } + }, + "required": [ + "traceId", + "spanId" + ], + "additionalProperties": false, + "description": "Trace context" + }, + "name": { + "type": "string", + "description": "Span name" + }, + "kind": { + "type": "string", + "enum": [ + "internal", + "server", + "client", + "producer", + "consumer" + ], + "description": "Span kind", + "default": "internal" + }, + "startTime": { + "type": "string", + "format": "date-time", + "description": "Span start time" + }, + "endTime": { + "type": "string", + "format": "date-time", + "description": "Span end time" + }, + "duration": { + "type": "number", + "minimum": 0, + "description": "Duration in milliseconds" + }, + "status": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "unset", + "ok", + "error" + ], + "description": "Status code" + }, + "message": { + "type": "string", + "description": "Status message" + } + }, + "required": [ + "code" + ], + "additionalProperties": false + }, + "attributes": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + }, + "description": "Span attributes", + "default": {} + }, + "events": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Event name" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Event timestamp" + }, + "attributes": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + }, + "description": "Event attributes" + } + }, + "required": [ + "name", + "timestamp" + ], + "additionalProperties": false, + "description": "Span event" + }, + "default": [] + }, + "links": { + "type": "array", + "items": { + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "pattern": "^[0-9a-f]{32}$", + "description": "Trace ID (32 hex chars)" + }, + "spanId": { + "type": "string", + "pattern": "^[0-9a-f]{16}$", + "description": "Span ID (16 hex chars)" + }, + "traceFlags": { + "type": "integer", + "minimum": 0, + "maximum": 255, + "description": "Trace flags bitmap", + "default": 1 + }, + "traceState": { + "type": "object", + "properties": { + "entries": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Trace state entries" + } + }, + "required": [ + "entries" + ], + "additionalProperties": false, + "description": "Trace state" + }, + "parentSpanId": { + "type": "string", + "pattern": "^[0-9a-f]{16}$", + "description": "Parent span ID (16 hex chars)" + }, + "sampled": { + "type": "boolean", + "default": true + }, + "remote": { + "type": "boolean", + "default": false + } + }, + "required": [ + "traceId", + "spanId" + ], + "additionalProperties": false, + "description": "Linked trace context" + }, + "attributes": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + }, + "description": "Link attributes" + } + }, + "required": [ + "context" + ], + "additionalProperties": false, + "description": "Span link" + }, + "default": [] + }, + "resource": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + }, + "description": "Resource attributes" + }, + "instrumentationLibrary": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Library name" + }, + "version": { + "type": "string", + "description": "Library version" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "required": [ + "context", + "name", + "startTime" + ], + "additionalProperties": false, + "description": "OpenTelemetry span" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SpanAttributeValue.json b/packages/spec/json-schema/system/SpanAttributeValue.json new file mode 100644 index 000000000..8bce5a1af --- /dev/null +++ b/packages/spec/json-schema/system/SpanAttributeValue.json @@ -0,0 +1,38 @@ +{ + "$ref": "#/definitions/SpanAttributeValue", + "definitions": { + "SpanAttributeValue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SpanAttributes.json b/packages/spec/json-schema/system/SpanAttributes.json new file mode 100644 index 000000000..1c9fdd3c9 --- /dev/null +++ b/packages/spec/json-schema/system/SpanAttributes.json @@ -0,0 +1,42 @@ +{ + "$ref": "#/definitions/SpanAttributes", + "definitions": { + "SpanAttributes": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + }, + "description": "Span attributes" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SpanEvent.json b/packages/spec/json-schema/system/SpanEvent.json new file mode 100644 index 000000000..9a12e94f8 --- /dev/null +++ b/packages/spec/json-schema/system/SpanEvent.json @@ -0,0 +1,62 @@ +{ + "$ref": "#/definitions/SpanEvent", + "definitions": { + "SpanEvent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Event name" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Event timestamp" + }, + "attributes": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + }, + "description": "Event attributes" + } + }, + "required": [ + "name", + "timestamp" + ], + "additionalProperties": false, + "description": "Span event" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SpanKind.json b/packages/spec/json-schema/system/SpanKind.json new file mode 100644 index 000000000..e77e8ce7c --- /dev/null +++ b/packages/spec/json-schema/system/SpanKind.json @@ -0,0 +1,17 @@ +{ + "$ref": "#/definitions/SpanKind", + "definitions": { + "SpanKind": { + "type": "string", + "enum": [ + "internal", + "server", + "client", + "producer", + "consumer" + ], + "description": "Span kind" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SpanLink.json b/packages/spec/json-schema/system/SpanLink.json new file mode 100644 index 000000000..18f4a1a3f --- /dev/null +++ b/packages/spec/json-schema/system/SpanLink.json @@ -0,0 +1,110 @@ +{ + "$ref": "#/definitions/SpanLink", + "definitions": { + "SpanLink": { + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "pattern": "^[0-9a-f]{32}$", + "description": "Trace ID (32 hex chars)" + }, + "spanId": { + "type": "string", + "pattern": "^[0-9a-f]{16}$", + "description": "Span ID (16 hex chars)" + }, + "traceFlags": { + "type": "integer", + "minimum": 0, + "maximum": 255, + "description": "Trace flags bitmap", + "default": 1 + }, + "traceState": { + "type": "object", + "properties": { + "entries": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Trace state entries" + } + }, + "required": [ + "entries" + ], + "additionalProperties": false, + "description": "Trace state" + }, + "parentSpanId": { + "type": "string", + "pattern": "^[0-9a-f]{16}$", + "description": "Parent span ID (16 hex chars)" + }, + "sampled": { + "type": "boolean", + "default": true + }, + "remote": { + "type": "boolean", + "default": false + } + }, + "required": [ + "traceId", + "spanId" + ], + "additionalProperties": false, + "description": "Linked trace context" + }, + "attributes": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + }, + "description": "Link attributes" + } + }, + "required": [ + "context" + ], + "additionalProperties": false, + "description": "Span link" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SpanStatus.json b/packages/spec/json-schema/system/SpanStatus.json new file mode 100644 index 000000000..93ae8a4d5 --- /dev/null +++ b/packages/spec/json-schema/system/SpanStatus.json @@ -0,0 +1,15 @@ +{ + "$ref": "#/definitions/SpanStatus", + "definitions": { + "SpanStatus": { + "type": "string", + "enum": [ + "unset", + "ok", + "error" + ], + "description": "Span status" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/StructuredLogEntry.json b/packages/spec/json-schema/system/StructuredLogEntry.json new file mode 100644 index 000000000..df60a3df3 --- /dev/null +++ b/packages/spec/json-schema/system/StructuredLogEntry.json @@ -0,0 +1,191 @@ +{ + "$ref": "#/definitions/StructuredLogEntry", + "definitions": { + "StructuredLogEntry": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp" + }, + "level": { + "type": "string", + "enum": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal" + ], + "description": "Log severity level" + }, + "message": { + "type": "string", + "description": "Log message" + }, + "context": { + "type": "object", + "additionalProperties": {}, + "description": "Structured context" + }, + "error": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "message": { + "type": "string" + }, + "stack": { + "type": "string" + }, + "code": { + "type": "string" + }, + "details": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false, + "description": "Error details" + }, + "trace": { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "description": "Trace ID" + }, + "spanId": { + "type": "string", + "description": "Span ID" + }, + "parentSpanId": { + "type": "string", + "description": "Parent span ID" + }, + "traceFlags": { + "type": "integer", + "description": "Trace flags" + } + }, + "required": [ + "traceId", + "spanId" + ], + "additionalProperties": false, + "description": "Distributed tracing context" + }, + "source": { + "type": "object", + "properties": { + "service": { + "type": "string", + "description": "Service name" + }, + "component": { + "type": "string", + "description": "Component name" + }, + "file": { + "type": "string", + "description": "Source file" + }, + "line": { + "type": "integer", + "description": "Line number" + }, + "function": { + "type": "string", + "description": "Function name" + } + }, + "additionalProperties": false, + "description": "Source information" + }, + "host": { + "type": "object", + "properties": { + "hostname": { + "type": "string" + }, + "pid": { + "type": "integer" + }, + "ip": { + "type": "string" + } + }, + "additionalProperties": false, + "description": "Host information" + }, + "environment": { + "type": "string", + "description": "Environment (e.g., production, staging)" + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "additionalProperties": false, + "description": "User context" + }, + "request": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "method": { + "type": "string" + }, + "path": { + "type": "string" + }, + "userAgent": { + "type": "string" + }, + "ip": { + "type": "string" + } + }, + "additionalProperties": false, + "description": "Request context" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom labels" + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "description": "Additional metadata" + } + }, + "required": [ + "timestamp", + "level", + "message" + ], + "additionalProperties": false, + "description": "Structured log entry" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/TimeSeries.json b/packages/spec/json-schema/system/TimeSeries.json new file mode 100644 index 000000000..939a828db --- /dev/null +++ b/packages/spec/json-schema/system/TimeSeries.json @@ -0,0 +1,69 @@ +{ + "$ref": "#/definitions/TimeSeries", + "definitions": { + "TimeSeries": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Series name" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Series labels" + }, + "dataPoints": { + "type": "array", + "items": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp" + }, + "value": { + "type": "number", + "description": "Value" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Labels" + } + }, + "required": [ + "timestamp", + "value" + ], + "additionalProperties": false, + "description": "Time series data point" + }, + "description": "Data points" + }, + "startTime": { + "type": "string", + "format": "date-time", + "description": "Start time" + }, + "endTime": { + "type": "string", + "format": "date-time", + "description": "End time" + } + }, + "required": [ + "name", + "dataPoints" + ], + "additionalProperties": false, + "description": "Time series" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/TimeSeriesDataPoint.json b/packages/spec/json-schema/system/TimeSeriesDataPoint.json new file mode 100644 index 000000000..bb34dbb76 --- /dev/null +++ b/packages/spec/json-schema/system/TimeSeriesDataPoint.json @@ -0,0 +1,33 @@ +{ + "$ref": "#/definitions/TimeSeriesDataPoint", + "definitions": { + "TimeSeriesDataPoint": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp" + }, + "value": { + "type": "number", + "description": "Value" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Labels" + } + }, + "required": [ + "timestamp", + "value" + ], + "additionalProperties": false, + "description": "Time series data point" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/TraceContext.json b/packages/spec/json-schema/system/TraceContext.json new file mode 100644 index 000000000..595973d3e --- /dev/null +++ b/packages/spec/json-schema/system/TraceContext.json @@ -0,0 +1,64 @@ +{ + "$ref": "#/definitions/TraceContext", + "definitions": { + "TraceContext": { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "pattern": "^[0-9a-f]{32}$", + "description": "Trace ID (32 hex chars)" + }, + "spanId": { + "type": "string", + "pattern": "^[0-9a-f]{16}$", + "description": "Span ID (16 hex chars)" + }, + "traceFlags": { + "type": "integer", + "minimum": 0, + "maximum": 255, + "description": "Trace flags bitmap", + "default": 1 + }, + "traceState": { + "type": "object", + "properties": { + "entries": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Trace state entries" + } + }, + "required": [ + "entries" + ], + "additionalProperties": false, + "description": "Trace state" + }, + "parentSpanId": { + "type": "string", + "pattern": "^[0-9a-f]{16}$", + "description": "Parent span ID (16 hex chars)" + }, + "sampled": { + "type": "boolean", + "default": true + }, + "remote": { + "type": "boolean", + "default": false + } + }, + "required": [ + "traceId", + "spanId" + ], + "additionalProperties": false, + "description": "Trace context (W3C Trace Context)" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/TraceContextPropagation.json b/packages/spec/json-schema/system/TraceContextPropagation.json new file mode 100644 index 000000000..7f22465da --- /dev/null +++ b/packages/spec/json-schema/system/TraceContextPropagation.json @@ -0,0 +1,79 @@ +{ + "$ref": "#/definitions/TraceContextPropagation", + "definitions": { + "TraceContextPropagation": { + "type": "object", + "properties": { + "formats": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "w3c", + "b3", + "b3_multi", + "jaeger", + "xray", + "ottrace", + "custom" + ], + "description": "Trace propagation format" + }, + "default": [ + "w3c" + ] + }, + "extract": { + "type": "boolean", + "default": true + }, + "inject": { + "type": "boolean", + "default": true + }, + "headers": { + "type": "object", + "properties": { + "traceId": { + "type": "string" + }, + "spanId": { + "type": "string" + }, + "traceFlags": { + "type": "string" + }, + "traceState": { + "type": "string" + } + }, + "additionalProperties": false + }, + "baggage": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "maxSize": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 8192 + }, + "allowedKeys": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "description": "Trace context propagation" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/TraceFlags.json b/packages/spec/json-schema/system/TraceFlags.json new file mode 100644 index 000000000..28bce107a --- /dev/null +++ b/packages/spec/json-schema/system/TraceFlags.json @@ -0,0 +1,12 @@ +{ + "$ref": "#/definitions/TraceFlags", + "definitions": { + "TraceFlags": { + "type": "integer", + "minimum": 0, + "maximum": 255, + "description": "Trace flags bitmap" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/TracePropagationFormat.json b/packages/spec/json-schema/system/TracePropagationFormat.json new file mode 100644 index 000000000..bac694169 --- /dev/null +++ b/packages/spec/json-schema/system/TracePropagationFormat.json @@ -0,0 +1,19 @@ +{ + "$ref": "#/definitions/TracePropagationFormat", + "definitions": { + "TracePropagationFormat": { + "type": "string", + "enum": [ + "w3c", + "b3", + "b3_multi", + "jaeger", + "xray", + "ottrace", + "custom" + ], + "description": "Trace propagation format" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/TraceSamplingConfig.json b/packages/spec/json-schema/system/TraceSamplingConfig.json new file mode 100644 index 000000000..77dcf2e17 --- /dev/null +++ b/packages/spec/json-schema/system/TraceSamplingConfig.json @@ -0,0 +1,186 @@ +{ + "$ref": "#/definitions/TraceSamplingConfig", + "definitions": { + "TraceSamplingConfig": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Sampling strategy" + }, + "ratio": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Sample ratio (0-1)" + }, + "rateLimit": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Traces per second" + }, + "parentBased": { + "type": "object", + "properties": { + "whenParentSampled": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Sampling strategy type", + "default": "always_on" + }, + "whenParentNotSampled": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Sampling strategy type", + "default": "always_off" + }, + "root": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Sampling strategy type", + "default": "trace_id_ratio" + }, + "rootRatio": { + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 0.1 + } + }, + "additionalProperties": false + }, + "composite": { + "type": "array", + "items": { + "type": "object", + "properties": { + "strategy": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Strategy type" + }, + "ratio": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "condition": { + "type": "object", + "additionalProperties": {}, + "description": "Condition for this strategy" + } + }, + "required": [ + "strategy" + ], + "additionalProperties": false + } + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Rule name" + }, + "match": { + "type": "object", + "properties": { + "service": { + "type": "string" + }, + "spanName": { + "type": "string" + }, + "attributes": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "decision": { + "type": "string", + "enum": [ + "drop", + "record_only", + "record_and_sample" + ], + "description": "Sampling decision" + }, + "rate": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "required": [ + "name", + "decision" + ], + "additionalProperties": false + }, + "default": [] + }, + "customSamplerId": { + "type": "string", + "description": "Custom sampler identifier" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Trace sampling configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/TraceState.json b/packages/spec/json-schema/system/TraceState.json new file mode 100644 index 000000000..3202bab79 --- /dev/null +++ b/packages/spec/json-schema/system/TraceState.json @@ -0,0 +1,23 @@ +{ + "$ref": "#/definitions/TraceState", + "definitions": { + "TraceState": { + "type": "object", + "properties": { + "entries": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Trace state entries" + } + }, + "required": [ + "entries" + ], + "additionalProperties": false, + "description": "Trace state" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/TracingConfig.json b/packages/spec/json-schema/system/TracingConfig.json new file mode 100644 index 000000000..f36960e6b --- /dev/null +++ b/packages/spec/json-schema/system/TracingConfig.json @@ -0,0 +1,536 @@ +{ + "$ref": "#/definitions/TracingConfig", + "definitions": { + "TracingConfig": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z_][a-z0-9_]*$", + "maxLength": 64, + "description": "Configuration name (snake_case, max 64 chars)" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "enabled": { + "type": "boolean", + "default": true + }, + "sampling": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Sampling strategy" + }, + "ratio": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Sample ratio (0-1)" + }, + "rateLimit": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Traces per second" + }, + "parentBased": { + "type": "object", + "properties": { + "whenParentSampled": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Sampling strategy type", + "default": "always_on" + }, + "whenParentNotSampled": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Sampling strategy type", + "default": "always_off" + }, + "root": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Sampling strategy type", + "default": "trace_id_ratio" + }, + "rootRatio": { + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 0.1 + } + }, + "additionalProperties": false + }, + "composite": { + "type": "array", + "items": { + "type": "object", + "properties": { + "strategy": { + "type": "string", + "enum": [ + "always_on", + "always_off", + "trace_id_ratio", + "rate_limiting", + "parent_based", + "probability", + "composite", + "custom" + ], + "description": "Strategy type" + }, + "ratio": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "condition": { + "type": "object", + "additionalProperties": {}, + "description": "Condition for this strategy" + } + }, + "required": [ + "strategy" + ], + "additionalProperties": false + } + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Rule name" + }, + "match": { + "type": "object", + "properties": { + "service": { + "type": "string" + }, + "spanName": { + "type": "string" + }, + "attributes": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "decision": { + "type": "string", + "enum": [ + "drop", + "record_only", + "record_and_sample" + ], + "description": "Sampling decision" + }, + "rate": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "required": [ + "name", + "decision" + ], + "additionalProperties": false + }, + "default": [] + }, + "customSamplerId": { + "type": "string", + "description": "Custom sampler identifier" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Trace sampling configuration", + "default": { + "type": "always_on" + } + }, + "propagation": { + "type": "object", + "properties": { + "formats": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "w3c", + "b3", + "b3_multi", + "jaeger", + "xray", + "ottrace", + "custom" + ], + "description": "Trace propagation format" + }, + "default": [ + "w3c" + ] + }, + "extract": { + "type": "boolean", + "default": true + }, + "inject": { + "type": "boolean", + "default": true + }, + "headers": { + "type": "object", + "properties": { + "traceId": { + "type": "string" + }, + "spanId": { + "type": "string" + }, + "traceFlags": { + "type": "string" + }, + "traceState": { + "type": "string" + } + }, + "additionalProperties": false + }, + "baggage": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "maxSize": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 8192 + }, + "allowedKeys": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "description": "Trace context propagation", + "default": { + "formats": [ + "w3c" + ] + } + }, + "openTelemetry": { + "type": "object", + "properties": { + "sdkVersion": { + "type": "string", + "description": "OTel SDK version" + }, + "exporter": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "otlp_http", + "otlp_grpc", + "jaeger", + "zipkin", + "console", + "datadog", + "honeycomb", + "lightstep", + "newrelic", + "custom" + ], + "description": "Exporter type" + }, + "endpoint": { + "type": "string", + "format": "uri", + "description": "Exporter endpoint" + }, + "protocol": { + "type": "string", + "description": "Protocol version" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "HTTP headers" + }, + "timeout": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 10000 + }, + "compression": { + "type": "string", + "enum": [ + "none", + "gzip" + ], + "default": "none" + }, + "batch": { + "type": "object", + "properties": { + "maxBatchSize": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 512 + }, + "maxQueueSize": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 2048 + }, + "exportTimeout": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 30000 + }, + "scheduledDelay": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5000 + } + }, + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Exporter configuration" + }, + "resource": { + "type": "object", + "properties": { + "serviceName": { + "type": "string", + "description": "Service name" + }, + "serviceVersion": { + "type": "string", + "description": "Service version" + }, + "serviceInstanceId": { + "type": "string", + "description": "Service instance ID" + }, + "serviceNamespace": { + "type": "string", + "description": "Service namespace" + }, + "deploymentEnvironment": { + "type": "string", + "description": "Deployment environment" + }, + "attributes": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "number" + } + }, + { + "type": "array", + "items": { + "type": "boolean" + } + } + ], + "description": "Span attribute value" + }, + "description": "Additional resource attributes" + } + }, + "required": [ + "serviceName" + ], + "additionalProperties": false, + "description": "Resource attributes" + }, + "instrumentation": { + "type": "object", + "properties": { + "autoInstrumentation": { + "type": "boolean", + "default": true + }, + "libraries": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Enabled libraries" + }, + "disabledLibraries": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Disabled libraries" + } + }, + "additionalProperties": false + }, + "semanticConventionsVersion": { + "type": "string", + "description": "Semantic conventions version" + } + }, + "required": [ + "exporter", + "resource" + ], + "additionalProperties": false, + "description": "OpenTelemetry compatibility configuration" + }, + "spanLimits": { + "type": "object", + "properties": { + "maxAttributes": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 128 + }, + "maxEvents": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 128 + }, + "maxLinks": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 128 + }, + "maxAttributeValueLength": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 4096 + } + }, + "additionalProperties": false + }, + "traceIdGenerator": { + "type": "string", + "enum": [ + "random", + "uuid", + "custom" + ], + "default": "random" + }, + "customTraceIdGeneratorId": { + "type": "string", + "description": "Custom generator identifier" + }, + "performance": { + "type": "object", + "properties": { + "asyncExport": { + "type": "boolean", + "default": true + }, + "exportInterval": { + "type": "integer", + "exclusiveMinimum": 0, + "default": 5000 + } + }, + "additionalProperties": false + } + }, + "required": [ + "name", + "label" + ], + "additionalProperties": false, + "description": "Tracing configuration" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/src/system/index.ts b/packages/spec/src/system/index.ts index 8764ca28b..d7e866291 100644 --- a/packages/spec/src/system/index.ts +++ b/packages/spec/src/system/index.ts @@ -15,6 +15,11 @@ export * from './feature.zod'; export * from './collaboration.zod'; export * from './types'; +// Observability Protocol +export * from './logging.zod'; +export * from './metrics.zod'; +export * from './tracing.zod'; + // Re-export Core System Definitions export * from './manifest.zod'; export * from './plugin.zod'; diff --git a/packages/spec/src/system/logging.test.ts b/packages/spec/src/system/logging.test.ts new file mode 100644 index 000000000..d3efb1b97 --- /dev/null +++ b/packages/spec/src/system/logging.test.ts @@ -0,0 +1,342 @@ +import { describe, it, expect } from 'vitest'; +import { + LogLevel, + ExtendedLogLevel, + LogDestinationType, + ConsoleDestinationConfigSchema, + FileDestinationConfigSchema, + HttpDestinationConfigSchema, + ExternalServiceDestinationConfigSchema, + LogDestinationSchema, + LogEnrichmentConfigSchema, + StructuredLogEntrySchema, + LoggingConfigSchema, + type LogDestination, + type StructuredLogEntry, + type LoggingConfig, +} from './logging.zod'; + +describe('ExtendedLogLevel', () => { + it('should accept valid log levels', () => { + const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']; + + levels.forEach((level) => { + expect(() => ExtendedLogLevel.parse(level)).not.toThrow(); + }); + }); + + it('should reject invalid log levels', () => { + expect(() => ExtendedLogLevel.parse('invalid')).toThrow(); + }); +}); + +describe('LogDestinationType', () => { + it('should accept valid destination types', () => { + const types = [ + 'console', 'file', 'syslog', 'elasticsearch', 'cloudwatch', + 'stackdriver', 'azure_monitor', 'datadog', 'splunk', 'loki', + 'http', 'kafka', 'redis', 'custom', + ]; + + types.forEach((type) => { + expect(() => LogDestinationType.parse(type)).not.toThrow(); + }); + }); +}); + +describe('ConsoleDestinationConfigSchema', () => { + it('should apply defaults', () => { + const config = ConsoleDestinationConfigSchema.parse({}); + + expect(config.stream).toBe('stdout'); + expect(config.colors).toBe(true); + expect(config.prettyPrint).toBe(false); + }); + + it('should accept custom values', () => { + const config = ConsoleDestinationConfigSchema.parse({ + stream: 'stderr', + colors: false, + prettyPrint: true, + }); + + expect(config.stream).toBe('stderr'); + expect(config.colors).toBe(false); + expect(config.prettyPrint).toBe(true); + }); +}); + +describe('FileDestinationConfigSchema', () => { + it('should accept valid file configuration', () => { + const config = FileDestinationConfigSchema.parse({ + path: '/var/log/app.log', + }); + + expect(config.path).toBe('/var/log/app.log'); + expect(config.encoding).toBe('utf8'); + expect(config.append).toBe(true); + }); + + it('should accept rotation configuration', () => { + const config = FileDestinationConfigSchema.parse({ + path: '/var/log/app.log', + rotation: { + maxSize: '50m', + maxFiles: 10, + compress: true, + interval: 'daily', + }, + }); + + expect(config.rotation?.maxSize).toBe('50m'); + expect(config.rotation?.maxFiles).toBe(10); + expect(config.rotation?.compress).toBe(true); + }); +}); + +describe('HttpDestinationConfigSchema', () => { + it('should accept valid HTTP configuration', () => { + const config = HttpDestinationConfigSchema.parse({ + url: 'https://logs.example.com/v1/logs', + }); + + expect(config.url).toBe('https://logs.example.com/v1/logs'); + expect(config.method).toBe('POST'); + expect(config.timeout).toBe(30000); + }); + + it('should accept authentication', () => { + const config = HttpDestinationConfigSchema.parse({ + url: 'https://logs.example.com/v1/logs', + auth: { + type: 'bearer', + token: 'secret-token', + }, + }); + + expect(config.auth?.type).toBe('bearer'); + expect(config.auth?.token).toBe('secret-token'); + }); + + it('should accept batch configuration', () => { + const config = HttpDestinationConfigSchema.parse({ + url: 'https://logs.example.com/v1/logs', + batch: { + maxSize: 500, + flushInterval: 10000, + }, + }); + + expect(config.batch?.maxSize).toBe(500); + expect(config.batch?.flushInterval).toBe(10000); + }); +}); + +describe('LogDestinationSchema', () => { + it('should accept valid console destination', () => { + const destination: LogDestination = { + name: 'console_output', + type: 'console', + console: { + stream: 'stdout', + }, + }; + + expect(() => LogDestinationSchema.parse(destination)).not.toThrow(); + }); + + it('should accept valid file destination', () => { + const destination: LogDestination = { + name: 'file_output', + type: 'file', + file: { + path: '/var/log/app.log', + }, + }; + + expect(() => LogDestinationSchema.parse(destination)).not.toThrow(); + }); + + it('should apply defaults', () => { + const destination = LogDestinationSchema.parse({ + name: 'test_dest', + type: 'console', + }); + + expect(destination.level).toBe('info'); + expect(destination.enabled).toBe(true); + expect(destination.format).toBe('json'); + }); + + it('should enforce snake_case naming', () => { + expect(() => LogDestinationSchema.parse({ + name: 'camelCase', + type: 'console', + })).toThrow(); + + expect(() => LogDestinationSchema.parse({ + name: 'kebab-case', + type: 'console', + })).toThrow(); + }); +}); + +describe('LogEnrichmentConfigSchema', () => { + it('should apply defaults', () => { + const config = LogEnrichmentConfigSchema.parse({}); + + expect(config.addHostname).toBe(true); + expect(config.addProcessId).toBe(true); + expect(config.addEnvironment).toBe(true); + expect(config.addCaller).toBe(false); + expect(config.addCorrelationIds).toBe(true); + }); + + it('should accept static fields', () => { + const config = LogEnrichmentConfigSchema.parse({ + staticFields: { + application: 'my-app', + version: '1.0.0', + }, + }); + + expect(config.staticFields?.application).toBe('my-app'); + expect(config.staticFields?.version).toBe('1.0.0'); + }); +}); + +describe('StructuredLogEntrySchema', () => { + it('should accept minimal log entry', () => { + const entry: StructuredLogEntry = { + timestamp: '2024-01-15T10:30:00.000Z', + level: 'info', + message: 'Application started', + }; + + expect(() => StructuredLogEntrySchema.parse(entry)).not.toThrow(); + }); + + it('should accept full log entry with all fields', () => { + const entry: StructuredLogEntry = { + timestamp: '2024-01-15T10:30:00.000Z', + level: 'error', + message: 'Database connection failed', + context: { database: 'postgres', attempt: 3 }, + error: { + name: 'ConnectionError', + message: 'Connection timeout', + stack: 'Error: ...', + }, + trace: { + traceId: 'abc123', + spanId: 'def456', + }, + source: { + service: 'api-server', + component: 'database-pool', + file: 'db.ts', + line: 42, + }, + host: { + hostname: 'server-01', + pid: 1234, + }, + environment: 'production', + user: { + id: 'user-123', + username: 'john', + }, + }; + + expect(() => StructuredLogEntrySchema.parse(entry)).not.toThrow(); + }); +}); + +describe('LoggingConfigSchema', () => { + it('should accept minimal configuration', () => { + const config: LoggingConfig = { + name: 'default_logging', + label: 'Default Logging', + destinations: [ + { + name: 'console', + type: 'console', + }, + ], + }; + + expect(() => LoggingConfigSchema.parse(config)).not.toThrow(); + }); + + it('should apply defaults', () => { + const config = LoggingConfigSchema.parse({ + name: 'test_logging', + label: 'Test Logging', + destinations: [], + }); + + expect(config.enabled).toBe(true); + expect(config.level).toBe('info'); + expect(config.redact).toContain('password'); + expect(config.redact).toContain('apiKey'); + }); + + it('should accept full configuration', () => { + const config: LoggingConfig = { + name: 'production_logging', + label: 'Production Logging', + enabled: true, + level: 'warn', + destinations: [ + { + name: 'console_dest', + type: 'console', + level: 'info', + }, + { + name: 'file_dest', + type: 'file', + file: { + path: '/var/log/app.log', + rotation: { + maxSize: '100m', + maxFiles: 7, + }, + }, + }, + ], + enrichment: { + staticFields: { app: 'my-app' }, + addHostname: true, + }, + sampling: { + enabled: true, + rate: 0.8, + }, + buffer: { + enabled: true, + size: 5000, + flushInterval: 2000, + }, + }; + + expect(() => LoggingConfigSchema.parse(config)).not.toThrow(); + }); + + it('should enforce snake_case naming', () => { + expect(() => LoggingConfigSchema.parse({ + name: 'CamelCase', + label: 'Test', + destinations: [], + })).toThrow(); + }); + + it('should enforce max length for name', () => { + const longName = 'a'.repeat(65); + expect(() => LoggingConfigSchema.parse({ + name: longName, + label: 'Test', + destinations: [], + })).toThrow(); + }); +}); diff --git a/packages/spec/src/system/logging.zod.ts b/packages/spec/src/system/logging.zod.ts new file mode 100644 index 000000000..32005f265 --- /dev/null +++ b/packages/spec/src/system/logging.zod.ts @@ -0,0 +1,569 @@ +import { z } from 'zod'; +import { LogLevel as BaseLogLevel } from './logger.zod'; + +/** + * Logging Protocol - Comprehensive Observability Logging + * + * Extends basic logger with enterprise-grade features: + * - Multiple log destinations (file, console, external services) + * - Structured logging with enrichment + * - Log aggregation and forwarding + * - Integration with external log management systems + * + * Complements logger.zod.ts (basic kernel logging) + */ + +/** + * Extended Log Level Enum + * Standard RFC 5424 severity levels with trace + */ +export const ExtendedLogLevel = z.enum([ + 'trace', // Very detailed debugging information + 'debug', // Debugging information + 'info', // Informational messages + 'warn', // Warning messages + 'error', // Error messages + 'fatal', // Fatal errors causing shutdown +]).describe('Extended log severity level'); + +export type ExtendedLogLevel = z.infer; + +// Re-export base LogLevel for compatibility +export { BaseLogLevel as LogLevel }; + +/** + * Log Destination Type Enum + * Where logs can be sent + */ +export const LogDestinationType = z.enum([ + 'console', // Standard output/error + 'file', // File system + 'syslog', // System logger + 'elasticsearch', // Elasticsearch + 'cloudwatch', // AWS CloudWatch + 'stackdriver', // Google Cloud Logging + 'azure_monitor', // Azure Monitor + 'datadog', // Datadog + 'splunk', // Splunk + 'loki', // Grafana Loki + 'http', // HTTP endpoint + 'kafka', // Apache Kafka + 'redis', // Redis streams + 'custom', // Custom implementation +]).describe('Log destination type'); + +export type LogDestinationType = z.infer; + +/** + * Console Destination Configuration + */ +export const ConsoleDestinationConfigSchema = z.object({ + /** + * Output stream + */ + stream: z.enum(['stdout', 'stderr']).optional().default('stdout'), + + /** + * Enable colored output + */ + colors: z.boolean().optional().default(true), + + /** + * Pretty print JSON + */ + prettyPrint: z.boolean().optional().default(false), +}).describe('Console destination configuration'); + +export type ConsoleDestinationConfig = z.infer; + +/** + * File Destination Configuration + */ +export const FileDestinationConfigSchema = z.object({ + /** + * File path + */ + path: z.string().describe('Log file path'), + + /** + * Enable log rotation + */ + rotation: z.object({ + /** + * Maximum file size before rotation (e.g., '10m', '100k', '1g') + */ + maxSize: z.string().optional().default('10m'), + + /** + * Maximum number of files to keep + */ + maxFiles: z.number().int().positive().optional().default(5), + + /** + * Compress rotated files + */ + compress: z.boolean().optional().default(true), + + /** + * Rotation interval (e.g., 'daily', 'weekly') + */ + interval: z.enum(['hourly', 'daily', 'weekly', 'monthly']).optional(), + }).optional(), + + /** + * File encoding + */ + encoding: z.string().optional().default('utf8'), + + /** + * Append to existing file + */ + append: z.boolean().optional().default(true), +}).describe('File destination configuration'); + +export type FileDestinationConfig = z.infer; + +/** + * HTTP Destination Configuration + */ +export const HttpDestinationConfigSchema = z.object({ + /** + * HTTP endpoint URL + */ + url: z.string().url().describe('HTTP endpoint URL'), + + /** + * HTTP method + */ + method: z.enum(['POST', 'PUT']).optional().default('POST'), + + /** + * Headers to include + */ + headers: z.record(z.string()).optional(), + + /** + * Authentication + */ + auth: z.object({ + type: z.enum(['basic', 'bearer', 'api_key']).describe('Auth type'), + username: z.string().optional(), + password: z.string().optional(), + token: z.string().optional(), + apiKey: z.string().optional(), + apiKeyHeader: z.string().optional().default('X-API-Key'), + }).optional(), + + /** + * Batch configuration + */ + batch: z.object({ + /** + * Maximum batch size + */ + maxSize: z.number().int().positive().optional().default(100), + + /** + * Flush interval in milliseconds + */ + flushInterval: z.number().int().positive().optional().default(5000), + }).optional(), + + /** + * Retry configuration + */ + retry: z.object({ + /** + * Maximum retry attempts + */ + maxAttempts: z.number().int().positive().optional().default(3), + + /** + * Initial retry delay in milliseconds + */ + initialDelay: z.number().int().positive().optional().default(1000), + + /** + * Backoff multiplier + */ + backoffMultiplier: z.number().positive().optional().default(2), + }).optional(), + + /** + * Timeout in milliseconds + */ + timeout: z.number().int().positive().optional().default(30000), +}).describe('HTTP destination configuration'); + +export type HttpDestinationConfig = z.infer; + +/** + * External Service Destination Configuration + * Generic configuration for cloud logging services + */ +export const ExternalServiceDestinationConfigSchema = z.object({ + /** + * Service-specific endpoint + */ + endpoint: z.string().url().optional(), + + /** + * Region (for cloud services) + */ + region: z.string().optional(), + + /** + * Credentials + */ + credentials: z.object({ + accessKeyId: z.string().optional(), + secretAccessKey: z.string().optional(), + apiKey: z.string().optional(), + projectId: z.string().optional(), + }).optional(), + + /** + * Log group/stream/index name + */ + logGroup: z.string().optional(), + logStream: z.string().optional(), + index: z.string().optional(), + + /** + * Service-specific configuration + */ + config: z.record(z.any()).optional(), +}).describe('External service destination configuration'); + +export type ExternalServiceDestinationConfig = z.infer; + +/** + * Log Destination Schema + * Configuration for a single log destination + */ +export const LogDestinationSchema = z.object({ + /** + * Destination name + */ + name: z.string() + .regex(/^[a-z_][a-z0-9_]*$/) + .describe('Destination name (snake_case)'), + + /** + * 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(), + + /** + * Format for this destination + */ + format: z.enum(['json', 'text', 'pretty']).optional().default('json'), + + /** + * Filter function reference (runtime only) + */ + filterId: z.string().optional().describe('Filter function identifier'), +}).describe('Log destination configuration'); + +export type LogDestination = z.infer; + +/** + * Log Enrichment Configuration + * Add contextual data to all log entries + */ +export const LogEnrichmentConfigSchema = z.object({ + /** + * Static fields to add to all logs + */ + staticFields: z.record(z.any()).optional().describe('Static fields added to every log'), + + /** + * Dynamic field enrichers (runtime only) + * References to functions that add dynamic context + */ + dynamicEnrichers: z.array(z.string()).optional().describe('Dynamic enricher function IDs'), + + /** + * Add hostname + */ + addHostname: z.boolean().optional().default(true), + + /** + * Add process ID + */ + addProcessId: z.boolean().optional().default(true), + + /** + * Add environment info + */ + addEnvironment: z.boolean().optional().default(true), + + /** + * Add timestamp in additional formats + */ + addTimestampFormats: z.object({ + unix: z.boolean().optional().default(false), + iso: z.boolean().optional().default(true), + }).optional(), + + /** + * Add caller information (file, line, function) + */ + addCaller: z.boolean().optional().default(false), + + /** + * Add correlation IDs + */ + addCorrelationIds: z.boolean().optional().default(true), +}).describe('Log enrichment configuration'); + +export type LogEnrichmentConfig = z.infer; + +/** + * Structured Log Entry Schema + * Enhanced structured log record with enrichment + */ +export const StructuredLogEntrySchema = z.object({ + /** + * Timestamp (ISO 8601) + */ + timestamp: z.string().datetime().describe('ISO 8601 timestamp'), + + /** + * Log level + */ + level: ExtendedLogLevel.describe('Log severity level'), + + /** + * Log message + */ + message: z.string().describe('Log message'), + + /** + * Structured context data + */ + context: z.record(z.any()).optional().describe('Structured context'), + + /** + * Error information + */ + error: z.object({ + name: z.string().optional(), + message: z.string().optional(), + stack: z.string().optional(), + code: z.string().optional(), + details: z.record(z.any()).optional(), + }).optional().describe('Error details'), + + /** + * Trace context + */ + trace: z.object({ + traceId: z.string().describe('Trace ID'), + spanId: z.string().describe('Span ID'), + parentSpanId: z.string().optional().describe('Parent span ID'), + traceFlags: z.number().int().optional().describe('Trace flags'), + }).optional().describe('Distributed tracing context'), + + /** + * Source information + */ + source: z.object({ + service: z.string().optional().describe('Service name'), + component: z.string().optional().describe('Component name'), + file: z.string().optional().describe('Source file'), + line: z.number().int().optional().describe('Line number'), + function: z.string().optional().describe('Function name'), + }).optional().describe('Source information'), + + /** + * Host information + */ + host: z.object({ + hostname: z.string().optional(), + pid: z.number().int().optional(), + ip: z.string().optional(), + }).optional().describe('Host information'), + + /** + * Environment + */ + environment: z.string().optional().describe('Environment (e.g., production, staging)'), + + /** + * User information + */ + user: z.object({ + id: z.string().optional(), + username: z.string().optional(), + email: z.string().optional(), + }).optional().describe('User context'), + + /** + * Request information + */ + request: z.object({ + id: z.string().optional(), + method: z.string().optional(), + path: z.string().optional(), + userAgent: z.string().optional(), + ip: z.string().optional(), + }).optional().describe('Request context'), + + /** + * Custom labels/tags + */ + labels: z.record(z.string()).optional().describe('Custom labels'), + + /** + * Additional metadata + */ + metadata: z.record(z.any()).optional().describe('Additional metadata'), +}).describe('Structured log entry'); + +export type StructuredLogEntry = z.infer; + +/** + * Logging Configuration Schema + * Main configuration for the logging system + */ +export const LoggingConfigSchema = z.object({ + /** + * Configuration name + */ + name: z.string() + .regex(/^[a-z_][a-z0-9_]*$/) + .max(64) + .describe('Configuration name (snake_case, max 64 chars)'), + + /** + * Display label + */ + label: z.string().describe('Display label'), + + /** + * Enable logging + */ + enabled: z.boolean().optional().default(true), + + /** + * Global minimum log level + */ + level: ExtendedLogLevel.optional().default('info'), + + /** + * Log destinations + */ + destinations: z.array(LogDestinationSchema).describe('Log destinations'), + + /** + * Log enrichment configuration + */ + enrichment: LogEnrichmentConfigSchema.optional().default({}), + + /** + * Fields to redact from logs + */ + redact: z.array(z.string()).optional().default([ + 'password', + 'passwordHash', + 'token', + 'apiKey', + 'secret', + 'creditCard', + 'ssn', + 'authorization', + ]).describe('Fields to redact'), + + /** + * Sampling configuration + */ + sampling: z.object({ + /** + * Enable sampling + */ + enabled: z.boolean().optional().default(false), + + /** + * Sample rate (0.0 to 1.0) + */ + rate: z.number().min(0).max(1).optional().default(1.0), + + /** + * Sample rate by level + */ + rateByLevel: z.record(z.number().min(0).max(1)).optional(), + }).optional(), + + /** + * Buffer configuration + */ + buffer: z.object({ + /** + * Enable buffering + */ + enabled: z.boolean().optional().default(true), + + /** + * Buffer size + */ + size: z.number().int().positive().optional().default(1000), + + /** + * Flush interval in milliseconds + */ + flushInterval: z.number().int().positive().optional().default(1000), + + /** + * Flush on shutdown + */ + flushOnShutdown: z.boolean().optional().default(true), + }).optional(), + + /** + * Performance configuration + */ + performance: z.object({ + /** + * Async logging + */ + async: z.boolean().optional().default(true), + + /** + * Worker threads for async logging + */ + workers: z.number().int().positive().optional().default(1), + }).optional(), +}).describe('Logging configuration'); + +export type LoggingConfig = z.infer; diff --git a/packages/spec/src/system/metrics.test.ts b/packages/spec/src/system/metrics.test.ts new file mode 100644 index 000000000..e4098dc87 --- /dev/null +++ b/packages/spec/src/system/metrics.test.ts @@ -0,0 +1,492 @@ +import { describe, it, expect } from 'vitest'; +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'; + +describe('MetricType', () => { + it('should accept valid metric types', () => { + const types = ['counter', 'gauge', 'histogram', 'summary']; + + types.forEach((type) => { + expect(() => MetricType.parse(type)).not.toThrow(); + }); + }); + + it('should reject invalid metric types', () => { + expect(() => MetricType.parse('invalid')).toThrow(); + }); +}); + +describe('MetricUnit', () => { + it('should accept valid units', () => { + const units = [ + 'milliseconds', 'seconds', 'bytes', 'kilobytes', + 'requests_per_second', 'percent', 'count', + ]; + + units.forEach((unit) => { + expect(() => MetricUnit.parse(unit)).not.toThrow(); + }); + }); +}); + +describe('MetricAggregationType', () => { + it('should accept valid aggregation types', () => { + const types = ['sum', 'avg', 'min', 'max', 'count', 'p50', 'p95', 'p99']; + + types.forEach((type) => { + expect(() => MetricAggregationType.parse(type)).not.toThrow(); + }); + }); +}); + +describe('HistogramBucketConfigSchema', () => { + it('should accept linear buckets', () => { + const config = HistogramBucketConfigSchema.parse({ + type: 'linear', + linear: { + start: 0, + width: 10, + count: 10, + }, + }); + + expect(config.type).toBe('linear'); + expect(config.linear?.start).toBe(0); + expect(config.linear?.width).toBe(10); + }); + + it('should accept exponential buckets', () => { + const config = HistogramBucketConfigSchema.parse({ + type: 'exponential', + exponential: { + start: 1, + factor: 2, + count: 8, + }, + }); + + expect(config.type).toBe('exponential'); + expect(config.exponential?.factor).toBe(2); + }); + + it('should accept explicit buckets', () => { + const config = HistogramBucketConfigSchema.parse({ + type: 'explicit', + explicit: { + boundaries: [0, 10, 50, 100, 500, 1000], + }, + }); + + expect(config.type).toBe('explicit'); + expect(config.explicit?.boundaries).toHaveLength(6); + }); +}); + +describe('MetricDefinitionSchema', () => { + it('should accept valid counter definition', () => { + const metric: MetricDefinition = { + name: 'http_requests_total', + type: 'counter', + description: 'Total HTTP requests', + }; + + expect(() => MetricDefinitionSchema.parse(metric)).not.toThrow(); + }); + + it('should accept gauge with labels', () => { + const metric: MetricDefinition = { + name: 'memory_usage_bytes', + type: 'gauge', + unit: 'bytes', + labelNames: ['service', 'instance'], + }; + + expect(() => MetricDefinitionSchema.parse(metric)).not.toThrow(); + }); + + it('should accept histogram with buckets', () => { + const metric: MetricDefinition = { + name: 'http_request_duration_ms', + type: 'histogram', + unit: 'milliseconds', + histogram: { + type: 'exponential', + exponential: { + start: 1, + factor: 2, + count: 10, + }, + }, + }; + + expect(() => MetricDefinitionSchema.parse(metric)).not.toThrow(); + }); + + it('should accept summary with quantiles', () => { + const metric: MetricDefinition = { + name: 'response_time_ms', + type: 'summary', + summary: { + quantiles: [0.5, 0.9, 0.95, 0.99], + maxAge: 300, + }, + }; + + expect(() => MetricDefinitionSchema.parse(metric)).not.toThrow(); + }); + + it('should apply defaults', () => { + const metric = MetricDefinitionSchema.parse({ + name: 'test_metric', + type: 'counter', + }); + + expect(metric.labelNames).toEqual([]); + expect(metric.enabled).toBe(true); + }); + + it('should enforce snake_case naming', () => { + expect(() => MetricDefinitionSchema.parse({ + name: 'camelCase', + type: 'counter', + })).toThrow(); + }); +}); + +describe('MetricDataPointSchema', () => { + it('should accept counter data point', () => { + const dataPoint: MetricDataPoint = { + name: 'requests_total', + type: 'counter', + timestamp: '2024-01-15T10:30:00.000Z', + value: 100, + }; + + expect(() => MetricDataPointSchema.parse(dataPoint)).not.toThrow(); + }); + + it('should accept gauge with labels', () => { + const dataPoint: MetricDataPoint = { + name: 'cpu_usage', + type: 'gauge', + timestamp: '2024-01-15T10:30:00.000Z', + value: 45.5, + labels: { + host: 'server-01', + core: '0', + }, + }; + + expect(() => MetricDataPointSchema.parse(dataPoint)).not.toThrow(); + }); + + it('should accept histogram data', () => { + const dataPoint: MetricDataPoint = { + name: 'request_duration', + type: 'histogram', + timestamp: '2024-01-15T10:30:00.000Z', + histogram: { + count: 100, + sum: 5000, + buckets: [ + { upperBound: 10, count: 20 }, + { upperBound: 50, count: 50 }, + { upperBound: 100, count: 25 }, + ], + }, + }; + + expect(() => MetricDataPointSchema.parse(dataPoint)).not.toThrow(); + }); +}); + +describe('TimeSeriesSchema', () => { + it('should accept time series data', () => { + const timeSeries = TimeSeriesSchema.parse({ + name: 'cpu_usage', + labels: { host: 'server-01' }, + dataPoints: [ + { timestamp: '2024-01-15T10:00:00.000Z', value: 45.5 }, + { timestamp: '2024-01-15T10:01:00.000Z', value: 46.2 }, + { timestamp: '2024-01-15T10:02:00.000Z', value: 44.8 }, + ], + }); + + expect(timeSeries.dataPoints).toHaveLength(3); + }); +}); + +describe('MetricAggregationConfigSchema', () => { + it('should accept aggregation with window', () => { + const config = MetricAggregationConfigSchema.parse({ + type: 'avg', + window: { + size: 300, + sliding: true, + slideInterval: 60, + }, + groupBy: ['service', 'instance'], + }); + + expect(config.type).toBe('avg'); + expect(config.window?.size).toBe(300); + }); +}); + +describe('ServiceLevelIndicatorSchema', () => { + it('should accept availability SLI', () => { + const sli: ServiceLevelIndicator = { + name: 'api_availability', + label: 'API Availability', + metric: 'http_requests_total', + type: 'availability', + successCriteria: { + threshold: 99.9, + operator: 'gte', + }, + window: { + size: 2592000, // 30 days + }, + }; + + expect(() => ServiceLevelIndicatorSchema.parse(sli)).not.toThrow(); + }); + + it('should accept latency SLI with percentile', () => { + const sli: ServiceLevelIndicator = { + name: 'api_latency_p99', + label: 'API Latency P99', + metric: 'http_request_duration', + type: 'latency', + successCriteria: { + threshold: 100, + operator: 'lte', + percentile: 0.99, + }, + window: { + size: 86400, // 1 day + rolling: true, + }, + }; + + expect(() => ServiceLevelIndicatorSchema.parse(sli)).not.toThrow(); + }); + + it('should apply defaults', () => { + const sli = ServiceLevelIndicatorSchema.parse({ + name: 'test_sli', + label: 'Test SLI', + metric: 'test_metric', + type: 'availability', + successCriteria: { + threshold: 99, + operator: 'gte', + }, + window: { + size: 3600, + }, + }); + + expect(sli.enabled).toBe(true); + }); +}); + +describe('ServiceLevelObjectiveSchema', () => { + it('should accept SLO with rolling period', () => { + const slo: ServiceLevelObjective = { + name: 'api_uptime_slo', + label: 'API Uptime SLO', + sli: 'api_availability', + target: 99.9, + period: { + type: 'rolling', + duration: 2592000, // 30 days + }, + }; + + expect(() => ServiceLevelObjectiveSchema.parse(slo)).not.toThrow(); + }); + + it('should accept SLO with calendar period', () => { + const slo: ServiceLevelObjective = { + name: 'monthly_slo', + label: 'Monthly SLO', + sli: 'test_sli', + target: 99.5, + period: { + type: 'calendar', + calendar: 'monthly', + }, + }; + + expect(() => ServiceLevelObjectiveSchema.parse(slo)).not.toThrow(); + }); + + it('should accept error budget configuration', () => { + const slo: ServiceLevelObjective = { + name: 'error_budget_slo', + label: 'Error Budget SLO', + sli: 'test_sli', + target: 99.9, + period: { type: 'rolling', duration: 2592000 }, + errorBudget: { + enabled: true, + alertThreshold: 75, + burnRateWindows: [ + { window: 3600, threshold: 14.4 }, + { window: 86400, threshold: 6 }, + ], + }, + }; + + expect(() => ServiceLevelObjectiveSchema.parse(slo)).not.toThrow(); + }); + + it('should apply defaults', () => { + const slo = ServiceLevelObjectiveSchema.parse({ + name: 'test_slo', + label: 'Test SLO', + sli: 'test_sli', + target: 99, + period: { type: 'rolling', duration: 86400 }, + }); + + expect(slo.enabled).toBe(true); + expect(slo.alerts).toEqual([]); + }); +}); + +describe('MetricExportConfigSchema', () => { + it('should accept Prometheus export', () => { + const config = MetricExportConfigSchema.parse({ + type: 'prometheus', + endpoint: '/metrics', + }); + + expect(config.type).toBe('prometheus'); + expect(config.interval).toBe(60); + }); + + it('should accept HTTP push export', () => { + const config = MetricExportConfigSchema.parse({ + type: 'http', + endpoint: 'https://metrics.example.com', + interval: 30, + auth: { + type: 'bearer', + token: 'secret-token', + }, + }); + + expect(config.interval).toBe(30); + expect(config.auth?.type).toBe('bearer'); + }); +}); + +describe('MetricsConfigSchema', () => { + it('should accept minimal configuration', () => { + const config: MetricsConfig = { + name: 'default_metrics', + label: 'Default Metrics', + }; + + expect(() => MetricsConfigSchema.parse(config)).not.toThrow(); + }); + + it('should apply defaults', () => { + const config = MetricsConfigSchema.parse({ + name: 'test_metrics', + label: 'Test Metrics', + }); + + expect(config.enabled).toBe(true); + expect(config.metrics).toEqual([]); + expect(config.defaultLabels).toEqual({}); + expect(config.collectionInterval).toBe(15); + }); + + it('should accept full configuration', () => { + const config: MetricsConfig = { + name: 'production_metrics', + label: 'Production Metrics', + enabled: true, + metrics: [ + { + name: 'http_requests_total', + type: 'counter', + labelNames: ['method', 'status'], + }, + ], + defaultLabels: { + environment: 'production', + region: 'us-east-1', + }, + slis: [ + { + name: 'api_availability', + label: 'API Availability', + metric: 'http_requests_total', + type: 'availability', + successCriteria: { + threshold: 99.9, + operator: 'gte', + }, + window: { size: 2592000 }, + }, + ], + slos: [ + { + name: 'api_slo', + label: 'API SLO', + sli: 'api_availability', + target: 99.9, + period: { type: 'rolling', duration: 2592000 }, + }, + ], + exports: [ + { + type: 'prometheus', + endpoint: '/metrics', + }, + ], + retention: { + period: 604800, + }, + }; + + expect(() => MetricsConfigSchema.parse(config)).not.toThrow(); + }); + + it('should enforce snake_case naming', () => { + expect(() => MetricsConfigSchema.parse({ + name: 'CamelCase', + label: 'Test', + })).toThrow(); + }); + + it('should enforce max length for name', () => { + const longName = 'a'.repeat(65); + expect(() => MetricsConfigSchema.parse({ + name: longName, + label: 'Test', + })).toThrow(); + }); +}); diff --git a/packages/spec/src/system/metrics.zod.ts b/packages/spec/src/system/metrics.zod.ts new file mode 100644 index 000000000..d4c065a78 --- /dev/null +++ b/packages/spec/src/system/metrics.zod.ts @@ -0,0 +1,704 @@ +import { z } from 'zod'; + +/** + * Metrics Protocol - Performance and Operational Metrics + * + * Comprehensive metrics collection and monitoring: + * - Counter, Gauge, Histogram, Summary metric types + * - Time-series data collection + * - SLI/SLO definitions + * - Metric aggregation and export + * - Integration with monitoring systems (Prometheus, etc.) + */ + +/** + * Metric Type Enum + * Standard Prometheus metric types + */ +export const MetricType = z.enum([ + 'counter', // Monotonically increasing value + 'gauge', // Value that can go up and down + 'histogram', // Observations bucketed by configurable ranges + 'summary', // Observations with quantiles +]).describe('Metric type'); + +export type MetricType = z.infer; + +/** + * Metric Unit Enum + * Standard units for metrics + */ +export const MetricUnit = z.enum([ + // Time units + 'nanoseconds', + 'microseconds', + 'milliseconds', + 'seconds', + 'minutes', + 'hours', + 'days', + + // Size units + 'bytes', + 'kilobytes', + 'megabytes', + 'gigabytes', + 'terabytes', + + // Rate units + 'requests_per_second', + 'events_per_second', + 'bytes_per_second', + + // Percentage + 'percent', + 'ratio', + + // Count + 'count', + 'operations', + + // Custom + 'custom', +]).describe('Metric unit'); + +export type MetricUnit = z.infer; + +/** + * Metric Aggregation Type + */ +export const MetricAggregationType = z.enum([ + 'sum', // Sum of all values + 'avg', // Average of all values + 'min', // Minimum value + 'max', // Maximum value + 'count', // Count of observations + 'p50', // 50th percentile (median) + 'p75', // 75th percentile + 'p90', // 90th percentile + 'p95', // 95th percentile + 'p99', // 99th percentile + 'p999', // 99.9th percentile + 'rate', // Rate of change + 'stddev', // Standard deviation +]).describe('Metric aggregation type'); + +export type MetricAggregationType = z.infer; + +/** + * Histogram Bucket Configuration + */ +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(), + + /** + * Exponential bucket configuration + */ + exponential: z.object({ + start: z.number().positive().describe('Start value'), + factor: z.number().positive().describe('Growth factor'), + count: z.number().int().positive().describe('Number of buckets'), + }).optional(), + + /** + * Explicit bucket boundaries + */ + explicit: z.object({ + boundaries: z.array(z.number()).describe('Bucket boundaries'), + }).optional(), +}).describe('Histogram bucket configuration'); + +export type HistogramBucketConfig = z.infer; + +/** + * Metric Labels Schema + * Key-value pairs for metric dimensions + */ +export const MetricLabelsSchema = z.record(z.string()).describe('Metric labels'); + +export type MetricLabels = z.infer; + +/** + * Metric Definition Schema + */ +export const MetricDefinitionSchema = z.object({ + /** + * Metric name (snake_case) + */ + name: z.string() + .regex(/^[a-z_][a-z0-9_]*$/) + .describe('Metric name (snake_case)'), + + /** + * Display label + */ + label: z.string().optional().describe('Display label'), + + /** + * Metric type + */ + type: MetricType.describe('Metric type'), + + /** + * Metric unit + */ + unit: MetricUnit.optional().describe('Metric unit'), + + /** + * Description + */ + description: z.string().optional().describe('Metric description'), + + /** + * Label names for this metric + */ + labelNames: z.array(z.string()).optional().default([]).describe('Label names'), + + /** + * Histogram configuration (for histogram type) + */ + histogram: HistogramBucketConfigSchema.optional(), + + /** + * Summary configuration (for summary type) + */ + summary: z.object({ + /** + * Quantiles to track + */ + quantiles: z.array(z.number().min(0).max(1)).optional().default([0.5, 0.9, 0.99]), + + /** + * Max age of observations in seconds + */ + maxAge: z.number().int().positive().optional().default(600), + + /** + * Number of age buckets + */ + ageBuckets: z.number().int().positive().optional().default(5), + }).optional(), + + /** + * Enabled flag + */ + enabled: z.boolean().optional().default(true), +}).describe('Metric definition'); + +export type MetricDefinition = z.infer; + +/** + * Metric Data Point Schema + * A single metric observation + */ +export const MetricDataPointSchema = z.object({ + /** + * Metric name + */ + 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(), + + /** + * Summary data + */ + summary: z.object({ + count: z.number().int().nonnegative().describe('Total count'), + sum: z.number().describe('Sum of all values'), + quantiles: z.array(z.object({ + quantile: z.number().min(0).max(1).describe('Quantile (0-1)'), + value: z.number().describe('Quantile value'), + })).describe('Summary quantiles'), + }).optional(), +}).describe('Metric data point'); + +export type MetricDataPoint = z.infer; + +/** + * Time Series Data Point Schema + */ +export const TimeSeriesDataPointSchema = z.object({ + /** + * Timestamp (ISO 8601) + */ + timestamp: z.string().datetime().describe('Timestamp'), + + /** + * Value + */ + value: z.number().describe('Value'), + + /** + * Labels/tags + */ + labels: z.record(z.string()).optional().describe('Labels'), +}).describe('Time series data point'); + +export type TimeSeriesDataPoint = z.infer; + +/** + * Time Series Schema + */ +export const TimeSeriesSchema = z.object({ + /** + * Series name + */ + name: z.string().describe('Series name'), + + /** + * Series labels + */ + labels: z.record(z.string()).optional().describe('Series labels'), + + /** + * Data points + */ + dataPoints: z.array(TimeSeriesDataPointSchema).describe('Data points'), + + /** + * Start time + */ + startTime: z.string().datetime().optional().describe('Start time'), + + /** + * End time + */ + endTime: z.string().datetime().optional().describe('End time'), +}).describe('Time series'); + +export type TimeSeries = z.infer; + +/** + * Metric Aggregation Configuration + */ +export const MetricAggregationConfigSchema = z.object({ + /** + * Aggregation type + */ + type: MetricAggregationType.describe('Aggregation type'), + + /** + * Time window for aggregation + */ + window: z.object({ + /** + * Window size in seconds + */ + size: z.number().int().positive().describe('Window size in seconds'), + + /** + * Sliding window (true) or tumbling window (false) + */ + sliding: z.boolean().optional().default(false), + + /** + * Slide interval for sliding windows + */ + slideInterval: z.number().int().positive().optional(), + }).optional(), + + /** + * Group by labels + */ + groupBy: z.array(z.string()).optional().describe('Group by label names'), + + /** + * Filters + */ + filters: z.record(z.any()).optional().describe('Filter criteria'), +}).describe('Metric aggregation configuration'); + +export type MetricAggregationConfig = z.infer; + +/** + * Service Level Indicator (SLI) Schema + */ +export const ServiceLevelIndicatorSchema = z.object({ + /** + * SLI name + */ + name: z.string() + .regex(/^[a-z_][a-z0-9_]*$/) + .describe('SLI name (snake_case)'), + + /** + * Display label + */ + label: z.string().describe('Display label'), + + /** + * Description + */ + description: z.string().optional().describe('SLI description'), + + /** + * Metric name this SLI is based on + */ + metric: z.string().describe('Base metric name'), + + /** + * SLI type + */ + type: z.enum([ + 'availability', // Percentage of successful requests + 'latency', // Response time percentile + 'throughput', // Requests per second + 'error_rate', // Error percentage + 'saturation', // Resource utilization + 'custom', // Custom calculation + ]).describe('SLI type'), + + /** + * Success criteria + */ + successCriteria: z.object({ + /** + * Threshold value + */ + threshold: z.number().describe('Threshold value'), + + /** + * Comparison operator + */ + operator: z.enum(['lt', 'lte', 'gt', 'gte', 'eq']).describe('Comparison operator'), + + /** + * Percentile (for latency SLIs) + */ + percentile: z.number().min(0).max(1).optional().describe('Percentile (0-1)'), + }).describe('Success criteria'), + + /** + * Measurement window + */ + window: z.object({ + /** + * Window size in seconds + */ + size: z.number().int().positive().describe('Window size in seconds'), + + /** + * Rolling window (true) or calendar-aligned (false) + */ + rolling: z.boolean().optional().default(true), + }).describe('Measurement window'), + + /** + * Enabled flag + */ + enabled: z.boolean().optional().default(true), +}).describe('Service Level Indicator'); + +export type ServiceLevelIndicator = z.infer; + +/** + * Service Level Objective (SLO) Schema + */ +export const ServiceLevelObjectiveSchema = z.object({ + /** + * SLO name + */ + name: z.string() + .regex(/^[a-z_][a-z0-9_]*$/) + .describe('SLO name (snake_case)'), + + /** + * Display label + */ + label: z.string().describe('Display label'), + + /** + * Description + */ + description: z.string().optional().describe('SLO description'), + + /** + * SLI this SLO is based on + */ + sli: z.string().describe('SLI name'), + + /** + * Target percentage (0-100) + */ + target: z.number().min(0).max(100).describe('Target percentage'), + + /** + * Time period for SLO + */ + period: z.object({ + /** + * Period type + */ + type: z.enum(['rolling', 'calendar']).describe('Period type'), + + /** + * Duration in seconds (for rolling) + */ + duration: z.number().int().positive().optional().describe('Duration in seconds'), + + /** + * Calendar period (for calendar) + */ + calendar: z.enum(['daily', 'weekly', 'monthly', 'quarterly', 'yearly']).optional(), + }).describe('Time period'), + + /** + * Error budget configuration + */ + errorBudget: z.object({ + /** + * Auto-calculated budget (1 - target) + */ + enabled: z.boolean().optional().default(true), + + /** + * Alert when budget consumed percentage exceeds threshold + */ + alertThreshold: z.number().min(0).max(100).optional().default(80), + + /** + * Burn rate alert windows + */ + burnRateWindows: z.array(z.object({ + /** + * Window size in seconds + */ + window: z.number().int().positive().describe('Window size'), + + /** + * Burn rate multiplier threshold + */ + threshold: z.number().positive().describe('Burn rate threshold'), + })).optional(), + }).optional(), + + /** + * Alert configuration + */ + alerts: z.array(z.object({ + /** + * Alert name + */ + name: z.string().describe('Alert name'), + + /** + * Severity + */ + severity: z.enum(['info', 'warning', 'critical']).describe('Alert severity'), + + /** + * Condition + */ + condition: z.object({ + type: z.enum(['slo_breach', 'error_budget', 'burn_rate']).describe('Condition type'), + threshold: z.number().optional().describe('Threshold value'), + }).describe('Alert condition'), + })).optional().default([]), + + /** + * Enabled flag + */ + enabled: z.boolean().optional().default(true), +}).describe('Service Level Objective'); + +export type ServiceLevelObjective = z.infer; + +/** + * Metric Export Configuration + */ +export const MetricExportConfigSchema = z.object({ + /** + * Export type + */ + type: z.enum([ + 'prometheus', // Prometheus exposition format + 'openmetrics', // OpenMetrics format + 'graphite', // Graphite plaintext protocol + 'statsd', // StatsD protocol + 'influxdb', // InfluxDB line protocol + 'datadog', // Datadog agent + 'cloudwatch', // AWS CloudWatch + 'stackdriver', // Google Cloud Monitoring + 'azure_monitor', // Azure Monitor + 'http', // HTTP push + 'custom', // Custom exporter + ]).describe('Export type'), + + /** + * Endpoint configuration + */ + endpoint: z.string().optional().describe('Export endpoint'), + + /** + * Export interval in seconds + */ + interval: z.number().int().positive().optional().default(60), + + /** + * Batch configuration + */ + batch: z.object({ + enabled: z.boolean().optional().default(true), + size: z.number().int().positive().optional().default(1000), + }).optional(), + + /** + * Authentication + */ + auth: z.object({ + type: z.enum(['none', 'basic', 'bearer', 'api_key']).describe('Auth type'), + username: z.string().optional(), + password: z.string().optional(), + token: z.string().optional(), + apiKey: z.string().optional(), + }).optional(), + + /** + * Additional configuration + */ + config: z.record(z.any()).optional().describe('Additional configuration'), +}).describe('Metric export configuration'); + +export type MetricExportConfig = z.infer; + +/** + * Metrics Configuration Schema + */ +export const MetricsConfigSchema = z.object({ + /** + * Configuration name + */ + name: z.string() + .regex(/^[a-z_][a-z0-9_]*$/) + .max(64) + .describe('Configuration name (snake_case, max 64 chars)'), + + /** + * Display label + */ + label: z.string().describe('Display label'), + + /** + * Enable metrics collection + */ + enabled: z.boolean().optional().default(true), + + /** + * Metric definitions + */ + metrics: z.array(MetricDefinitionSchema).optional().default([]), + + /** + * Default labels applied to all metrics + */ + defaultLabels: MetricLabelsSchema.optional().default({}), + + /** + * Aggregation configurations + */ + aggregations: z.array(MetricAggregationConfigSchema).optional().default([]), + + /** + * Service Level Indicators + */ + slis: z.array(ServiceLevelIndicatorSchema).optional().default([]), + + /** + * Service Level Objectives + */ + slos: z.array(ServiceLevelObjectiveSchema).optional().default([]), + + /** + * Export configurations + */ + exports: z.array(MetricExportConfigSchema).optional().default([]), + + /** + * Collection interval in seconds + */ + collectionInterval: z.number().int().positive().optional().default(15), + + /** + * Retention configuration + */ + retention: z.object({ + /** + * Retention period in seconds + */ + period: z.number().int().positive().optional().default(604800), // 7 days + + /** + * Downsampling configuration + */ + downsampling: z.array(z.object({ + /** + * After this duration, downsample to this resolution + */ + afterSeconds: z.number().int().positive().describe('Downsample after seconds'), + + /** + * Resolution in seconds + */ + resolution: z.number().int().positive().describe('Downsampled resolution'), + })).optional(), + }).optional(), + + /** + * Cardinality limits + */ + cardinalityLimits: z.object({ + /** + * Maximum unique label combinations per metric + */ + maxLabelCombinations: z.number().int().positive().optional().default(10000), + + /** + * Action when limit exceeded + */ + onLimitExceeded: z.enum(['drop', 'sample', 'alert']).optional().default('alert'), + }).optional(), +}).describe('Metrics configuration'); + +export type MetricsConfig = z.infer; diff --git a/packages/spec/src/system/tracing.test.ts b/packages/spec/src/system/tracing.test.ts new file mode 100644 index 000000000..91edc7129 --- /dev/null +++ b/packages/spec/src/system/tracing.test.ts @@ -0,0 +1,517 @@ +import { describe, it, expect } from 'vitest'; +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'; + +describe('TraceFlagsSchema', () => { + it('should accept valid trace flags', () => { + expect(() => TraceFlagsSchema.parse(0)).not.toThrow(); + expect(() => TraceFlagsSchema.parse(1)).not.toThrow(); + expect(() => TraceFlagsSchema.parse(255)).not.toThrow(); + }); + + it('should reject invalid trace flags', () => { + expect(() => TraceFlagsSchema.parse(-1)).toThrow(); + expect(() => TraceFlagsSchema.parse(256)).toThrow(); + }); +}); + +describe('TraceContextSchema', () => { + it('should accept valid trace context', () => { + const context: TraceContext = { + traceId: '0123456789abcdef0123456789abcdef', + spanId: '0123456789abcdef', + traceFlags: 1, + }; + + expect(() => TraceContextSchema.parse(context)).not.toThrow(); + }); + + it('should apply defaults', () => { + const context = TraceContextSchema.parse({ + traceId: '0123456789abcdef0123456789abcdef', + spanId: '0123456789abcdef', + }); + + expect(context.traceFlags).toBe(1); + expect(context.sampled).toBe(true); + expect(context.remote).toBe(false); + }); + + it('should accept parent span ID', () => { + const context = TraceContextSchema.parse({ + traceId: '0123456789abcdef0123456789abcdef', + spanId: '0123456789abcdef', + parentSpanId: 'fedcba9876543210', + }); + + expect(context.parentSpanId).toBe('fedcba9876543210'); + }); + + it('should validate trace ID format', () => { + expect(() => TraceContextSchema.parse({ + traceId: 'invalid', + spanId: '0123456789abcdef', + })).toThrow(); + + expect(() => TraceContextSchema.parse({ + traceId: '0123456789abcdef', // Too short + spanId: '0123456789abcdef', + })).toThrow(); + }); + + it('should validate span ID format', () => { + expect(() => TraceContextSchema.parse({ + traceId: '0123456789abcdef0123456789abcdef', + spanId: 'invalid', + })).toThrow(); + + expect(() => TraceContextSchema.parse({ + traceId: '0123456789abcdef0123456789abcdef', + spanId: '01234567', // Too short + })).toThrow(); + }); +}); + +describe('SpanKind', () => { + it('should accept valid span kinds', () => { + const kinds = ['internal', 'server', 'client', 'producer', 'consumer']; + + kinds.forEach((kind) => { + expect(() => SpanKind.parse(kind)).not.toThrow(); + }); + }); +}); + +describe('SpanStatus', () => { + it('should accept valid span statuses', () => { + const statuses = ['unset', 'ok', 'error']; + + statuses.forEach((status) => { + expect(() => SpanStatus.parse(status)).not.toThrow(); + }); + }); +}); + +describe('SpanAttributeValueSchema', () => { + it('should accept various attribute types', () => { + expect(() => SpanAttributeValueSchema.parse('string value')).not.toThrow(); + expect(() => SpanAttributeValueSchema.parse(42)).not.toThrow(); + expect(() => SpanAttributeValueSchema.parse(true)).not.toThrow(); + expect(() => SpanAttributeValueSchema.parse(['a', 'b', 'c'])).not.toThrow(); + expect(() => SpanAttributeValueSchema.parse([1, 2, 3])).not.toThrow(); + expect(() => SpanAttributeValueSchema.parse([true, false])).not.toThrow(); + }); +}); + +describe('SpanEventSchema', () => { + it('should accept span event', () => { + const event = SpanEventSchema.parse({ + name: 'exception', + timestamp: '2024-01-15T10:30:00.000Z', + attributes: { + 'exception.type': 'Error', + 'exception.message': 'Something went wrong', + }, + }); + + expect(event.name).toBe('exception'); + }); +}); + +describe('SpanLinkSchema', () => { + it('should accept span link', () => { + const link = SpanLinkSchema.parse({ + context: { + traceId: '0123456789abcdef0123456789abcdef', + spanId: '0123456789abcdef', + }, + attributes: { + 'link.type': 'follows_from', + }, + }); + + expect(link.context.traceId).toBe('0123456789abcdef0123456789abcdef'); + }); +}); + +describe('SpanSchema', () => { + it('should accept minimal span', () => { + const span: Span = { + context: { + traceId: '0123456789abcdef0123456789abcdef', + spanId: '0123456789abcdef', + }, + name: 'http.request', + startTime: '2024-01-15T10:30:00.000Z', + }; + + expect(() => SpanSchema.parse(span)).not.toThrow(); + }); + + it('should accept full span with all fields', () => { + const span: Span = { + context: { + traceId: '0123456789abcdef0123456789abcdef', + spanId: '0123456789abcdef', + parentSpanId: 'fedcba9876543210', + traceFlags: 1, + sampled: true, + }, + name: 'GET /api/users', + kind: 'server', + startTime: '2024-01-15T10:30:00.000Z', + endTime: '2024-01-15T10:30:00.150Z', + duration: 150, + status: { + code: 'ok', + }, + attributes: { + 'http.method': 'GET', + 'http.url': '/api/users', + 'http.status_code': 200, + }, + events: [ + { + name: 'request.received', + timestamp: '2024-01-15T10:30:00.000Z', + }, + ], + links: [], + resource: { + 'service.name': 'api-server', + 'service.version': '1.0.0', + }, + instrumentationLibrary: { + name: 'opentelemetry-instrumentation-http', + version: '0.35.0', + }, + }; + + expect(() => SpanSchema.parse(span)).not.toThrow(); + }); + + it('should apply defaults', () => { + const span = SpanSchema.parse({ + context: { + traceId: '0123456789abcdef0123456789abcdef', + spanId: '0123456789abcdef', + }, + name: 'test.span', + startTime: '2024-01-15T10:30:00.000Z', + }); + + expect(span.kind).toBe('internal'); + expect(span.attributes).toEqual({}); + expect(span.events).toEqual([]); + expect(span.links).toEqual([]); + }); +}); + +describe('SamplingDecision', () => { + it('should accept valid decisions', () => { + const decisions = ['drop', 'record_only', 'record_and_sample']; + + decisions.forEach((decision) => { + expect(() => SamplingDecision.parse(decision)).not.toThrow(); + }); + }); +}); + +describe('SamplingStrategyType', () => { + it('should accept valid strategy types', () => { + const types = [ + 'always_on', 'always_off', 'trace_id_ratio', 'rate_limiting', + 'parent_based', 'probability', 'composite', 'custom', + ]; + + types.forEach((type) => { + expect(() => SamplingStrategyType.parse(type)).not.toThrow(); + }); + }); +}); + +describe('TraceSamplingConfigSchema', () => { + it('should accept always_on strategy', () => { + const config: TraceSamplingConfig = { + type: 'always_on', + }; + + expect(() => TraceSamplingConfigSchema.parse(config)).not.toThrow(); + }); + + it('should accept trace_id_ratio strategy', () => { + const config: TraceSamplingConfig = { + type: 'trace_id_ratio', + ratio: 0.1, + }; + + expect(() => TraceSamplingConfigSchema.parse(config)).not.toThrow(); + }); + + it('should accept parent_based strategy', () => { + const config: TraceSamplingConfig = { + type: 'parent_based', + parentBased: { + whenParentSampled: 'always_on', + whenParentNotSampled: 'always_off', + root: 'trace_id_ratio', + rootRatio: 0.2, + }, + }; + + expect(() => TraceSamplingConfigSchema.parse(config)).not.toThrow(); + }); + + it('should accept sampling rules', () => { + const config: TraceSamplingConfig = { + type: 'always_on', + rules: [ + { + name: 'health_check', + match: { + spanName: '/health', + }, + decision: 'drop', + }, + { + name: 'errors', + match: { + attributes: { 'http.status_code': 500 }, + }, + decision: 'record_and_sample', + rate: 1.0, + }, + ], + }; + + expect(() => TraceSamplingConfigSchema.parse(config)).not.toThrow(); + }); + + it('should apply defaults', () => { + const config = TraceSamplingConfigSchema.parse({ + type: 'always_on', + }); + + expect(config.rules).toEqual([]); + }); +}); + +describe('TracePropagationFormat', () => { + it('should accept valid formats', () => { + const formats = ['w3c', 'b3', 'b3_multi', 'jaeger', 'xray', 'ottrace', 'custom']; + + formats.forEach((format) => { + expect(() => TracePropagationFormat.parse(format)).not.toThrow(); + }); + }); +}); + +describe('TraceContextPropagationSchema', () => { + it('should apply defaults', () => { + const config = TraceContextPropagationSchema.parse({}); + + expect(config.formats).toEqual(['w3c']); + expect(config.extract).toBe(true); + expect(config.inject).toBe(true); + }); + + it('should accept multiple formats', () => { + const config = TraceContextPropagationSchema.parse({ + formats: ['w3c', 'b3', 'jaeger'], + }); + + expect(config.formats).toHaveLength(3); + }); + + it('should accept baggage configuration', () => { + const config = TraceContextPropagationSchema.parse({ + baggage: { + enabled: true, + maxSize: 4096, + allowedKeys: ['user-id', 'request-id'], + }, + }); + + expect(config.baggage?.maxSize).toBe(4096); + }); +}); + +describe('OtelExporterType', () => { + it('should accept valid exporter types', () => { + const types = [ + 'otlp_http', 'otlp_grpc', 'jaeger', 'zipkin', 'console', + 'datadog', 'honeycomb', 'lightstep', 'newrelic', 'custom', + ]; + + types.forEach((type) => { + expect(() => OtelExporterType.parse(type)).not.toThrow(); + }); + }); +}); + +describe('OpenTelemetryCompatibilitySchema', () => { + it('should accept minimal configuration', () => { + const config: OpenTelemetryCompatibility = { + exporter: { + type: 'console', + }, + resource: { + serviceName: 'my-service', + }, + }; + + expect(() => OpenTelemetryCompatibilitySchema.parse(config)).not.toThrow(); + }); + + it('should accept OTLP HTTP exporter', () => { + const config: OpenTelemetryCompatibility = { + exporter: { + type: 'otlp_http', + endpoint: 'https://otel-collector.example.com/v1/traces', + headers: { + 'Authorization': 'Bearer token', + }, + }, + resource: { + serviceName: 'api-server', + serviceVersion: '1.0.0', + deploymentEnvironment: 'production', + }, + }; + + expect(() => OpenTelemetryCompatibilitySchema.parse(config)).not.toThrow(); + }); + + it('should accept batch configuration', () => { + const config: OpenTelemetryCompatibility = { + exporter: { + type: 'otlp_grpc', + endpoint: 'otel-collector:4317', + batch: { + maxBatchSize: 1024, + maxQueueSize: 4096, + exportTimeout: 60000, + scheduledDelay: 10000, + }, + }, + resource: { + serviceName: 'test-service', + }, + }; + + expect(() => OpenTelemetryCompatibilitySchema.parse(config)).not.toThrow(); + }); + + it('should apply defaults', () => { + const config = OpenTelemetryCompatibilitySchema.parse({ + exporter: { + type: 'console', + }, + resource: { + serviceName: 'test', + }, + }); + + expect(config.exporter.timeout).toBe(10000); + expect(config.exporter.compression).toBe('none'); + }); +}); + +describe('TracingConfigSchema', () => { + it('should accept minimal configuration', () => { + const config: TracingConfig = { + name: 'default_tracing', + label: 'Default Tracing', + }; + + expect(() => TracingConfigSchema.parse(config)).not.toThrow(); + }); + + it('should apply defaults', () => { + const config = TracingConfigSchema.parse({ + name: 'test_tracing', + label: 'Test Tracing', + }); + + expect(config.enabled).toBe(true); + expect(config.sampling?.type).toBe('always_on'); + expect(config.propagation?.formats).toEqual(['w3c']); + expect(config.traceIdGenerator).toBe('random'); + }); + + it('should accept full configuration', () => { + const config: TracingConfig = { + name: 'production_tracing', + label: 'Production Tracing', + enabled: true, + sampling: { + type: 'parent_based', + parentBased: { + root: 'trace_id_ratio', + rootRatio: 0.1, + }, + }, + propagation: { + formats: ['w3c', 'b3'], + extract: true, + inject: true, + }, + openTelemetry: { + exporter: { + type: 'otlp_http', + endpoint: 'https://otel-collector/v1/traces', + }, + resource: { + serviceName: 'api-server', + serviceVersion: '2.0.0', + deploymentEnvironment: 'production', + }, + }, + spanLimits: { + maxAttributes: 256, + maxEvents: 256, + maxLinks: 64, + }, + traceIdGenerator: 'random', + }; + + expect(() => TracingConfigSchema.parse(config)).not.toThrow(); + }); + + it('should enforce snake_case naming', () => { + expect(() => TracingConfigSchema.parse({ + name: 'CamelCase', + label: 'Test', + })).toThrow(); + }); + + it('should enforce max length for name', () => { + const longName = 'a'.repeat(65); + expect(() => TracingConfigSchema.parse({ + name: longName, + label: 'Test', + })).toThrow(); + }); +}); diff --git a/packages/spec/src/system/tracing.zod.ts b/packages/spec/src/system/tracing.zod.ts new file mode 100644 index 000000000..2d239c80a --- /dev/null +++ b/packages/spec/src/system/tracing.zod.ts @@ -0,0 +1,695 @@ +import { z } from 'zod'; + +/** + * Tracing Protocol - Distributed Tracing & Observability + * + * Comprehensive distributed tracing based on OpenTelemetry standards: + * - Trace context propagation + * - Span creation and management + * - Sampling strategies + * - Integration with tracing backends (Jaeger, Zipkin, etc.) + * - W3C Trace Context standard compliance + */ + +/** + * Trace State Schema + * W3C Trace Context tracestate header + */ +export const TraceStateSchema = z.object({ + /** + * Vendor-specific key-value pairs + */ + entries: z.record(z.string()).describe('Trace state entries'), +}).describe('Trace state'); + +export type TraceState = z.infer; + +/** + * Trace Flags Enum + * W3C Trace Context trace flags + */ +export const TraceFlagsSchema = z.number().int().min(0).max(255).describe('Trace flags bitmap'); + +export type TraceFlags = z.infer; + +/** + * Trace Context Schema + * W3C Trace Context standard + */ +export const TraceContextSchema = z.object({ + /** + * Trace ID (128-bit identifier, 32 hex chars) + */ + traceId: z.string() + .regex(/^[0-9a-f]{32}$/) + .describe('Trace ID (32 hex chars)'), + + /** + * Span ID (64-bit identifier, 16 hex chars) + */ + spanId: z.string() + .regex(/^[0-9a-f]{16}$/) + .describe('Span ID (16 hex chars)'), + + /** + * Trace flags (8-bit) + */ + traceFlags: TraceFlagsSchema.optional().default(1), + + /** + * Trace state (vendor-specific) + */ + traceState: TraceStateSchema.optional(), + + /** + * Parent span ID + */ + parentSpanId: z.string() + .regex(/^[0-9a-f]{16}$/) + .optional() + .describe('Parent span ID (16 hex chars)'), + + /** + * Is sampled + */ + sampled: z.boolean().optional().default(true), + + /** + * Remote context (from incoming request) + */ + remote: z.boolean().optional().default(false), +}).describe('Trace context (W3C Trace Context)'); + +export type TraceContext = z.infer; + +/** + * Span Kind Enum + * OpenTelemetry span kinds + */ +export const SpanKind = z.enum([ + 'internal', // Internal operation + 'server', // Server-side request handling + 'client', // Client-side request + 'producer', // Message producer + 'consumer', // Message consumer +]).describe('Span kind'); + +export type SpanKind = z.infer; + +/** + * Span Status Enum + * OpenTelemetry span status + */ +export const SpanStatus = z.enum([ + 'unset', // Default status + 'ok', // Successful operation + 'error', // Error occurred +]).describe('Span status'); + +export type SpanStatus = z.infer; + +/** + * Span Attribute Value Schema + */ +export const SpanAttributeValueSchema = z.union([ + z.string(), + z.number(), + z.boolean(), + z.array(z.string()), + z.array(z.number()), + z.array(z.boolean()), +]).describe('Span attribute value'); + +export type SpanAttributeValue = z.infer; + +/** + * Span Attributes Schema + * OpenTelemetry semantic conventions + */ +export const SpanAttributesSchema = z.record(SpanAttributeValueSchema).describe('Span attributes'); + +export type SpanAttributes = z.infer; + +/** + * Span Event Schema + */ +export const SpanEventSchema = z.object({ + /** + * Event name + */ + name: z.string().describe('Event name'), + + /** + * Event timestamp (ISO 8601) + */ + timestamp: z.string().datetime().describe('Event timestamp'), + + /** + * Event attributes + */ + attributes: SpanAttributesSchema.optional().describe('Event attributes'), +}).describe('Span event'); + +export type SpanEvent = z.infer; + +/** + * Span Link Schema + * Links to other spans + */ +export const SpanLinkSchema = z.object({ + /** + * Linked trace context + */ + context: TraceContextSchema.describe('Linked trace context'), + + /** + * Link attributes + */ + attributes: SpanAttributesSchema.optional().describe('Link attributes'), +}).describe('Span link'); + +export type SpanLink = z.infer; + +/** + * Span Schema + * OpenTelemetry span representation + */ +export const SpanSchema = z.object({ + /** + * Trace context + */ + context: TraceContextSchema.describe('Trace context'), + + /** + * Span name + */ + name: z.string().describe('Span name'), + + /** + * Span kind + */ + kind: SpanKind.optional().default('internal'), + + /** + * Start time (ISO 8601) + */ + startTime: z.string().datetime().describe('Span start time'), + + /** + * End time (ISO 8601) + */ + endTime: z.string().datetime().optional().describe('Span end time'), + + /** + * Duration in milliseconds + */ + duration: z.number().nonnegative().optional().describe('Duration in milliseconds'), + + /** + * Span status + */ + status: z.object({ + code: SpanStatus.describe('Status code'), + message: z.string().optional().describe('Status message'), + }).optional(), + + /** + * Span attributes + */ + attributes: SpanAttributesSchema.optional().default({}), + + /** + * Span events + */ + events: z.array(SpanEventSchema).optional().default([]), + + /** + * Span links + */ + links: z.array(SpanLinkSchema).optional().default([]), + + /** + * Resource attributes + */ + resource: SpanAttributesSchema.optional().describe('Resource attributes'), + + /** + * Instrumentation library + */ + instrumentationLibrary: z.object({ + name: z.string().describe('Library name'), + version: z.string().optional().describe('Library version'), + }).optional(), +}).describe('OpenTelemetry span'); + +export type Span = z.infer; + +/** + * Sampling Decision Enum + */ +export const SamplingDecision = z.enum([ + 'drop', // Do not record or export + 'record_only', // Record but do not export + 'record_and_sample', // Record and export +]).describe('Sampling decision'); + +export type SamplingDecision = z.infer; + +/** + * Sampling Strategy Type Enum + */ +export const SamplingStrategyType = z.enum([ + 'always_on', // Always sample + 'always_off', // Never sample + 'trace_id_ratio', // Sample based on trace ID ratio + 'rate_limiting', // Rate-limited sampling + 'parent_based', // Respect parent span sampling decision + 'probability', // Probability-based sampling + 'composite', // Combine multiple strategies + 'custom', // Custom sampling logic +]).describe('Sampling strategy type'); + +export type SamplingStrategyType = z.infer; + +/** + * Trace Sampling Configuration Schema + */ +export const TraceSamplingConfigSchema = z.object({ + /** + * Sampling strategy type + */ + 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'), + + /** + * Parent-based configuration + */ + parentBased: z.object({ + /** + * Sampler to use when parent is sampled + */ + whenParentSampled: SamplingStrategyType.optional().default('always_on'), + + /** + * Sampler to use when parent is not sampled + */ + whenParentNotSampled: SamplingStrategyType.optional().default('always_off'), + + /** + * Sampler to use when there is no parent (root span) + */ + root: SamplingStrategyType.optional().default('trace_id_ratio'), + + /** + * Root sampler ratio + */ + rootRatio: z.number().min(0).max(1).optional().default(0.1), + }).optional(), + + /** + * Composite sampling (multiple strategies) + */ + composite: z.array(z.object({ + strategy: SamplingStrategyType.describe('Strategy type'), + ratio: z.number().min(0).max(1).optional(), + condition: z.record(z.any()).optional().describe('Condition for this strategy'), + })).optional(), + + /** + * Sampling rules + */ + rules: z.array(z.object({ + /** + * Rule name + */ + name: z.string().describe('Rule name'), + + /** + * Match condition + */ + match: z.object({ + /** + * Service name pattern + */ + service: z.string().optional(), + + /** + * Span name pattern (regex) + */ + spanName: z.string().optional(), + + /** + * Attribute filters + */ + attributes: z.record(z.any()).optional(), + }).optional(), + + /** + * Sampling decision for matching spans + */ + decision: SamplingDecision.describe('Sampling decision'), + + /** + * Sample rate for this rule + */ + rate: z.number().min(0).max(1).optional(), + })).optional().default([]), + + /** + * Custom sampler ID (for custom strategy) + */ + customSamplerId: z.string().optional().describe('Custom sampler identifier'), +}).describe('Trace sampling configuration'); + +export type TraceSamplingConfig = z.infer; + +/** + * Trace Context Propagation Format Enum + */ +export const TracePropagationFormat = z.enum([ + 'w3c', // W3C Trace Context + 'b3', // Zipkin B3 (single header) + 'b3_multi', // Zipkin B3 (multi header) + 'jaeger', // Jaeger propagation + 'xray', // AWS X-Ray + 'ottrace', // OpenTracing + 'custom', // Custom format +]).describe('Trace propagation format'); + +export type TracePropagationFormat = z.infer; + +/** + * Trace Context Propagation Schema + */ +export const TraceContextPropagationSchema = z.object({ + /** + * Propagation formats (in priority order) + */ + formats: z.array(TracePropagationFormat).optional().default(['w3c']), + + /** + * Extract context from incoming requests + */ + extract: z.boolean().optional().default(true), + + /** + * Inject context into outgoing requests + */ + inject: z.boolean().optional().default(true), + + /** + * Custom header mappings + */ + headers: z.object({ + /** + * Trace ID header name + */ + traceId: z.string().optional(), + + /** + * Span ID header name + */ + spanId: z.string().optional(), + + /** + * Trace flags header name + */ + traceFlags: z.string().optional(), + + /** + * Trace state header name + */ + traceState: z.string().optional(), + }).optional(), + + /** + * Baggage propagation + */ + baggage: z.object({ + /** + * Enable baggage propagation + */ + enabled: z.boolean().optional().default(true), + + /** + * Maximum baggage size in bytes + */ + maxSize: z.number().int().positive().optional().default(8192), + + /** + * Allowed baggage keys (whitelist) + */ + allowedKeys: z.array(z.string()).optional(), + }).optional(), +}).describe('Trace context propagation'); + +export type TraceContextPropagation = z.infer; + +/** + * OpenTelemetry Exporter Type Enum + */ +export const OtelExporterType = z.enum([ + 'otlp_http', // OTLP over HTTP + 'otlp_grpc', // OTLP over gRPC + 'jaeger', // Jaeger + 'zipkin', // Zipkin + 'console', // Console (for debugging) + 'datadog', // Datadog + 'honeycomb', // Honeycomb + 'lightstep', // Lightstep + 'newrelic', // New Relic + 'custom', // Custom exporter +]).describe('OpenTelemetry exporter type'); + +export type OtelExporterType = z.infer; + +/** + * OpenTelemetry Compatibility Schema + */ +export const OpenTelemetryCompatibilitySchema = z.object({ + /** + * OpenTelemetry SDK version + */ + sdkVersion: z.string().optional().describe('OTel SDK version'), + + /** + * Exporter configuration + */ + exporter: z.object({ + /** + * Exporter type + */ + type: OtelExporterType.describe('Exporter type'), + + /** + * Endpoint URL + */ + endpoint: z.string().url().optional().describe('Exporter endpoint'), + + /** + * Protocol version + */ + protocol: z.string().optional().describe('Protocol version'), + + /** + * Headers + */ + headers: z.record(z.string()).optional().describe('HTTP headers'), + + /** + * Timeout in milliseconds + */ + timeout: z.number().int().positive().optional().default(10000), + + /** + * Compression + */ + compression: z.enum(['none', 'gzip']).optional().default('none'), + + /** + * Batch configuration + */ + batch: z.object({ + /** + * Maximum batch size + */ + maxBatchSize: z.number().int().positive().optional().default(512), + + /** + * Maximum queue size + */ + maxQueueSize: z.number().int().positive().optional().default(2048), + + /** + * Export timeout in milliseconds + */ + exportTimeout: z.number().int().positive().optional().default(30000), + + /** + * Scheduled delay in milliseconds + */ + scheduledDelay: z.number().int().positive().optional().default(5000), + }).optional(), + }).describe('Exporter configuration'), + + /** + * Resource attributes (service identification) + */ + resource: z.object({ + /** + * Service name + */ + serviceName: z.string().describe('Service name'), + + /** + * Service version + */ + serviceVersion: z.string().optional().describe('Service version'), + + /** + * Service instance ID + */ + serviceInstanceId: z.string().optional().describe('Service instance ID'), + + /** + * Service namespace + */ + serviceNamespace: z.string().optional().describe('Service namespace'), + + /** + * Deployment environment + */ + deploymentEnvironment: z.string().optional().describe('Deployment environment'), + + /** + * Additional resource attributes + */ + attributes: SpanAttributesSchema.optional().describe('Additional resource attributes'), + }).describe('Resource attributes'), + + /** + * Instrumentation configuration + */ + instrumentation: z.object({ + /** + * Auto-instrumentation enabled + */ + autoInstrumentation: z.boolean().optional().default(true), + + /** + * Instrumentation libraries to enable + */ + libraries: z.array(z.string()).optional().describe('Enabled libraries'), + + /** + * Instrumentation libraries to disable + */ + disabledLibraries: z.array(z.string()).optional().describe('Disabled libraries'), + }).optional(), + + /** + * Semantic conventions version + */ + semanticConventionsVersion: z.string().optional().describe('Semantic conventions version'), +}).describe('OpenTelemetry compatibility configuration'); + +export type OpenTelemetryCompatibility = z.infer; + +/** + * Tracing Configuration Schema + */ +export const TracingConfigSchema = z.object({ + /** + * Configuration name + */ + name: z.string() + .regex(/^[a-z_][a-z0-9_]*$/) + .max(64) + .describe('Configuration name (snake_case, max 64 chars)'), + + /** + * Display label + */ + label: z.string().describe('Display label'), + + /** + * Enable tracing + */ + enabled: z.boolean().optional().default(true), + + /** + * Sampling configuration + */ + sampling: TraceSamplingConfigSchema.optional().default({ type: 'always_on' }), + + /** + * Context propagation + */ + propagation: TraceContextPropagationSchema.optional().default({ formats: ['w3c'] }), + + /** + * OpenTelemetry configuration + */ + openTelemetry: OpenTelemetryCompatibilitySchema.optional(), + + /** + * Span limits + */ + spanLimits: z.object({ + /** + * Maximum number of attributes per span + */ + maxAttributes: z.number().int().positive().optional().default(128), + + /** + * Maximum number of events per span + */ + maxEvents: z.number().int().positive().optional().default(128), + + /** + * Maximum number of links per span + */ + maxLinks: z.number().int().positive().optional().default(128), + + /** + * Maximum attribute value length + */ + maxAttributeValueLength: z.number().int().positive().optional().default(4096), + }).optional(), + + /** + * Trace ID generator + */ + traceIdGenerator: z.enum(['random', 'uuid', 'custom']).optional().default('random'), + + /** + * Custom trace ID generator ID + */ + customTraceIdGeneratorId: z.string().optional().describe('Custom generator identifier'), + + /** + * Performance configuration + */ + performance: z.object({ + /** + * Async span export + */ + asyncExport: z.boolean().optional().default(true), + + /** + * Background export interval in milliseconds + */ + exportInterval: z.number().int().positive().optional().default(5000), + }).optional(), +}).describe('Tracing configuration'); + +export type TracingConfig = z.infer;