Skip to content

Commit b9f6434

Browse files
adrianrioboclaude
andcommitted
feat(gitlab): correlate CI job logs with OTel traces via journald
When both GitLab runner and OTel are configured, Podman is set to use the journald log driver so CI job container output is captured by systemd journal. The otelcol config gains a journald/gitlab-jobs receiver that parses CONTAINER_NAME to extract job_id, project_id, and runner_token, and the existing filelog/gitlab-runner receiver gets regex_parser operators to promote job= and runner= fields from the runner daemon log body into attributes. Both streams share job_id, enabling cross-stream correlation in any OTel backend. The journald log driver change is gated on a new LogToJournald field in GitLabRunnerArgs, set only when hasOtel is true in the IBM Power and IBM Z providers. The otelcol receivers remain gated on the existing MonitorGitLabRunner flag. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 81ed7ae commit b9f6434

7 files changed

Lines changed: 72 additions & 20 deletions

File tree

pkg/integrations/gitlab/glrunner.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@ func (args *GitLabRunnerArgs) GetUserDataValues() *integrations.UserDataValues {
4444
return nil
4545
}
4646
return &integrations.UserDataValues{
47-
Name: args.Name,
48-
Token: args.AuthToken, // Use auth token (set by Pulumi during deployment)
49-
CliURL: downloadURL(),
50-
RepoURL: args.URL,
51-
Unsecure: args.Unsecure,
52-
Concurrent: args.Concurrent,
47+
Name: args.Name,
48+
Token: args.AuthToken, // Use auth token (set by Pulumi during deployment)
49+
CliURL: downloadURL(),
50+
RepoURL: args.URL,
51+
Unsecure: args.Unsecure,
52+
Concurrent: args.Concurrent,
53+
LogToJournald: args.LogToJournald,
5354
}
5455
}
5556

pkg/integrations/gitlab/snippet-linux.sh

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ if [ -z "$_dns_servers" ] && command -v nmcli &>/dev/null; then
3232
| tr '\n' ' ' | xargs)
3333
fi
3434
if [ -z "$_dns_servers" ]; then
35-
_dns_servers=$(awk '/^nameserver/ && $2 !~ /^127\./ {print $2}' /etc/resolv.conf \
35+
_dns_servers=$(awk '/^nameserver/ && $2 !~ /^127\./ && $2 != "::1" {print $2}' /etc/resolv.conf \
3636
| tr '\n' ' ' | xargs)
3737
fi
3838
if [ -n "$_dns_servers" ]; then
@@ -66,6 +66,32 @@ if [ -n "$_dns_servers" ]; then
6666
fi
6767
fi
6868

69+
{{- if .LogToJournald}}
70+
# Set journald as the container log driver so CI job output is captured by the
71+
# systemd journal and can be correlated with runner daemon logs via job_id.
72+
sudo mkdir -p /etc/containers
73+
if [ ! -f /etc/containers/containers.conf ]; then
74+
printf '[containers]\nlog_driver = "journald"\n' \
75+
| sudo tee /etc/containers/containers.conf > /dev/null
76+
elif grep -q '^\[containers\]' /etc/containers/containers.conf; then
77+
if awk '/^\[containers\]/{f=1;next} /^\[/{f=0} f && /^log_driver/{found=1} END{exit !found}' \
78+
/etc/containers/containers.conf; then
79+
# Replace existing log_driver within [containers]
80+
awk '/^\[containers\]/{s=1} /^\[/ && !/^\[containers\]/{s=0}
81+
s && /^log_driver/{$0="log_driver = \"journald\""} 1' \
82+
/etc/containers/containers.conf \
83+
| sudo tee /etc/containers/containers.conf.tmp > /dev/null \
84+
&& sudo mv /etc/containers/containers.conf.tmp /etc/containers/containers.conf
85+
else
86+
sudo sed -i '/^\[containers\]/a log_driver = "journald"' \
87+
/etc/containers/containers.conf
88+
fi
89+
else
90+
printf '\n[containers]\nlog_driver = "journald"\n' \
91+
| sudo tee -a /etc/containers/containers.conf > /dev/null
92+
fi
93+
{{- end}}
94+
6995
# Register runner using docker executor backed by Podman
7096
# --docker-privileged is required for Podman: containers need CAP_SYS_ADMIN to mount /proc
7197
sudo gitlab-runner register \

pkg/integrations/gitlab/types.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type GitLabRunnerArgs struct {
2626
Arch *Arch // Target architecture
2727
User string // OS user to run as (only used when Unsecure is true)
2828
AuthToken string // Runner authentication token (set by Pulumi during deployment)
29-
Unsecure bool // When false (default) a dedicated gitlab-runner system user is created; when true the runner service runs as User
30-
Concurrent int // Maximum number of concurrent jobs (written to config.toml; 0 means leave at default of 1)
29+
Unsecure bool // When false (default) a dedicated gitlab-runner system user is created; when true the runner service runs as User
30+
Concurrent int // Maximum number of concurrent jobs (written to config.toml; 0 means leave at default of 1)
31+
LogToJournald bool // When true, sets Podman log_driver=journald so CI job output is captured by systemd journal for OTel correlation
3132
}

pkg/integrations/integrations.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@ import (
66
)
77

88
type UserDataValues struct {
9-
CliURL string
10-
User string
11-
Name string
12-
Token string
13-
Labels string
14-
Port string
15-
RepoURL string
16-
Executor string
17-
Unsecure bool
18-
Concurrent int
9+
CliURL string
10+
User string
11+
Name string
12+
Token string
13+
Labels string
14+
Port string
15+
RepoURL string
16+
Executor string
17+
Unsecure bool
18+
Concurrent int
19+
LogToJournald bool
1920
}
2021

2122
type IntegrationConfig interface {

pkg/integrations/otelcol/snippet-linux.sh

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,31 @@ receivers:
143143
- type: remove
144144
id: remove_file_name
145145
field: attributes["log.file.name"]
146+
- type: regex_parser
147+
id: parse_job_id
148+
parse_from: body
149+
regex: '\bjob=(?P<job_id>\d+)'
150+
on_error: send
151+
- type: regex_parser
152+
id: parse_runner_token
153+
parse_from: body
154+
regex: '\brunner=(?P<runner_token>\w+)'
155+
on_error: send
146156
attributes:
147157
index: "{{.Index}}"
148158
_sourceCategory: gitlab-runner
149159
_sourceHost: ${env:HOSTNAME}
160+
journald/gitlab-jobs:
161+
operators:
162+
- type: regex_parser
163+
id: parse_container_name
164+
parse_from: attributes["CONTAINER_NAME"]
165+
regex: '^runner-(?P<runner_token>.+?)-project-(?P<project_id>\d+)-concurrent-(?P<concurrent_id>\d+)-(?P<job_id>\d+)$'
166+
on_error: send
167+
attributes:
168+
index: "{{.Index}}"
169+
_sourceCategory: gitlab-runner-jobs
170+
_sourceHost: ${env:HOSTNAME}
150171
{{- end}}
151172
processors:
152173
filter/drop_null_bytes:
@@ -185,7 +206,7 @@ service:
185206
level: "basic"
186207
pipelines:
187208
logs:
188-
receivers: [filelog/syslog, filelog/secure, filelog/audit{{if .MonitorGitLabRunner}}, filelog/gitlab-runner{{end}}]
209+
receivers: [filelog/syslog, filelog/secure, filelog/audit{{if .MonitorGitLabRunner}}, filelog/gitlab-runner, journald/gitlab-jobs{{end}}]
189210
processors: [filter/drop_null_bytes, resource, batch]
190211
exporters: [otlphttp]
191212
OTELEOF

pkg/provider/ibmcloud/action/ibm-power/ibm-power.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ func (r *pwRequest) deploy(ctx *pulumi.Context) error {
192192
}
193193
gateway := subnetInfo.Gateway
194194
localArgs := *glRunnerArgs
195+
localArgs.LogToJournald = hasOtel
195196
piUserDataInput = authToken.ApplyT(func(token string) (*string, error) {
196197
localArgs.AuthToken = token
197198
glSnippet, err := integrations.GetIntegrationSnippetAsCloudInitWritableFile(&localArgs, defaultUser)

pkg/provider/ibmcloud/action/ibm-z/ibm-z.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ func (r *zRequest) buildUserDataInput() (pulumi.StringPtrInput, error) {
362362
hasOtel := otelSet == 3
363363
if r.glAuthToken != nil {
364364
localArgs := *r.glRunnerArgsCopy
365+
localArgs.LogToJournald = hasOtel
365366
return r.glAuthToken.ApplyT(func(token string) (*string, error) {
366367
localArgs.AuthToken = token
367368
glSnippet, err := integrations.GetIntegrationSnippetAsCloudInitWritableFile(&localArgs, defaultUser)

0 commit comments

Comments
 (0)