Logging
Structured logging and log management for Olympus Cloud.
Overview
The logging infrastructure provides centralized log collection and analysis:
| Component | Technology | Purpose |
|---|---|---|
| Collection | Cloud Logging | Centralized aggregation |
| Structure | JSON format | Queryable logs |
| Tracing | Cloud Trace | Request correlation |
| Analysis | Log Analytics | SQL-based queries |
| Export | BigQuery | Long-term retention |
Logging Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Applications │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Cloud │ │ Cloud │ │ Cloudflare│ │ OlympusEdge│ │
│ │ Run │ │ Functions│ │ Workers │ │ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼─────────────┼─────────────┼─────────────┼───────────────┘
│ │ │ │
└─────────────┴──────┬──────┴─────────────┘
│
┌──────────────▼───────────────┐
│ Cloud Logging │
│ • Log Router │
│ • Log Buckets │
│ • Log-based Metrics │
└──────────────┬───────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ BigQuery │ │ Log Analytics │ │ Cloud Storage │
│ (Analysis) │ │ (SQL Queries) │ │ (Archive) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Structured Logging
Rust Implementation
// src/logging.rs
use tracing::{info, warn, error, span, Level};
use tracing_subscriber::{fmt, EnvFilter, layer::SubscriberExt};
use serde::Serialize;
#[derive(Serialize)]
struct LogContext {
tenant_id: Option<String>,
user_id: Option<String>,
request_id: String,
trace_id: Option<String>,
span_id: Option<String>,
}
pub fn init_logging() {
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info"));
let subscriber = tracing_subscriber::registry()
.with(filter)
.with(
fmt::layer()
.json()
.with_current_span(true)
.with_span_list(true)
.with_file(true)
.with_line_number(true)
);
tracing::subscriber::set_global_default(subscriber)
.expect("Failed to set subscriber");
}
// Logging macros with context
#[macro_export]
macro_rules! log_info {
($ctx:expr, $($arg:tt)*) => {
tracing::info!(
tenant_id = %$ctx.tenant_id,
user_id = %$ctx.user_id,
request_id = %$ctx.request_id,
$($arg)*
)
};
}
// Usage
pub async fn create_order(ctx: &RequestContext, order: CreateOrderRequest) -> Result<Order> {
let span = span!(Level::INFO, "create_order",
order_type = %order.order_type,
item_count = order.items.len()
);
let _enter = span.enter();
log_info!(ctx, "Creating order");
let order = order_service.create(order).await?;
log_info!(ctx, order_id = %order.id, total = %order.total, "Order created");
Ok(order)
}
Log Format
{
"timestamp": "2026-01-18T10:30:00.000Z",
"severity": "INFO",
"message": "Order created",
"logging.googleapis.com/trace": "projects/olympuscloud-prod/traces/abc123",
"logging.googleapis.com/spanId": "def456",
"logging.googleapis.com/labels": {
"tenant_id": "acme-corp",
"location_id": "loc-xyz789",
"user_id": "user-123",
"request_id": "req-abc"
},
"jsonPayload": {
"order_id": "order-789",
"total": 45.99,
"item_count": 3
},
"resource": {
"type": "cloud_run_revision",
"labels": {
"service_name": "commerce-service",
"revision_name": "commerce-service-00042"
}
}
}
Go Implementation
// pkg/logging/logger.go
package logging
import (
"context"
"cloud.google.com/go/logging"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type Logger struct {
zap *zap.Logger
client *logging.Client
}
func NewLogger(projectID string) (*Logger, error) {
config := zap.NewProductionConfig()
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
config.EncoderConfig.MessageKey = "message"
config.EncoderConfig.LevelKey = "severity"
zapLogger, err := config.Build()
if err != nil {
return nil, err
}
return &Logger{zap: zapLogger}, nil
}
func (l *Logger) Info(ctx context.Context, msg string, fields ...zap.Field) {
fields = append(fields, extractContextFields(ctx)...)
l.zap.Info(msg, fields...)
}
func (l *Logger) Error(ctx context.Context, msg string, err error, fields ...zap.Field) {
fields = append(fields, zap.Error(err))
fields = append(fields, extractContextFields(ctx)...)
l.zap.Error(msg, fields...)
}
func extractContextFields(ctx context.Context) []zap.Field {
var fields []zap.Field
if tenantID := ctx.Value("tenant_id"); tenantID != nil {
fields = append(fields, zap.String("tenant_id", tenantID.(string)))
}
if requestID := ctx.Value("request_id"); requestID != nil {
fields = append(fields, zap.String("request_id", requestID.(string)))
}
return fields
}
TypeScript (Workers)
// workers/logging.ts
interface LogEntry {
timestamp: string;
severity: 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR';
message: string;
labels?: Record<string, string>;
[key: string]: unknown;
}
export class Logger {
constructor(
private service: string,
private env: Env
) {}
info(message: string, data?: Record<string, unknown>) {
this.log('INFO', message, data);
}
error(message: string, error?: Error, data?: Record<string, unknown>) {
this.log('ERROR', message, {
...data,
error: error ? {
name: error.name,
message: error.message,
stack: error.stack,
} : undefined,
});
}
private log(
severity: LogEntry['severity'],
message: string,
data?: Record<string, unknown>
) {
const entry: LogEntry = {
timestamp: new Date().toISOString(),
severity,
message,
service: this.service,
...data,
};
console.log(JSON.stringify(entry));
}
}
// Usage
const logger = new Logger('router-worker', env);
logger.info('Request received', { path: url.pathname, method: request.method });
Log Levels
Level Guidelines
| Level | Use Case | Example |
|---|---|---|
| DEBUG | Development details | Variable values, SQL queries |
| INFO | Normal operations | Request received, order created |
| WARNING | Potential issues | Retry attempt, deprecation |
| ERROR | Failures | API error, validation failure |
| CRITICAL | System failures | Database down, OOM |
Configuration
# config/logging.yaml
development:
level: debug
format: pretty
staging:
level: debug
format: json
production:
level: info
format: json
sampling:
initial: 100
thereafter: 100
Request Tracing
Trace Context Propagation
// src/tracing.rs
use tracing::{instrument, Span};
use opentelemetry::trace::{TraceContextExt, Tracer};
#[instrument(skip(ctx, request))]
pub async fn handle_request(
ctx: &RequestContext,
request: Request,
) -> Result<Response> {
// Extract trace context from headers
let trace_context = extract_trace_context(&request);
// Add to current span
Span::current().record("trace_id", &trace_context.trace_id);
Span::current().record("span_id", &trace_context.span_id);
// Process request
let response = process(request).await?;
Ok(response)
}
fn extract_trace_context(request: &Request) -> TraceContext {
// X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE
let header = request.headers()
.get("X-Cloud-Trace-Context")
.and_then(|v| v.to_str().ok());
match header {
Some(value) => parse_trace_context(value),
None => TraceContext::new(),
}
}
Cross-Service Tracing
// Propagate trace to downstream services
async fn call_downstream_service(
client: &reqwest::Client,
url: &str,
trace_context: &TraceContext,
) -> Result<Response> {
let request = client
.get(url)
.header("X-Cloud-Trace-Context", trace_context.to_header())
.build()?;
client.execute(request).await
}
Log Queries
Cloud Logging Queries
-- Find errors for a specific tenant
resource.type="cloud_run_revision"
severity>=ERROR
jsonPayload.tenant_id="acme-corp"
-- Find slow requests
resource.type="cloud_run_revision"
jsonPayload.latency_ms > 500
-- Trace a specific request
resource.type="cloud_run_revision"
jsonPayload.request_id="req-abc123"
-- Count orders by tenant
resource.type="cloud_run_revision"
jsonPayload.event="order.created"
| GROUP BY jsonPayload.tenant_id
| COUNT(*)
Log Analytics SQL
-- Error rate by service (last hour)
SELECT
resource.labels.service_name AS service,
COUNT(*) AS total_logs,
COUNTIF(severity = 'ERROR') AS errors,
COUNTIF(severity = 'ERROR') / COUNT(*) * 100 AS error_rate
FROM
`olympuscloud-prod.global._Default._Default`
WHERE
timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
GROUP BY
service
ORDER BY
error_rate DESC;
-- P99 latency by endpoint
SELECT
jsonPayload.path AS endpoint,
APPROX_QUANTILES(CAST(jsonPayload.latency_ms AS INT64), 100)[OFFSET(99)] AS p99_latency
FROM
`olympuscloud-prod.global._Default._Default`
WHERE
timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
AND jsonPayload.latency_ms IS NOT NULL
GROUP BY
endpoint
ORDER BY
p99_latency DESC
LIMIT 20;
Log Routing
Log Sinks
# terraform/logging.tf
# Production logs to BigQuery
resource "google_logging_project_sink" "bigquery" {
name = "bigquery-sink"
destination = "bigquery.googleapis.com/projects/${var.project_id}/datasets/logs"
filter = <<-EOT
resource.type="cloud_run_revision"
severity>=INFO
EOT
bigquery_options {
use_partitioned_tables = true
}
}
# Error logs to Pub/Sub for alerting
resource "google_logging_project_sink" "errors" {
name = "error-sink"
destination = "pubsub.googleapis.com/projects/${var.project_id}/topics/error-logs"
filter = <<-EOT
resource.type="cloud_run_revision"
severity>=ERROR
EOT
}
# Compliance logs to Cloud Storage
resource "google_logging_project_sink" "compliance" {
name = "compliance-sink"
destination = "storage.googleapis.com/olympus-compliance-logs"
filter = <<-EOT
resource.type="cloud_run_revision"
jsonPayload.compliance=true
EOT
}
Log Retention
| Log Type | Bucket | Retention | Archive |
|---|---|---|---|
| Default | _Default | 30 days | BigQuery |
| Security | security-logs | 365 days | Cloud Storage |
| Compliance | compliance-logs | 7 years | Cloud Storage |
| Debug | debug-logs | 7 days | None |
Sensitive Data Handling
PII Redaction
// src/logging/redaction.rs
use regex::Regex;
lazy_static! {
static ref EMAIL_REGEX: Regex = Regex::new(r"[\w.+-]+@[\w-]+\.[\w.-]+").unwrap();
static ref PHONE_REGEX: Regex = Regex::new(r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b").unwrap();
static ref CARD_REGEX: Regex = Regex::new(r"\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b").unwrap();
}
pub fn redact_pii(text: &str) -> String {
let mut result = text.to_string();
result = EMAIL_REGEX.replace_all(&result, "[EMAIL_REDACTED]").to_string();
result = PHONE_REGEX.replace_all(&result, "[PHONE_REDACTED]").to_string();
result = CARD_REGEX.replace_all(&result, "[CARD_REDACTED]").to_string();
result
}
// Use in logging middleware
pub fn sanitize_log_entry(entry: &mut LogEntry) {
if let Some(message) = entry.message.as_mut() {
*message = redact_pii(message);
}
// Remove sensitive fields
entry.remove("password");
entry.remove("api_key");
entry.remove("token");
}
Field-Level Exclusions
# config/logging.yaml
excluded_fields:
- password
- api_key
- access_token
- refresh_token
- card_number
- cvv
- ssn
redacted_fields:
- email
- phone
- address
Log-Based Metrics
Creating Metrics
# terraform/log_metrics.tf
resource "google_logging_metric" "error_count" {
name = "error_count"
filter = <<-EOT
resource.type="cloud_run_revision"
severity>=ERROR
EOT
metric_descriptor {
metric_kind = "DELTA"
value_type = "INT64"
labels {
key = "service"
value_type = "STRING"
description = "Service name"
}
}
label_extractors = {
"service" = "EXTRACT(resource.labels.service_name)"
}
}
resource "google_logging_metric" "order_latency" {
name = "order_creation_latency"
filter = <<-EOT
resource.type="cloud_run_revision"
jsonPayload.event="order.created"
EOT
metric_descriptor {
metric_kind = "DELTA"
value_type = "DISTRIBUTION"
unit = "ms"
}
value_extractor = "EXTRACT(jsonPayload.latency_ms)"
bucket_options {
explicit_buckets {
bounds = [10, 25, 50, 100, 250, 500, 1000, 2500, 5000]
}
}
}
Debugging
Local Development
# View logs in development
cargo run 2>&1 | jq '.'
# Filter by level
cargo run 2>&1 | jq 'select(.level == "ERROR")'
# Filter by field
cargo run 2>&1 | jq 'select(.tenant_id == "test-tenant")'
Production Debugging
# Stream logs
gcloud logging tail "resource.type=cloud_run_revision AND resource.labels.service_name=platform-service"
# Search for specific request
gcloud logging read "jsonPayload.request_id=req-abc123" --limit 100
# Export to file
gcloud logging read "severity>=ERROR" --format json > errors.json
Related Documentation
- Metrics - Metrics collection
- Alerting - Alert configuration
- Architecture Overview - Platform architecture