Skip to content

Commit a92c7a7

Browse files
committed
feat(podman-logs): improve logs formatting and behavior like docker-compose
1 parent f618ff3 commit a92c7a7

File tree

1 file changed

+194
-25
lines changed

1 file changed

+194
-25
lines changed

podman_compose.py

Lines changed: 194 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3215,7 +3215,28 @@ async def compose_up(compose: PodmanCompose, args: argparse.Namespace) -> int |
32153215

32163216
max_service_length = 0
32173217
for cnt in compose.containers:
3218-
curr_length = len(cnt["_service"])
3218+
# Saltar contenedores excluidos
3219+
if cnt["_service"] in excluded:
3220+
continue
3221+
3222+
service_name = cnt["_service"]
3223+
container_name = cnt["name"]
3224+
3225+
if getattr(args, 'names', False):
3226+
# Con -n: mostrar solo servicio_numero (sin prefijo de proyecto)
3227+
expected_name = compose.format_name(service_name, str(cnt["num"]))
3228+
3229+
if container_name == expected_name:
3230+
# Es un nombre generado automáticamente, mostrar solo servicio_numero
3231+
display_name = compose.join_name_parts(service_name, str(cnt["num"]))
3232+
else:
3233+
# Es un container_name personalizado, usarlo tal como está
3234+
display_name = container_name
3235+
else:
3236+
# Sin -n: mostrar nombre completo del contenedor (comportamiento por defecto)
3237+
display_name = container_name
3238+
3239+
curr_length = len(display_name)
32193240
max_service_length = curr_length if curr_length > max_service_length else max_service_length
32203241

32213242
tasks: set[asyncio.Task] = set()
@@ -3237,11 +3258,33 @@ async def handle_sigint() -> None:
32373258
loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(handle_sigint()))
32383259

32393260
for i, cnt in enumerate(compose.containers):
3240-
# Add colored service prefix to output by piping output through sed
3261+
# Add colored service prefix to output like docker-compose
32413262
color_idx = i % len(compose.console_colors)
32423263
color = compose.console_colors[color_idx]
3243-
space_suffix = " " * (max_service_length - len(cnt["_service"]) + 1)
3244-
log_formatter = "{}[{}]{}|\x1b[0m".format(color, cnt["_service"], space_suffix)
3264+
3265+
# Determinar el nombre a mostrar
3266+
service_name = cnt["_service"]
3267+
container_name = cnt["name"]
3268+
3269+
if getattr(args, 'names', False):
3270+
# Con -n: mostrar solo servicio_numero (sin prefijo de proyecto)
3271+
expected_name = compose.format_name(service_name, str(cnt["num"]))
3272+
3273+
if container_name == expected_name:
3274+
# Es un nombre generado automáticamente, mostrar solo servicio_numero
3275+
display_name = compose.join_name_parts(service_name, str(cnt["num"]))
3276+
else:
3277+
# Es un container_name personalizado, usarlo tal como está
3278+
display_name = container_name
3279+
else:
3280+
# Sin -n: mostrar nombre completo del contenedor (comportamiento por defecto)
3281+
display_name = container_name
3282+
3283+
# Calcular espacios para alinear el | exactamente
3284+
# max_service_length + 1 espacio, menos la longitud del display_name actual
3285+
space_suffix = " " * (max_service_length + 1 - len(display_name))
3286+
log_formatter = "{}{}{}|\x1b[0m".format(color, display_name, space_suffix)
3287+
32453288
if cnt["_service"] in excluded:
32463289
log.debug("** skipping: %s", cnt["name"])
32473290
continue
@@ -3596,29 +3639,149 @@ async def compose_logs(compose: PodmanCompose, args: argparse.Namespace) -> None
35963639
if not args.services and not args.latest:
35973640
args.services = container_names_by_service.keys()
35983641
compose.assert_services(args.services)
3642+
35993643
targets = []
3644+
service_by_container = {}
3645+
36003646
for service in args.services:
3601-
targets.extend(container_names_by_service[service])
3602-
podman_args = []
3603-
if args.follow:
3604-
podman_args.append("-f")
3605-
if args.latest:
3606-
podman_args.append("-l")
3607-
if args.names:
3608-
podman_args.append("-n")
3609-
if args.since:
3610-
podman_args.extend(["--since", args.since])
3611-
# the default value is to print all logs which is in podman = 0 and not
3612-
# needed to be passed
3613-
if args.tail and args.tail != "all":
3614-
podman_args.extend(["--tail", args.tail])
3615-
if args.timestamps:
3616-
podman_args.append("-t")
3617-
if args.until:
3618-
podman_args.extend(["--until", args.until])
3619-
for target in targets:
3620-
podman_args.append(target)
3621-
await compose.podman.run([], "logs", podman_args)
3647+
containers = container_names_by_service[service]
3648+
targets.extend(containers)
3649+
for container in containers:
3650+
service_by_container[container] = service
3651+
3652+
should_use_colors = (
3653+
(len(args.services) > 1 or args.names)
3654+
and not args.latest
3655+
and sys.stdout.isatty()
3656+
and not getattr(args, "no_color", False)
3657+
)
3658+
3659+
if should_use_colors:
3660+
# Calcular la longitud máxima para alineación, igual que en compose_up
3661+
max_service_length = 0
3662+
for target in targets:
3663+
cnt = compose.container_by_name[target]
3664+
service_name = cnt["_service"]
3665+
container_name = cnt["name"]
3666+
3667+
if getattr(args, 'names', False):
3668+
# Con -n: mostrar solo servicio_numero (sin prefijo de proyecto)
3669+
expected_name = compose.format_name(service_name, str(cnt["num"]))
3670+
3671+
if container_name == expected_name:
3672+
# Es un nombre generado automáticamente, mostrar solo servicio_numero
3673+
display_name = compose.join_name_parts(service_name, str(cnt["num"]))
3674+
else:
3675+
# Es un container_name personalizado, usarlo tal como está
3676+
display_name = container_name
3677+
else:
3678+
# Sin -n: mostrar nombre completo del contenedor (comportamiento por defecto)
3679+
display_name = container_name
3680+
3681+
curr_length = len(display_name)
3682+
max_service_length = (
3683+
curr_length if curr_length > max_service_length else max_service_length
3684+
)
3685+
3686+
tasks = []
3687+
service_colors = {}
3688+
3689+
for target in targets:
3690+
cnt = compose.container_by_name[target]
3691+
service_name = cnt["_service"]
3692+
container_name = cnt["name"]
3693+
3694+
# Aplicar la misma lógica de display_name que en compose_up
3695+
if getattr(args, 'names', False):
3696+
# Con -n: mostrar solo servicio_numero (sin prefijo de proyecto)
3697+
expected_name = compose.format_name(service_name, str(cnt["num"]))
3698+
3699+
if container_name == expected_name:
3700+
# Es un nombre generado automáticamente, mostrar solo servicio_numero
3701+
display_name = compose.join_name_parts(service_name, str(cnt["num"]))
3702+
else:
3703+
# Es un container_name personalizado, usarlo tal como está
3704+
display_name = container_name
3705+
else:
3706+
# Sin -n: mostrar nombre completo del contenedor (comportamiento por defecto)
3707+
display_name = container_name
3708+
3709+
# Asignar color por servicio (no por contenedor individual)
3710+
if service_name not in service_colors:
3711+
color_idx = len(service_colors) % len(compose.console_colors)
3712+
service_colors[service_name] = compose.console_colors[color_idx]
3713+
3714+
color = service_colors[service_name]
3715+
3716+
# Calcular espacios para alinear el | exactamente, igual que en compose_up
3717+
# max_service_length + 1 espacio, menos la longitud del display_name actual
3718+
space_suffix = " " * (max_service_length + 1 - len(display_name))
3719+
log_formatter = "{}{}{}|\x1b[0m".format(color, display_name, space_suffix)
3720+
3721+
podman_args = []
3722+
if args.follow:
3723+
podman_args.append("-f")
3724+
if args.names:
3725+
podman_args.append("-n")
3726+
if args.since:
3727+
podman_args.extend(["--since", args.since])
3728+
if args.tail and args.tail != "all":
3729+
podman_args.extend(["--tail", args.tail])
3730+
if args.timestamps:
3731+
podman_args.append("-t")
3732+
if args.until:
3733+
podman_args.extend(["--until", args.until])
3734+
podman_args.append(target)
3735+
3736+
task = asyncio.create_task(
3737+
compose.podman.run([], "logs", podman_args, log_formatter=log_formatter),
3738+
name=f"logs-{service_name}-{target}",
3739+
)
3740+
tasks.append(task)
3741+
3742+
async def handle_sigint() -> None:
3743+
log.info("Caught SIGINT or Ctrl+C, stopping log streaming...")
3744+
for task in tasks:
3745+
if not task.done():
3746+
task.cancel()
3747+
3748+
if sys.platform != 'win32':
3749+
loop = asyncio.get_event_loop()
3750+
loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(handle_sigint()))
3751+
3752+
try:
3753+
await asyncio.gather(*tasks)
3754+
except KeyboardInterrupt:
3755+
for task in tasks:
3756+
if not task.done():
3757+
task.cancel()
3758+
await asyncio.gather(*tasks, return_exceptions=True)
3759+
except Exception as e:
3760+
log.error("Error in logs command: %s", e)
3761+
for task in tasks:
3762+
if not task.done():
3763+
task.cancel()
3764+
await asyncio.gather(*tasks, return_exceptions=True)
3765+
raise
3766+
else:
3767+
podman_args = []
3768+
if args.follow:
3769+
podman_args.append("-f")
3770+
if args.latest:
3771+
podman_args.append("-l")
3772+
if args.names:
3773+
podman_args.append("-n")
3774+
if args.since:
3775+
podman_args.extend(["--since", args.since])
3776+
if args.tail and args.tail != "all":
3777+
podman_args.extend(["--tail", args.tail])
3778+
if args.timestamps:
3779+
podman_args.append("-t")
3780+
if args.until:
3781+
podman_args.extend(["--until", args.until])
3782+
for target in targets:
3783+
podman_args.append(target)
3784+
await compose.podman.run([], "logs", podman_args)
36223785

36233786

36243787
@cmd_run(podman_compose, "config", "displays the compose file")
@@ -3877,6 +4040,12 @@ def compose_up_parse(parser: argparse.ArgumentParser) -> None:
38774040
help="Return the exit code of the selected service container. "
38784041
"Implies --abort-on-container-exit.",
38794042
)
4043+
parser.add_argument(
4044+
"-n",
4045+
"--names",
4046+
action="store_true",
4047+
help="Show short service names instead of full container names in logs",
4048+
)
38804049

38814050

38824051
@cmd_parse(podman_compose, "down")

0 commit comments

Comments
 (0)