Skip to main content

Logging

Structured logging and log management for Olympus Cloud.

Overview

The logging infrastructure provides centralized log collection and analysis:

ComponentTechnologyPurpose
CollectionCloud LoggingCentralized aggregation
StructureJSON formatQueryable logs
TracingCloud TraceRequest correlation
AnalysisLog AnalyticsSQL-based queries
ExportBigQueryLong-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

LevelUse CaseExample
DEBUGDevelopment detailsVariable values, SQL queries
INFONormal operationsRequest received, order created
WARNINGPotential issuesRetry attempt, deprecation
ERRORFailuresAPI error, validation failure
CRITICALSystem failuresDatabase 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 TypeBucketRetentionArchive
Default_Default30 daysBigQuery
Securitysecurity-logs365 daysCloud Storage
Compliancecompliance-logs7 yearsCloud Storage
Debugdebug-logs7 daysNone

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