From 7216721eaa3f8909d605c51980ff737586ded893 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 8 Sep 2026 23:18:25 -0400 Subject: [PATCH] log: a group names its span log.group used the group label as the span name. The labels are prose, thus Honeycomb held spans named `Installing Nix`, `Detecting systemd...` and `Directly spawning the daemon, since systemd is not available.`. A group still opens a span, because a group is a piece of work with a start and an end, which is what a span is. The name of that span is now an argument of its own. A span name names an operation: it stays short, and it stays the same from run to run, because Honeycomb groups by it. A label is prose that a person reads in the workflow log. One argument cannot be both. The function of a group now receives the span, so that the work inside can put attributes on it without a second call to open a span. It receives the span on an object, so that a later version can put more in it and no call site has to change. --- src/log.ts | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/log.ts b/src/log.ts index f994672..9387de5 100644 --- a/src/log.ts +++ b/src/log.ts @@ -13,7 +13,7 @@ import { stringifyError } from "./errors.js"; import { type LogLevel, emitLogRecord, withSpan } from "./telemetry.js"; import * as actionsCore from "@actions/core"; -import type { Attributes } from "@opentelemetry/api"; +import type { Attributes, Span } from "@opentelemetry/api"; /** * `@actions/core` accepts an Error in place of a message for the annotation @@ -85,24 +85,40 @@ export function setFailed(message: Message, attributes?: Attributes): void { } /** - * Run `fn` inside both a collapsible group in the workflow log and an active - * OpenTelemetry span of the same name. + * Represents a collapsable log group and span. + */ +export interface Group { + /** The span of this group. */ + span: Span; +} + +/** + * Run a callback inside both a collapsible group in the workflow log and an active + * OpenTelemetry span. * - * This is the replacement for a `startGroup`/`endGroup` pair: the group closes + * This replaces `startGroup`/`endGroup`: the group closes * and the span ends even if `fn` throws, and a throwing `fn` marks the span * failed before re-throwing. + * + * `name` is the span name and `label` is the console heading + * + * @param name - The span name, such as `install_nix`. + * @param label - The heading of the group in the workflow log. + * @param fn - The work of the group. It receives the group's span. + * @param attributes - Attributes for the span. */ export async function group( name: string, - fn: () => Promise, + label: string, + fn: (group: Group) => Promise, attributes?: Attributes, ): Promise { return await withSpan( name, - async () => { - actionsCore.startGroup(name); + async (span) => { + actionsCore.startGroup(label); try { - return await fn(); + return await fn({ span }); } finally { actionsCore.endGroup(); }