While reviewing GitLab SaaS logs in Google SecOps, I noticed a number of fields not being parsed.
Addendum, Gitlab seems to send multiple different formatted JSONs based on different events.
Often using different variables for user as well as timestamps. Also time stamps in different formats.
For your own usage, suggest you change this to match your own domain:
#normalize userid
mutate {
replace => {"log_userid" => "%{gitlab_event.idm.read_only_udm.principal.user.userid}"}
on_error => "no_userid_set"
}
if [no_userid_set] {
mutate {replace => {"log_userid" => ""}}
}
if [log_userid] =~ /^[a-zA-Z]+\d+$/ {
mutate {
gsub => [ "log_userid", "\d+$", "" ]
}
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{log_userid}"
"user_email" => "%{log_userid}@domain.com"
}
merge => {
"gitlab_event.idm.read_only_udm.principal.user.email_addresses" => "user_email"
}
}
}
# GitLab SaaS Parser Extension
# Author: J Spoor
# Version: 1.0 WIP
# Product: GitLab SaaS
# Supported Format: JSON
# Last Updated: 2026-02-18
filter {
# Initialize
# End Initialize
json {
source => "message"
array_function => "split_columns"
on_error => "not_json"
}
# statedump {
# label => "Pre Extension"
# }
mutate {
replace => {"log_type" => "%{object_kind}"}
on_error => "no_object_kind"
}
if [no_object_kind] {
mutate {
replace => {"log_type" => "%{event_name}"}
# on_error => "no_event_name"
}
}
mutate {
replace => {"log_event_type" => "%{event_type}"}
on_error => "no_event_type"
}
if ![no_event_type] {
if [event_type] == "award" {
mutate {
replace => {"log_type" => "award"}
}
}
}
if [log_type] == "build"{
mutate {replace => {"log_build_status" => "%{build_status}"}}
mutate {replace => {"log_timestamp" => "%{build_created_at}"}}
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user.username}"
"gitlab_event.idm.read_only_udm.metadata.description" => "Built: build status: %{build_status}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "built"
}
}
date {
match => ["log_timestamp", "yyyy-MM-dd HH:mm:ss ZZZ"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
on_error => "date_match_failed"
}
} else if [log_type] == "push" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user_username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user_name}"
"gitlab_event.idm.read_only_udm.metadata.description" => "Push"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "push"
}
}
mutate {
copy => {"log_commit_timestamp" => "commits.0.timestamp"}
on_error => "no_commits"
}
if ![no_commits] {
date {
match => ["log_commit_timestamp","yyyy-MM-ddTHH:mm:ssZZ"]
target = "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
}
}
} else if [log_type] == "note" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user.username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user.name}"
"log_timestamp" => "%{object_attributes.created_at}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "note"
}
}
date {
match => ["log_timestamp", "yyyy-MM-dd HH:mm:ss ZZZ"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
on_error => "date_match_failed"
}
} else if [log_type] == "merge_request" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user.username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user.name}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "merge_request"
"log_timestamp" => "%{object_attributes.created_at}"
}
}
date {
match => ["log_timestamp", "yyyy-MM-dd HH:mm:ss ZZZ"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
on_error => "date_match_failed"
}
} else if [log_type] == "tag_push" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user_username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user_name}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "tag_push"
}
copy => {"log_timestamp" => "commits.0.timestamp"}
}
date {
match => ["log_timestamp","yyyy-MM-ddTHH:mm:ssZZ"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
}
} else if [log_type] == "deployment" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user.username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user.name}"
"log_timestamp" => "%{status_changed_at}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "deployment"
}
}
date {
match => ["log_timestamp","yyyy-MM-dd HH:mm:ss Z"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
}
} else if [log_type] == "release" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{commit.author.name}"
# "gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user.name}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "release"
"log_timestamp" => "%{created_at}"
}
}
date {
match => ["log_timestamp","yyyy-MM-dd HH:mm:ss ZZZ"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
}
} else if [log_type] == "award" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user.username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user.name}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "emoji"
"log_timestamp" => "%{object_attributes.created_at}"
}
}
date {
match => ["log_timestamp","yyyy-MM-dd HH:mm:ss ZZZ"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
}
} else if [log_type] == "user_add_to_group" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user_username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user_name}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "user_add_to_group"
"log_timestamp" => "%{created_at}"
}
}
date {
match => ["log_timestamp","ISO8601"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
}
} else if [log_type] == "user_remove_from_group" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user_username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user_name}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "user_remove_from_group"
}
}
date {
match => ["log_timestamp","ISO8601"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
}
} else if [log_type] == "user_update_for_group" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user_username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user_name}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "user_update_for_group"
"log_timestamp" => "%{created_at}"
}
}
date {
match => ["log_timestamp","ISO8601"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
}
} else if [log_type] == "access_token" {
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{object_attributes.name}"
"gitlab_event.idm.read_only_udm.metadata.description" => "%{event_name}"
"gitlab_event.idm.read_only_udm.metadata.product_event_type" => "access_token"
}
copy => {"temp_attributes.id" => "object_attributes.id"}
}
mutate {
convert => { "temp_attributes.id" => "string"}
}
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{temp_attributes.id}"
}
}
# No timestamps...
} else if [log_type] == "project_create" {
mutate {copy => {"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "owners.0.name"}}
mutate {replace => {"log_timestamp" => "%{created_at}"}}
date {
match => ["log_timestamp","ISO8601"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
}
# Add more
} else if [log_type] == "project_destroy" {
mutate {
copy => {
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "owners.0.name"
}
}
# Add Timestamp
# Add more
} else if [log_type] == "pipeline" {
mutate {replace => {"gitlab_event.idm.read_only_udm.metadata.event_type" => "STATUS_UNCATEGORIZED"}}
mutate {copy => {"project_id" => "project.id"}}
mutate {convert => {"project_id" => "string"}}
mutate {replace => {"gitlab_event.idm.read_only_udm.target.resource.product_object_id" => "%{project_id}"}}
mutate {
replace => {
"gitlab_event.idm.read_only_udm.metadata.vendor_name" => "GITLAB"
"gitlab_event.idm.read_only_udm.metadata.product_name" => "GITLAB"
"gitlab_event.idm.read_only_udm.metadata.log_type" => "GITLAB"
}
}
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{user.username}"
"gitlab_event.idm.read_only_udm.principal.user.user_display_name" => "%{user.name}"
"gitlab_event.idm.read_only_udm.principal.user.principal.email" => "%{user.email}"
}
}
# Add Timestamp
#Tmp
mutate {
copy => {
"gitlab_event.idm.read_only_udm.metadata.event_timestamp" => "@timestamp"
}
}
} else {
mutate {
replace => {"@timestamp" => "%{commit.started_at}"}
on_error => "no_commit_timestamp"
}
if ![no_commit_timestamp] {
date {
match => ["@timestamp", "yyyy-MM-dd HH:mm:ss ZZZ"]
target => "gitlab_event.idm.read_only_udm.metadata.event_timestamp"
# on_error =>
}
}
}
#normalize userid
mutate {
replace => {"log_userid" => "%{gitlab_event.idm.read_only_udm.principal.user.userid}"}
on_error => "no_userid_set"
}
if [no_userid_set] {
mutate {replace => {"log_userid" => ""}}
}
if [log_userid] =~ /^[a-zA-Z]+\d+$/ {
mutate {
gsub => [ "log_userid", "\d+$", "" ]
}
mutate {
replace => {
"gitlab_event.idm.read_only_udm.principal.user.userid" => "%{log_userid}"
"user_email" => "%{log_userid}@domain.com"
}
merge => {
"gitlab_event.idm.read_only_udm.principal.user.email_addresses" => "user_email"
}
}
}
# mutate {gsub => ["log_userid","\\d+",""]}
mutate { merge => { "@output" => "gitlab_event"} }
#statedump {
# label => "Post Merge"
#}
}
