Skip to content

Commit 487d6f7

Browse files
authored
Add optional opentelemetry file export tracing (#723)
Add opt-in functionality (with the `trace` feature) that writes OpenTelemetry File Export tracing information during build and compile. The tracing data includes buildpack attributes (like buildpack id, name), spans for build and detect, events for `build-success`, `detect-pass`, `detect-fail`, and also sets errors and span status in the case of errors.
1 parent 6cd51ce commit 487d6f7

File tree

14 files changed

+434
-27
lines changed

14 files changed

+434
-27
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
## [Unreleased]
1111

12+
### Added
13+
14+
- `libcnb`:
15+
- An optional `trace` feature has been added that emits OpenTelemetry tracing
16+
data to a [File Export](https://opentelemetry.io/docs/specs/otel/protocol/file-exporter/). ([#723](https://github.com/heroku/libcnb.rs/pull/723))
1217

1318
## [0.16.0] - 2023-11-17
1419

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ members = [
1515
"test-buildpacks/readonly-layer-files",
1616
"test-buildpacks/sbom",
1717
"test-buildpacks/store",
18+
"test-buildpacks/tracing",
1819
]
1920

2021
[workspace.package]

clippy.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
allow-unwrap-in-tests = true
2+
doc-valid-idents = ["OpenTelemetry", ".."]

libcnb/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,23 @@ include = ["src/**/*", "LICENSE", "README.md"]
1414
[lints]
1515
workspace = true
1616

17+
[features]
18+
trace = ["dep:opentelemetry", "dep:opentelemetry_sdk", "dep:opentelemetry-stdout"]
19+
1720
[dependencies]
1821
anyhow = { version = "1.0.75", optional = true }
1922
cyclonedx-bom = { version = "0.4.3", optional = true }
2023
libcnb-common.workspace = true
2124
libcnb-data.workspace = true
2225
libcnb-proc-macros.workspace = true
26+
opentelemetry = { version = "0.21.0", optional = true }
27+
opentelemetry_sdk = { version = "0.21.0", optional = true }
28+
opentelemetry-stdout = { version = "0.2.0", optional = true, features = ["trace"] }
2329
serde = { version = "1.0.192", features = ["derive"] }
2430
thiserror = "1.0.50"
2531
toml.workspace = true
2632

2733
[dev-dependencies]
2834
fastrand = "2.0.1"
2935
tempfile = "3.8.1"
36+
serde_json = "1.0.108"

libcnb/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ mod error;
1818
mod exit_code;
1919
mod platform;
2020
mod runtime;
21+
#[cfg(feature = "trace")]
22+
mod tracing;
2123
mod util;
2224

2325
pub use buildpack::Buildpack;
@@ -27,6 +29,9 @@ pub use libcnb_common::toml_file::*;
2729
pub use platform::*;
2830
pub use runtime::*;
2931

32+
#[cfg(all(test, not(feature = "trace")))]
33+
use serde_json as _;
34+
3035
/// Provides types for CNB data formats. Is a re-export of the `libcnb-data` crate.
3136
#[doc(inline)]
3237
pub use libcnb_data as data;

libcnb/src/runtime.rs

Lines changed: 110 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ use crate::detect::{DetectContext, InnerDetectResult};
55
use crate::error::Error;
66
use crate::platform::Platform;
77
use crate::sbom::cnb_sbom_path;
8+
#[cfg(feature = "trace")]
9+
use crate::tracing::start_trace;
810
use crate::util::is_not_found_error_kind;
911
use crate::{exit_code, TomlFileError, LIBCNB_SUPPORTED_BUILDPACK_API};
1012
use libcnb_common::toml_file::{read_toml_file, write_toml_file};
13+
use libcnb_data::buildpack::ComponentBuildpackDescriptor;
1114
use libcnb_data::store::Store;
1215
use serde::de::DeserializeOwned;
1316
use serde::Deserialize;
@@ -121,31 +124,63 @@ pub fn libcnb_runtime_detect<B: Buildpack>(
121124
) -> crate::Result<i32, B::Error> {
122125
let app_dir = env::current_dir().map_err(Error::CannotDetermineAppDirectory)?;
123126

127+
let buildpack_dir = read_buildpack_dir()?;
128+
129+
let buildpack_descriptor: ComponentBuildpackDescriptor<<B as Buildpack>::Metadata> =
130+
read_buildpack_descriptor()?;
131+
132+
#[cfg(feature = "trace")]
133+
let mut trace = start_trace(&buildpack_descriptor.buildpack, "detect");
134+
135+
let mut trace_error = |err: &dyn std::error::Error| {
136+
#[cfg(feature = "trace")]
137+
trace.set_error(err);
138+
};
124139
let stack_id: StackId = env::var("CNB_STACK_ID")
125140
.map_err(Error::CannotDetermineStackId)
126-
.and_then(|stack_id_string| stack_id_string.parse().map_err(Error::StackIdError))?;
127-
128-
let platform = B::Platform::from_path(&args.platform_dir_path)
129-
.map_err(Error::CannotCreatePlatformFromPath)?;
141+
.and_then(|stack_id_string| stack_id_string.parse().map_err(Error::StackIdError))
142+
.map_err(|err| {
143+
trace_error(&err);
144+
err
145+
})?;
146+
147+
let platform = B::Platform::from_path(&args.platform_dir_path).map_err(|inner_err| {
148+
let err = Error::CannotCreatePlatformFromPath(inner_err);
149+
trace_error(&err);
150+
err
151+
})?;
130152

131153
let build_plan_path = args.build_plan_path;
132154

133155
let detect_context = DetectContext {
134156
app_dir,
157+
buildpack_dir,
135158
stack_id,
136159
platform,
137-
buildpack_dir: read_buildpack_dir()?,
138-
buildpack_descriptor: read_buildpack_descriptor()?,
160+
buildpack_descriptor,
139161
};
140162

141-
match buildpack.detect(detect_context)?.0 {
142-
InnerDetectResult::Fail => Ok(exit_code::DETECT_DETECTION_FAILED),
163+
let detect_result = buildpack.detect(detect_context).map_err(|err| {
164+
trace_error(&err);
165+
err
166+
})?;
167+
168+
match detect_result.0 {
169+
InnerDetectResult::Fail => {
170+
#[cfg(feature = "trace")]
171+
trace.add_event("detect-failed");
172+
Ok(exit_code::DETECT_DETECTION_FAILED)
173+
}
143174
InnerDetectResult::Pass { build_plan } => {
144175
if let Some(build_plan) = build_plan {
145-
write_toml_file(&build_plan, build_plan_path)
146-
.map_err(Error::CannotWriteBuildPlan)?;
176+
write_toml_file(&build_plan, build_plan_path).map_err(|inner_err| {
177+
let err = Error::CannotWriteBuildPlan(inner_err);
178+
trace_error(&err);
179+
err
180+
})?;
147181
}
148-
182+
#[cfg(feature = "trace")]
183+
trace.add_event("detect-passed");
149184
Ok(exit_code::DETECT_DETECTION_PASSED)
150185
}
151186
}
@@ -163,31 +198,63 @@ pub fn libcnb_runtime_build<B: Buildpack>(
163198

164199
let app_dir = env::current_dir().map_err(Error::CannotDetermineAppDirectory)?;
165200

201+
let buildpack_dir = read_buildpack_dir()?;
202+
203+
let buildpack_descriptor: ComponentBuildpackDescriptor<<B as Buildpack>::Metadata> =
204+
read_buildpack_descriptor()?;
205+
206+
#[cfg(feature = "trace")]
207+
let mut trace = start_trace(&buildpack_descriptor.buildpack, "build");
208+
209+
let mut trace_error = |err: &dyn std::error::Error| {
210+
#[cfg(feature = "trace")]
211+
trace.set_error(err);
212+
};
213+
166214
let stack_id: StackId = env::var("CNB_STACK_ID")
167215
.map_err(Error::CannotDetermineStackId)
168-
.and_then(|stack_id_string| stack_id_string.parse().map_err(Error::StackIdError))?;
169-
170-
let platform = Platform::from_path(&args.platform_dir_path)
171-
.map_err(Error::CannotCreatePlatformFromPath)?;
216+
.and_then(|stack_id_string| stack_id_string.parse().map_err(Error::StackIdError))
217+
.map_err(|err| {
218+
trace_error(&err);
219+
err
220+
})?;
221+
222+
let platform = Platform::from_path(&args.platform_dir_path).map_err(|inner_err| {
223+
let err = Error::CannotCreatePlatformFromPath(inner_err);
224+
trace_error(&err);
225+
err
226+
})?;
172227

173-
let buildpack_plan =
174-
read_toml_file(&args.buildpack_plan_path).map_err(Error::CannotReadBuildpackPlan)?;
228+
let buildpack_plan = read_toml_file(&args.buildpack_plan_path).map_err(|inner_err| {
229+
let err = Error::CannotReadBuildpackPlan(inner_err);
230+
trace_error(&err);
231+
err
232+
})?;
175233

176234
let store = match read_toml_file::<Store>(layers_dir.join("store.toml")) {
177235
Err(TomlFileError::IoError(io_error)) if is_not_found_error_kind(&io_error) => Ok(None),
178236
other => other.map(Some),
179237
}
180-
.map_err(Error::CannotReadStore)?;
238+
.map_err(Error::CannotReadStore)
239+
.map_err(|err| {
240+
trace_error(&err);
241+
err
242+
})?;
181243

182-
let build_result = buildpack.build(BuildContext {
244+
let build_context = BuildContext {
183245
layers_dir: layers_dir.clone(),
184246
app_dir,
185247
stack_id,
186248
platform,
187249
buildpack_plan,
188-
buildpack_dir: read_buildpack_dir()?,
189-
buildpack_descriptor: read_buildpack_descriptor()?,
250+
buildpack_dir,
251+
buildpack_descriptor,
190252
store,
253+
};
254+
255+
let build_result = buildpack.build(build_context).map_err(|err| {
256+
trace_error(&err);
257+
err
191258
})?;
192259

193260
match build_result.0 {
@@ -198,31 +265,47 @@ pub fn libcnb_runtime_build<B: Buildpack>(
198265
launch_sboms,
199266
} => {
200267
if let Some(launch) = launch {
201-
write_toml_file(&launch, layers_dir.join("launch.toml"))
202-
.map_err(Error::CannotWriteLaunch)?;
268+
write_toml_file(&launch, layers_dir.join("launch.toml")).map_err(|inner_err| {
269+
let err = Error::CannotWriteLaunch(inner_err);
270+
trace_error(&err);
271+
err
272+
})?;
203273
};
204274

205275
if let Some(store) = store {
206-
write_toml_file(&store, layers_dir.join("store.toml"))
207-
.map_err(Error::CannotWriteStore)?;
276+
write_toml_file(&store, layers_dir.join("store.toml")).map_err(|inner_err| {
277+
let err = Error::CannotWriteStore(inner_err);
278+
trace_error(&err);
279+
err
280+
})?;
208281
};
209282

210283
for build_sbom in build_sboms {
211284
fs::write(
212285
cnb_sbom_path(&build_sbom.format, &layers_dir, "build"),
213286
&build_sbom.data,
214287
)
215-
.map_err(Error::CannotWriteBuildSbom)?;
288+
.map_err(Error::CannotWriteBuildSbom)
289+
.map_err(|err| {
290+
trace_error(&err);
291+
err
292+
})?;
216293
}
217294

218295
for launch_sbom in launch_sboms {
219296
fs::write(
220297
cnb_sbom_path(&launch_sbom.format, &layers_dir, "launch"),
221298
&launch_sbom.data,
222299
)
223-
.map_err(Error::CannotWriteLaunchSbom)?;
300+
.map_err(Error::CannotWriteLaunchSbom)
301+
.map_err(|err| {
302+
trace_error(&err);
303+
err
304+
})?;
224305
}
225306

307+
#[cfg(feature = "trace")]
308+
trace.add_event("build-success");
226309
Ok(exit_code::GENERIC_SUCCESS)
227310
}
228311
}

0 commit comments

Comments
 (0)