What a Kubernetes (klog) line looks like
The freeform sample below is fed verbatim into the engine to produce every parser on this page.
I0703 14:22:15.123456 1234 controller.go:210] "Reconciling object" namespace="shop" name="web" reason="Scheduled"
E0703 14:22:19.884211 1290 controller.go:245] "Sync failed" namespace="infra" name="api" reason="ImagePullBackOff" Detected fields
The engine classified this sample as freeform and consolidated 8 fields across 2 lines. Fields marked literal were identical on every sample line, so they are baked into the pattern as anchors rather than captured.
- literal : literal
- timestamp : timestamp
- number : number
- literal2 : literal
- quoted_string : quoted_string
- literal3 : literal
- literal4 : literal
- literal5 : literal
Regex (named capture groups)
# sample: I0703 14:22:15.123456 1234 controller.go:210] "Reconciling object" namespace="shop" name="web" reason="Scheduled"
# groups: literal=I0703, timestamp=14:22:15.123456, number=1234, literal2=controller.go:210], quoted_string=Reconciling object, literal3=namespace="shop", literal4=name="web", literal5=reason="Scheduled"
^(?<literal>[A-Za-z]+\d+) (?<timestamp>\d+:\d+:\d+\.\d+) (?<number>-?\d+(?:\.\d+)?) (?<literal2>[A-Za-z]+\.[A-Za-z]+:\d+\]) "(?<quoted_string>[^"]*)" (?<literal3>[A-Za-z]+="[A-Za-z]+") (?<literal4>[A-Za-z]+="[A-Za-z]+") (?<literal5>[A-Za-z]+="[A-Za-z]+")$ Grok pattern (Logstash / Elastic)
# custom patterns
KUBERNETES_NOTDQUOTE [^"]*
%{NOTSPACE:literal} %{TIME:timestamp} %{NUMBER:number} %{NOTSPACE:literal2} "%{KUBERNETES_NOTDQUOTE:quoted_string}" %{NOTSPACE:literal3} %{NOTSPACE:literal4} %{GREEDYDATA:literal5} - note custom patterns emitted — save the '# custom patterns' block to a file in your patterns_dir
Wazuh decoder (OS_Regex XML)
<!--
Generated by LogForge - Wazuh decoder (OS_Regex dialect, not PCRE)
sample: I0703 14:22:15.123456 1234 controller.go:210] "Reconciling object" namespace="shop" name="web" reason="Scheduled"
test with: /var/ossec/bin/wazuh-logtest
-->
<decoder name="kubernetes-freeform">
<prematch>^\w+ </prematch>
</decoder>
<decoder name="kubernetes-freeform">
<parent>kubernetes-freeform</parent>
<regex>^(\w+) (\S+) (\d+) (\S+) "(\.+)" (\S+) (\S+) (\S+)</regex>
<order>literal, timestamp, number, literal2, quoted_string, literal3, literal4, literal5</order>
</decoder>
<!-- ============================================================
ALERT RULE (starter) — put this in a RULES file, e.g.
/var/ossec/etc/rules/local_rules.xml. Decoders and rules live
in SEPARATE files. The rule matches the decoder above through
<decoded_as>; set <level> and add <field>/<match> conditions so
it alerts only on the events you care about. Rule ids 100000+
are the user range — change them if they collide with yours.
============================================================ -->
<group name="kubernetes,">
<rule id="100000" level="3">
<decoded_as>kubernetes-freeform</decoded_as>
<description>kubernetes: literal=$(literal) timestamp=$(timestamp)</description>
</rule>
<!-- Example — a higher-level alert gated on one field (uncomment and edit):
<rule id="100001" level="10">
<if_sid>100000</if_sid>
<field name="literal">^CHANGE_ME$</field>
<description>kubernetes: a value you care about from $(literal)</description>
</rule>
-->
</group>
- note no stable literal prefix found — <prematch> anchors on the leading field pattern; tighten it for your environment
- note field "quoted_string": free-text capture (\.+) bounded by a quote anchor — OS_Regex greediness may over-consume if the anchor repeats
- note added a starter alert <rule> (level 3, matched to the decoder via <decoded_as>) — put it in a RULES file (not the decoders file), set the level, and add <field>/<match> conditions; the commented example child rule shows the pattern
- note decoder order and prematch specificity may need site-specific tuning (other decoders in your ruleset can shadow these) — validate with /var/ossec/bin/wazuh-logtest
Wazuh's OS_Regex is not PCRE — a bare . is a literal dot and \. matches any character.
Test Wazuh OS_Regex patterns →
rsyslog template / liblognorm rulebase
version=2
# kubernetes — liblognorm v2 rulebase (generated by LogForge)
# Usage with rsyslog (mmnormalize runs liblognorm):
# module(load="mmnormalize")
# action(type="mmnormalize" rulebase="/etc/rsyslog.d/kubernetes.rb" useRawMsg="on")
# Literal "%" is escaped as "%%"; raw tabs are written as \x09.
rule=kubernetes:%literal:word% %timestamp:word% %number:number% %literal2:word% "%quoted_string:char-to{"extradata":"\""}%" %literal3:word% %literal4:word% %literal5:word%
- note field "timestamp": samples do not uniformly match engine type "timestamp"; using a generic parser
- note chosen parser types: literal=word, timestamp=word, number=number, literal2=word, quoted_string=char-to("), literal3=word, literal4=word, literal5=word
Splunk
# props.conf (search-time extraction)
[<REPLACE_WITH_SOURCETYPE>]
EXTRACT-logforge = (?<literal>[A-Za-z]+\d+) (?<timestamp>\d+:\d+:\d+\.\d+) (?<number>-?\d+(?:\.\d+)?) (?<literal2>[A-Za-z]+\.[A-Za-z]+:\d+\]) "(?<quoted_string>[^"]*)" (?<literal3>[A-Za-z]+="[A-Za-z]+") (?<literal4>[A-Za-z]+="[A-Za-z]+") (?<literal5>[A-Za-z]+="[A-Za-z]+")
# Quick search-time test in SPL:
# | rex field=_raw "(?<literal>[A-Za-z]+\\d+) (?<timestamp>\\d+:\\d+:\\d+\\.\\d+) (?<number>-?\\d+(?:\\.\\d+)?) (?<literal2>[A-Za-z]+\\.[A-Za-z]+:\\d+\\]) \"(?<quoted_string>[^\"]*)\" (?<literal3>[A-Za-z]+=\"[A-Za-z]+\") (?<literal4>[A-Za-z]+=\"[A-Za-z]+\") (?<literal5>[A-Za-z]+=\"[A-Za-z]+\")" - note EXTRACT-<class> names must be unique within a sourcetype stanza — rename EXTRACT-logforge if you already use that class for this sourcetype
- note a timestamp field was detected: this EXTRACT only makes it a searchable field. To set the event _time at index time, add TIME_PREFIX and TIME_FORMAT to this props.conf stanza (TIME_FORMAT uses Splunk strptime, e.g. %Y-%m-%dT%H:%M:%S) — this generator does not guess the strptime format.
ES ingest
PUT _ingest/pipeline/kubernetes
{
"description": "LogForge-generated ingest pipeline for kubernetes",
"processors": [
{
"grok": {
"field": "message",
"patterns": [
"%{NOTSPACE:literal} %{TIME:timestamp} %{NUMBER:number} %{NOTSPACE:literal2} \"%{KUBERNETES_NOTDQUOTE:quoted_string}\" %{NOTSPACE:literal3} %{NOTSPACE:literal4} %{GREEDYDATA:literal5}"
],
"pattern_definitions": {
"KUBERNETES_NOTDQUOTE": "[^\"]*"
}
}
}
]
} - note grok: custom patterns emitted — save the '# custom patterns' block to a file in your patterns_dir
- note test in Kibana Dev Tools with: POST _ingest/pipeline/kubernetes/_simulate (supply a docs[] array whose _source.message holds a sample line)
Graylog
# Grok patterns to add under System > Grok Patterns:
# (Graylog needs these custom patterns installed globally BEFORE the rule/extractor below will work.)
# KUBERNETES_NOTDQUOTE [^"]*
# --- Graylog processing pipeline rule (primary) ---
# Paste under System > Pipelines > Manage rules, then attach the rule to a pipeline stage.
rule "kubernetes-parse"
when
has_field("message")
then
let gp = grok(pattern: "%{NOTSPACE:literal} %{TIME:timestamp} %{NUMBER:number} %{NOTSPACE:literal2} \"%{KUBERNETES_NOTDQUOTE:quoted_string}\" %{NOTSPACE:literal3} %{NOTSPACE:literal4} %{GREEDYDATA:literal5}", value: to_string($message.message), only_named_captures: true);
set_fields(gp);
end
# --- Graylog import-ready extractor JSON (secondary) ---
# Save as a .json file and import under System > Inputs > (input) > Manage extractors > Actions > Import extractors.
{
"extractors": [
{
"title": "kubernetes",
"extractor_type": "grok",
"converters": [],
"order": 0,
"cursor_strategy": "copy",
"source_field": "message",
"target_field": "",
"extractor_config": {
"grok_pattern": "%{NOTSPACE:literal} %{TIME:timestamp} %{NUMBER:number} %{NOTSPACE:literal2} \"%{KUBERNETES_NOTDQUOTE:quoted_string}\" %{NOTSPACE:literal3} %{NOTSPACE:literal4} %{GREEDYDATA:literal5}",
"named_captures_only": true
},
"condition_type": "none",
"condition_value": ""
}
],
"version": "5.0.0"
} - note grok: custom patterns emitted — save the '# custom patterns' block to a file in your patterns_dir
- note 1 custom grok pattern(s) (KUBERNETES_NOTDQUOTE) must be installed globally first under System > Grok Patterns — see the block at the top of the output
- note primary artifact is the processing-pipeline rule; the extractor JSON is an equivalent import-ready alternative for the classic extractor UI
Datadog
logforge_rule %{notSpace:literal} %{data:timestamp} %{number:number} %{notSpace:literal2} "%{quotedString:quoted_string}" %{notSpace:literal3} %{notSpace:literal4} %{notSpace:literal5} - note emitted rule name is "logforge_rule"; rename it to match your "kubernetes" convention if desired
- note field "timestamp" (timestamp): could not derive a Joda/Java date format from the sample shape; using %{data} — add a date("…") format by hand if you need a parsed timestamp
- note paste this line into a Grok Parser processor in a Datadog Log Pipeline; matchers are anchored left-to-right and rule whitespace matches log whitespace. Complex or multi-shape logs may need Helper Rules.
Fluent Bit
[PARSER]
Name kubernetes
Format regex
Regex ^(?<literal>[A-Za-z]+\d+) (?<timestamp>\d+:\d+:\d+\.\d+) (?<number>-?\d+(?:\.\d+)?) (?<literal2>[A-Za-z]+\.[A-Za-z]+:\d+\]) "(?<quoted_string>[^"]*)" (?<literal3>[A-Za-z]+="[A-Za-z]+") (?<literal4>[A-Za-z]+="[A-Za-z]+") (?<literal5>[A-Za-z]+="[A-Za-z]+")$
Time_Key timestamp
Time_Format %H:%M:%S.%L
# Fluentd <parse> block:
# <parse>
# @type regexp
# expression /^(?<literal>[A-Za-z]+\d+) (?<timestamp>\d+:\d+:\d+\.\d+) (?<number>-?\d+(?:\.\d+)?) (?<literal2>[A-Za-z]+\.[A-Za-z]+:\d+\]) "(?<quoted_string>[^"]*)" (?<literal3>[A-Za-z]+="[A-Za-z]+") (?<literal4>[A-Za-z]+="[A-Za-z]+") (?<literal5>[A-Za-z]+="[A-Za-z]+")$/
# time_key timestamp
# time_format %H:%M:%S.%L
# </parse>
- note Time_Key set to "timestamp"; Time_Format "%H:%M:%S.%L" is a best-effort strptime derived from the sample shape — verify it against your data (Fluent Bit uses %L for fractional seconds and %z for numeric offsets)
Vector
[transforms.kubernetes_parse]
type = "remap"
inputs = ["REPLACE_WITH_SOURCE"]
source = '''
. |= parse_regex!(.message, r'(?P<literal>[A-Za-z]+\d+) (?P<timestamp>\d+:\d+:\d+\.\d+) (?P<number>-?\d+(?:\.\d+)?) (?P<literal2>[A-Za-z]+\.[A-Za-z]+:\d+\]) "(?P<quoted_string>[^"]*)" (?P<literal3>[A-Za-z]+="[A-Za-z]+") (?P<literal4>[A-Za-z]+="[A-Za-z]+") (?P<literal5>[A-Za-z]+="[A-Za-z]+")')
''' Loki
# promtail pipeline for "kubernetes" (generated by LogForge)
# Add these stages under a scrape_config in your promtail config:
# scrape_configs:
# - job_name: kubernetes
# pipeline_stages:
# (the stages below are indented to sit under pipeline_stages)
pipeline_stages:
- regex:
expression: '^(?P<literal>[A-Za-z]+\d+) (?P<timestamp>\d+:\d+:\d+\.\d+) (?P<number>-?\d+(?:\.\d+)?) (?P<literal2>[A-Za-z]+\.[A-Za-z]+:\d+\]) "(?P<quoted_string>[^"]*)" (?P<literal3>[A-Za-z]+="[A-Za-z]+") (?P<literal4>[A-Za-z]+="[A-Za-z]+") (?P<literal5>[A-Za-z]+="[A-Za-z]+")$'
- note no low-cardinality field found to promote to a Loki label — omitted the `- labels:` stage; every captured field stays in the extracted map for later stages
- note left in the extracted map (NOT promoted to labels — high cardinality would explode Loki streams): literal, timestamp, number, literal2, quoted_string, literal3, literal4, literal5
syslog-ng
parser p_kubernetes {
regexp-parser(
prefix(".kubernetes.")
patterns("(?<literal>[A-Za-z]+\\d+) (?<timestamp>\\d+:\\d+:\\d+\\.\\d+) (?<number>-?\\d+(?:\\.\\d+)?) (?<literal2>[A-Za-z]+\\.[A-Za-z]+:\\d+\\]) \"(?<quoted_string>[^\"]*)\" (?<literal3>[A-Za-z]+=\"[A-Za-z]+\") (?<literal4>[A-Za-z]+=\"[A-Za-z]+\") (?<literal5>[A-Za-z]+=\"[A-Za-z]+\")")
);
}; - note captured fields are stored as name-value pairs under the prefix ".kubernetes." (e.g. a group (?<srcip>…) becomes ".kubernetes.srcip")
FAQ
- How do I read the klog prefix on a Kubernetes log line?
- The prefix is Lmmdd hh:mm:ss.uuuuuu threadid file:line]. The first character is the severity letter (I/W/E/F = Info/Warning/Error/Fatal), then the month-day, the time with microseconds, the thread ID, and the source file and line. Everything after the ] is the message. It is positional, so match it by position rather than by keys.
- Why is there no year in a Kubernetes (klog) timestamp?
- klog inherits glog's format, which records only MMDD and the time — never the year. Parsers must supply the year from context and handle the December-to-January rollover, the same problem RFC 3164 syslog has. The microsecond precision is present, which helps order rapid control-plane events.
- What is structured klog and how does it change parsing?
- Newer Kubernetes components emit the message as an "msg" value followed by explicit key="value" pairs (pod=, namespace=, node=). This sits after the same fixed klog prefix, so a robust parser reads the header positionally, then switches modes: free-text tail for classic klog, key=value tail for structured klog.
- What do the severity letters mean and which should I alert on?
- I is informational, W a warning, E an error, and F fatal (the component logs it and then exits). Alert on F immediately — it means a crash — and on sustained bursts of E from the apiserver or controllers. Many W lines (like reflector "too old resource version") are normal watch churn and are not by themselves actionable.
Try it on your own Kubernetes (klog) lines
Paste a few real lines, review the detected fields, and copy whichever format your stack needs. Free, no account, nothing uploaded.
Open this sample in LogForge →