diff --git a/services/backup/Cargo.toml b/services/backup/Cargo.toml index 0ae8be522..0b52348f1 100644 --- a/services/backup/Cargo.toml +++ b/services/backup/Cargo.toml @@ -1,41 +1,41 @@ [package] name = "backup" version = "0.1.0" description = "Backup service" edition.workspace = true license.workspace = true homepage.workspace = true [dependencies] anyhow = { workspace = true } async-stream = { workspace = true } aws-config = { workspace = true } aws-sdk-dynamodb = { workspace = true } chrono = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } comm-lib = { path = "../../shared/comm-lib", features = [ "http", "blob-client", "aws", "grpc_clients", "crypto" ] } grpc_clients = { path = "../../shared/grpc_clients" } once_cell = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tokio-stream = { workspace = true } tracing = { workspace = true } tracing-futures = { workspace = true, features = ["futures-03"] } -tracing-subscriber = { workspace = true, features = ["env-filter"] } +tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } uuid = { workspace = true, features = ["v4"] } actix-web = { workspace = true } tracing-actix-web = { workspace = true } reqwest = { workspace = true } derive_more = { workspace = true } actix-multipart = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } actix-web-actors = { workspace = true } actix = { workspace = true } actix-http = { workspace = true } bincode = { workspace = true } diff --git a/services/backup/src/constants.rs b/services/backup/src/constants.rs index 6eb32c6ee..0cb2c4e92 100644 --- a/services/backup/src/constants.rs +++ b/services/backup/src/constants.rs @@ -1,44 +1,45 @@ // Assorted constants pub const MPSC_CHANNEL_BUFFER_CAPACITY: usize = 1; pub const ID_SEPARATOR: &str = ":"; pub const ATTACHMENT_HOLDER_SEPARATOR: &str = ";"; pub const WS_FRAME_SIZE: usize = 1_048_576; // 1MiB pub const LOG_DEFAULT_PAGE_SIZE: i32 = 20; pub const LOG_BACKUP_ID_SEPARATOR: &str = "#"; // Configuration defaults pub const DEFAULT_HTTP_PORT: u16 = 50052; pub const DEFAULT_BLOB_SERVICE_URL: &str = "http://localhost:50053"; // Environment variable names pub const LOG_LEVEL_ENV_VAR: &str = tracing_subscriber::filter::EnvFilter::DEFAULT_ENV; +pub const COMM_SERVICES_USE_JSON_LOGS: &str = "COMM_SERVICES_USE_JSON_LOGS"; // DynamoDB constants pub mod backup_table { pub const TABLE_NAME: &str = "backup-service-backup"; pub const CREATED_INDEX: &str = "userID-created-index"; pub mod attr { pub const USER_ID: &str = "userID"; pub const BACKUP_ID: &str = "backupID"; pub const CREATED: &str = "created"; pub const USER_DATA: &str = "userData"; pub const USER_KEYS: &str = "userKeys"; pub const ATTACHMENTS: &str = "attachments"; pub const SIWE_BACKUP_MSG: &str = "siweBackupMsg"; } } pub mod log_table { pub const TABLE_NAME: &str = "backup-service-log"; pub mod attr { pub const BACKUP_ID: &str = "backupID"; pub const LOG_ID: &str = "logID"; pub const CONTENT_DB: &str = "content"; pub const CONTENT_BLOB_INFO: &str = "blobInfo"; pub const ATTACHMENTS: &str = "attachments"; } } diff --git a/services/backup/src/main.rs b/services/backup/src/main.rs index ec88879f7..97b0f6819 100644 --- a/services/backup/src/main.rs +++ b/services/backup/src/main.rs @@ -1,40 +1,56 @@ use anyhow::Result; use comm_lib::{auth::AuthService, blob::client::BlobServiceClient}; +use constants::COMM_SERVICES_USE_JSON_LOGS; +use std::env; use tracing::Level; use tracing_subscriber::EnvFilter; pub mod config; pub mod constants; pub mod database; pub mod error; pub mod http; pub mod identity; // re-export this to be available as crate::CONFIG pub use config::CONFIG; fn configure_logging() -> Result<()> { + let use_json_logs: bool = env::var(COMM_SERVICES_USE_JSON_LOGS) + .unwrap_or("false".to_string()) + .parse() + .unwrap_or_default(); + let filter = EnvFilter::builder() .with_default_directive(Level::INFO.into()) .with_env_var(constants::LOG_LEVEL_ENV_VAR) .from_env_lossy(); - let subscriber = tracing_subscriber::fmt().with_env_filter(filter).finish(); - tracing::subscriber::set_global_default(subscriber)?; + if use_json_logs { + let subscriber = tracing_subscriber::fmt() + .json() + .with_env_filter(filter) + .finish(); + tracing::subscriber::set_global_default(subscriber)?; + } else { + let subscriber = tracing_subscriber::fmt().with_env_filter(filter).finish(); + tracing::subscriber::set_global_default(subscriber)?; + } + Ok(()) } #[tokio::main] async fn main() -> Result<()> { config::parse_cmdline_args(); configure_logging()?; let aws_config = config::load_aws_config().await; let db_client = database::DatabaseClient::new(&aws_config); let blob_client = BlobServiceClient::new(CONFIG.blob_service_url.clone()); let auth_service = AuthService::new(&aws_config, &CONFIG.identity_endpoint); http::run_http_server(db_client, blob_client, auth_service).await?; Ok(()) } diff --git a/services/terraform/remote/service_backup.tf b/services/terraform/remote/service_backup.tf index 975769f8e..b2748e7a3 100644 --- a/services/terraform/remote/service_backup.tf +++ b/services/terraform/remote/service_backup.tf @@ -1,198 +1,202 @@ locals { backup_service_image_tag = local.is_staging ? "0.5.0-staging" : "0.5.0" backup_service_container_name = "backup-service-server" backup_service_server_image = "commapp/backup-server:${local.backup_service_image_tag}" backup_service_domain_name = "backup.${local.root_domain}" # HTTP port & configuration for ECS Service Connect backup_service_container_http_port = 50052 backup_sc_port_name = "backup-service-ecs-http" backup_sc_dns_name = "backup-service" # URL accessible by other services in the same Service Connect namespace # This renders to 'http://backup-service:50052' backup_local_url = "http://${local.backup_sc_dns_name}:${local.backup_service_container_http_port}" } resource "aws_ecs_task_definition" "backup_service" { family = "backup-service-task-def" container_definitions = jsonencode([ { name = local.backup_service_container_name image = local.backup_service_server_image essential = true portMappings = [ { name = local.backup_sc_port_name containerPort = local.backup_service_container_http_port protocol = "tcp" appProtocol = "http" }, ] environment = [ { name = "RUST_LOG" value = local.is_staging ? "info,backup=debug,comm_lib=debug" : "info" }, { name = "BLOB_SERVICE_URL", value = local.blob_local_url # If this ever fails, we can fallback to blob public URL: # "https://${local.blob_service_domain_name}" }, { name = "IDENTITY_SERVICE_ENDPOINT", value = local.identity_local_url }, { name = "COMM_SERVICES_DISABLE_CSAT_VERIFICATION", value = local.is_staging ? "false" : "true" + }, + { + name = "COMM_SERVICES_USE_JSON_LOGS", + value = local.comm_services_use_json_logs } ] logConfiguration = { "logDriver" = "awslogs" "options" = { "awslogs-create-group" = "true" "awslogs-group" = "/ecs/backup-service-task-def" "awslogs-region" = "us-east-2" "awslogs-stream-prefix" = "ecs" } } } ]) task_role_arn = aws_iam_role.backup_service.arn execution_role_arn = aws_iam_role.ecs_task_execution.arn network_mode = "bridge" cpu = "256" memory = "256" requires_compatibilities = ["EC2"] # Set this to true if you want to keep old revisions # when this definition is changed skip_destroy = false } resource "aws_ecs_service" "backup_service" { name = "backup-service" cluster = aws_ecs_cluster.comm_services.id launch_type = "EC2" task_definition = aws_ecs_task_definition.backup_service.arn force_new_deployment = true desired_count = 1 lifecycle { ignore_changes = [desired_count] } service_connect_configuration { enabled = true service { discovery_name = local.backup_sc_dns_name port_name = local.backup_sc_port_name client_alias { port = local.backup_service_container_http_port dns_name = local.backup_sc_dns_name } } } # HTTP load_balancer { target_group_arn = aws_lb_target_group.backup_service_http.arn container_name = local.backup_service_container_name container_port = local.backup_service_container_http_port } deployment_circuit_breaker { enable = true rollback = true } enable_execute_command = true enable_ecs_managed_tags = true } # Security group to configure access to the service resource "aws_security_group" "backup_service" { name = "backup-service-ecs-sg" vpc_id = aws_vpc.default.id ingress { from_port = local.backup_service_container_http_port to_port = local.backup_service_container_http_port protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] description = "HTTP port" } # Allow all outbound traffic egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } lifecycle { create_before_destroy = true } } resource "aws_lb_target_group" "backup_service_http" { name = "backup-service-ecs-http-tg" port = local.backup_service_container_http_port protocol = "HTTP" vpc_id = aws_vpc.default.id target_type = "instance" health_check { enabled = true healthy_threshold = 2 unhealthy_threshold = 3 protocol = "HTTP" path = "/health" matcher = "200-204" } } # Load Balancer resource "aws_lb" "backup_service" { load_balancer_type = "application" name = "backup-service-lb" internal = false subnets = [ aws_subnet.public_a.id, aws_subnet.public_b.id, aws_subnet.public_c.id, ] } resource "aws_lb_listener" "backup_service_https" { load_balancer_arn = aws_lb.backup_service.arn port = "443" protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06" certificate_arn = data.aws_acm_certificate.backup_service.arn default_action { type = "forward" target_group_arn = aws_lb_target_group.backup_service_http.arn } lifecycle { # Target group cannot be destroyed if it is used replace_triggered_by = [aws_lb_target_group.backup_service_http] # Required to avoid no-op plan differences ignore_changes = [default_action[0].forward[0].stickiness[0].duration] } } # SSL Certificate data "aws_acm_certificate" "backup_service" { domain = local.backup_service_domain_name statuses = ["ISSUED"] }