Skip to content

Commit 54889a1

Browse files
committed
impl(cloud-run-o11y): observability demo
1 parent aaae947 commit 54889a1

File tree

13 files changed

+749
-0
lines changed

13 files changed

+749
-0
lines changed

.dockerignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# A .ignore file for Docker
16+
target/

Cargo.lock

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ default-members = [
6060
]
6161

6262
members = [
63+
"demos/cloud-run-o11y",
6364
"guide/samples",
6465
"src/auth",
6566
"src/bigquery",
@@ -373,9 +374,11 @@ http-body = { default-features = false, version = "1" }
373374
humantime = { default-features = false, version = "2" }
374375
hyper = { default-features = false, version = "1.6" }
375376
jsonwebtoken = { default-features = false, version = "10.2" }
377+
markdown = { default-features = false, version = "1.0" }
376378
opentelemetry = { default-features = false, version = "0.31" }
377379
opentelemetry-proto = { default-features = false, version = "0.31" }
378380
opentelemetry_sdk = { default-features = false, version = "0.31" }
381+
opentelemetry-http = { default-features = false, version = "0.31" }
379382
opentelemetry-otlp = { default-features = false, version = "0.31" }
380383
parse-size = { default-features = false, version = "1.1" }
381384
percent-encoding = { default-features = false, version = "2.3" }

demos/cloud-run-o11y/Cargo.toml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
[package]
16+
name = "demo-cloud-run-o11y"
17+
version = "0.0.0"
18+
publish = false
19+
description = "Use Rust on Cloud Run with observability."
20+
edition.workspace = true
21+
authors.workspace = true
22+
license.workspace = true
23+
repository.workspace = true
24+
keywords.workspace = true
25+
categories.workspace = true
26+
rust-version.workspace = true
27+
28+
[dependencies]
29+
anyhow.workspace = true
30+
axum = { workspace = true, features = ["http1", "tokio"] }
31+
chrono = { workspace = true, features = ["std"] }
32+
clap = { workspace = true, features = ["env", "std"] }
33+
google-cloud-aiplatform-v1 = { workspace = true, features = ["default-rustls-provider", "prediction-service"] }
34+
google-cloud-auth = { workspace = true, features = ["default", "idtoken"] }
35+
google-cloud-gax = { workspace = true }
36+
google-cloud-storage = { workspace = true, features = ["default"] }
37+
integration-tests-o11y = { path = "../../tests/o11y" }
38+
markdown.workspace = true
39+
opentelemetry = { workspace = true, features = ["trace"] }
40+
opentelemetry-http = { workspace = true }
41+
opentelemetry_sdk = { workspace = true }
42+
rand.workspace = true
43+
serde = { workspace = true }
44+
serde_json.workspace = true
45+
thiserror.workspace = true
46+
tokio = { workspace = true, features = ["full", "macros"] }
47+
tracing-opentelemetry = { workspace = true, default-features = true }
48+
tracing-serde = { workspace = true }
49+
tracing-subscriber = { workspace = true, features = ["json", "std"] }
50+
tracing.workspace = true
51+
uuid = { workspace = true, features = ["v4"] }
52+
53+
[lints]
54+
workspace = true

demos/cloud-run-o11y/Dockerfile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
FROM rust:1.93-slim-bookworm AS builder
16+
17+
WORKDIR /usr/src/workspace
18+
COPY . .
19+
RUN env RUSTFLAGS="--cfg google_cloud_unstable_tracing" cargo build -p demo-cloud-run-o11y --release
20+
21+
# Use a lightweight Debian image for the runtime
22+
FROM debian:bookworm-slim
23+
24+
# Install CA certificates needed for HTTPS/GCP API calls
25+
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
26+
27+
# Copy the compiled binary from the builder stage
28+
COPY --from=builder /usr/src/workspace/target/release/demo-cloud-run-o11y /usr/local/bin/demo-cloud-run-o11y
29+
30+
# Expose standard Cloud Run port
31+
EXPOSE 8080
32+
33+
# Run the application
34+
ENTRYPOINT ["/usr/local/bin/demo-cloud-run-o11y"]

demos/cloud-run-o11y/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Highlights the observability features in the Rust SDK
2+
3+
This directory contains a demo application demonstrating how to deploy Rust
4+
applications to Cloud Run and monitor them with Google Cloud AppHub.
5+
6+
## Building and Deploying
7+
8+
Because this application relies on other crates in the Rust workspace, you must
9+
build the Docker image from the root of the workspace.
10+
11+
1. Ensure you are authenticated with Google Cloud:
12+
13+
```bash
14+
gcloud auth login
15+
gcloud config set project YOUR_PROJECT_ID
16+
GOOGLE_CLOUD_PROJECT="$(gcloud config get project)"
17+
```
18+
19+
1. Create an Artifact Registry repository (if you don't already have one):
20+
21+
```bash
22+
gcloud artifacts repositories create cloud-run-apps \
23+
--repository-format=docker \
24+
--location=us-central1 \
25+
--description="Docker repository for Cloud Run apps"
26+
```
27+
28+
1. Grant Cloud Run permission to read from the repository (using the default
29+
Compute Engine service account):
30+
31+
```bash
32+
PROJECT_NUMBER=$(gcloud projects describe ${GOOGLE_CLOUD_PROJECT} --format="value(projectNumber)")
33+
gcloud artifacts repositories add-iam-policy-binding cloud-run-apps \
34+
--location=us-central1 \
35+
--member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
36+
--role="roles/artifactregistry.reader"
37+
```
38+
39+
1. Build the Docker image using Google Cloud Build (run from the workspace
40+
root):
41+
42+
```bash
43+
gcloud builds submit . --config demos/cloud-run-o11y/cloudbuild.yaml
44+
```
45+
46+
1. Deploy the built image to Cloud Run:
47+
48+
```bash
49+
gcloud run deploy cloud-run-o11y \
50+
--image us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/cloud-run-apps/demo-cloud-run-o11y \
51+
--allow-unauthenticated \
52+
--region us-central1 \
53+
--set-env-vars=GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT}
54+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
steps:
16+
- name: 'gcr.io/cloud-builders/docker'
17+
args: ['build', '-t', 'us-central1-docker.pkg.dev/${PROJECT_ID}/cloud-run-apps/demo-cloud-run-o11y', '-f', 'demos/cloud-run-o11y/Dockerfile', '.']
18+
automapSubstitutions: true
19+
20+
images:
21+
- 'us-central1-docker.pkg.dev/${PROJECT_ID}/cloud-run-apps/demo-cloud-run-o11y'
22+
23+
options:
24+
machineType: 'E2_HIGHCPU_32'

demos/cloud-run-o11y/src/args.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use clap::Parser;
16+
17+
/// Command-line arguments.
18+
#[derive(Clone, Debug, Parser)]
19+
#[command(version, about, long_about = super::DESCRIPTION)]
20+
pub struct Args {
21+
/// The default project name, if not found via resource discovery.
22+
#[arg(long, env = "GOOGLE_CLOUD_PROJECT", default_value = "")]
23+
pub project_id: String,
24+
25+
/// The default project name, if not found via resource discovery.
26+
#[arg(long, env = "K_SERVICE", default_value = "")]
27+
pub service_name: String,
28+
29+
/// The default port.
30+
#[arg(long, env = "PORT", default_value = "8080")]
31+
pub port: String,
32+
33+
/// Use the regional version of Vertex AI.
34+
#[arg(long)]
35+
pub regional: Option<String>,
36+
}

demos/cloud-run-o11y/src/error.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use axum::http::StatusCode;
16+
use axum::response::{IntoResponse, Response};
17+
use google_cloud_auth::build_errors::Error as BuildError;
18+
use google_cloud_gax::client_builder::Error as ClientBuilderError;
19+
use google_cloud_gax::error::Error;
20+
21+
#[derive(Debug, thiserror::Error)]
22+
pub enum AppError {
23+
#[error("the backend reported an error: {0}")]
24+
Backend(#[source] Error),
25+
#[error("the backend response had an unexpected format: {0}")]
26+
BadResponseFormat(String),
27+
#[error("there was a problem contacting the backend: {0}")]
28+
Request(#[source] Error),
29+
#[error("cannot initialize the service credentials: {0}")]
30+
Credentials(#[source] BuildError),
31+
#[error("cannot initialize a client: {0}")]
32+
Client(#[source] ClientBuilderError),
33+
}
34+
35+
impl From<Error> for AppError {
36+
fn from(value: Error) -> Self {
37+
if value.status().is_some() {
38+
Self::Backend(value)
39+
} else {
40+
Self::Request(value)
41+
}
42+
}
43+
}
44+
45+
impl From<BuildError> for AppError {
46+
fn from(value: BuildError) -> Self {
47+
Self::Credentials(value)
48+
}
49+
}
50+
51+
impl From<ClientBuilderError> for AppError {
52+
fn from(value: ClientBuilderError) -> Self {
53+
Self::Client(value)
54+
}
55+
}
56+
57+
impl IntoResponse for AppError {
58+
fn into_response(self) -> Response {
59+
tracing::error!("internal service error: {self:?}");
60+
let (status, message) = match self {
61+
Self::Backend(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")),
62+
Self::BadResponseFormat(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")),
63+
Self::Request(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:#?}")),
64+
Self::Credentials(e) => (StatusCode::UNAUTHORIZED, format!("{e:#?}")),
65+
Self::Client(e) => (StatusCode::SERVICE_UNAVAILABLE, format!("{e:#?}")),
66+
};
67+
(status, message).into_response()
68+
}
69+
}

0 commit comments

Comments
 (0)