Skip to content

Commit 46ad226

Browse files
authored
feat: log coglet build info and SDK version at startup (#2829)
* feat: log coglet build info and SDK version at startup Print coglet version, git sha, dirty flag, build time, and debug/release mode to logs on boot. Also log cog SDK and Python versions. Expose git_sha and build_time in the /health-check response so running containers can be identified without log access. Add COGLET_GIT_DIRTY to build.rs and track .git/index for rebuild on staging changes. * refactor: rename 'cog' to 'python_sdk' in VersionInfo Align with naming convention: 'cog' is the suite, 'coglet' is the in-container server, 'python_sdk' is the SDK models are built on. * fix: clean:go now removes dist/go/, add build summary clean:go was removing ./cog (legacy path) but build:cog outputs to dist/go/*/cog, so 'mise clean && mise build' left a stale CLI binary. Also add a _build_summary task that prints artifact paths and versions after 'mise build'. * fix: update test assertion to match renamed python_sdk version field
1 parent 028b6fe commit 46ad226

6 files changed

Lines changed: 124 additions & 22 deletions

File tree

crates/coglet-python/build.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ fn main() {
2020
.unwrap_or_else(|| "unknown".to_string());
2121
println!("cargo:rustc-env=COGLET_GIT_SHA={git_sha}");
2222

23+
// Git dirty flag
24+
let git_dirty = Command::new("git")
25+
.args(["status", "--porcelain"])
26+
.output()
27+
.ok()
28+
.filter(|o| o.status.success())
29+
.map(|o| {
30+
if String::from_utf8_lossy(&o.stdout).trim().is_empty() {
31+
"false"
32+
} else {
33+
"true"
34+
}
35+
})
36+
.unwrap_or("unknown");
37+
println!("cargo:rustc-env=COGLET_GIT_DIRTY={git_dirty}");
38+
2339
// Build timestamp (UTC, ISO 8601)
2440
let build_time = Command::new("date")
2541
.args(["-u", "+%Y-%m-%dT%H:%M:%SZ"])
@@ -40,9 +56,10 @@ fn main() {
4056
.unwrap_or_else(|| "unknown".to_string());
4157
println!("cargo:rustc-env=COGLET_RUSTC_VERSION={rustc_version}");
4258

43-
// Rebuild if git HEAD changes
59+
// Rebuild if git HEAD changes or files are staged
4460
println!("cargo:rerun-if-changed=../../.git/HEAD");
4561
println!("cargo:rerun-if-changed=../../.git/refs");
62+
println!("cargo:rerun-if-changed=../../.git/index");
4663
}
4764

4865
/// Convert a semver version string to PEP 440 format.

crates/coglet-python/src/lib.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ pub struct BuildInfo {
4343
#[pyo3(get)]
4444
git_sha: String,
4545
#[pyo3(get)]
46+
dirty: bool,
47+
#[pyo3(get)]
4648
build_time: String,
4749
#[pyo3(get)]
4850
rustc_version: String,
@@ -53,8 +55,12 @@ pub struct BuildInfo {
5355
impl BuildInfo {
5456
fn __repr__(&self) -> String {
5557
format!(
56-
"BuildInfo(version='{}', git_sha='{}', build_time='{}', rustc_version='{}')",
57-
self.version, self.git_sha, self.build_time, self.rustc_version
58+
"BuildInfo(version='{}', git_sha='{}', dirty={}, build_time='{}', rustc_version='{}')",
59+
self.version,
60+
self.git_sha,
61+
if self.dirty { "True" } else { "False" },
62+
self.build_time,
63+
self.rustc_version
5864
)
5965
}
6066
}
@@ -64,10 +70,20 @@ impl BuildInfo {
6470
Self {
6571
version: env!("COGLET_PEP440_VERSION").to_string(),
6672
git_sha: env!("COGLET_GIT_SHA").to_string(),
73+
dirty: env!("COGLET_GIT_DIRTY") == "true",
6774
build_time: env!("COGLET_BUILD_TIME").to_string(),
6875
rustc_version: env!("COGLET_RUSTC_VERSION").to_string(),
6976
}
7077
}
78+
79+
/// Git SHA with optional `-dirty` suffix.
80+
fn sha_display(&self) -> String {
81+
if self.dirty {
82+
format!("{}-dirty", self.git_sha)
83+
} else {
84+
self.git_sha.clone()
85+
}
86+
}
7187
}
7288

7389
fn set_active() {
@@ -133,8 +149,10 @@ fn init_tracing(
133149
}
134150
}
135151

136-
fn detect_version(py: Python<'_>) -> VersionInfo {
137-
let mut version = VersionInfo::new();
152+
fn detect_version(py: Python<'_>, build: &BuildInfo) -> VersionInfo {
153+
let mut version = VersionInfo::new()
154+
.with_git_sha(build.sha_display())
155+
.with_build_time(build.build_time.clone());
138156

139157
if let Ok(sys) = py.import("sys")
140158
&& let Ok(py_version) = sys.getattr("version")
@@ -148,7 +166,7 @@ fn detect_version(py: Python<'_>) -> VersionInfo {
148166
&& let Ok(cog_version) = cog.getattr("__version__")
149167
&& let Ok(v) = cog_version.extract::<String>()
150168
{
151-
version = version.with_cog(v);
169+
version = version.with_python_sdk(v);
152170
}
153171

154172
version
@@ -280,7 +298,18 @@ fn serve_impl(
280298
let (setup_log_tx, setup_log_rx) = tokio::sync::mpsc::unbounded_channel();
281299
init_tracing(false, Some(setup_log_tx));
282300

283-
info!("coglet {}", env!("CARGO_PKG_VERSION"));
301+
let build = BuildInfo::new();
302+
info!(
303+
"coglet {} ({}, built {}{})",
304+
env!("CARGO_PKG_VERSION"),
305+
build.sha_display(),
306+
build.build_time,
307+
if cfg!(debug_assertions) {
308+
", debug"
309+
} else {
310+
""
311+
},
312+
);
284313

285314
let config = ServerConfig {
286315
host,
@@ -297,7 +326,12 @@ fn serve_impl(
297326
info!("await_explicit_shutdown: installed SIGTERM ignore handler");
298327
}
299328

300-
let version = detect_version(py);
329+
let version = detect_version(py, &build);
330+
info!(
331+
"python sdk {}",
332+
version.python_sdk.as_deref().unwrap_or("unknown")
333+
);
334+
info!("python {}", version.python.as_deref().unwrap_or("unknown"));
301335

302336
let Some(pred_ref) = predictor_ref else {
303337
info!("No predictor specified, serving health endpoints only");

crates/coglet-python/tests/test_coglet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ def test_returns_version_info(self, sync_predictor: Path):
336336
assert "version" in health
337337
assert "coglet" in health["version"]
338338
assert "python" in health["version"]
339-
assert "cog" in health["version"]
339+
assert "python_sdk" in health["version"]
340340

341341

342342
class TestSyncPredictor:

crates/coglet/src/snapshots/coglet__version__tests__version_full.snap

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ expression: info
44
---
55
{
66
"coglet": "0.1.0",
7-
"cog": "0.9.0",
7+
"git_sha": "abc1234-dirty",
8+
"build_time": "2026-03-12T18:00:00Z",
9+
"python_sdk": "0.9.0",
810
"python": "3.11.0"
911
}

crates/coglet/src/version.rs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ pub const COGLET_VERSION: &str = env!("CARGO_PKG_VERSION");
88
pub struct VersionInfo {
99
/// Coglet runtime version.
1010
pub coglet: &'static str,
11-
/// Cog Python SDK version (if available).
11+
/// Git SHA (with optional `-dirty` suffix).
1212
#[serde(skip_serializing_if = "Option::is_none")]
13-
pub cog: Option<String>,
13+
pub git_sha: Option<String>,
14+
/// Build timestamp (UTC, ISO 8601).
15+
#[serde(skip_serializing_if = "Option::is_none")]
16+
pub build_time: Option<String>,
17+
/// Python SDK version (if available).
18+
#[serde(skip_serializing_if = "Option::is_none")]
19+
pub python_sdk: Option<String>,
1420
/// Python version.
1521
#[serde(skip_serializing_if = "Option::is_none")]
1622
pub python: Option<String>,
@@ -20,7 +26,9 @@ impl Default for VersionInfo {
2026
fn default() -> Self {
2127
Self {
2228
coglet: COGLET_VERSION,
23-
cog: None,
29+
git_sha: None,
30+
build_time: None,
31+
python_sdk: None,
2432
python: None,
2533
}
2634
}
@@ -32,9 +40,21 @@ impl VersionInfo {
3240
Self::default()
3341
}
3442

35-
/// Set cog SDK version.
36-
pub fn with_cog(mut self, version: String) -> Self {
37-
self.cog = Some(version);
43+
/// Set git SHA (with optional `-dirty` suffix).
44+
pub fn with_git_sha(mut self, sha: String) -> Self {
45+
self.git_sha = Some(sha);
46+
self
47+
}
48+
49+
/// Set build timestamp.
50+
pub fn with_build_time(mut self, time: String) -> Self {
51+
self.build_time = Some(time);
52+
self
53+
}
54+
55+
/// Set Python SDK version.
56+
pub fn with_python_sdk(mut self, version: String) -> Self {
57+
self.python_sdk = Some(version);
3858
self
3959
}
4060

@@ -53,17 +73,21 @@ mod tests {
5373
fn version_info_has_coglet_version() {
5474
let info = VersionInfo::new();
5575
assert_eq!(info.coglet, COGLET_VERSION);
56-
assert!(info.cog.is_none());
76+
assert!(info.python_sdk.is_none());
5777
assert!(info.python.is_none());
5878
}
5979

6080
#[test]
6181
fn version_info_builder_pattern() {
6282
let info = VersionInfo::new()
63-
.with_cog("0.9.0".to_string())
83+
.with_git_sha("abc1234".to_string())
84+
.with_build_time("2026-03-12T18:00:00Z".to_string())
85+
.with_python_sdk("0.9.0".to_string())
6486
.with_python("3.11.0".to_string());
6587

66-
assert_eq!(info.cog, Some("0.9.0".to_string()));
88+
assert_eq!(info.git_sha, Some("abc1234".to_string()));
89+
assert_eq!(info.build_time, Some("2026-03-12T18:00:00Z".to_string()));
90+
assert_eq!(info.python_sdk, Some("0.9.0".to_string()));
6791
assert_eq!(info.python, Some("3.11.0".to_string()));
6892
}
6993

@@ -72,7 +96,9 @@ mod tests {
7296
// Only coglet when no optional fields set
7397
let info = VersionInfo {
7498
coglet: "0.1.0",
75-
cog: None,
99+
git_sha: None,
100+
build_time: None,
101+
python_sdk: None,
76102
python: None,
77103
};
78104
insta::assert_json_snapshot!("version_minimal", info);
@@ -82,7 +108,9 @@ mod tests {
82108
fn version_info_serializes_full() {
83109
let info = VersionInfo {
84110
coglet: "0.1.0",
85-
cog: Some("0.9.0".to_string()),
111+
git_sha: Some("abc1234-dirty".to_string()),
112+
build_time: Some("2026-03-12T18:00:00Z".to_string()),
113+
python_sdk: Some("0.9.0".to_string()),
86114
python: Some("3.11.0".to_string()),
87115
};
88116
insta::assert_json_snapshot!("version_full", info);

mise.toml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,29 @@ alias = "build:all"
9696
description = "Build all components"
9797
run = [
9898
{ tasks = ["build:cog", "build:coglet:wheel:linux-x64", "build:sdk"] },
99+
{ task = "_build_summary" },
99100
]
100101

102+
[tasks._build_summary]
103+
hide = true
104+
description = "Print build artifacts summary"
105+
run = """
106+
#!/usr/bin/env bash
107+
echo ""
108+
echo "=== Build Artifacts ==="
109+
if BINARY=$(ls dist/go/*/cog 2>/dev/null | head -1); then
110+
VERSION=$("$BINARY" --version 2>/dev/null || echo "unknown")
111+
echo " cli: $BINARY ($VERSION)"
112+
fi
113+
for whl in dist/coglet-*.whl; do
114+
[ -f "$whl" ] && echo " coglet: $whl"
115+
done
116+
for whl in dist/cog-*.whl; do
117+
[ -f "$whl" ] && echo " python-sdk: $whl"
118+
done
119+
echo ""
120+
"""
121+
101122
[tasks.install]
102123
depends = ["build:cog"]
103124
description = "Build and symlink cog CLI"
@@ -535,7 +556,7 @@ run = [
535556

536557
[tasks."clean:go"]
537558
description = "Clean Go build artifacts"
538-
run = "rm -f cog base-image"
559+
run = "rm -rf cog base-image dist/go"
539560

540561
[tasks."clean:rust"]
541562
description = "Clean Rust build artifacts"

0 commit comments

Comments
 (0)