Skip to content

Commit 402eed8

Browse files
authored
Merge pull request #21 from barbacane-dev/feat/observability-middleware-plugin
Remove dead x-barbacane-observability extension that was parsed but never used at runtime Implement observability as a proper middleware plugin with SLO monitoring, detailed logging, and latency histograms Add --trace-sampling CLI flag for global trace sampling control (0.0-1.0)
2 parents db67790 + 5283faf commit 402eed8

20 files changed

Lines changed: 530 additions & 281 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6262
- Type-aware index response separating OpenAPI and AsyncAPI specs
6363
- Internal `x-barbacane-*` extensions stripped from served specs
6464

65+
#### Plugins
66+
- New `observability` middleware plugin for per-operation observability:
67+
- Latency SLO monitoring with `latency_slo_ms` config
68+
- Detailed request/response logging with `detailed_request_logs` and `detailed_response_logs`
69+
- Custom latency histogram emission with `emit_latency_histogram`
70+
- Emits `barbacane_plugin_observability_slo_violation` counter when SLO exceeded
71+
6572
#### Other
6673
- HTTP/2 support with automatic protocol detection via ALPN
6774
- API lifecycle support with `deprecated` flag and `x-sunset` extension (RFC 8594)
6875
- `Deprecation` and `Sunset` response headers for deprecated routes
6976
- Fixture-based test specs for comprehensive integration testing
70-
- SLO violation metrics (`barbacane_slo_violation_total`)
7177
- Deprecation metrics (`barbacane_deprecated_route_requests_total`)
7278

7379
### Changed
@@ -79,6 +85,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7985
- CORS plugin now includes JSON Schema (`config-schema.json`) for UI configuration
8086
- Plugin deletion errors now display user-friendly messages in the UI
8187

88+
### Removed
89+
- `x-barbacane-observability` extension (dead code - was parsed but never used at runtime)
90+
- Per-operation observability should be achieved via the middleware plugin system
91+
- Global trace sampling remains configurable via `--trace-sampling` CLI flag
92+
8293
## [0.1.0] - 2026-01-28
8394

8495
### Added

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ members = [
1515
exclude = [
1616
"plugins/mock",
1717
"plugins/lambda",
18+
"plugins/observability",
1819
]
1920
resolver = "2"
2021

crates/barbacane-compiler/src/manifest.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,6 @@ plugins:
470470
name: "rate-limit".to_string(),
471471
config: serde_json::json!({}),
472472
}],
473-
observability: Default::default(),
474473
extensions: BTreeMap::new(),
475474
operations: vec![
476475
Operation {
@@ -484,7 +483,6 @@ plugins:
484483
config: serde_json::json!({}),
485484
}),
486485
middlewares: None,
487-
observability: None,
488486
deprecated: false,
489487
sunset: None,
490488
extensions: BTreeMap::new(),
@@ -505,7 +503,6 @@ plugins:
505503
name: "jwt-auth@1.0.0".to_string(),
506504
config: serde_json::json!({}),
507505
}]),
508-
observability: None,
509506
deprecated: false,
510507
sunset: None,
511508
extensions: BTreeMap::new(),
@@ -538,7 +535,6 @@ plugins:
538535
title: "Test".to_string(),
539536
api_version: "1.0.0".to_string(),
540537
global_middlewares: vec![],
541-
observability: Default::default(),
542538
extensions: BTreeMap::new(),
543539
operations: vec![Operation {
544540
path: "/health".to_string(),
@@ -551,7 +547,6 @@ plugins:
551547
config: serde_json::json!({}),
552548
}),
553549
middlewares: None,
554-
observability: None,
555550
deprecated: false,
556551
sunset: None,
557552
extensions: BTreeMap::new(),
@@ -583,7 +578,6 @@ plugins:
583578
title: "Test".to_string(),
584579
api_version: "1.0.0".to_string(),
585580
global_middlewares: vec![],
586-
observability: Default::default(),
587581
extensions: BTreeMap::new(),
588582
operations: vec![Operation {
589583
path: "/proxy".to_string(),
@@ -596,7 +590,6 @@ plugins:
596590
config: serde_json::json!({}),
597591
}),
598592
middlewares: None,
599-
observability: None,
600593
deprecated: false,
601594
sunset: None,
602595
extensions: BTreeMap::new(),

crates/barbacane-spec-parser/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ pub mod parser;
1010
pub use error::ParseError;
1111
pub use model::{
1212
ApiSpec, AsyncAction, Channel, ContentSchema, DispatchConfig, Message, MiddlewareConfig,
13-
ObservabilityConfig, Operation, Parameter, RequestBody, SpecFormat,
13+
Operation, Parameter, RequestBody, SpecFormat,
1414
};
1515
pub use parser::{parse_spec, parse_spec_file};

crates/barbacane-spec-parser/src/model.rs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ pub struct ApiSpec {
1818
pub operations: Vec<Operation>,
1919
/// Global middlewares from root-level `x-barbacane-middlewares`.
2020
pub global_middlewares: Vec<MiddlewareConfig>,
21-
/// Global observability config from root-level `x-barbacane-observability`.
22-
#[serde(default)]
23-
pub observability: ObservabilityConfig,
2421
/// Raw `x-barbacane-*` extensions at root level.
2522
pub extensions: BTreeMap<String, serde_json::Value>,
2623
}
@@ -50,9 +47,6 @@ pub struct Operation {
5047
pub dispatch: Option<DispatchConfig>,
5148
/// Operation-level middlewares (replaces global chain if present).
5249
pub middlewares: Option<Vec<MiddlewareConfig>>,
53-
/// Operation-level observability config (overrides global).
54-
#[serde(default)]
55-
pub observability: Option<ObservabilityConfig>,
5650
/// Whether this operation is deprecated (OpenAPI `deprecated` field).
5751
#[serde(default)]
5852
pub deprecated: bool,
@@ -118,23 +112,6 @@ pub struct ContentSchema {
118112
pub schema: Option<serde_json::Value>,
119113
}
120114

121-
/// Observability configuration from `x-barbacane-observability`.
122-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
123-
pub struct ObservabilityConfig {
124-
/// Override trace sampling rate (0.0 to 1.0).
125-
#[serde(default)]
126-
pub trace_sampling: Option<f64>,
127-
128-
/// Enable detailed validation failure logging.
129-
#[serde(default)]
130-
pub detailed_validation_logs: Option<bool>,
131-
132-
/// Latency SLO threshold in milliseconds.
133-
/// Emit `barbacane_slo_violation_total` metric when exceeded.
134-
#[serde(default)]
135-
pub latency_slo_ms: Option<u64>,
136-
}
137-
138115
/// AsyncAPI message definition.
139116
#[derive(Debug, Clone, Serialize, Deserialize)]
140117
pub struct Message {

crates/barbacane-spec-parser/src/parser.rs

Lines changed: 2 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use serde_json::Value;
44

55
use crate::error::ParseError;
66
use crate::model::{
7-
ApiSpec, ContentSchema, DispatchConfig, Message, MiddlewareConfig, ObservabilityConfig,
8-
Operation, Parameter, RequestBody, SpecFormat,
7+
ApiSpec, ContentSchema, DispatchConfig, Message, MiddlewareConfig, Operation, Parameter,
8+
RequestBody, SpecFormat,
99
};
1010

1111
/// HTTP methods we recognize in OpenAPI paths.
@@ -50,9 +50,6 @@ pub fn parse_spec(input: &str) -> Result<ApiSpec, ParseError> {
5050
// Extract global middlewares
5151
let global_middlewares = extract_middlewares(root_obj);
5252

53-
// Extract global observability config
54-
let observability = extract_observability(root_obj);
55-
5653
// Parse operations based on format
5754
let operations = match format {
5855
SpecFormat::OpenApi => parse_openapi_paths(root_obj)?,
@@ -67,7 +64,6 @@ pub fn parse_spec(input: &str) -> Result<ApiSpec, ParseError> {
6764
api_version,
6865
operations,
6966
global_middlewares,
70-
observability,
7167
extensions,
7268
})
7369
}
@@ -134,19 +130,6 @@ fn extract_dispatch(obj: &serde_json::Map<String, Value>) -> Option<DispatchConf
134130
.and_then(|v| serde_json::from_value(v.clone()).ok())
135131
}
136132

137-
/// Extract x-barbacane-observability from an object.
138-
fn extract_observability(obj: &serde_json::Map<String, Value>) -> ObservabilityConfig {
139-
obj.get("x-barbacane-observability")
140-
.and_then(|v| serde_json::from_value(v.clone()).ok())
141-
.unwrap_or_default()
142-
}
143-
144-
/// Extract optional x-barbacane-observability from an object.
145-
fn extract_observability_opt(obj: &serde_json::Map<String, Value>) -> Option<ObservabilityConfig> {
146-
obj.get("x-barbacane-observability")
147-
.and_then(|v| serde_json::from_value(v.clone()).ok())
148-
}
149-
150133
/// Parse OpenAPI 3.x paths into operations.
151134
fn parse_openapi_paths(
152135
root: &serde_json::Map<String, Value>,
@@ -195,8 +178,6 @@ fn parse_openapi_paths(
195178
None
196179
};
197180

198-
let observability = extract_observability_opt(op_obj);
199-
200181
// Extract deprecated flag (standard OpenAPI field)
201182
let deprecated = op_obj
202183
.get("deprecated")
@@ -219,7 +200,6 @@ fn parse_openapi_paths(
219200
request_body,
220201
dispatch,
221202
middlewares,
222-
observability,
223203
deprecated,
224204
sunset,
225205
extensions,
@@ -375,9 +355,6 @@ fn parse_asyncapi_channels(
375355
None
376356
};
377357

378-
// Extract observability
379-
let observability = extract_observability_opt(op_obj);
380-
381358
// Extract deprecated and sunset
382359
let deprecated = op_obj
383360
.get("deprecated")
@@ -399,7 +376,6 @@ fn parse_asyncapi_channels(
399376
request_body,
400377
dispatch,
401378
middlewares,
402-
observability,
403379
deprecated,
404380
sunset,
405381
extensions,
@@ -822,56 +798,6 @@ paths:
822798
assert!(op.extensions.contains_key("x-barbacane-cache"));
823799
}
824800

825-
#[test]
826-
fn parse_observability_config() {
827-
let yaml = r#"
828-
openapi: "3.1.0"
829-
info:
830-
title: Test API
831-
version: "1.0.0"
832-
x-barbacane-observability:
833-
trace_sampling: 0.1
834-
detailed_validation_logs: true
835-
latency_slo_ms: 50
836-
paths:
837-
/fast:
838-
get:
839-
x-barbacane-dispatch:
840-
name: mock
841-
x-barbacane-observability:
842-
trace_sampling: 1.0
843-
latency_slo_ms: 10
844-
/slow:
845-
get:
846-
x-barbacane-dispatch:
847-
name: mock
848-
"#;
849-
let spec = parse_spec(yaml).unwrap();
850-
851-
// Check global observability config
852-
assert_eq!(spec.observability.trace_sampling, Some(0.1));
853-
assert_eq!(spec.observability.detailed_validation_logs, Some(true));
854-
assert_eq!(spec.observability.latency_slo_ms, Some(50));
855-
856-
// Check operation-level override
857-
let fast_op = spec
858-
.operations
859-
.iter()
860-
.find(|op| op.path == "/fast")
861-
.unwrap();
862-
let fast_obs = fast_op.observability.as_ref().unwrap();
863-
assert_eq!(fast_obs.trace_sampling, Some(1.0));
864-
assert_eq!(fast_obs.latency_slo_ms, Some(10));
865-
866-
// Check operation without override
867-
let slow_op = spec
868-
.operations
869-
.iter()
870-
.find(|op| op.path == "/slow")
871-
.unwrap();
872-
assert!(slow_op.observability.is_none());
873-
}
874-
875801
#[test]
876802
fn parse_request_body() {
877803
let yaml = r#"
@@ -1233,40 +1159,4 @@ channels: {}
12331159
let result = parse_spec(yaml);
12341160
assert!(matches!(result, Err(ParseError::SchemaError(_))));
12351161
}
1236-
1237-
#[test]
1238-
fn parse_asyncapi_observability() {
1239-
let yaml = r#"
1240-
asyncapi: "3.0.0"
1241-
info:
1242-
title: Observable API
1243-
version: "1.0.0"
1244-
x-barbacane-observability:
1245-
trace_sampling: 0.5
1246-
channels:
1247-
events:
1248-
address: events
1249-
messages:
1250-
Event:
1251-
payload:
1252-
type: object
1253-
operations:
1254-
handleEvent:
1255-
action: receive
1256-
channel:
1257-
$ref: '#/channels/events'
1258-
x-barbacane-observability:
1259-
trace_sampling: 1.0
1260-
x-barbacane-dispatch:
1261-
name: kafka
1262-
"#;
1263-
let spec = parse_spec(yaml).unwrap();
1264-
1265-
// Global observability
1266-
assert_eq!(spec.observability.trace_sampling, Some(0.5));
1267-
1268-
// Operation-level override
1269-
let op = &spec.operations[0];
1270-
assert_eq!(op.observability.as_ref().unwrap().trace_sampling, Some(1.0));
1271-
}
12721162
}

crates/barbacane-telemetry/src/config.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
//! Telemetry configuration.
22
3-
use std::time::Duration;
4-
53
/// Log output format.
64
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
75
pub enum LogFormat {
@@ -136,28 +134,3 @@ impl TelemetryConfig {
136134
self
137135
}
138136
}
139-
140-
/// Per-operation observability configuration from `x-barbacane-observability`.
141-
#[derive(Debug, Clone, Default)]
142-
pub struct ObservabilityConfig {
143-
/// Override trace sampling rate for this operation (0.0 to 1.0).
144-
pub trace_sampling: Option<f64>,
145-
146-
/// Enable detailed validation failure logging.
147-
pub detailed_validation_logs: Option<bool>,
148-
149-
/// Latency SLO threshold. Emit metric when exceeded.
150-
pub latency_slo: Option<Duration>,
151-
}
152-
153-
impl ObservabilityConfig {
154-
/// Merge with global config, returning effective sampling rate.
155-
pub fn effective_trace_sampling(&self, global: f64) -> f64 {
156-
self.trace_sampling.unwrap_or(global)
157-
}
158-
159-
/// Check if detailed validation logs are enabled.
160-
pub fn should_log_validation_details(&self) -> bool {
161-
self.detailed_validation_logs.unwrap_or(false)
162-
}
163-
}

crates/barbacane-telemetry/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub mod metrics;
2525
pub mod prometheus;
2626
pub mod tracing;
2727

28-
pub use config::{LogFormat, ObservabilityConfig, OtlpProtocol, TelemetryConfig};
28+
pub use config::{LogFormat, OtlpProtocol, TelemetryConfig};
2929
pub use logging::events;
3030
pub use metrics::MetricsRegistry;
3131
pub use prometheus::PROMETHEUS_CONTENT_TYPE;

0 commit comments

Comments
 (0)