# SPDX-License-Identifier: MPL-2.0
# Copyright 2020-2022 John Mille <john@compose-x.io>
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ecs_composex.common.settings import ComposeXSettings
from ecs_composex.ecs.ecs_family import ComposeFamily
from troposphere.logs import LogGroup
from itertools import chain
from compose_x_common.compose_x_common import keyisset
from troposphere import Ref, Region, Sub
from ecs_composex.common.cfn_conditions import define_stack_name
from ecs_composex.common.logging import LOG
from ecs_composex.compose.compose_services.service_logging import ServiceLogging
from ecs_composex.ecs.ecs_firelens.firelens_advanced_rendered_settings import (
handle_firelens_advanced_settings,
)
from ecs_composex.ecs.ecs_firelens.firelens_logger_helpers import (
parse_set_update_firelens_configuration_options,
)
from ecs_composex.ecs.ecs_params import CLUSTER_NAME_T, LOG_GROUP_RETENTION
from .cw_logging import (
add_container_level_log_group,
create_log_group,
logging_from_defined_region,
)
[docs]class FamilyLogging:
"""
:ivar LogGroup family_log_group:
"""
def __init__(
self,
family: ComposeFamily,
):
self._family = family
self.logging_group_name = Sub(
f"${{STACK_NAME}}/svc/ecs/${{{CLUSTER_NAME_T}}}/{family.logical_name}",
STACK_NAME=define_stack_name(family.template if family.template else None),
)
self._family_log_group: LogGroup = create_log_group(
self.family, group_name=self.logging_group_name, grant_task_role_access=True
)
self.firelens_service = None
self.firelens_config_service = None
self.services_logging: dict = {}
self.firelens_advanced_config = None
@property
def family(self) -> ComposeFamily:
return self._family
@property
def family_log_group(self) -> LogGroup:
return self._family_log_group
@property
def cw_log_retention(self) -> int:
return max(
_svc.logging.cw_retention_period for _svc in self.family.ordered_services
)
[docs] def update_cw_log_retention(self):
if self.family.stack:
self.family.stack.Parameters.update(
{LOG_GROUP_RETENTION.title: self.cw_log_retention}
)
@property
def api_health_enabled(self):
for _config in self.services_logging.values():
if keyisset("EnableApiHeathCheck", _config.firelens_advanced):
return True
return False
@property
def buffer_limit_mb(self):
"""
Returns the amount, in MB, of RAM to use for the log router (fluentbit) container
"""
_mb = (2**10) ** 2
_max = 512
default = 64
defined = [
_config.firelens_advanced["LogDriverBufferLimit"]
for _config in self.services_logging.values()
if keyisset("LogDriverBufferLimit", _config.firelens_advanced)
]
if not defined:
return default
sum_defined = int(sum(defined) / _mb)
return sum_defined
@property
def cpu_limits(self) -> float:
default = 0.1
defined = [
_config.firelens_advanced["SidecarCpuReservation"]
for _config in self.services_logging.values()
if keyisset("SidecarCpuReservation", _config.firelens_advanced)
]
if not defined:
return default
return max(defined)
@property
def grace_period(self):
default = 30
defined = [
_config.firelens_advanced["GracePeriod"]
for _config in self.services_logging.values()
if keyisset("GracePeriod", _config.firelens_advanced)
]
if not defined:
return default
max_defined = max(defined)
if max_defined > 120:
return 120
elif max_defined < 0:
return default
else:
return max_defined
[docs] def init_family_services_log_configuration(self) -> None:
for service in chain(
self.family.managed_sidecars, self.family.ordered_services
):
default_family_options = {
"awslogs-group": Ref(self.family_log_group),
"awslogs-region": Region,
"awslogs-stream-prefix": service.name,
}
self.set_init_family_service_logging(service, default_family_options)
self.update_cw_log_retention()
[docs] def set_init_family_service_logging(self, service, awslogs_options):
service.logging = ServiceLogging(service, awslogs_options)
service.logging.set_update_log_configuration()
setattr(
service.container_definition,
"LogConfiguration",
service.logging.log_configuration,
)
self.services_logging[service] = service.logging
[docs] def handle_firelens(self, settings):
from ecs_composex.ecs.ecs_firelens.firelens_managed_sidecar_service import (
FLUENT_BIT_AGENT_NAME,
FluentBit,
render_agent_config,
)
self.firelens_service = FluentBit(
FLUENT_BIT_AGENT_NAME,
render_agent_config(
self.family,
self.api_health_enabled,
memory_limits=int(self.buffer_limit_mb),
cpu_limits=float(self.cpu_limits),
),
)
self.firelens_service.logging = ServiceLogging(
self.firelens_service,
{
"awslogs-group": Ref(self.family_log_group),
"awslogs-region": Region,
"awslogs-stream-prefix": self.firelens_service.name,
},
)
self.firelens_service.set_firelens_configuration()
self.firelens_service.add_to_family(self.family, True)
self.firelens_service.set_as_dependency_to_family_services()
self.update_family_services_logging_configuration(settings)
self.firelens_service.logging.set_update_log_configuration(
LogDriver="awslogs",
Options={
"awslogs-group": Ref(self.family_log_group),
"awslogs-region": Region,
"awslogs-stream-prefix": self.firelens_service.name,
},
)
[docs] def update_family_services_logging_configuration(
self,
settings: ComposeXSettings,
apply_to_sidecars: bool = False,
):
"""
Updates all the container definitions of the ComposeFamily services
"""
for service in chain(
self.family.managed_sidecars, self.family.ordered_services
):
if service is self.firelens_service:
continue
if service.is_aws_sidecar and not apply_to_sidecars:
continue
if service.logging.log_driver == "awsfirelens":
LOG.debug(
f"{self.family.name}.{service.name} - LogDriver is already awsfirelens"
)
parse_set_update_firelens_configuration_options(
self.family, service, settings
)
self.firelens_advanced_config = handle_firelens_advanced_settings(
self.family, settings
)
[docs] def handle_awslogs_logging(self, use_firelens: list):
"""
Method to go over each service logging configuration and accordingly define the IAM permissions needed for
the exec role
If the region was passed in the log driver options, just grant access to any lo group
ElIf the group name is set and is a string, passed by the log driver options, just grant access to it.
"""
if not self.family.template:
raise AttributeError(
self.family.name,
"Template not yet initialized. Must have a valid template to configure logging",
)
for service in chain(
self.family.managed_sidecars, self.family.ordered_services
):
if service in use_firelens:
continue
if keyisset("awslogs-region", service.logging.log_options) and isinstance(
service.logging.log_options["awslogs-region"], str
):
logging_from_defined_region(self.family, service)
elif keyisset(
"awslogs-group", service.logging.log_options
) and not isinstance(
service.logging.log_options["awslogs-group"], (Ref, Sub)
):
add_container_level_log_group(
self.family, service, f"{service.logical_name}LogGroupAccess"
)