Skip to content

[otel-advisor] OTel improvement: enrich resource attributes with service.version, github.repository, github.run_id, and github.event_nameΒ #24658

@github-actions

Description

@github-actions

πŸ“‘ OTel Instrumentation Improvement: Enrich Resource Attributes

Analysis Date: 2026-04-05
Priority: High
Effort: Small (< 2h)

Problem

buildOTLPPayload() in actions/setup/js/send_otlp_span.cjs (line 107) sets only one resource attribute β€” service.name. In OpenTelemetry, resource attributes describe what produced the telemetry and are indexed at the service level by every major backend (Grafana, Honeycomb, Datadog). Span attributes describe individual operations and are not indexed for service-level filtering.

Currently:

  • github.repository, gh-aw.run.id are repeated in every span as span attributes, wasting payload bytes and preventing service-level grouping.
  • service.version is passed as scopeVersion (the instrumentation scope) but is never set as a resource attribute, so backends can't filter or alert by gh-aw version.
  • GITHUB_EVENT_NAME (push, pull_request, workflow_dispatch, etc.) is never captured anywhere, making it impossible to filter spans or dashboards by what triggered the workflow.

A DevOps engineer cannot today answer "Show me all failed spans for repository github/gh-aw triggered by workflow_dispatch on version 1.2.3" β€” not because the data doesn't exist in the environment, but because it was never added to the OTLP payload.

Why This Matters (DevOps Perspective)

  • Dashboard filtering: Grafana/Honeycomb service views group by resource attributes. Without service.version, there is no way to compare latency or failure rates across gh-aw releases.
  • Repository attribution: Multi-repo OTel collectors receive spans from many tenants. github.repository as a resource attribute enables per-repo SLOs and alerting.
  • Trigger-based alerting: Knowing github.event_name lets on-call engineers quickly narrow whether failures correlate with a specific trigger (e.g., only workflow_dispatch runs are failing).
  • Reduced per-span cost: Moving github.repository and github.run_id from span attributes (repeated on every span) to resource attributes means the backend stores them once per batch, not per span.

Current Behavior

// Current: actions/setup/js/send_otlp_span.cjs (lines 103–130)
function buildOTLPPayload({ traceId, spanId, parentSpanId, spanName, startMs, endMs,
                            serviceName, scopeVersion, attributes, statusCode, statusMessage }) {
  // ...
  return {
    resourceSpans: [
      {
        resource: {
          attributes: [buildAttr("service.name", serviceName)],  // ← only service.name
        },
        scopeSpans: [
          {
            scope: { name: "gh-aw", version: scopeVersion || "unknown" },
            // scopeVersion is captured here (instrumentation scope) but NOT as a resource attr
            spans: [ /* ... span-level attrs carry gh-aw.repository, gh-aw.run.id */ ],
          },
        ],
      },
    ],
  };
}

And in sendJobSetupSpan (line 366) and sendJobConclusionSpan (line 497), GITHUB_EVENT_NAME is never read:

// Environment variables consumed by sendJobSetupSpan β€” GITHUB_EVENT_NAME is absent
const workflowName = process.env.GH_AW_INFO_WORKFLOW_NAME || process.env.GITHUB_WORKFLOW || "";
const engineId     = process.env.GH_AW_INFO_ENGINE_ID || "";
const runId        = process.env.GITHUB_RUN_ID || "";
const actor        = process.env.GITHUB_ACTOR || "";
const repository   = process.env.GITHUB_REPOSITORY || "";
// ← GITHUB_EVENT_NAME never read

Proposed Change

Step 1 β€” Enrich buildOTLPPayload to accept and populate resource attributes:

// Proposed change to actions/setup/js/send_otlp_span.cjs

// Extend OTLPSpanOptions typedef:
// `@property` {Array<{key: string, value: object}>} [resourceAttributes] - Extra resource attributes

function buildOTLPPayload({ traceId, spanId, parentSpanId, spanName, startMs, endMs,
                            serviceName, scopeVersion, attributes,
                            resourceAttributes,          // ← new parameter
                            statusCode, statusMessage }) {
  const code = typeof statusCode === "number" ? statusCode : 1;
  const status = { code };
  if (statusMessage) status.message = statusMessage;

  const baseResourceAttrs = [buildAttr("service.name", serviceName)];
  if (scopeVersion && scopeVersion !== "unknown") {
    baseResourceAttrs.push(buildAttr("service.version", scopeVersion));
  }
  const allResourceAttrs = resourceAttributes
    ? [...baseResourceAttrs, ...resourceAttributes]
    : baseResourceAttrs;

  return {
    resourceSpans: [
      {
        resource: { attributes: allResourceAttrs },
        scopeSpans: [
          {
            scope: { name: "gh-aw", version: scopeVersion || "unknown" },
            spans: [
              {
                traceId, spanId,
                ...(parentSpanId ? { parentSpanId } : {}),
                name: spanName,
                kind: 2,
                startTimeUnixNano: toNanoString(startMs),
                endTimeUnixNano: toNanoString(endMs),
                status,
                attributes,
              },
            ],
          },
        ],
      },
    ],
  };
}

Step 2 β€” Pass resource attributes from sendJobSetupSpan and sendJobConclusionSpan:

// In sendJobSetupSpan (actions/setup/js/send_otlp_span.cjs, around line 360):
const eventName   = process.env.GITHUB_EVENT_NAME || "";   // ← add this line

const resourceAttributes = [
  buildAttr("github.repository", repository),
  buildAttr("github.run_id",     runId),
];
if (eventName) resourceAttributes.push(buildAttr("github.event_name", eventName));

const payload = buildOTLPPayload({
  traceId, spanId,
  spanName: jobName ? `gh-aw.${jobName}.setup` : "gh-aw.job.setup",
  startMs, endMs,
  serviceName,
  scopeVersion: process.env.GH_AW_INFO_VERSION || "unknown",
  attributes,
  resourceAttributes,   // ← pass resource attributes
});

Apply the same pattern in sendJobConclusionSpan.

Expected Outcome

After this change:

  • In Grafana / Honeycomb / Datadog: Spans can be grouped and filtered by service.version (compare failure rates across gh-aw releases), github.repository (per-repo SLOs), and github.event_name (per-trigger dashboards). The service catalog in backends like Grafana Tempo will automatically surface these dimensions.
  • In the JSONL mirror (/tmp/gh-aw/otel.jsonl): Resource attributes appear in the resource.attributes array, giving post-hoc debuggers repository and version context without needing to cross-reference job logs.
  • For on-call engineers: Incident triage narrows from "all gh-aw failures" to "failures on version X, triggered by pull_request in this repository" β€” dramatically reducing MTTR.

Implementation Steps

  • Extend the OTLPSpanOptions typedef in send_otlp_span.cjs to include resourceAttributes?: Array<{key: string, value: object}>
  • Update buildOTLPPayload to: (a) promote scopeVersion β†’ service.version resource attr; (b) merge caller-supplied resourceAttributes into the resource block
  • In sendJobSetupSpan: read GITHUB_EVENT_NAME; build resourceAttributes array with github.repository, github.run_id, github.event_name; pass to buildOTLPPayload
  • In sendJobConclusionSpan: apply the same resource attribute additions
  • Update actions/setup/js/action_otlp.test.cjs to assert: resource.attributes contains service.version, github.repository, github.run_id, and github.event_name
  • Run cd actions/setup/js && npx vitest run to confirm tests pass
  • Run make fmt to ensure formatting
  • Open a PR referencing this issue

Related Files

  • actions/setup/js/send_otlp_span.cjs β€” buildOTLPPayload, sendJobSetupSpan, sendJobConclusionSpan
  • actions/setup/js/action_setup_otlp.cjs β€” calls sendJobSetupSpan
  • actions/setup/js/action_conclusion_otlp.cjs β€” calls sendJobConclusionSpan
  • actions/setup/js/action_otlp.test.cjs β€” tests to update

Generated by the Daily OTel Instrumentation Advisor workflow

Generated by Daily OTel Instrumentation Advisor Β· ● 147.5K Β· β—·

  • expires on Apr 12, 2026, 4:00 AM UTC

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions