Skip to content

Commit e26ced5

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

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
@@ -3204,7 +3204,28 @@ async def compose_up(compose: PodmanCompose, args: argparse.Namespace) -> int |
32043204

32053205
max_service_length = 0
32063206
for cnt in compose.containers:
3207-
curr_length = len(cnt["_service"])
3207+
# Saltar contenedores excluidos
3208+
if cnt["_service"] in excluded:
3209+
continue
3210+
3211+
service_name = cnt["_service"]
3212+
container_name = cnt["name"]
3213+
3214+
if getattr(args, 'names', False):
3215+
# Con -n: mostrar solo servicio_numero (sin prefijo de proyecto)
3216+
expected_name = compose.format_name(service_name, str(cnt["num"]))
3217+
3218+
if container_name == expected_name:
3219+
# Es un nombre generado automáticamente, mostrar solo servicio_numero
3220+
display_name = compose.join_name_parts(service_name, str(cnt["num"]))
3221+
else:
3222+
# Es un container_name personalizado, usarlo tal como está
3223+
display_name = container_name
3224+
else:
3225+
# Sin -n: mostrar nombre completo del contenedor (comportamiento por defecto)
3226+
display_name = container_name
3227+
3228+
curr_length = len(display_name)
32083229
max_service_length = curr_length if curr_length > max_service_length else max_service_length
32093230

32103231
tasks: set[asyncio.Task] = set()
@@ -3226,11 +3247,33 @@ async def handle_sigint() -> None:
32263247
loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(handle_sigint()))
32273248

32283249
for i, cnt in enumerate(compose.containers):
3229-
# Add colored service prefix to output by piping output through sed
3250+
# Add colored service prefix to output like docker-compose
32303251
color_idx = i % len(compose.console_colors)
32313252
color = compose.console_colors[color_idx]
3232-
space_suffix = " " * (max_service_length - len(cnt["_service"]) + 1)
3233-
log_formatter = "{}[{}]{}|\x1b[0m".format(color, cnt["_service"], space_suffix)
3253+
3254+
# Determinar el nombre a mostrar
3255+
service_name = cnt["_service"]
3256+
container_name = cnt["name"]
3257+
3258+
if getattr(args, 'names', False):
3259+
# Con -n: mostrar solo servicio_numero (sin prefijo de proyecto)
3260+
expected_name = compose.format_name(service_name, str(cnt["num"]))
3261+
3262+
if container_name == expected_name:
3263+
# Es un nombre generado automáticamente, mostrar solo servicio_numero
3264+
display_name = compose.join_name_parts(service_name, str(cnt["num"]))
3265+
else:
3266+
# Es un container_name personalizado, usarlo tal como está
3267+
display_name = container_name
3268+
else:
3269+
# Sin -n: mostrar nombre completo del contenedor (comportamiento por defecto)
3270+
display_name = container_name
3271+
3272+
# Calcular espacios para alinear el | exactamente
3273+
# max_service_length + 1 espacio, menos la longitud del display_name actual
3274+
space_suffix = " " * (max_service_length + 1 - len(display_name))
3275+
log_formatter = "{}{}{}|\x1b[0m".format(color, display_name, space_suffix)
3276+
32343277
if cnt["_service"] in excluded:
32353278
log.debug("** skipping: %s", cnt["name"])
32363279
continue
@@ -3585,29 +3628,149 @@ async def compose_logs(compose: PodmanCompose, args: argparse.Namespace) -> None
35853628
if not args.services and not args.latest:
35863629
args.services = container_names_by_service.keys()
35873630
compose.assert_services(args.services)
3631+
35883632
targets = []
3633+
service_by_container = {}
3634+
35893635
for service in args.services:
3590-
targets.extend(container_names_by_service[service])
3591-
podman_args = []
3592-
if args.follow:
3593-
podman_args.append("-f")
3594-
if args.latest:
3595-
podman_args.append("-l")
3596-
if args.names:
3597-
podman_args.append("-n")
3598-
if args.since:
3599-
podman_args.extend(["--since", args.since])
3600-
# the default value is to print all logs which is in podman = 0 and not
3601-
# needed to be passed
3602-
if args.tail and args.tail != "all":
3603-
podman_args.extend(["--tail", args.tail])
3604-
if args.timestamps:
3605-
podman_args.append("-t")
3606-
if args.until:
3607-
podman_args.extend(["--until", args.until])
3608-
for target in targets:
3609-
podman_args.append(target)
3610-
await compose.podman.run([], "logs", podman_args)
3636+
containers = container_names_by_service[service]
3637+
targets.extend(containers)
3638+
for container in containers:
3639+
service_by_container[container] = service
3640+
3641+
should_use_colors = (
3642+
(len(args.services) > 1 or args.names)
3643+
and not args.latest
3644+
and sys.stdout.isatty()
3645+
and not getattr(args, "no_color", False)
3646+
)
3647+
3648+
if should_use_colors:
3649+
# Calcular la longitud máxima para alineación, igual que en compose_up
3650+
max_service_length = 0
3651+
for target in targets:
3652+
cnt = compose.container_by_name[target]
3653+
service_name = cnt["_service"]
3654+
container_name = cnt["name"]
3655+
3656+
if getattr(args, 'names', False):
3657+
# Con -n: mostrar solo servicio_numero (sin prefijo de proyecto)
3658+
expected_name = compose.format_name(service_name, str(cnt["num"]))
3659+
3660+
if container_name == expected_name:
3661+
# Es un nombre generado automáticamente, mostrar solo servicio_numero
3662+
display_name = compose.join_name_parts(service_name, str(cnt["num"]))
3663+
else:
3664+
# Es un container_name personalizado, usarlo tal como está
3665+
display_name = container_name
3666+
else:
3667+
# Sin -n: mostrar nombre completo del contenedor (comportamiento por defecto)
3668+
display_name = container_name
3669+
3670+
curr_length = len(display_name)
3671+
max_service_length = (
3672+
curr_length if curr_length > max_service_length else max_service_length
3673+
)
3674+
3675+
tasks = []
3676+
service_colors = {}
3677+
3678+
for target in targets:
3679+
cnt = compose.container_by_name[target]
3680+
service_name = cnt["_service"]
3681+
container_name = cnt["name"]
3682+
3683+
# Aplicar la misma lógica de display_name que en compose_up
3684+
if getattr(args, 'names', False):
3685+
# Con -n: mostrar solo servicio_numero (sin prefijo de proyecto)
3686+
expected_name = compose.format_name(service_name, str(cnt["num"]))
3687+
3688+
if container_name == expected_name:
3689+
# Es un nombre generado automáticamente, mostrar solo servicio_numero
3690+
display_name = compose.join_name_parts(service_name, str(cnt["num"]))
3691+
else:
3692+
# Es un container_name personalizado, usarlo tal como está
3693+
display_name = container_name
3694+
else:
3695+
# Sin -n: mostrar nombre completo del contenedor (comportamiento por defecto)
3696+
display_name = container_name
3697+
3698+
# Asignar color por servicio (no por contenedor individual)
3699+
if service_name not in service_colors:
3700+
color_idx = len(service_colors) % len(compose.console_colors)
3701+
service_colors[service_name] = compose.console_colors[color_idx]
3702+
3703+
color = service_colors[service_name]
3704+
3705+
# Calcular espacios para alinear el | exactamente, igual que en compose_up
3706+
# max_service_length + 1 espacio, menos la longitud del display_name actual
3707+
space_suffix = " " * (max_service_length + 1 - len(display_name))
3708+
log_formatter = "{}{}{}|\x1b[0m".format(color, display_name, space_suffix)
3709+
3710+
podman_args = []
3711+
if args.follow:
3712+
podman_args.append("-f")
3713+
if args.names:
3714+
podman_args.append("-n")
3715+
if args.since:
3716+
podman_args.extend(["--since", args.since])
3717+
if args.tail and args.tail != "all":
3718+
podman_args.extend(["--tail", args.tail])
3719+
if args.timestamps:
3720+
podman_args.append("-t")
3721+
if args.until:
3722+
podman_args.extend(["--until", args.until])
3723+
podman_args.append(target)
3724+
3725+
task = asyncio.create_task(
3726+
compose.podman.run([], "logs", podman_args, log_formatter=log_formatter),
3727+
name=f"logs-{service_name}-{target}",
3728+
)
3729+
tasks.append(task)
3730+
3731+
async def handle_sigint() -> None:
3732+
log.info("Caught SIGINT or Ctrl+C, stopping log streaming...")
3733+
for task in tasks:
3734+
if not task.done():
3735+
task.cancel()
3736+
3737+
if sys.platform != 'win32':
3738+
loop = asyncio.get_event_loop()
3739+
loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(handle_sigint()))
3740+
3741+
try:
3742+
await asyncio.gather(*tasks)
3743+
except KeyboardInterrupt:
3744+
for task in tasks:
3745+
if not task.done():
3746+
task.cancel()
3747+
await asyncio.gather(*tasks, return_exceptions=True)
3748+
except Exception as e:
3749+
log.error("Error in logs command: %s", e)
3750+
for task in tasks:
3751+
if not task.done():
3752+
task.cancel()
3753+
await asyncio.gather(*tasks, return_exceptions=True)
3754+
raise
3755+
else:
3756+
podman_args = []
3757+
if args.follow:
3758+
podman_args.append("-f")
3759+
if args.latest:
3760+
podman_args.append("-l")
3761+
if args.names:
3762+
podman_args.append("-n")
3763+
if args.since:
3764+
podman_args.extend(["--since", args.since])
3765+
if args.tail and args.tail != "all":
3766+
podman_args.extend(["--tail", args.tail])
3767+
if args.timestamps:
3768+
podman_args.append("-t")
3769+
if args.until:
3770+
podman_args.extend(["--until", args.until])
3771+
for target in targets:
3772+
podman_args.append(target)
3773+
await compose.podman.run([], "logs", podman_args)
36113774

36123775

36133776
@cmd_run(podman_compose, "config", "displays the compose file")
@@ -3866,6 +4029,12 @@ def compose_up_parse(parser: argparse.ArgumentParser) -> None:
38664029
help="Return the exit code of the selected service container. "
38674030
"Implies --abort-on-container-exit.",
38684031
)
4032+
parser.add_argument(
4033+
"-n",
4034+
"--names",
4035+
action="store_true",
4036+
help="Show short service names instead of full container names in logs",
4037+
)
38694038

38704039

38714040
@cmd_parse(podman_compose, "down")

0 commit comments

Comments
 (0)